JS 原理的实现 实现一个 Promise() Promise 的特点:
new Promise 时需要传递一个函数 fn 作为执行器(执行器会立刻执行)
执行器中传递了两个参数:resolve 成功的函数、reject 失败的函数(他们调用时可以接受任何值的参数 value)
promise 状态只能从 pending 态转到 resolved 或者 rejected,如果状态发生改变执行相应缓存队列中的任务
promise 实例,每个实例都有一个 then 方法,这个方法传递两个参数,一个是成功回调 onfulfilled,另一个是失败回调 onrejected
promise 实例调用 then 时,会判断当前状态,如果 pending,就…;如果 resolved,就让 onfulfilled 执行;如果 rejectd,就 onrejected 执行
promise 中可以同一个实例 then 多次,如果状态是 pengding 需要将函数存放起来 等待状态确定后 在依次将对应的函数执行 (发布订阅)
基于上述特点,实现的 Promise 如下
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 function MyPromise (fn ) { const that = this ; this .value = null ; this .status = "pending" ; this .resolvedAry = []; this .rejectedAry = []; function resolve (value ) { if (that.status === "pending" ) { that.status === "resolved" ; that.value = value; that.resolvedAry .map (cb => cb (that.value )); } } function reject (value ) { if (that.status === "pending" ) { that.status = "rejected" ; that.value = value; that.rejectedAry .map (cb => cb (that.value )); } } try { fn (resolve, reject); } catch (e) { reject (e); } } MyPromise .prototype .then = function (onfulfilled, onrejected ) { const that = this ; onfulfilled = typeof onfulfilled === "function" ? onfulfilled : f => f; onrejected = typeof onrejected === "function" ? onrejected : e => { throw e; }; if (this .status === "pending" ) { this .resolvedAry .push (onfulfilled); this .rejectedAry .push (onrejected); } if (this .status === "resolved" ) { onfulfilled (that.value ); } if (this .status === "rejected" ) { onrejected (that.value ); } };
实现Promise.all() Promise.all 接收一个 promise 对象的数组作为参数,当这个数组里的所有 promise 对象全部变为resolve或 有 reject 状态出现的时候,它才会去调用 .then 方法,它们是并发执行的。eg:
1 2 3 4 5 6 var p1 = Promise.resolve(1), p2 = Promise.resolve(2), p3 = Promise.resolve(3); // 若任一 Promise 状态是reject,则无法执行 .then Promise.all([p1, p2, p3]).then(function (results) { console.log(results); // [1, 2, 3] });
总结 promise.all 的特点
接收一个 Promise 实例的数组或具有 Iterator 接口的对象
如果元素不是 Promise 对象,则使用 Promise.resolve 转成 Promise 对象
如果全部成功,状态变为 resolved,返回值将组成一个数组传给回调
只要有一个失败,状态就变为 rejected,返回值将直接传递给回调all() 的返回值也是新的 Promise 对象
实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function promiseAll(promises) { return new Promise(function(resolve, reject) { if (!isArray(promises)) { return reject(new TypeError('arguments must be an array')); } var resolvedCounter = 0; var promiseNum = promises.length; var resolvedValues = new Array(promiseNum); for (var i = 0; i < promiseNum; i++) { (function(i) { Promise.resolve(promises[i]).then(function(value) { resolvedCounter++ resolvedValues[i] = value if (resolvedCounter == promiseNum) { return resolve(resolvedValues) } }, function(reason) { return reject(reason) }) })(i) } }) }
手写实现 call(), apply(), bind() call()
首先 context 为可选参数,如果不传的话默认上下文为 window
接下来给 context 创建一个 fn 属性,并将值设置为需要调用的函数
因为 call 可以传入多个参数作为调用函数的参数,所以需要将参数剥离出来
然后调用函数并将对象上的函数删除
1 2 3 4 5 6 7 8 9 10 11 Function .prototype .myCall = function (context ) { if (typeof this !== "function" ) { throw new TypeError ("Error" ); } context = context || window ; context.fn = this ; const args = [...arguments ].slice (1 ); const result = context.fn (...args); delete context.fn ; return result; };
apply() 类似,区别在于对参数的处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Function .prototype .myApply = function (context ) { if (typeof this !== "function" ) { throw new TypeError ("Error" ); } context = context || window ; context.fn = this ; let result; if (arguments [1 ]) { result = context.fn (...arguments [1 ]); } else { result = context.fn (); } delete context.fn ; return result; };
bind() bind 返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过 new 的方式
对于直接调用来说,这里选择了 apply 的方式实现,但是对于参数需要注意以下情况:因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2)
,所以我们需要将两边的参数拼接起来,于是就有了这样的实现 args.concat(...arguments)
通过 new 的方式,在之前的章节中我们学习过如何判断 this,对于 new 的情况来说,不会被任何方式改变 this,所以对于这种情况我们需要忽略传入的 this。(似乎这种 new 的用法很少见)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Function .prototype .myBind = function (context ) { if (typeof this !== "function" ) { throw new TypeError ("Error" ); } const _this = this ; const args = [...arguments ].slice (1 ); return function F ( ) { if (this instanceof F) { return new _this (...args, ...arguments ); } return _this.apply (context, args.concat (...arguments )); }; };
instanceof instanceof 可以正确的判断对象的类型,其内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。实现原理如下:
首先获取类型的原型
然后获得对象的原型
然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null
1 2 3 4 5 6 7 8 9 function muInstanceof (left, right ) { let prototype = right.prototype ; left = left.__proto__ ; while (true ) { if (left === null || left === undefined ) return false ; if (prototype === left) return true ; left = left.__proto__ ; } }
手写实现 new 在调用 new 的过程中会发生四件事情:
新生成了一个对象
链接到原型
绑定 this(使用构造函数的 this)
返回新对象(原始类型的话忽略,如果是引用类型的话就返回这个对象)
1 2 3 4 5 6 7 Function .prototype .myNew () = function (func, ...args ) { let obj = {}; obj._proto_ = func.prototype ; let result = func.apply (obj, args); return result instanceof Object ? result : obj; };
手写实现 Object.create() Object.create()方法创建一个新对象,使用参数对象(而不是参数对象.prtototype)来提供新创建的对象的__proto__
1 2 3 4 5 Object .prototype .mycreate = function (obj ) { function F ( ) {} F.prototype = obj; return new F (); };
0.1 +0.2 !== 0.3 对于纯小数来说,十进制的 0.375 会被存储为: 0.011 其代表 (1/2)^2 + (1/2)^3 = 1/4 + 1/8 = 0.375 但是对 0.1 这种数值来说,算下来是 0.000110011… 由于存储空间有限,最后计算机会舍弃后面的数值,所以我们最后就只能得到一个近似值。JS 采用 IEEE 754 双精度版本(64 位)浮点数标准,也是只能取得一个近似值。除了那些能表示成 (x/2)^n 的数可以被精确表示以外,其余小数都是以近似值得方式存在的。
1 2 3 4 5 console .log (0.1000000000000001 );console .log (0.10000000000000001 );
1 2 3 0.100000000000000002 === 0.1 ; 0.200000000000000002 === 0.2 ; 0.1 + 0.2 === 0.30000000000000004 ;
解决:
1 parseFloat ((0.1 + 0.2 ).toFixed (10 )) === 0.3 ;
双向数据绑定实现原理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 let obj = {};let input = document .getElementById ("input" );let span = document .getElementById ("span" );Object .defineProperty (obj, "text" , { configurable : true , enumerable : true , get ( ) { console .log ("获取数据" ); }, set (newVal ) { console .log ("数据更新" ); input.value = newVal; span.innerHTML = newVal; } }); input.addEventListener ("keyup" , function (e ) { obj.text = e.target .value ; });
JS 功能的实现 数组去重 1 let arr = [1 , 5 , 8 , 3 , 7 , 5 , 1 , 8 , 100 ];
1 2 3 4 5 6 let result = arr.sort ().reduce ((acc, curr ) => { if (acc.length === 0 || curr !== acc[acc.length - 1 ]) { acc.push (curr); } return acc; }, []);
1 let result = Array .from (new Set (arr));
for…of + Object (性能最优)。利用对象的属性不会重复这一特性,校验数组元素是否重复
1 2 3 4 5 6 7 8 let result = [];obj = {}; for (i of arr) { if (!obj[i]) { result.push (i); obj[i] = 1 ; } }
数组扁平化 1 let arr = (arr = [1 , [2 , [3 , [4 , 5 , , "tset" ]]], 6 ]);
Array.prototype.flat()。参数是要提取的嵌套数组的深度(数字),默认值是 1;也可以直接写 Infinity 作为参数。但是会移除数组中的空项
1 let result = arr.flat (Infinity );
1 2 3 4 5 6 7 function myFlat (arr ) { return arr.reduce ( (acc, curr ) => acc.concat (Array .isArray (curr) ? myFlat (curr) : curr), [] ); }
扩展运算符 + Array.prototype.some() 这里的 isArray 用法稍微有点特殊。并且空项不会被移除,会保留为undefined
1 2 3 while (arr.some (Array .isArray )) { arr = [].concat (...arr); }
函数的 arguement 怎么转数组 Array.from(), 扩展运算符
for…in, for…of, forEach() for…in 一般用来遍历对象。有几个缺点:
for…in 会遍历手动添加的原型链上的键。比如你自己写个Object.prototype.test = function { xxx }
,是能遍历到这个test
属性的
用来遍历数组的时候是遍历数组的键名(index),并且这个 index 是字符串
某些情况下,for…in 循环会以任意顺序遍历键名
for…of
遍历数组得到数组的 value
for (let [k, v] of Object.entries(obj)) { }
用来遍历对象,能获取对象的键和键值
1 2 3 4 5 6 7 8 9 let obj = { name : "jacle" , age : 22 , job : "student" }; let arr = ["jacle" , 22 , "student" ];for (let [key, value] of Object .entries (obj)) { console .log (key, ":" , value); }
forEach() 遍历数组,问题是无法用 break,return 跳出循环 如何中断 forEach 循环:
使用 try 监视代码块,在需要中断的地方抛出异常
官方推荐方法(替换方法):用 every 和 some 替代 forEach 函数。every 在碰到 return false 的时候,中止循环。some 在碰到 return ture 的时候,中止循环
数组中是否包含某个值
Array.prototype.indexOf(值)
。返回 index 或 -1 。该方法也可用于 String
Array.prototype.includes(值)
。返回布尔值。该方法也可用于 String
Array.prototype.find(callback[,thisVal])
。返回数组中满足条件的第一个元素的值 ,如果没有,返回 undefined
1 2 let arr = [1 , 2 , 3 , 4 ];let result = arr.find (item => item > 100 );
Array.prototype.findeIndex(callback[,thisArg])
。返回数组中满足条件的第一个元素的下标,如果没有找到,返回-1]
对象按规则排序 有一个数组,里面都是对象,现在要针对对象中的某一个 key 进行排序,顺序是已给定的数组。 比如原数组为[{a:'ww'},{a:'ff'},{a:'pe'}]
, 顺序是[{ww:1},{pe:3},{hf:2},{oo:4},{ff:5}]
那么输出是 [{a:'ww'},{a:'pe'},{a:'ff'}]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var objarr = [{ a : "ww" }, { a : "ff" }, { a : "pe" }];var rulearr = [{ ww : 1 }, { pe : 3 }, { hf : 2 }, { oo : 4 }, { ff : 5 }];function sortByRule (objarr, key, rulearr ) { let ResultArr = []; rulearr.forEach (item => { let value = Object .getOwnPropertyNames (item)[0 ]; let order = item[value]; ResultArr [order] = objarr.find (item => item[key] === value); }); return ResultArr .filter (item => item); }
n 到 m 范围的随机整数数
Math.random()生成[0, 1)的数,所以
要整数有向下取整 Math.floor(),向上 Math.ceil()。所以[1, m]的数是
1 2 3 Math .ceil (Math .random () * m); Math .floor (Math .random () * m) + 1 ; Math .floor (Math .random () * m);
1 Math .floor (Math .random () * (max - min + 1 ) + min);
深拷贝与浅拷贝的实现 赋值(=),浅拷贝与深拷贝的区别
浅拷贝 浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据
Object.assign 只会拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,是浅拷贝
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。Object.assign(target, source)。直接用 Object.assign(target, source) 这种用法就能更新target对象。当然也能用来赋值新对象
1 2 3 4 5 6 let a = { age : 1 }; let b = Object .assign ({}, a);a.age = 2 ; console .log (b.age );
1 2 3 4 5 6 let a = { age : 1 }; let b = { ...a };a.age = 2 ; console .log (b.age );
数据的浅拷贝还有 let arr2 = [].concat(arr1)
和 let arr2 = arr1.slice()
深拷贝 对对象以及对象的所有子对象进行拷贝 通过 JSON.parse(JSON.stringify(object))来实现
1 2 3 4 5 6 7 8 9 let a = { age : 1 , jobs : { first : "FE" } }; let b = JSON .parse (JSON .stringify (a));a.jobs .first = "native" ; console .log (b.jobs .first );
局限性 :会忽略 undefined;会忽略 symbol;不能序列化函数;不能解决循环引用的对象 自己实现一个深拷贝的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 function deepClone (obj ) { function isObj (o ) { return (typeof o === "object" || typeof o === "function" ) & (o !== null ); } let newobj = Array .isArray (obj) ? [...obj] : { ...obj }; Reflect .ownKeys (newobj).forEach (key => { newobj[key] = isObj (obj[key]) ? deepClone (obj[key]) : obj[key]; }); return newobj; }
时间戳的获取 时间戳是指格林威治时间1970年01月01日00时00分00秒起至当下的总秒数。JS中拿到时间戳:
1 2 3 4 5 6 7 8 9 10 11 第一种方法: var timestamp = Date.parse(new Date()); 第二种方法: var timestamp = (new Date()).valueOf(); 第三种方法: var timestamp = new Date().getTime(); 第四种方法: var timestamp = Date.now();
参考资料实现 JavaScript 异步方法 Promise.all