前端“四大手写”

手写bind、深拷贝、EventHub、Promise。

手写bind

MDN bind 释义链接

定义

bind用法不难,就是把新的this绑定到某个函数func上,并返回func的一个拷贝。

用法

function.bind(thisArg[, arg1[, arg2[, …]]])

功能点

  • 改变原函数的this指向,即绑定上下文,返回原函数的拷贝
  • 绑定函数被调用时,bind的额外参数将置于实参之前传递给被绑定的方法
  • 注意,一个 绑定函数 也能使用 new 操作符创建对象,这种行为就像把原函数当成构造器,thisArg 参数无效。也就是 new 操作符修改 this 指向的优先级更高。

实现

只使用ES6语法

  • 优点:因为可以使用 const 、… 操作符,代码简洁
  • 缺点:兼容性稍差,不能兼容IE等古老浏览器。
1
2
3
4
5
6
function myBind(thisArg, ...args) {
const fn = this; // 这里的fn就是调用bind的函数 func
return function(...args2) {
return fn.apply(thisArg, ...args, ...args2)
}
}

兼容IE

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
Function.prototype.myBind = function (thisArg) {
var fn = this

if(typeof fn !== 'function') {
throw new Error('non function!')
}

var slice = Array.prototype.slice
var arg1 = slice.call(arguments, 1)
console.log('arg1', arg1)

return function() {
var arg2 = slice.call(arguments, 0)
console.log('arg2', arg2)

return fn.apply(thisArg, arg1.concat(arg2))}

}

function foo(name) {
this.name = name

}
let obj = {}
var bar = foo.myBind(obj, 'Cinaiet')

new关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Function.prototype.myBind2 = function(thisArg) {
var fn = this
if(typeof fn !== 'function') {
throw new Error('non function!')
}
var slice = Array.prototype.slice
var arg = slice.call(arguments, 1)

function resFn() {
var arg2 = slice.call(arguments, 0)
return fn.apply(
resFn.prototype.isPrototypeOf(this) ? this : thisArg,
arg.concat(arg2)
)
}

resFn.prototype = fn.prototype

return resFn
}

手写深拷贝

释义

  • 为什么要深拷贝? 不希望数据被修改或数据只需要部分修改
  • 如何实现深拷贝? 简单需求用JSON反序列化,复杂需求用递归克隆

JSON反序列化

1
2
3
4
5
6
var obj = {
a: 'asd',
b: 'vvv',
c: 'ccc
}
const b = JSON.parse(JSON.Stringify(obj))

缺点

  • JSON value不支持的类型,都拷贝不了。
  • undefined、函数、循环引用、正则等都不支持。

递归克隆

核心

  1. 对象分类
  2. 递归
  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
function deepClone(source, wm = new WeakMap()) {
if(!isObject(source)) return source
// 判断是否拷贝过当前对象
if(wm.has(source)) return wm.get(source)
let res = Array.isArray(source) ? [] : {}
wm.set(source, res)

for(let key in source) {
if(Object.prototype.hasOwnProperty.call(source, key)) {
if(isObject(source[key])) {
res[key] = deepClone(source[key], wm)
} else {
res[key] = source[key]
}
}
}

return res
}


function isObject(obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}

延伸: 深拷贝与浅拷贝的区别

浅拷贝: 只复制一层对象的属性,复制出来的数据,指向的数据内存地址是一样的,修改任意一个都会使复制出的数据同样发生变化。

浅拷贝: 不仅复制原对象的属性,还会将原对象属性所包含的对象也依次采用深复制的方法递归复制到新的对象上。

手写EventHub(发布订阅)

核心

  1. on 发布监听
  2. emit 负责遍历查找发布的eventName 的方法
  3. remove 负责移除eventName 及事件
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
class CreateEventHub{
constructor() {
this.cache = {}
}

on(eventName, fn) { // 添加监听
this.cache[eventName] = this.cache[eventName] || []
this.cache[eventName].push(fn)
}

emit(eventName, data) { // 执行调用
(this.cache[eventName] || []).forEach(fn => fn(data))
}

destry(eventName, fn) {
this.cache[eventName] = this.cache[eventName] || []
const index = this.cache[eventName].indexOf(fn)
console.log(index)
if(index !== -1 ) {
this.cache[eventName].splice(index, 1)
}
}
}

const eh = new CreateEventHub()
eh.on('eventName', func)

手写Promise

核心

  • new Promise(fn) fn只能为函数,且要立即执行
  • Promise.then(res, fail) 接收的res和fail也是函数,且会在resole(reject)被调用时才会执行
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
function MyPromise(fn) {
this.state = PENDDING
this.success = undefined
this.fail = undefined
this.onFuilfilled = []
this.onRejected = []
var _this = this



function resolve(success) {
if(_this.state === PENDDING) {
_this.state = FUlFILLED
_this.success = success
_this.onFuilfilled.forEach(func => func(success))
}
}

function reject(fail) {
if(_this.state === PENDDING) {
_this.state = REJECTED
_this.success = fail
_this.onRejected.forEach(func => func(fail))
}
}

try{
fn(resolve, reject)
} catch(err) {
reject(err)
}

}

MyPromise.prototype.then = function(onFuilfilled, onRejected) {
if(this.state === FUlFILLED) {
typeof onFuilfilled === 'function' && onFuilfilled(this.success)
}

if(this.state === REJECTED) {
typeof onRejected === 'function' && onRejected(this.fail)
}

if(this.state === PENDDING) {
this.onFuilfilled.push(onFuilfilled)
this.onRejected.push(onRejected)
}
}

以上。