常见手写题大汇总

1.防抖 debounce

防抖的原理:你尽管触发事件,但是我一定在事件触发的 n 秒之后才执行,如果你在触发事件 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒之后在执行。

1
2
3
4
5
6
7
8
9
10
11
function debounce(func, delay) {
let timer;
return function (...args) {
if (timer) clearTimeout(timer);

timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
};
}

2.节流 throttle

节流的原理:如果你持续触发事件,没隔一段时间,只会执行一次事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 使用事件流
function throttle(func, delay) {
let previous = 0;
return function (...args) {
let now = +new Date();
if (now - previous > delay) {
func.apply(this, args);
previous = now;
}
};
}

// 使用定时器
function throttle(func, delay) {
let timer;
return function (...args) {
if (timer) return;
timer = setTimeout(() => {
func.apply(this, args);
timer = null;
}, delay);
};
}

两者的区别在于:
防抖:

1
if (timer) clearTimeout(timer);

节流:

1
if (timer) return;

3.函数柯里化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function add(a, b, c) {
return a + b + c;
}
add(1, 2, 3);
let addCurry = curry(add);
addCurry(1)(2)(3);

function curry(fn) {
let judge = (...args1) => {
if (args1.length == fn.length) return fn(...args1);
return (...args2) => judge(...args1, ...args2);
};
return judge;
}

4.偏函数

1
2
3
4
5
6
7
8
9
10
11
function add(a, b, c) {
return a + b + c;
}
let partialAdd = partial(add, 1);
partialAdd(2, 3);

function partial(fn, ...args1) {
return (...args2) => {
return fn(...args1, ...args2);
};
}

5.instanceof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function myInstanceof(L = null, R) {
// 对于左侧参数如果是非对象直接返回false
if (Object(L) !== L) return false;
// 对于右侧参数可以认为只能为函数且不能没有Prototype属性
if (typeof R !== "function" || !R.prototype)
throw new TypeError("Right-hand side of " instanceof " is not an object");
// 声明一个变量获取对象的__proto__
let link = L.__proto__;
// 做循环(当link最终指向null,如果指向null的情况下都找不到那就返回false)
while (link !== null) {
// 如果找到说明R.prototype在L的原型链上,即返回true
if (link === R.prototype) return true;
// 逐级向下
link = link.__proto__;
}
return false;
}

6.new

1
2
3
4
5
6
7
8
9
10
11
let newObj = myNew(Fn, "Leo", 18);

function myNew(Fn) {
if (typeof Fn !== "function")
throw new TypeError("This is not a constructor"); // Fn校验
var args = Array.from(arguments).slice(1); // 取入参
var obj = {}; // 1.创建一个空的简单JavaScript对象(即` {} `)
obj.__proto__ = Fn.prototype; // 2. 为步骤1新创建的对象添加属性` __proto__ `,将该属性链接至构造函数的原型对象
var res = Fn.call(obj, ...args); // 3. 将步骤1新创建的对象作为this的上下文并传入参数;
return Object(res) === res ? res : obj; // 4. 如果该函数没有返回对象,则返回this。
}

7.浅拷贝 深拷贝

浅拷贝:对于复杂数据类型而言,复制了一份栈内存中的地址。

深拷贝:对于复杂数据类型而言,开辟了一块新的堆内存并且在栈内存里得到一份新的指向地址。新旧数据改变互不影响。对于简单数据类型而言,复制了一份栈内存中的 value。

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
const getType = (data) => {
return Object.prototype.toString.call(data).slice(8, -1);
};

const isObject = (data) => {
return (
(typeof data === "object" && data !== null) || getType(data) === "Function"
);
};

const cloneDeep = (data, hash = new WeakMap()) => {
// 第一步:判断是简单数据类型还是复杂数据类型,若是简单数据类型直接返回
if (!isObject(data)) return data;

// 第二步:获取准确的类型
const targetType = getType(data);

// 第三步:查询是否已经存在此对象,存在找出(此处处理循环引用问题)
if (hash.has(data)) return hash.get(data);

// 第四步:函数处理
if (targetType === "Function") {
return function () {
// 带入原作用域链以及参数
return data.call(this, ...arguments);
};
}

// 第五步:正则处理
if (targetType === "RegExp") {
const reg = new RegExp(data.source, data.flags);
// 连续执行test()函数,关注lastIndex属性
if (data.flags) {
reg.lastIndex = 0;
}
return reg;
}

// 第六步:关于日期对象处理
if (targetType === "Date") return new Date(data);

// 第七步:关于Set数据处理
if (targetType === "Set") {
const newSetTarget = new Set();
data.forEach((v) => {
newSetTarget.add(v);
});
return newSetTarget;
}

// 第八步:关于Map数据处理
if (targetType === "Map") {
const newMapTarget = new Map();
data.forEach((v, k) => {
if (isObject(v)) {
newMapTarget.set(k, cloneDeep(v));
} else {
newMapTarget.set(k, v);
}
});
return newMapTarget;
}

// 第九步:关于Symbol
if (targetType === "Symbol")
return Object(Symbol.prototype.valueOf.call(data));

// 第十步:数组/对象处理
let copiedData = targetType === "Array" ? [] : {};

// 第十一步:存哈希对象(处理自循环引用,用WeakMap主要鉴于其唯一性及v8的垃圾回收机制考虑)
hash.set(data, copiedData);

// 第十二步:循环处理
for (let key in data) {
// 目前只取显现属性(可扩展:如果有特殊需求可以读取其他属性)
if (data.hasOwnProperty(key)) {
// 判断是简单数据类型还是复杂数据类型
if (isObject(data[key])) {
// 复杂对象递归处理
copiedData[key] = cloneDeep(data[key], hash);
} else {
// 简单数据类型
copiedData[key] = data[key];
}
}
}

// 第十三步:返回
return copiedData;
};

8.数组去重

1
2
3
4
5
6
7
8
9
10
// ES5
function unique(arr) {
var res = arr.filter(function (item, index, array) {
return array.indexOf(item) === index;
});
return res;
}

// ES6
var unique = (arr) => [...new Set(arr)];

9.数组扁平化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ES5
function flatten(arr) {
var result = [];
for (var i = 0, len = arr.length; i < len; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}

// ES6
function flatten(arr) {
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}

10.发布订阅模式

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
class EventEmitter {
constructor() {
this.cache = {};
}
on(name, fn) {
if (this.cache[name]) {
this.cache[name].push(fn);
} else {
this.cache[name] = [fn];
}
}
off(name, fn) {
let tasks = this.cache[name];
if (tasks) {
const index = tasks.findIndex((f) => f === fn || f.callback === fn);
if (index >= 0) {
tasks.splice(index, 1);
}
}
}
emit(name, once = false, ...args) {
if (this.cache[name]) {
// 创建副本,如果回调函数内继续注册相同事件,会造成死循环
let tasks = this.cache[name].slice();
for (let fn of tasks) {
fn(...args);
}
if (once) {
delete this.cache[name];
}
}
}
}

// 测试
let eventBus = new EventEmitter();
let fn1 = function (name, age) {
console.log(`${name} ${age}`);
};
let fn2 = function (name, age) {
console.log(`hello, ${name} ${age}`);
};
eventBus.on("aaa", fn1);
eventBus.on("aaa", fn2);
eventBus.emit("aaa", false, "布兰", 12);
// '布兰 12'
// 'hello, 布兰 12'