# 手写代码

# 1. 防抖和节流

防抖和节流

# 2. 字符串相加

给定两个字符串形式的非负整数num1和num2,计算它们的和并同样以字符串形式返回。 你不能使用任何內建的用于处理大整数的库(比如 BigInteger),也不能直接将输入的字符串转换为整数形式。 输入:num1 = "11", num2 = "123" 输出:"134"

function addStr(num1, num2) {
    let i = num1.length - 1
    let j = num2.length -1
    let add = 0
    let arr = []
    while(i >= 0 || j >= 0 || add != 0) {
        let x = i >= 0 ? num1[i] - '0' : 0
        let y = j >= 0 ? num2[j] - '0' : 0
        let result = x + y + add
        arr.push(result % 10)
        add = Math.floor(result / 10)
        i --
        j --
    }
    return arr.reverse().join('')
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 3. 有效括号匹配

给定一个只包括'(',')','{','}','[',']'的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。

知识点:栈的特性

function isSuit(s) {
    if (!s) return false
    let arr = []
    for(let i = 0; i < s.length; i++) {
        let val = s.charAt(i)
        if (val === '{' || val === '(' || val === '[') {
            arr.push(val)
        } else {
            if (!arr.length) return false
            if (val === '}' && arr.pop() !== '{') return false
            if (val === ')' && arr.pop() !== '(') return false
            if (val === ']' && arr.pop() !== '[') return false
        }
    }
    return arr.length === 0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let obj = {
    '{': '}',
    '(': ')',
    '[': ']'
}
function isSuit(s) {
    if (!s) return false
    let arr = []
    for(let i = 0; i < s.length; i++) {
        let val = s.charAt(i)
        if (obj[val]) {
            arr.push(obj[val])
        } else {
            if (!arr.length) return false
            if (val !== arr.pop()) return false
        }
    }
    return arr.length === 0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 4. 手写Object.assign()

主要是将所有可枚举属性的值从一个或多个源对象复制到目标对象,同时返回目标对象。

语法:

Object.assign(target, ...sources)
1

其中target是目标对象,sources是源对象,可以有多个,返回修改后的目标对象target。

如果目标对象中的属性具有相同的键,则属性值将被源对象中的属性值覆盖。

  • 示例
let a = {
    name: 'fmy',
    age: '26'
}
let b = {
    name: 'zlm',
    age: '27',
    weight: '50kg'
}
let c = {
    name: 'zky',
    sex: '女'
}

console.log(Object.assign(a, b, c))
console.log(a)
console.log(b)
console.log(c)

// 上面console的打印结果
{ name: 'zky', age: '27', weight: '50kg', sex: '女' }
{ name: 'zky', age: '27', weight: '50kg', sex: '女' }
{ name: 'zlm', age: '27', weight: '50kg' }
{ name: 'zky', sex: '女' }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if(typeof Object.assign2 !=='function') {
  // 注意1:原生情况下挂载到Object上的属性是不可枚举的,但是直接Object上挂载属性a之后是可枚举的,所以这里必须使用Object.defineProperty,并设置enumerable: false以及writable: true, configurable :true。当然默认情况下不设置就是 false。
  Object.defineProperty(Object, 'assign2', {
        value: function(target) {
            'use strict'
            // 注意2: 判断目标对象不能为undefined或null
            if(target == null || target == undefined) {
                throw new TypeEoor('Cannot convert null or undefined to object')
            }

            // TODO 没太明白这一步的意义
            // 注意3: Object.assign()目标对象是原始类型时,会包装成对象,这里使用Object(..)就可以了
            const to = Object(target)

            for(let i = 1; i< arguments.length; i++) {
                const nextSource = arguments[i]
                if(nextSource != null && nextSource != undefined) { // 注意2
                // 注意4:Object.assign 方法肯定不会拷贝原型链上的属性,所以模拟实现时需要用 hasOwnProperty(..) 判断处理下,但是直接使用 myObject.hasOwnProperty(..) 是有问题的,因为有的对象可能没有连接到 Object.prototype 上(比如通过 Object.create(null) 来创建),这种情况下,使用 myObject.hasOwnProperty(..) 就会失败,解决方法便是如下所示:
                for(const key in nextSource) {
                    if(Object.prototype.hasOwnProperty.call(nextSource, key)) {
                    to[key] = nextSource[key]
                    }
                }
                }
            }
            return to
        },
        writable: true,
        configurable: true
    })
}
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

# 5. 手写instanceof

instanceof原理就是一层一层查找__proto__,如果和构造函数的prototype相等则返回true,如果一直没有查找成功则返回false

// left 表达式左边的变量
// right 表达式右边的变量
function instanceofMy(left, right) {
    let target = right.prototype
    let L = left.__proto__
    while(true) {
        if (L === null) return false
        if (L === target) return true
        L = L.__proto__
    }
}
1
2
3
4
5
6
7
8
9
10
11

# 6. 手写ajax

function request(method, url) {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest()
        xhr.open(method, url)

        xhr.onreadystatechange = function() {
            if (this.readyState != 4) return
            if (this.status === 200) {
                resolve(this.response)
            } else {
                reject()
            }
        }

        xhr.onerror = function () {
            reject()
        }

        // 设置响应的数据类型
        xhr.responseType = 'json'
       
        // 设置请求头信息
        xhr.setRequestHeader('Accept', 'application/json')

        xhr.send()
    })
}
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

# 7. 手写promise

# 基础版实现(支持异步)

const PENDING = 'PENDING' // pending
const FULLFILLED = 'FULLFILLED' // 成功
const REJECTED = 'REJECTED' // 失败
class MyPromise {
    constructor(exector) {
        this.value = undefined // 成功时传入的数据
        this.reason = undefined // 失败时传入的数据
        this.status = PENDING

        this.onResolveCallBack = [] // 成功时的待执行函数列表
        this.onRejectedCallBack = [] // 失败时的待执行函数列表

        let resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULLFILLED
                this.value = value
                this.onResolveCallBack.forEach(fn => fn())
            }
        }

        let reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED
                this.reason = reason
                this.onRejectedCallBack.forEach(fn => fn())
            }
        }
        try {
            exector(resolve, reject)
        } catch(e) {
            reject(2)
        }
    }

    // then接受两个函数为入参
    then(onSuccess, onFailed) {
        if (this.status === FULLFILLED) onSuccess(this.value)
        if (this.status === REJECTED) onFailed(this.reason)

        // 当执行的是异步操作时,then方法比异步先执行,此时status为pending
        // 成功和失败待执行函数列表先推入一个执行函数,
        // 待异步函数在resolve或reject里面执行完成时,再从待执行函数列表取出执行
        if (this.status === PENDING) {
            this.onResolveCallBack.push(() => {
                onSuccess(this.value)
            })
            this.onRejectedCallBack.push(() => {
                onFailed(this.reason)
            })
        }
    }
}

