功夫不负有心人,只要基础打的牢,一切梦想都可以成真。面试高峰期已经度过,可是我还在被窝刷着面试题,虽然说是为了生活,但是我更多的还是在学习中寻找快乐。最近写了一下常见的面试手写题目,这里就列举5个常见的JavaScript手写功能,有的是在学习中自己写的,也算是学习的笔记吧,如果有不对的地方,欢迎评论区指正。

1、防抖和节流

1.1、防抖

函数防抖,指的是在触发函数之后,函数在指定时间之内只会执行一次,如果在规定时间之内再次触发函数,就会重新计算函数的执行时间。简单来说,就是如果多次触发函数,每次间隔的时间不超过规定时间,他只会执行最后一次。

代码实现

函数逻辑:设置一个定时器,当重复调用函数的时候,每一次都会清除定时器,然后重新定时,在规定时间内没有重复调用,就会执行剩下的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// fnc是想要要执行的函数,delay是防抖规定的时间
function debounce(fnc,delay){
// 定义一个定时timer
let timer = null;
// 闭包函数确保timer不会被销毁
return function(){
// 记录事件参数
let args = arguments;
// 每次调用就会清除上一次定时
clearTimeout(timer);
// 然后开启下一次定时
timer = setTimeout(()=>{
fnc.apply(this,args);
},delay);
}
}

1.2、节流

节流就是限制函数在单位时间之内如果触发多次,而在只会执行一次,当过了这个时间段,在下一个时间段内,才会再次执行第二次,以此类推。

代码实现

代码:

函数逻辑:先开启一个定时器,定时器任务完成后清空,如果在规定时间之内重复触发函数,将不会做任何操作,直到定时器完成之后。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// fnc是想要要执行的函数,delay是防抖规定的时间
function throttle(fnc,delay){
// 定义定时器timer
let timer = null;
// 返回闭包函数
return function(){
// 记录事件参数
let args = arguments;
if(!timer){
timer = setTimeout(()=>{
// 执行事件函数
fnc.apply(this,args);
// 执行完毕后重置定时器
timer = null;
},delay)
}
}
}

2、深浅拷贝

2.1浅拷贝

只是复制指向某个对象的指针,没有复制对象本身,当原对象的值发生改变,浅拷贝的对象对应的值也会发生改变,他们都是在共用一块内存。

代码实现

函数逻辑:遍历对象,然后把属性和属性值都放在一个新的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
var shallowCopy = function(obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是对象
var newObj = obj instanceof Array ? [] : {};
// 遍历obj,并且判断是obj的属性才拷贝
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}

2.2、深拷贝

创造一个一模一样的对象,两个对象不在同一块内存,修改原始的对象,并不会影响新的对象。

代码实现

函数逻辑:拷贝的时候判断一下属性值的类型,如果是对象,就递归调用深拷贝函数

1
2
3
4
5
6
7
8
9
10
var deepCopy = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}

3、数组去重

在实际的开发当中,数组去重是我们经常要用到的一个方法,虽然不是很复杂,但是能将这些知识融会贯通还是非常难得的。这里简单写一下几种数组去重的方法,并不是最好的,只是简单记录一下。

方法1:双层 for 循环

函数逻辑:先定义一个包含原始数组第一个元素的数组,然后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不重复则添加到新数组中,最后返回新数组。

1
2
3
4
5
6
7
8
9
10
11
12
function distinct(arr) {
for (let i=0, len=arr.length; i<len; i++) {
for (let j=i+1; j<len; j++) {
if (arr[i] == arr[j]) {
arr.splice(j, 1);
len--;
j--;
}
}
}
return arr;
}

方法2:Array.filter() 加 indexOf

函数逻辑:利用indexOf检测元素在数组中第一次出现的位置是否和元素现在的位置相等,如果不等则说明该元素是重复元素

1
2
3
4
5
6
function distinct(a, b) {
let arr = a.concat(b);
return arr.filter((item, index)=> {
return arr.indexOf(item) === index
})
}

方法3:ES6 中的 Set 去重

函数逻辑:ES6 提供了新的数据结构 Set,Set 结构的一个特性就是成员值都是唯一的,没有重复的值。

1
let unique = (a) => [...new Set(a)]

还有更多的方法,这里就不写了。(主要是我懒!)

4、Promise

Promise是一个管理异步编程的方案,他是一个构造函数,可以用new关键字创建实例。他有3种状态:pending、fulfilled、rejected,他这3种状态不会受外界影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class MyPromise {
constructor(executor) {
this.status = 'pending' // 初始状态为等待
this.value = null // 成功的值
this.reason = null // 失败的原因
this.onFulfilledCallbacks = [] // 成功的回调函数数组
this.onRejectedCallbacks = [] // 失败的回调函数数组
let resolve = value => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn()) // 调用成功的回调函数
}
}
let reject = reason => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = reason
this.onRejectedCallbacks.forEach(fn => fn()) // 调用失败的回调函数
}
};
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.status === 'fulfilled') {
setTimeout(() => {
const x = onFulfilled(this.value);
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
})
}
if (this.status === 'rejected') {
setTimeout(() => {
const x = onRejected(this.reason)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
})
}
if (this.status === 'pending') {
this.onFulfilledCallbacks.push(() => { // 将成功的回调函数放入成功数组
setTimeout(() => {
const x = onFulfilled(this.value)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
})
})
this.onRejectedCallbacks.push(() => { // 将失败的回调函数放入失败数组
setTimeout(() => {
const x = onRejected(this.reason)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
})
})
}
})
}
}

简单的promise就实现了,但这也只是最基础的,想要实现promise其他功能你可以看看这一篇文章。

可能是目前最易理解的手写promise - 掘金 (juejin.cn)

5、继承

ES5 继承(寄生组合继承)

1
2
3
4
5
6
7
8
9
10
11
12
13
function Parent(name) {
this.name = name
}
Parent.prototype.eat = function () {
console.log(this.name + ' is eating')
}

function Child(name, age) {
Parent.call(this, name)
this.age = age
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.contructor = Child

ES6 继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Parent {
constructor(name) {
this.name = name
}
eat() {
console.log(this.name + ' is eating')
}
}

class Child extends Parent {
constructor(name, age) {
super(name)
this.age = age
}
}

这些都是在面试当中经常遇到的面试题目,当然面试题目不是背出来的,而是去理解他的含义,当了解这一切之后,你就会发现其实比背下来更容易。更多的知识还在准备当中。