# 手写代码
# 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('')
}
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
}
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
}
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)
其中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: '女' }
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
})
}
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__
}
}
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()
})
}
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)
})
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 源码的重中之重
梳理思路&知识点
then 的参数 onSuccess 和 onFailed 可以缺省,如果 onSuccess 或者 onFailed 不是函数,将其忽略,且依旧可以在下面的 then 中获取到之前返回的值;
promise 可以 then 多次,每次执行完 promise.then 方法后返回的都是一个“新的promise";
如果 then 的返回值 x 是一个普通值,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调中;如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调中;
如果 then 的返回值 x 是一个 promise,那么会等这个 promise 执行完,promise 如果成功,就走下一个 then 的成功;如果失败,就走下一个 then 的失败;如果抛出异常,就走下一个 then 的失败;
如果 then 的返回值 x 和 promise 是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个 then 的失败的回调中;
如果 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) // '测试'
})
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)
})
}
2
3
4
5
测试功能
MyPromise.resolve(
new MyPromise((resolve, reject) => {
resolve('ok')
})
).then(data => {
console.log(data)
})
2
3
4
5
6
7
# Promise.reject()
static reject(value) {
return new MyPromise((resolve, reject) => {
reject(value)
})
}
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)
}
}
})
}
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)
})
}
})
}
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)
})
})
})
}
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
})
})
}
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
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
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())
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
}
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
}
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
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15