let fmy = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功啦')
    }, 200)
}).then((data) => {
    console.log('1111', data)
}, (err) => {
    console.log('2222', err)
})
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

# 实现链式调用&值透传

# 值透传特性?

当我们不在 then 中放入参数,例:promise.then().then(),那么其后面的 then 依旧可以得到之前 then 返回的值,这就是所谓的值的穿透

# 什么是链式调用?

promise的优势是链式调用,当then函数中return了一个值,不管是什么值,都能在下一个then中获取到

如何实现

每次调用 then 的时候,我们都重新创建一个 promise 对象,并把上一个 then 的返回结果传给这个新的 promise 的 then 方法,不就可以一直 then 下去了么?那我们来试着实现一下。这也是手写 Promise 源码的重中之重

梳理思路&知识点

  1. then 的参数 onSuccess 和 onFailed 可以缺省,如果 onSuccess 或者 onFailed 不是函数,将其忽略,且依旧可以在下面的 then 中获取到之前返回的值;

  2. promise 可以 then 多次,每次执行完 promise.then 方法后返回的都是一个“新的promise";

  3. 如果 then 的返回值 x 是一个普通值,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调中;如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调中;

  4. 如果 then 的返回值 x 是一个 promise,那么会等这个 promise 执行完,promise 如果成功,就走下一个 then 的成功;如果失败,就走下一个 then 的失败;如果抛出异常,就走下一个 then 的失败;

  5. 如果 then 的返回值 x 和 promise 是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个 then 的失败的回调中;

  6. 如果 then 的返回值 x 是一个 promise,且 x 同时调用 resolve 函数和 reject 函数,则第一次调用优先,其他所有调用被忽略;

