为了更好地理解一些js方法的原理,决定自己捏捏轮子
call,apply,bind
call()
| Function.prototype.myCall = function(...args){ const newThis = args[0] || window; const realArgs = args.slice(1); const func = this;
const funcSym = Symbol('func'); newThis[funcSym] = func; const res = newThis[funcSym](...realArgs); delete newThis[funcSym]; return res; }
|
apply()
apply()
与call()
之间除了接收的参数不同,可以说没有其他区别。因此大致的实现是和call()
一样的,除了在处理接收到参数方面点区别,这里就不贴了。
bind()
bind()
是ES5引入的函数。bind()
与call()
的区别在于它不会立即执行函数,而是返回一个修改了this
的新函数,执行需要调用该新函数。
前面已经实现了call()
, 这里就直接借用call()
来实现了。
| Function.prototype.myBind = function(...args){ const newThis = args[0]; const realArgs = args.slice(1); const func = this; return function(...newArgs){ return func.call(newThis, ...realArgs, ...newArgs) } }
|
Promise
Promise极简
1.实现了基本的异步、回调功能。(then和catch)
2.未实现链式调用,如.then().catch()
Promise()
的参数是一个带有resolve
和reject
两个参数的函数,据此我们可以先抽象出MyPromise的结构
| function MyPromise(executor) { this.status = "pending" executor(resolve.bind(this), reject.bind(this))
function resolve(params) {} function reject(error) {} } MyPromise.prototype.then = function (resolve, reject) {}
|
实现resolve()
,rejected()
和then()
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
| function MyPromise(executor) { this.status = "pending" this.successCallback = undefined this.failCallback = undefined
executor(resolve.bind(this), reject.bind(this))
function resolve(params) { if (this.status === "pending"){ setTimeout(() => { this.status = "fulfilled" this.successCallback(params) }, 0); } } function reject(error) { if(this.status === "pending"){ setTimeout(() => { this.status = "rejected" this.failCallback(error) }, 0); } } }
MyPromise.prototype.then = function (resolve, reject) { this.successCallback = resolve this.failCallback = reject }
MyPromise.prototype.catch = function (reject) { this.failCallback = reject }
|
测试
| const p4 = new MyPromise((resolve, reject) => { console.log(1) resolve(2) console.log(3) }).then(v => console.log(v))
const p1 = new MyPromise((resolve, reject) => { reject("err-1") }).catch(err=> console.log("err:", err))
|
Promise.any()
虽然说我们无法使用.then().catch()
这种链式调用来捕获错误,但实际上then()
本身就可以捕获错误了(利用第二个参数reject
),因此实现MyPromise.any()
这里我就只使用then()
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
| MyPromise.any = function (list) { return new MyPromise((resolve, reject) => { let isFulfilled = false let len = 0 let AggregateError = [] for (i of list) { i.then( (value) => { if (!isFulfilled) { isFulfilled = true resolve(value) } }, (error) => { len++ AggregateError.push(error) if (len === list.length) { reject(AggregateError) } } ) } }) }
|
测试
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
| const p1 = new MyPromise((resolve, reject) => { reject("err-1") }) const p2 = new MyPromise((resolve, reject) => { reject("err-2") }) const p3 = new MyPromise((resolve, reject) => { resolve(3) }) let p = new MyPromise.any([p1, p2, p3]).then( (v) => { console.log("any:", v) }, (err) => { console.log("any-err", err) } )
const p1 = new MyPromise((resolve, reject) => { reject("err-1") }) const p2 = new MyPromise((resolve, reject) => { reject("err-2") }) const p3 = new MyPromise((resolve, reject) => { reject("err-3") }) let p = new MyPromise.any([p1, p2, p3]).then( (v) => { console.log("any:", v) }, (err) => { console.log("any-err", err) } )
|
还有一个神奇的实现思路是利用Promise.all()。把所有Promise
实例(包括Promise.all()
本身)的resolve
和 reject
参数位置交换一下,这样就可以把all()
转换成any()
了。这是利用了和any()
和all()
二者存在类似于与
和或
的关系(数电学过吧),因此把all()
的逻辑取反就得到了any()
,代码如下(这个是网上抄的,我找不到原文了)
| function reverse (promise) { return new Promise( (resolve, reject) => Promise.resolve(promise).then(reject, resolve)) } MyPromise.any = (iterable) => reverse(Promise.all([...iterable].map(reverse)))
|
测试
| MyPromise.any([ Promise.reject('✗'), Promise.reject('✗'), Promise.resolve('✓'), ]).then(function(value) { console.log(value) });
MyPromise.any([ Promise.reject('✗'), Promise.reject('✗'), ]).catch(function(reasons) { console.log(reasons) });
|
ajax
相关函数/属性
XMLHttpRequest.open( method, url, async ) – 初始化一个请求。详见
async:是否异步
XMLHttpRequest.send() – 发送 HTTP 请求。详见
异步请求(默认):会在请求发送后立即返回;
同步请求:直到响应到达后才会返回。
XMLHttpRequest.onreadystatechange –只要 readyState
值发生变化,就会执行
XMLHttpRequest.readyState – XMLHttpRequest 的状态码
值 |
状态 |
说明 |
0 |
UNSENT |
代理被创建,但尚未调用 open() 方法。 |
1 |
OPENED |
open() 方法已经被调用。 |
2 |
HEADERS_RECEIVED |
send() 方法已经被调用,并且头部和状态已经可获得。 |
3 |
LOADING |
下载中; responseText 属性已经包含部分数据。 |
4 |
DONE |
下载操作已完成。 |
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let xhr = null xhr = new XMLHttpRequest(); if (xhr != null) { xhr.onreadystatechange = stateChange xhr.open('GET', 'http://127.0.0.1:3000/', false) xhr.send() } function stateChange() { if (xhr.readyState === 4) { if (xhr.status === 200) { console.log(xhr.responseText) alert('success') } else { console.log(xhr.status) alert("something error") } } }
|
请求结果判断200或者0状态就可以了,跳转不需要判断的,因为返回的是最后跳转的网址的状态
另外,ie6以前没有XMLHttpRequest….so,改用这个
| xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
|
深拷贝
预处理对象obj1
| let obj1 = { id: '1', name: 'litstronger', hobbies: null, childObj: { test: "deepClone", }, childArray: [1, 2, 3], regExp: /a/, func: function () { console.log(1) }, arrowFunc: () => { console.log('arrow func') } }
|
利用JSON处理
使用JSON来深拷贝应该是比较实用的,简单且能满足大多数场景下的需求。缺点是无法处理函数
,正则
这两种对象。
| console.log(JSON.parse(JSON.stringify(obj1)))
|
output:
| { id: 1, name: 'litstronger', hobbies: null, childObj: { test: 'deepClone' }, childArray: [ 1, 2, 3 ], regExp: {} }
|
可以看到处理后正则变空,函数直接整个没了。
手写递归处理
1.简陋结构
| function deepClone(target){ if(target instanceof Object){ let tempObj = {} for(let key in target){ tempObj[key] = deepClone(target[key]) } return tempObj } else{ return target } }
|
output:
| { id: '1', name: 'litstronger', hobbies: null, childObj: { test: 'deepClone' }, childArray: { '0': 1, '1': 2, '2': 3 }, regExp: {}, func: {}, arrowFunc: {} }
|
显然,这里只能处理最基本的对象{}
,数组的话需要再做处理。
2.处理数组
也不难,因为基本对象和数组都可以用中括号[]进行属性访问,所以在处理上是一样的。只需要在创建tempObj时加个判断
| let tempObj = Array.isArray(target)?[]:{}
|
如下
| function deepClone(target){ if(target instanceof Object){ let tempObj = Array.isArray(target)?[]:{} for(let key in target){ tempObj[key] = deepClone(target[key]) } return tempObj } else{ return target } }
|
output:
| { id: '1', name: 'litstronger', hobbies: null, childObj: { test: 'deepClone' }, childArray: [ 1, 2, 3 ], regExp: {}, func: {}, arrowFunc: {} }
|
可见,数组拷贝也正常了
3.循环引用
咕。 (咕了的终究还是要还的)
循环引用就是对象的引用出现了循环,比如
| obj1.childObj2 = obj1 deepClone(obj1)
|
解决的思路类似于去重,可以建一个Map来记录已经处理过的属性,以原对象的属性作为键(target),以拷贝的内容作为值(tempObj)
如果Map中已经有记录了就直接返回记录值,否则新增键值对。如下
| m = new Map() if (m.has(target)) { return m.get(target) } m.set(target, tempObj)
|
由于每次递归调用中我们都需要用到Map的最新内容,考虑把它作为函数参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function deepClone(target, m = new Map()) { if (target instanceof Object) { let tempObj = Array.isArray(target) ? [] : {} if (m.has(target)) { return m.get(target) } m.set(target, tempObj) for (let key in target) { tempObj[key] = deepClone(target[key], m) } return tempObj } else { return target } } obj1.childObj2 = obj1 obj1.childObj.childObj2 = obj1 console.log(deepClone(obj1))
|
output
| <ref *1> { id: '1', name: 'litstronger', hobbies: null, childObj: { test: 'deepClone', childObj2: [Circular *1] }, childArray: [ 1, 2, 3 ], regExp: {}, func: {}, arrowFunc: {}, childObj2: [Circular *1] }
|
4.处理函数和正则
啊。这两个不处理心里别扭啊。(别扭就别扭吧,反正没啥用orz
数组
数组需要考虑两种情况:普通函数、箭头函数。
区分:可以利用箭头函数没有prototype
的特点
拷贝:
综上,对函数的处理如下
| if (typeOfValue('Function')(target)) { if (target.prototype){ return target.bind(null) } else { return eval(target.toString()) } }
|
这个我想了蛮久了,网上找到的方法似乎都挺麻烦的,比如用正则匹配参数和函数体。
lodash库的深拷贝对函数的处理也是直接返回。可能拷贝函数真没什么应用场景吧。
截取代码如下
| const isFunc = typeof value == 'function' if (isFunc || !cloneableTags[tag]) { return object ? value : {} }
|
正则
使用RegExp()新创建一个正则需要两个参数,分别是主体内容部分(source)和标志(flags),比如在/a/i
中
| let reg = /a/i console.log(reg.source, reg.flags)
let reg2 = RegExp(reg.source, reg.flags) console.log(reg === reg2)
|
至于正则内部的其他属性,还是需要遍历拷贝,使用前文的拷贝方式就可以了。
5.判断类型
用于区分基本对象、数组、函数和正则
| let typeOfValue = type => v => `[object ${type}]` === Object.prototype.toString.call(v)
|
6.合并
把上述代码块整合起来(除了循环引用部分),看看结果吧
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
| let obj1 = { id: '1', name: 'litstronger', age: '18', childObj: { test: "deepClone", }, childArray: [1, 2, 3], regExp: /a/, func: function () { console.log(1) }, arrowFunc: () => { console.log('arrow func') } }
let typeOfValue = type => v => `[object ${type}]` === Object.prototype.toString.call(v)
function deepClone(target) { if (target instanceof Object) { let tempObj if (typeOfValue('RegExp')(target)) { tempObj = new RegExp(target.source, target.flags) } else if (typeOfValue('Function')(target)) { if (target.prototype) return target.bind(null) else { return eval(target.toString()) } } else { tempObj = Array.isArray(target) ? [] : {} }
for(let key in target){ tempObj[key] = deepClone(target[key]) } return tempObj } else { return target } } console.log(obj2) console.log("正则", obj1.regExp === obj2.regExp) console.log("普通函数", obj1.func === obj2.func) console.log("箭头函数", obj1.arrowFunc === obj2.arrowFunc)
|
output:
| { id: '1', name: 'litstronger', age: '18', childObj: { test: 'deepClone' }, childArray: [ 1, 2, 3 ], regExp: /a/, func: [Function: bound func], arrowFunc: [Function (anonymous)] } 正则 false 普通函数 false 箭头函数 false
|