const PENDING = 'PENDING' // pending
const FULLFILLED = 'FULLFILLED' // 成功
const REJECTED = 'REJECTED' // 失败
class MyPromise {
    constructor(exector) {
        this.value = undefined // 成功时传入的数据
        this.reason = undefined // 失败时传入的数据
        this.status = PENDING

        this.onResolveCallBack = [] // 成功时的待执行函数列表
        this.onRejectedCallBack = [] // 失败时的待执行函数列表

        let resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULLFILLED
                this.value = value
                this.onResolveCallBack.forEach(fn => fn())
            }
        }

        let reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED
                this.reason = reason
                this.onRejectedCallBack.forEach(fn => fn())
            }
        }
        try {
            exector(resolve, reject)
        } catch(e) {
            reject(2)
        }
    }

    // then接受两个函数为入参
    then(onSuccess, onFailed) {

        // then参数期望是函数,传入非函数则会发生值穿透。
        // 值传透可以理解为,当传入then的不是函数的时候,这个then是无效的
        onSuccess = typeof onSuccess === 'function' ? onSuccess : val => val
        onFailed = typeof onFailed === 'function' ? onFailed : err => err
        return new MyPromise((resolve, reject) => {
            if (this.status === FULLFILLED) {
                try {
                    // 由于原生的 Promise 是V8引擎提供的微任务,我们无法还原V8引擎的实现,所以这里使用 setTimeout 模拟异步,所以原生的是微任务,这里是宏任务。
                    setTimeout(() => {
                        let result = onSuccess(this.value)
                        result instanceof MyPromise ? result.then(resolve, reject) : resolve(result)
                    }, 0)
                } catch (e) {
                    reject(e)
                }
            }
            if (this.status === REJECTED) {
                try {
                    setTimeout(() => {
                        let result = onFailed(this.reason)
                        result instanceof MyPromise ? result.then(resolve, reject) : resolve(result)
                    }, 0)
                } catch (e) {
                    reject(e)
                }
            }

            if (this.status === PENDING) {
                this.onResolveCallBack.push(() => {
                    try {
                        setTimeout(() => {
                            let result = onSuccess(this.value)
                            result instanceof MyPromise ? result.then(resolve, reject) : resolve(result)
                        }, 0)
                    } catch (e) {
                        reject(e)
                    }
                })
                this.onRejectedCallBack.push(() => {
                    try {
                        setTimeout(() => {
                            let result = onFailed(this.reason)
                            result instanceof MyPromise ? result.then(resolve, reject) :  reject(result)
                        }, 0)
                    } catch (e) {
                        reject(e)
                    }
                })
            }
        })
    }

    catch(onFailed) {
        return this.then(null, onFailed)
    }
}

// 测试功能
let p = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功啦')
    }, 200)
})

// 第一个then入参为函数,值么有透传
p.then(() => {}).then((data) => {
    console.log(data) // undefined
})

// 第一个then没有入参,值透传
p.then().then((data) => {
    console.log(data) // '成功啦'
})

// 第一个then入参函数,但是么有返回值
p.then((res) => {
    console.log(res) // '成功啦'
}).then((data) => {
    console.log(data) // undefined
})

// 第一个then入参函数,return '测试'
p.then((res) => {
    console.log(res) // '成功啦'
    return '测试'
}).then((data) => {
    console.log(data) // '测试'
})
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125

# Promise.resolve()

static

ES6 类(class)通过 static 关键字定义静态方法。不能在类的实例上调用静态方法,而应该通过类本身调用。这些通常是实用程序方法

static resolve(data) {
    return new MyPromise((resolve, reject) => {
        resolve(data)
    })
}
1
2
3
4
5

测试功能

MyPromise.resolve(
    new MyPromise((resolve, reject) => {
        resolve('ok')
    })
).then(data => {
    console.log(data)
})
1
2
3
4
5
6
7

# Promise.reject()

static reject(value) {
    return new MyPromise((resolve, reject) => {
        reject(value)
    })
}
1
2
3
4
5

# Promise.all()

TIP

入参为数组

  • 如果所有传入的 promise 都变为完成状态,Promise.all 返回,并且结果是一个数组,与入参的顺序一一对应。
  • 如果传入的 promise 中有一个失败(rejected),Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成
static all(arr) {
    let len = arr.length
    let result = []
    return new MyPromise((resolve, reject) => {
        let count = 0
        for(let i =0; i< len; i++) {
            MyPromise.resolve(arr[i]).then((data) => {
                count++
                result[i] = data
                if (count === len) resolve(result)
            }).catch(err => {
                reject(err)
            }
        }
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Promise.allSettled()

TIP

入参为数组

  • 该Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果
static allSettled(arr) {
    let result = []
    return new MyPromise((resolve, reject) => {
        for(let i = 0; < arr.length; i++) {
            MyPromise.resolve(arr[i]).then((value) => {
                result[i] = value
                if (result.length === arr.length) resolve(result)
            }, err => {
                result[i] = err
                if (result.length === arr.length) resolve(result)
            })
        }
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Promise.race()

TIP

入参为数组

只要给定的迭代中的一个promise解决或拒绝,就采用第一个promise的值作为它的值

static race(arr) {
    return new MyPromise((resolve, reject) => {
        arr.forEach(p => {
            MyPromise.resolve(p).then((value) => {
                resolve(value)
            }, err => {
                reject(err)
            })
        })
    })
}
1
2
3
4
5
6
7
8
9
10
11

# Promise.finally()

无论成功或者失败都会执行的函数

static finally(callback) {
    return this.then((val) => {
        return MyPromise.resolve(callback()).then(() => val)
    }, err => {
        return MyPromise.resolve(callback()).then(() => {
            throw err
        })
    })
}
1
2
3
4
5
6
7
8
9

# 8. 手写eventBus

发布订阅模式

class EventBus {
    constructor() {
        this.events = {}
    }

    on(name, fn) {
        if (this.events[name]) {
            this.events[name].push(fn)
        } else {
            this.events[name] = [fn]
        }
    }

    emit(name, ...args) {
        if (this.events[name]) {
            let arr = this.events[name]
            for (let fn of arr) {
                fn(...args)
            }
        }
    }

    off(name, fn) {
        let arr = this.events[name]
        if (arr.indexOf(fn) > -1) {
            arr.splice(arr.indexOf(fn), 1)
        }
    }
}

let a = new EventBus()
// 测试
let fn1 = function(name, age) {
	console.log(`${name} ${age}`)
}
let fn2 = function(name, age) {
	console.log(`hello, ${name} ${age}`)
}
let fn3 = function(name, age) {
	console.log(`hello3, ${name} ${age}`)
}
a.on('aaa', fn1)
a.on('aaa', fn2)
a.on('aaa', fn3)
a.off('aaa', fn3)
a.emit('aaa', true, 'fmy', 26)
// fn3没有触发,打印了fn1和fn2
// fmy 26
// hello, fmy 26
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

# 9. 手写函数柯里化

/**
 * 函数
 * @param fun    待柯里化的原函数
 * @param len   fun.length可得到函数参数个数
 * @param args  已接收的参数列表
 */
function curry(fun, ...args) {
   return function () {
       args = [...args, ...arguments]
        if(args.length >= fun.length) {
            return fun.apply(this, args)
        } else {
            return curry.call(this, fun, ...args)
        }
   } 
}

// 验证一下
let _fn = curry(function(a,b,c,d,e) {
    return a + b + c + d + e
})

console.log(_fn(1,2,3,4,5)) // 15
console.log(_fn(1)(2)(3,4,5)) // 15
console.log(_fn(1,2)(3,4)(5)) // 15
console.log(_fn(1)(2)(3)(4)(5)) // 15
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

上面的方法不好拓展7个、8个更多个参数,改进版本如下:

function add () {
    var args = [...arguments]
    var fn = function () {
        args = [...args, ...arguments];
        return add.apply(null, args);
    };
    fn.valueOf = function () {
        return args.reduce(function (a, b) {
            return a + b;
        });
    };
    return fn;
}
console.log(add(1)(2, 3)(7).valueOf())
1
2
3
4
5
6
7
8
9
10
11
12
13
14

缺点,最终返回的是一个函数

# 10. 手写call、apply、bind

三者都是改变this指向

# call

语法

fun.call(thisArg, param1, param2, ...)

返回fun执行的结果

Function.prototype.myCall = function (context) {
    context = context || window
    context.fn = this // 函数的this指向隐式绑定到context上
    let args = [...arguments].slice(1)
    let result = context.fn(...args)
    delete context.fn
    return fn
}
1
2
3
4
5
6
7
8

# apply

apply和call类似,区别是apply参数以数组形式传递

语法

fun.apply(thisArg, [param1,param2,...])

返回fun执行的结果

Function.prototype.myApply = function(context) {
    context = context || window
    context.fn = this // 函数的this指向隐式绑定到context上
    let result
    if (arguments[1]) result = context.fn(arguments[1])
    else result = context.fn()
    delete context.fn
    return result
}
1
2
3
4
5
6
7
8
9

# bind

bind转换后不是立即执行的,而是返回一个函数,这个函数可以作为构造函数使用,此时this应该指向构造出的实例,而不是bind绑定的第一个参数

语法

fun.bind(thisArg, param1, param2, ...)

返回fun的拷贝,并拥有指定的this值和初始参数

Function.prototype.myBind = function (context) {
    context.fn = this
    let _this = this
    let args = [...arguments].slice(1)
    let result = function() {
        // this是否是result的实例 也就是返回的result是否通过new调用
        const isNew = this instanceof result 
        // new调用就绑定到this上,否则就绑定到传入的context上
        const contextChild = isNew ? this : Object(context)
        return _this.call(contextChild, ...args, ...arguments)
    }
    // 复制源函数的prototype给result 一些情况下函数没有prototype,比如箭头函数
    if (_this.prototype) result.prototype = Object.create(_this.prototype)
    return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15