网站建设是在商标哪个类别,网站代码特效广告,网站背景视频是怎么做的,网站设计制作费--------------------
实现防抖函数#xff08;debounce#xff09; 防抖函数原理#xff1a;把触发非常频繁的事件合并成一次去执行 在指定时间内只执行一次回调函数#xff0c;如果在指定的时间内又触发了该事件#xff0c;则回调函数的执行时间会基于此刻重新开始计算…--------------------
实现防抖函数debounce 防抖函数原理把触发非常频繁的事件合并成一次去执行 在指定时间内只执行一次回调函数如果在指定的时间内又触发了该事件则回调函数的执行时间会基于此刻重新开始计算 防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行节流是将多次执行变成每隔一段时间执行 eg. 像百度搜索就应该用防抖当我连续不断输入时不会发送请求当我一段时间内不输入了才会发送一次请求如果小于这段时间继续输入的话时间会重新计算也不会发送请求。 手写简化版:
// func是用户传入需要防抖的函数
// wait是等待时间
const debounce (func, wait 50) {// 缓存一个定时器idlet timer 0// 这里返回的函数是每次用户实际调用的防抖函数// 如果已经设定过定时器了就清空上一次的定时器// 开始一个新的定时器延迟执行用户传入的方法return function(...args) {if (timer) clearTimeout(timer)timer setTimeout(() {func.apply(this, args)}, wait)}
}适用场景
文本输入的验证连续输入文字后发送 AJAX 请求进行验证验证一次就好按钮提交场景防止多次提交按钮只执行最后提交的一次服务端验证场景表单验证需要服务端配合只执行一段连续的输入事件的最后一次还有搜索联想词功能类似
--------------------
实现节流函数throttle 节流函数原理:指频繁触发事件时只会在指定的时间段内执行事件回调即触发事件间隔大于等于指定的时间才会执行回调函数。总结起来就是 事件按照一段时间的间隔来进行触发 。 像dom的拖拽如果用消抖的话就会出现卡顿的感觉因为只在停止的时候执行了一次这个时候就应该用节流在一定时间内多次执行会流畅很多 手写简版
使用时间戳的节流函数会在第一次触发事件时立即执行以后每过 wait 秒之后才执行一次并且最后一次触发事件不会被执行
时间戳方式
// func是用户传入需要防抖的函数
// wait是等待时间
const throttle (func, wait 50) {// 上一次执行该函数的时间let lastTime 0return function(...args) {// 当前时间let now new Date()// 将当前时间和上一次执行函数时间对比// 如果差值大于设置的等待时间就执行函数if (now - lastTime wait) {lastTime nowfunc.apply(this, args)}}
}setInterval(throttle(() {console.log(1)}, 500),1
)定时器方式 使用定时器的节流函数在第一次触发时不会执行而是在 delay 秒之后才执行当最后一次停止触发后还会再执行一次函数 function throttle(func, delay){var timer null;returnfunction(){var context this;var args arguments;if(!timer){timer setTimeout(function(){func.apply(context, args);timer null;},delay);}}
}适用场景
DOM 元素的拖拽功能实现mousemove搜索联想keyup计算鼠标移动的距离mousemoveCanvas 模拟画板功能mousemove监听滚动事件判断是否到页面底部自动加载更多拖拽场景固定时间内只执行一次防止超高频次触发位置变动缩放场景监控浏览器resize动画场景避免短时间内多次触发动画引起性能问题
总结
函数防抖 将几次操作合并为一次操作进行。原理是维护一个计时器规定在delay时间后触发函数但是在delay时间内再次触发的话就会取消之前的计时器而重新设置。这样一来只有最后一次操作能被触发。函数节流 使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。
--------------------
实现instanceOf
思路
步骤1先取得当前类的原型当前实例对象的原型链步骤2一直循环执行原型链的查找机制 取得当前实例对象原型链的原型链proto proto.__proto__沿着原型链一直向上查找如果 当前实例的原型链__proto__上找到了当前类的原型prototype则返回 true如果 一直找到Object.prototype.__proto__ nullObject的基类(null)上面都没找到则返回 false
// 实例.__ptoto__ 类.prototype
function _instanceof(example, classFunc) {// 由于instance要检测的是某对象需要有一个前置判断条件//基本数据类型直接返回falseif(typeof example ! object || example null) return false;let proto Object.getPrototypeOf(example);while(true) {if(proto null) return false;// 在当前实例对象的原型链上找到了当前类if(proto classFunc.prototype) return true;// 沿着原型链__ptoto__一层一层向上查proto Object.getPrototypeof(proto); // 等于proto.__ptoto__}
}console.log(test, _instanceof(null, Array)) // false
console.log(test, _instanceof([], Array)) // true
console.log(test, _instanceof(, Array)) // false
console.log(test, _instanceof({}, Object)) // true--------------------
实现new的过程
new操作符做了这些事
创建一个全新的对象这个对象的__proto__要指向构造函数的原型prototype执行构造函数使用 call/apply 改变 this 的指向返回值为object类型则作为new方法的返回值返回否则返回上述全新对象
function myNew(fn, ...args) {// 基于原型链 创建一个新对象let newObj Object.create(fn.prototype);// 添加属性到新对象上 并获取obj函数的结果let res fn.apply(newObj, args); // 改变this指向// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象return typeof res object ? res: newObj;
}// 用法
function Person(name, age) {this.name name;this.age age;
}
Person.prototype.say function() {console.log(this.age);
};
let p1 myNew(Person, poety, 18);
console.log(p1.name);
console.log(p1);
p1.say();--------------------
实现call方法
call做了什么:
将函数设为对象的属性执行和删除这个函数指定this到函数并传入给定参数执行函数如果不传入参数默认指向为 window
// 模拟 call bar.mycall(null);
//实现一个call方法
// 原理利用 context.xxx self obj.xx func--obj.xx()
Function.prototype.myCall function(context window, ...args) {if (typeof this ! function) {throw new Error(type error)}// this--func context-- obj args-- 传递过来的参数// 在context上加一个唯一值不影响context上的属性let key Symbol(key)context[key] this; // context为调用的上下文,this此处为函数将这个函数作为context的方法// let args [...arguments].slice(1) //第一个参数为obj所以删除,伪数组转为数组// 绑定参数 并执行函数let result context[key](...args);// 清除定义的this 不删除会导致context属性越来越多delete context[key];// 返回结果 return result;
};//用法f.call(obj,arg1)
function f(a,b){console.log(ab)console.log(this.name)
}
let obj{name:1
}
f.myCall(obj,1,2) //否则this指向window--------------------
实现apply方法 思路: 利用this的上下文特性。apply其实就是改一下参数的问题 Function.prototype.myApply function(context window, args) {// this--func context-- obj args-- 传递过来的参数// 在context上加一个唯一值不影响context上的属性let key Symbol(key)context[key] this; // context为调用的上下文,this此处为函数将这个函数作为context的方法// let args [...arguments].slice(1) //第一个参数为obj所以删除,伪数组转为数组let result context[key](...args); // 这里和call传参不一样// 清除定义的this 不删除会导致context属性越来越多delete context[key]; // 返回结果return result;
}// 使用
function f(a,b){console.log(a,b)console.log(this.name)
}
let obj{name:张三
}
f.myApply(obj,[1,2]) //arguments[1]--------------------
实现bind方法 bind 的实现对比其他两个函数略微地复杂了一点涉及到参数合并(类似函数柯里化)因为 bind 需要返回一个函数需要判断一些边界问题以下是 bind 的实现 bind 返回了一个函数对于函数来说有两种方式调用一种是直接调用一种是通过 new 的方式我们先来说直接调用的方式对于直接调用来说这里选择了 apply 的方式实现但是对于参数需要注意以下情况因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2)所以我们需要将两边的参数拼接起来最后来说通过 new 的方式对于 new 的情况来说不会被任何方式改变 this所以对于这种情况我们需要忽略传入的 this
简洁版本
对于普通函数绑定this指向对于构造函数要保证原函数的原型对象上的属性不能丢失
Function.prototype.myBind function(context window, ...args) {// this表示调用bind的函数let self this;//返回了一个函数...innerArgs为实际调用时传入的参数let fBound function(...innerArgs) { //this instanceof fBound为true表示构造函数的情况。如new func.bind(obj)// 当作为构造函数时this 指向实例此时 this instanceof fBound 结果为 true可以让实例获得来自绑定函数的值// 当作为普通函数时this 指向 window此时结果为 false将绑定函数的 this 指向 contextreturn self.apply(this instanceof fBound ? this : context, args.concat(innerArgs));}// 如果绑定的是构造函数那么需要继承构造函数原型属性和方法保证原函数的原型对象上的属性不丢失// 实现继承的方式: 使用Object.createfBound.prototype Object.create(this.prototype);return fBound;
}// 测试用例function Person(name, age) {console.log(Person name, name);console.log(Person age, age);console.log(Person this, this); // 构造函数this指向实例对象
}// 构造函数原型的方法
Person.prototype.say function() {console.log(person say);
}// 普通函数
function normalFun(name, age) {console.log(普通函数 name, name); console.log(普通函数 age, age); console.log(普通函数 this, this); // 普通函数this指向绑定bind的第一个参数 也就是例子中的obj
}var obj {name: poetries,age: 18
}// 先测试作为构造函数调用
var bindFun Person.myBind(obj, poetry1) // undefined
var a new bindFun(10) // Person name: poetry1、Person age: 10、Person this: fBound {}
a.say() // person say// 再测试作为普通函数调用
var bindNormalFun normalFun.myBind(obj, poetry2) // undefined
bindNormalFun(12) // 普通函数name: poetry2 普通函数 age: 12 普通函数 this: {name: poetries, age: 18}注意 bind之后不能再次修改this的指向bind多次后执行函数this还是指向第一次bind的对象 --------------------
实现深拷贝
简洁版本
简单版
const newObj JSON.parse(JSON.stringify(oldObj));局限性
他无法实现对函数 、RegExp等特殊对象的克隆会抛弃对象的constructor,所有的构造函数会指向Object对象有循环引用,会报错
面试简版
function deepClone(obj) {// 如果是 值类型 或 null则直接returnif(typeof obj ! object || obj null) {return obj}// 定义结果对象let copy {}// 如果对象是数组则定义结果数组if(obj.constructor Array) {copy []}// 遍历对象的keyfor(let key in obj) {// 如果key是对象的自有属性if(obj.hasOwnProperty(key)) {// 递归调用深拷贝方法copy[key] deepClone(obj[key])}}return copy
} 调用深拷贝方法若属性为值类型则直接返回若属性为引用类型则递归遍历。这就是我们在解这一类题时的核心的方法。 进阶版
解决拷贝循环引用问题解决拷贝对应原型问题
// 递归拷贝 (类型判断)
function deepClone(value,hash new WeakMap){ // 弱引用不用mapweakMap更合适一点// null 和 undefiend 是不需要拷贝的if(value null){ return value;}if(value instanceof RegExp) { return new RegExp(value) }if(value instanceof Date) { return new Date(value) }// 函数是不需要拷贝if(typeof value ! object) return value;let obj new value.constructor(); // [] {}// 说明是一个对象类型if(hash.get(value)){return hash.get(value)}hash.set(value,obj);for(let key in value){ // in 会遍历当前对象上的属性 和 __proto__指代的属性// 补拷贝 对象的__proto__上的属性if(value.hasOwnProperty(key)){// 如果值还有可能是对象 就继续拷贝obj[key] deepClone(value[key],hash);}}return obj// 区分对象和数组 Object.prototype.toString.call
}// testvar o {};
o.x o;
var o1 deepClone(o); // 如果这个对象拷贝过了 就返回那个拷贝的结果就可以了
console.log(o1);实现完整的深拷贝
1. 简易版及问题
JSON.parse(JSON.stringify());估计这个api能覆盖大多数的应用场景没错谈到深拷贝我第一个想到的也是它。但是实际上对于某些严格的场景来说这个方法是有巨大的坑的。问题如下 无法解决循环引用的问题。举个例子
const a {val:2};
a.target a;拷贝a会出现系统栈溢出因为出现了无限递归的情况。 无法拷贝一些特殊的对象诸如 RegExp, Date, Set, Map等无法拷贝函数(划重点)。
因此这个api先pass掉我们重新写一个深拷贝简易版如下:
const deepClone (target) {if (typeof target object target ! null) {const cloneTarget Array.isArray(target) ? []: {};for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] deepClone(target[prop]);}}return cloneTarget;} else {return target;}
}现在我们以刚刚发现的三个问题为导向一步步来完善、优化我们的深拷贝代码。
2. 解决循环引用
现在问题如下:
let obj {val : 100};
obj.target obj;deepClone(obj);//报错: RangeError: Maximum call stack size exceeded这就是循环引用。我们怎么来解决这个问题呢 创建一个Map。记录下已经拷贝过的对象如果说已经拷贝过那直接返回它行了。 const isObject (target) (typeof target object || typeof target function) target ! null;const deepClone (target, map new Map()) { if(map.get(target)) return target; if (isObject(target)) { map.set(target, true); const cloneTarget Array.isArray(target) ? []: {}; for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] deepClone(target[prop],map); } } return cloneTarget; } else { return target; }
}现在来试一试
const a {val:2};
a.target a;
let newA deepClone(a);
console.log(newA)//{ val: 2, target: { val: 2, target: [Circular] } }好像是没有问题了, 拷贝也完成了。但还是有一个潜在的坑, 就是map 上的 key 和 map 构成了强引用关系这是相当危险的。我给你解释一下与之相对的弱引用的概念你就明白了 在计算机程序设计中弱引用与强引用相对 被弱引用的对象可以在任何时候被回收而对于强引用来说只要这个强引用还在那么对象无法被回收。拿上面的例子说map 和 a一直是强引用的关系 在程序结束之前a 所占的内存空间一直不会被释放。 怎么解决这个问题 很简单让 map 的 key 和 map 构成弱引用即可。ES6给我们提供了这样的数据结构它的名字叫WeakMap它是一种特殊的Map, 其中的键是弱引用的。其键必须是对象而值可以是任意的 稍微改造一下即可:
const deepClone (target, map new WeakMap()) {//...
}3. 拷贝特殊对象
可继续遍历
对于特殊的对象我们使用以下方式来鉴别:
Object.prototype.toString.call(obj);梳理一下对于可遍历对象会有什么结果
[object Map]
[object Set]
[object Array]
[object Object]
[object Arguments]以这些不同的字符串为依据我们就可以成功地鉴别这些对象。
const getType Object.prototype.toString.call(obj);const canTraverse {[object Map]: true,[object Set]: true,[object Array]: true,[object Object]: true,[object Arguments]: true,
};const deepClone (target, map new Map()) {if(!isObject(target)) return target;let type getType(target);let cloneTarget;if(!canTraverse[type]) {// 处理不能遍历的对象return;}else {// 这波操作相当关键可以保证对象的原型不丢失let ctor target.prototype;cloneTarget new ctor();}if(map.get(target)) return target;map.put(target, true);if(type mapTag) {//处理Maptarget.forEach((item, key) {cloneTarget.set(deepClone(key), deepClone(item));})}if(type setTag) {//处理Settarget.forEach(item {target.add(deepClone(item));})}// 处理数组和对象for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] deepClone(target[prop]);}}return cloneTarget;
}不可遍历的对象
const boolTag [object Boolean];
const numberTag [object Number];
const stringTag [object String];
const dateTag [object Date];
const errorTag [object Error];
const regexpTag [object RegExp];
const funcTag [object Function];对于不可遍历的对象不同的对象有不同的处理。
const handleRegExp (target) {const { source, flags } target;return new target.constructor(source, flags);
}const handleFunc (target) {// 待会的重点部分
}const handleNotTraverse (target, tag) {const Ctor targe.constructor;switch(tag) {case boolTag:case numberTag:case stringTag:case errorTag: case dateTag:return new Ctor(target);case regexpTag:return handleRegExp(target);case funcTag:return handleFunc(target);default:return new Ctor(target);}
}4. 拷贝函数
虽然函数也是对象但是它过于特殊我们单独把它拿出来拆解。提到函数在JS种有两种函数一种是普通函数另一种是箭头函数。每个普通函数都是Function的实例而箭头函数不是任何类的实例每次调用都是不一样的引用。那我们只需要处理普通函数的情况箭头函数直接返回它本身就好了。
那么如何来区分两者呢 答案是: 利用原型。箭头函数是不存在原型的。 const handleFunc (func) {// 箭头函数直接返回自身if(!func.prototype) return func;const bodyReg /(?{)(.|\n)(?})/m;const paramReg /(?\().(?\)\s{)/;const funcString func.toString();// 分别匹配 函数参数 和 函数体const param paramReg.exec(funcString);const body bodyReg.exec(funcString);if(!body) return null;if (param) {const paramArr param[0].split(,);return new Function(...paramArr, body[0]);} else {return new Function(body[0]);}
}5. 完整代码展示
const getType obj Object.prototype.toString.call(obj);const isObject (target) (typeof target object || typeof target function) target ! null;const canTraverse {[object Map]: true,[object Set]: true,[object Array]: true,[object Object]: true,[object Arguments]: true,
};
const mapTag [object Map];
const setTag [object Set];
const boolTag [object Boolean];
const numberTag [object Number];
const stringTag [object String];
const symbolTag [object Symbol];
const dateTag [object Date];
const errorTag [object Error];
const regexpTag [object RegExp];
const funcTag [object Function];const handleRegExp (target) {const { source, flags } target;return new target.constructor(source, flags);
}const handleFunc (func) {// 箭头函数直接返回自身if(!func.prototype) return func;const bodyReg /(?{)(.|\n)(?})/m;const paramReg /(?\().(?\)\s{)/;const funcString func.toString();// 分别匹配 函数参数 和 函数体const param paramReg.exec(funcString);const body bodyReg.exec(funcString);if(!body) return null;if (param) {const paramArr param[0].split(,);return new Function(...paramArr, body[0]);} else {return new Function(body[0]);}
}const handleNotTraverse (target, tag) {const Ctor target.constructor;switch(tag) {case boolTag:return new Object(Boolean.prototype.valueOf.call(target));case numberTag:return new Object(Number.prototype.valueOf.call(target));case stringTag:return new Object(String.prototype.valueOf.call(target));case symbolTag:return new Object(Symbol.prototype.valueOf.call(target));case errorTag: case dateTag:return new Ctor(target);case regexpTag:return handleRegExp(target);case funcTag:return handleFunc(target);default:return new Ctor(target);}
}const deepClone (target, map new WeakMap()) {if(!isObject(target)) return target;let type getType(target);let cloneTarget;if(!canTraverse[type]) {// 处理不能遍历的对象return handleNotTraverse(target, type);}else {// 这波操作相当关键可以保证对象的原型不丢失let ctor target.constructor;cloneTarget new ctor();}if(map.get(target)) return target;map.set(target, true);if(type mapTag) {//处理Maptarget.forEach((item, key) {cloneTarget.set(deepClone(key, map), deepClone(item, map));})}if(type setTag) {//处理Settarget.forEach(item {cloneTarget.add(deepClone(item, map));})}// 处理数组和对象for (let prop in target) {if (target.hasOwnProperty(prop)) {cloneTarget[prop] deepClone(target[prop], map);}}return cloneTarget;
}--------------------
实现类的继承
实现类的继承-简版 类的继承在几年前是重点内容有n种继承方式各有优劣es6普及后越来越不重要那么多种写法有点『回字有四样写法』的意思如果还想深入理解的去看红宝书即可我们目前只实现一种最理想的继承方式。 // 寄生组合继承
function Parent(name) {this.name name
}
Parent.prototype.say function() {console.log(this.name say);
}
Parent.prototype.play function() {console.log(this.name play);
}function Child(name, parent) {// 将父类的构造函数绑定在子类上Parent.call(this, parent)this.name name
}/** 1. 这一步不用Child.prototype Parent.prototype的原因是怕共享内存修改父类原型对象就会影响子类2. 不用Child.prototype new Parent()的原因是会调用2次父类的构造方法另一次是call会存在一份多余的父类实例属性
3. Object.create是创建了父类原型的副本与父类原型完全隔离
*/
Child.prototype Object.create(Parent.prototype);
Child.prototype.say function() {console.log(this.name say);
}// 注意记得把子类的构造指向子类本身
Child.prototype.constructor Child;// 测试
var parent new Parent(parent);
parent.say() var child new Child(child);
child.say()
child.play(); // 继承父类的方法ES5实现继承-详细
第一种方式是借助call实现继承
function Parent1(){this.name parent1;
}
function Child1(){Parent1.call(this);this.type child1
}
console.log(new Child1);这样写的时候子类虽然能够拿到父类的属性值但是问题是父类中一旦存在方法那么子类无法继承。那么引出下面的方法 第二种方式借助原型链实现继承
function Parent2() {this.name parent2;this.play [1, 2, 3]}function Child2() {this.type child2;}Child2.prototype new Parent2();console.log(new Child2());看似没有问题父类的方法和属性都能够访问但实际上有一个潜在的不足。举个例子
var s1 new Child2();var s2 new Child2();s1.play.push(4);console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]明明我只改变了s1的play属性为什么s2也跟着变了呢很简单因为两个实例使用的是同一个原型对象
第三种方式将前两种组合
function Parent3 () {this.name parent3;this.play [1, 2, 3];}function Child3() {Parent3.call(this);this.type child3;}Child3.prototype new Parent3();var s3 new Child3();var s4 new Child3();s3.play.push(4);console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]之前的问题都得以解决。但是这里又徒增了一个新问题那就是Parent3的构造函数会多执行了一次Child3.prototype new Parent3();。这是我们不愿看到的。那么如何解决这个问题 第四种方式: 组合继承的优化1
function Parent4 () {this.name parent4;this.play [1, 2, 3];}function Child4() {Parent4.call(this);this.type child4;}Child4.prototype Parent4.prototype;这里让将父类原型对象直接给到子类父类构造函数只执行一次而且父类属性和方法均能访问但是我们来测试一下 var s3 new Child4();var s4 new Child4();console.log(s3)子类实例的构造函数是Parent4显然这是不对的应该是Child4。 第五种方式(最推荐使用)优化2
function Parent5 () {this.name parent5;this.play [1, 2, 3];}function Child5() {Parent5.call(this);this.type child5;}Child5.prototype Object.create(Parent5.prototype);Child5.prototype.constructor Child5;这是最推荐的一种方式接近完美的继承。 --------------------
实现Promise相关方法
实现Promise的resolve 实现 resolve 静态方法有三个要点: 传参为一个 Promise, 则直接返回它。传参为一个 thenable 对象返回的 Promise 会跟随这个对象采用它的最终状态作为自己的状态。其他情况直接返回以该值为成功状态的promise对象。
Promise.resolve (param) {if(param instanceof Promise) return param;return new Promise((resolve, reject) {if(param param.then typeof param.then function) {// param 状态变为成功会调用resolve将新 Promise 的状态变为成功反之亦然param.then(resolve, reject);}else {resolve(param);}})
}实现 Promise.reject Promise.reject 中传入的参数会作为一个 reason 原封不动地往下传, 实现如下: Promise.reject function (reason) {return new Promise((resolve, reject) {reject(reason);});
}实现 Promise.prototype.finally 前面的promise不管成功还是失败都会走到finally中并且finally之后还可以继续then说明它还是一个then方法是关键并且会将初始的promise值原封不动的传递给后面的then. Promise.prototype.finally最大的作用
finally里的函数无论如何都会执行并会把前面的值原封不动传递给下一个then方法中如果finally函数中有promise等异步任务会等它们全部执行完毕再结合之前的成功与否状态返回值
Promise.prototype.finally六大情况用法
// 情况1
Promise.resolve(123).finally((data) { // 这里传入的函数无论如何都会执行console.log(data); // undefined
})// 情况2 (这里finally方法相当于做了中间处理起一个过渡的作用)
Promise.resolve(123).finally((data) {console.log(data); // undefined
}).then(data {console.log(data); // 123
})// 情况3 (这里只要reject都会走到下一个then的err中)
Promise.reject(123).finally((data) {console.log(data); // undefined
}).then(data {console.log(data);
}, err {console.log(err, err); // 123 err
})// 情况4 (一开始就成功之后会等待finally里的promise执行完毕后再把前面的data传递到下一个then中)
Promise.resolve(123).finally((data) {console.log(data); // undefinedreturn new Promise((resolve, reject) {setTimeout(() {resolve(ok);}, 3000)})
}).then(data {console.log(data, success); // 123 success
}, err {console.log(err, err);
})// 情况5 (虽然一开始成功但是只要finally函数中的promise失败了就会把其失败的值传递到下一个then的err中)
Promise.resolve(123).finally((data) {console.log(data); // undefinedreturn new Promise((resolve, reject) {setTimeout(() {reject(rejected);}, 3000)})
}).then(data {console.log(data, success);
}, err {console.log(err, err); // rejected err
})// 情况6 (虽然一开始失败但是也要等finally中的promise执行完才能把一开始的err传递到err的回调中)
Promise.reject(123).finally((data) {console.log(data); // undefinedreturn new Promise((resolve, reject) {setTimeout(() {resolve(resolve);}, 3000)})
}).then(data {console.log(data, success);
}, err {console.log(err, err); // 123 err
})源码实现
Promise.prototype.finally function (callback) {return this.then((data) {// 让函数执行 内部会调用方法如果方法是promise需要等待它完成// 如果当前promise执行时失败了会把err传递到err的回调函数中return Promise.resolve(callback()).then(() data); // data 上一个promise的成功态}, err {return Promise.resolve(callback()).then(() {throw err; // 把之前的失败的err抛出去});})
}实现 Promise.all 对于 all 方法而言需要完成下面的核心功能: 传入参数为一个空的可迭代对象则直接进行resolve。如果参数中有一个promise失败那么Promise.all返回的promise对象失败。在任何情况下Promise.all 返回的 promise 的完成状态的结果都是一个数组
Promise.all function(promises) {return new Promise((resolve, reject) {let result [];let index 0;let len promises.length;if(len 0) {resolve(result);return;}for(let i 0; i len; i) {// 为什么不直接 promise[i].then, 因为promise[i]可能不是一个promisePromise.resolve(promise[i]).then(data {result[i] data;index;if(index len) resolve(result);}).catch(err {reject(err);})}})
}实现promise.allsettle MDN: Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise并带有一个对象数组每个对象表示对应的promise结果 当您有多个彼此不依赖的异步任务成功完成时或者您总是想知道每个promise的结果时通常使用它。 【译】Promise.allSettled 跟 Promise.all 类似, 其参数接受一个Promise的数组, 返回一个新的Promise, 唯一的不同在于, 其不会进行短路, 也就是说当Promise全部处理完成后我们可以拿到每个Promise的状态, 而不管其是否处理成功。 用法 | 测试用例
let fs require(fs).promises;let getName fs.readFile(./name.txt, utf8); // 读取文件成功
let getAge fs.readFile(./age.txt, utf8);Promise.allSettled([1, getName, getAge, 2]).then(data {console.log(data);
});
// 输出结果
/*[{ status: fulfilled, value: 1 },{ status: fulfilled, value: zf },{ status: fulfilled, value: 11 },{ status: fulfilled, value: 2 }]
*/let getName fs.readFile(./name123.txt, utf8); // 读取文件失败
let getAge fs.readFile(./age.txt, utf8);
// 输出结果
/*[{ status: fulfilled, value: 1 },{status: rejected,value: [Error: ENOENT: no such file or directory, open ./name123.txt] {errno: -2,code: ENOENT,syscall: open,path: ./name123.txt}},{ status: fulfilled, value: 11 },{ status: fulfilled, value: 2 }]
*/实现
function isPromise (val) {return typeof val.then function; // (123).then undefined
}Promise.allSettled function(promises) {return new Promise((resolve, reject) {let arr [];let times 0;const setData (index, data) {arr[index] data;if (times promises.length) {resolve(arr);}console.log(times, times)}for (let i 0; i promises.length; i) {let current promises[i];if (isPromise(current)) {current.then((data) {setData(i, { status: fulfilled, value: data });}, err {setData(i, { status: rejected, value: err })})} else {setData(i, { status: fulfilled, value: current })}}})
}实现 Promise.race race 的实现相比之下就简单一些只要有一个 promise 执行完直接 resolve 并停止执行 Promise.race function(promises) {return new Promise((resolve, reject) {let len promises.length;if(len 0) return;for(let i 0; i len; i) {Promise.resolve(promise[i]).then(data {resolve(data);return;}).catch(err {reject(err);return;})}})
}实现一个简版Promise
// 使用
var promise new Promise((resolve,reject) {if (操作成功) {resolve(value)} else {reject(error)}
})
promise.then(function (value) {// success
},function (value) {// failure
})function myPromise(constructor) {let self this;self.status pending // 定义状态改变前的初始状态self.value undefined; // 定义状态为resolved的时候的状态self.reason undefined; // 定义状态为rejected的时候的状态function resolve(value) {if(self.status pending) {self.value value;self.status resolved;}}function reject(reason) {if(self.status pending) {self.reason reason;self.status rejected;}}// 捕获构造异常try {constructor(resolve,reject);} catch(e) {reject(e);}
}// 添加 then 方法
myPromise.prototype.then function(onFullfilled,onRejected) {let self this;switch(self.status) {case resolved:onFullfilled(self.value);break;case rejected:onRejected(self.reason);break;default: }
}var p new myPromise(function(resolve,reject) {resolve(1)
});
p.then(function(x) {console.log(x) // 1
})使用class实现
class MyPromise {constructor(fn) {this.resolvedCallbacks [];this.rejectedCallbacks [];this.state PENDING;this.value ;fn(this.resolve.bind(this), this.reject.bind(this));}resolve(value) {if (this.state PENDING) {this.state RESOLVED;this.value value;this.resolvedCallbacks.map(cb cb(value)); }}reject(value) {if (this.state PENDING) {this.state REJECTED;this.value value;this.rejectedCallbacks.map(cb cb(value));}}then(onFulfilled, onRejected) {if (this.state PENDING) {this.resolvedCallbacks.push(onFulfilled);this.rejectedCallbacks.push(onRejected);}if (this.state RESOLVED) {onFulfilled(this.value);}if (this.state REJECTED) {onRejected(this.value);}}
}Promise 实现-详细
可以把 Promise 看成一个状态机。初始是 pending 状态可以通过函数 resolve和 reject 将状态转变为 resolved或者 rejected 状态状态一旦改变就不能再次变化。then 函数会返回一个 Promise 实例并且该返回值是一个新的实例而不是之前的实例。因为 Promise 规范规定除了 pending 状态其他状态是不可以改变的如果返回的是一个相同实例的话多个 then 调用就失去意义了。对于 then来说本质上可以把它看成是 flatMap
// 三种状态
const PENDING pending;
const RESOLVED resolved;
const REJECTED rejected;
// promise 接收一个函数参数该函数会立即执行
function MyPromise(fn) {let _this this;_this.currentState PENDING;_this.value undefined;// 用于保存 then 中的回调只有当 promise// 状态为 pending 时才会缓存并且每个实例至多缓存一个_this.resolvedCallbacks [];_this.rejectedCallbacks [];_this.resolve function (value) {if (value instanceof MyPromise) {// 如果 value 是个 Promise递归执行return value.then(_this.resolve, _this.reject)}setTimeout(() { // 异步执行保证执行顺序if (_this.currentState PENDING) {_this.currentState RESOLVED;_this.value value;_this.resolvedCallbacks.forEach(cb cb());}})};_this.reject function (reason) {setTimeout(() { // 异步执行保证执行顺序if (_this.currentState PENDING) {_this.currentState REJECTED;_this.value reason;_this.rejectedCallbacks.forEach(cb cb());}})}// 用于解决以下问题// new Promise(() throw Error(error))try {fn(_this.resolve, _this.reject);} catch (e) {_this.reject(e);}
}MyPromise.prototype.then function (onResolved, onRejected) {var self this;// 规范 2.2.7then 必须返回一个新的 promisevar promise2;// 规范 2.2.onResolved 和 onRejected 都为可选参数// 如果类型不是函数需要忽略同时也实现了透传// Promise.resolve(4).then().then((value) console.log(value))onResolved typeof onResolved function ? onResolved : v v;onRejected typeof onRejected function ? onRejected : r throw r;if (self.currentState RESOLVED) {return (promise2 new MyPromise(function (resolve, reject) {// 规范 2.2.4保证 onFulfilledonRjected 异步执行// 所以用了 setTimeout 包裹下setTimeout(function () {try {var x onResolved(self.value);resolutionProcedure(promise2, x, resolve, reject);} catch (reason) {reject(reason);}});}));}if (self.currentState REJECTED) {return (promise2 new MyPromise(function (resolve, reject) {setTimeout(function () {// 异步执行onRejectedtry {var x onRejected(self.value);resolutionProcedure(promise2, x, resolve, reject);} catch (reason) {reject(reason);}});}));}if (self.currentState PENDING) {return (promise2 new MyPromise(function (resolve, reject) {self.resolvedCallbacks.push(function () {// 考虑到可能会有报错所以使用 try/catch 包裹try {var x onResolved(self.value);resolutionProcedure(promise2, x, resolve, reject);} catch (r) {reject(r);}});self.rejectedCallbacks.push(function () {try {var x onRejected(self.value);resolutionProcedure(promise2, x, resolve, reject);} catch (r) {reject(r);}});}));}
};
// 规范 2.3
function resolutionProcedure(promise2, x, resolve, reject) {// 规范 2.3.1x 不能和 promise2 相同避免循环引用if (promise2 x) {return reject(new TypeError(Error));}// 规范 2.3.2// 如果 x 为 Promise状态为 pending 需要继续等待否则执行if (x instanceof MyPromise) {if (x.currentState PENDING) {x.then(function (value) {// 再次调用该函数是为了确认 x resolve 的// 参数是什么类型如果是基本类型就再次 resolve// 把值传给下个 thenresolutionProcedure(promise2, value, resolve, reject);}, reject);} else {x.then(resolve, reject);}return;}// 规范 2.3.3.3.3// reject 或者 resolve 其中一个执行过得话忽略其他的let called false;// 规范 2.3.3判断 x 是否为对象或者函数if (x ! null (typeof x object || typeof x function)) {// 规范 2.3.3.2如果不能取出 then就 rejecttry {// 规范 2.3.3.1let then x.then;// 如果 then 是函数调用 x.thenif (typeof then function) {// 规范 2.3.3.3then.call(x,y {if (called) return;called true;// 规范 2.3.3.3.1resolutionProcedure(promise2, y, resolve, reject);},e {if (called) return;called true;reject(e);});} else {// 规范 2.3.3.4resolve(x);}} catch (e) {if (called) return;called true;reject(e);}} else {// 规范 2.3.4x 为基本类型resolve(x);}
}实现Promisify
const fs require(fs)
const path require(path)// node中使用
// const fs require(fs).promises 12.18版
// const promisify require(util).promisify// 包装node api promise化 典型的高级函数
const promisify fn{return (...args){return new Promise((resolve,reject){fn(...args, (err,data){if(err) {reject(err)} resolve(data)})})}
}// const read promisify(fs.readFile)// read(path.join(__dirname, ./promise.js), utf8).then(d{
// console.log(d)
// })// promise化node所有api
const promisifyAll target{Reflect.ownKeys(target).forEach(key{if(typeof target[key] function) {target[keyAsync] promisify(target[key])}})return target
}// promise化fs下的函数
const promisifyNew promisifyAll(fs)promisifyNew.readFileAsync(path.join(__dirname, ./promise.js), utf8).then(d{console.log(d)
})module.exports {promisify,promisifyAll
}完整实现Promises/A规范
/*** Promises/A规范 实现一个promise* https://promisesaplus.com/
*/const EMUM {PENDING: PENDING,FULFILLED: FULFILLED,REJECTED: REJECTED
}// x 返回值
// promise2 then的时候new的promise
// promise2的resolve, reject
const resolvePromise (x, promise2, resolve, reject){// 解析promise的值解析promise2是成功还是失败 传递到下层thenif(x promise2) {reject(new TypeError(类型错误))}// 这里的x如果是一个promise的话 可能是其他的promise可能调用了成功 又调用了失败// 防止resolve的时候 又throw err抛出异常到reject了let called// 如果x是promise 那么就采用他的状态// 有then方法是promiseif(typeof x object typeof x! null || typeof x function) {// x是对象或函数try {let then x.then // 缓存不用多次取值if(typeof then function) {// 是promise调用then方法里面有this需要传入this为x才能取到then方法里面的值this.valuethen.call(x, y{// 成功// y值可能也是一个promise 如resolve(new Promise()) 此时的ynew Promise()// 递归解析y直到拿到普通的值resolve(x出去)if(called) return;called true;resolvePromise(y, promise2, resolve, reject)},r{// 一旦失败直接失败if(called) return;called true;reject(r)})} else {// 普通对象不是promiseresolve(x)}} catch (e) {// 对象取值可能报错用defineProperty定义get 抛出异常if(called) return;called true;reject(e)}} else {// x是普通值resolve(x) // 直接成功}}
class myPromise {constructor(executor) {this.status EMUM.PENDING // 当前状态this.value undefined // resolve接收值this.reason undefined // reject失败返回值/*** 同一个promise可以then多次(发布订阅模式)* 调用then时 当前状态是等待态需要将当前成功或失败的回调存放起来订阅* 调用resolve时 将订阅函数进行执行发布*/// 成功队列this.onResolvedCallbacks []// 失败队列this.onRejectedCallbacks []const resolve value {// 如果value是一个promise需要递归解析// 如 myPromise.resolve(new myPromise()) 需要解析valueif(value instanceof myPromise) {// 不停的解析 直到值不是promisereturn value.then(resolve,reject)}if(this.status EMUM.PENDING) {this.status EMUM.FULFILLEDthis.value valuethis.onResolvedCallbacks.forEach(fnfn())}}const reject reason {if(this.status EMUM.PENDING) {this.status EMUM.REJECTEDthis.reason reasonthis.onRejectedCallbacks.forEach(fnfn())}}try {executor(resolve,reject)} catch(e) {reject(e)}}then(onFulFilled, onRejected) {// 透传 处理默认不传的情况// new Promise((resolve,reject){// resolve(1)// }).then().then().then(d{})// new Promise((resolve,reject){// resolve(1)// }).then(vv).then(vv).then(d{})// new Promise((resolve,reject){// reject(1)// }).then().then().then(null, e{console.log(e)})// new Promise((resolve,reject){// reject(1)// }).then(null,e{throw e}).then(null,e{throw e}).then(null,e{console.log(e)})onFulFilled typeof onFulFilled function ? onFulFilled : v vonRejected typeof onRejected function ? onRejected : err {throw err}// 调用then 创建一个新的promiselet promise2 new myPromise((resolve,reject){// 根据value判断是resolve 还是reject value也可能是promiseif(this.status EMUM.FULFILLED) {setTimeout(() {try {// 成功回调结果let x onFulFilled(this.value)// 解析promiseresolvePromise(x, promise2,resolve,reject)} catch (error) {reject(error)}}, 0);}if(this.status EMUM.REJECTED) {setTimeout(() {try {let x onRejected(this.reason)// 解析promiseresolvePromise(x, promise2,resolve,reject)} catch (error) {reject(error)}}, 0);}// 用户还未调用resolve或reject方法if(this.status EMUM.PENDING) {this.onResolvedCallbacks.push((){try {let x onFulFilled(this.value)// 解析promiseresolvePromise(x, promise2,resolve,reject)} catch (error) {reject(error)}})this.onRejectedCallbacks.push((){try {let x onRejected(this.reason)// 解析promiseresolvePromise(x, promise2,resolve,reject)} catch (error) {reject(error)}})}})return promise2}catch(errCallback) {// 等同于没有成功把失败放进去而已return this.then(null, errCallback)}// myPromise.resolve 具备等待功能的 如果参数的promise会等待promise解析完毕在向下执行static resolve(val) {return new myPromise((resolve,reject){resolve(val)})}// myPromise.reject 直接将值返回static reject(reason) {return new myPromise((resolve,reject){reject(reason)})}// finally传入的函数 无论成功或失败都执行// Promise.reject(100).finally((){console.log(1)}).then(dconsole.log(success,d)).catch(erconsole.log(faild,er))// Promise.reject(100).finally(()new Promise()).then(dconsole.log(d)).catch(er)finally(callback) {return this.then((val){return myPromise.resolve(callback()).then(()val)},(err){return myPromise.resolve(callback()).then((){throw err})})}// Promise.allstatic all(values) {return new myPromise((resolve,reject){let resultArr []let orderIndex 0const processResultByKey (value,index){resultArr[index] value // 处理完全部if(orderIndex values.length) {resolve(resultArr) // 处理完成的结果返回去}}for (let i 0; i values.length; i) {const value values[i];// 是promiseif(value typeof value.then function) {value.then((val){processResultByKey(val,i)},reject)} else {// 不是promise情况processResultByKey(value,i)}}})}static race(promises) {// 采用最新成功或失败的作为结果return new myPromise((resolve,reject){for (let i 0; i promises.length; i) {let val promises[i]if(val typeof val.then function) {// 任何一个promise先调用resolve或reject就返回结果了 也就是返回执行最快的那个promise的结果val.then(resolve,reject)}else{// 普通值resolve(val)}}})}
}/*** 测试用例-*/
// let promise1 new myPromise((resolve,reject){
// setTimeout(() {
// resolve(成功)
// }, 900);
// })// promise1.then(val{
// console.log(success, val)
// },reason{
// console.log(fail, reason)
// })/*** then的使用方式 普通值意味不是promise* * 1、then中的回调有两个方法 成功或失败 他们的结果返回普通值会传递给外层的下一个then中* 2、可以在成功或失败中抛出异常走到下一次then的失败中* 3、返回的是一个promsie那么会用这个promise的状态作为结果会用promise的结果向下传递* 4、错误处理会默认先找离自己最新的错误处理找不到就向下查找找打了就执行*/// read(./name.txt).then(data{
// return 123
// }).then(data{// }).then(null,err{// })
// // .catch(err{ // catch就是没有成功的promise// // })/*** promise.then实现原理通过每次返回一个新的promise来实现promise一旦成功就不能失败失败就不能成功* */// function read(data) {
// return new myPromise((resolve,reject){
// setTimeout(() {
// resolve(new myPromise((resolve,reject)resolve(data)))
// }, 1000);
// })
// }// let promise2 read({name: poetry}).then(data{
// return data
// }).then().then().then(data{
// console.log(data,-data-)
// },(err){
// console.log(err,-err-)
// })// finally测试
// myPromise
// .resolve(100)
// .finally((){
// return new myPromise((resolve,reject)setTimeout(() {
// resolve(100)
// }, 100))
// })
// .then(dconsole.log(finally success,d))
// .catch(erconsole.log(er, finally err))/*** promise.all 测试* * myPromise.all 解决并发问题 多个异步并发获取最终的结果
*/// myPromise.all([1,2,3,4,new myPromise((resolve,reject){
// setTimeout(() {
// resolve(ok1)
// }, 1000);
// }),new myPromise((resolve,reject){
// setTimeout(() {
// resolve(ok2)
// }, 1000);
// })]).then(d{
// console.log(d,myPromise.all.resolve)
// }).catch(err{
// console.log(err,myPromise.all.reject)
// })// 实现promise中断请求
let promise new Promise((resolve,reject){setTimeout(() {// 模拟接口调用 ajax调用超时resolve(成功) }, 10000);
})function promiseWrap(promise) {// 包装一个promise 可以控制原来的promise是成功 还是失败let abortlet newPromsie new myPromise((resolve,reject){abort reject})// 只要控制newPromsie失败就可以控制被包装的promise走向失败// Promise.race 任何一个先成功或者失败 就可以获得结果let p myPromise.race([promise, newPromsie])p.abort abortreturn p
}let newPromise promiseWrap(promise)setTimeout(() {// 超过3秒超时newPromise.abort(请求超时)
}, 3000);newPromise.then(d{console.log(d,d)
}).catch(err{console.log(err,err)
})// 使用promises-aplus-tests 测试写的promise是否规范
// 全局安装 cnpm i -g promises-aplus-tests
// 命令行执行 promises-aplus-tests promise.js
// 测试入口 产生延迟对象
myPromise.defer myPromise.deferred function () {let dfd {}dfd.promise new myPromise((resolve,reject){dfd.resolve resolvedfd.reject reject})return dfd
}// 延迟对象用户
// 
// promise解决嵌套问题
// function readData(url) {
// let dfd myPromise.defer()
// fs.readFile(url, utf8, function (err,data) {
// if(err) {
// dfd.reject()
// }
// dfd.resolve(data)
// })
// return dfd.promise
// }
// readData().then(d{
// return d
// })module.exports myPromise--------------------
实现发布订阅模式
简介
发布订阅者模式一种对象间一对多的依赖关系但一个对象的状态发生改变时所依赖它的对象都将得到状态改变的通知。
主要的作用(优点)
广泛应用于异步编程中(替代了传递回调函数)对象之间松散耦合的编写代码
缺点
创建订阅者本身要消耗一定的时间和内存多个发布者和订阅者嵌套一起的时候程序难以跟踪维护
实现的思路
创建一个对象(缓存列表)on方法用来把回调函数fn都加到缓存列表中emit 根据key值去执行对应缓存列表中的函数off方法可以根据key值取消订阅
class EventEmiter {constructor() {// 事件对象存放订阅的名字和事件this._events {}}// 订阅事件的方法on(eventName,callback) {if(!this._events) {this._events {}}// 合并之前订阅的cbthis._events[eventName] [...(this._events[eventName] || []),callback]}// 触发事件的方法emit(eventName, ...args) {if(!this._events[eventName]) {return}// 遍历执行所有订阅的事件this._events[eventName].forEach(fnfn(...args))}off(eventName,cb) {if(!this._events[eventName]) {return}// 删除订阅的事件this._events[eventName] this._events[eventName].filter(fnfn ! cb fn.l ! cb)}// 绑定一次 触发后将绑定的移除掉 再次触发掉once(eventName,callback) {const one (...args){// 等callback执行完毕在删除callback(args)this.off(eventName,one)}one.l callback // 自定义属性this.on(eventName,one)}
}测试用例
let event new EventEmiter()let login1 function(...args) {console.log(login success1, args)
}
let login2 function(...args) {console.log(login success2, args)
}
// event.on(login,login1)
event.once(login,login2)
event.off(login,login1) // 解除订阅
event.emit(login, 1,2,3,4,5)
event.emit(login, 6,7,8,9)
event.emit(login, 10,11,12) 发布订阅者模式和观察者模式的区别
发布/订阅模式是观察者模式的一种变形两者区别在于发布/订阅模式在观察者模式的基础上在目标和观察者之间增加一个调度中心。观察者模式是由具体目标调度比如当事件触发Subject 就会去调用观察者的方法所以观察者模式的订阅者与发布者之间是存在依赖的。发布/订阅模式由统一调度中心调用因此发布者和订阅者不需要知道对方的存在。
--------------------
实现观察者模式 观察者模式基于发布订阅模式 有观察者也有被观察者 观察者需要放到被观察者中被观察者的状态变化需要通知观察者 我变化了 内部也是基于发布订阅模式收集观察者状态变化后要主动通知观察者
class Subject { // 被观察者 学生constructor(name) {this.state happythis.observers []; // 存储所有的观察者}// 收集所有的观察者attach(o){ // Subject. prototype. attchthis.observers.push(o)}// 更新被观察者 状态的方法setState(newState) {this.state newState; // 更新状态// this 指被观察者 学生this.observers.forEach(o o.update(this)) // 通知观察者 更新它们的状态}
}class Observer{ // 观察者 父母和老师constructor(name) {this.name name}update(student) {console.log(当前 this.name 被通知了, 当前学生的状态是 student.state)}
}let student new Subject(学生); let parent new Observer(父母);
let teacher new Observer(老师); // 被观察者存储观察者的前提需要先接纳观察者
student. attach(parent);
student. attach(teacher);
student. setState(被欺负了);--------------------
实现单例模式 核心要点: 用闭包和Proxy属性拦截 function proxy(func) {let instance;let handler {constructor(target, args) {if(!instance) {instance Reflect.constructor(fun, args);}return instance;}}return new Proxy(func, handler);
}--------------------
实现Ajax
步骤
创建 XMLHttpRequest 实例发出 HTTP 请求服务器返回 XML 格式的字符串JS 解析 XML并更新局部页面不过随着历史进程的推进XML 已经被淘汰取而代之的是 JSON。
了解了属性和方法之后根据 AJAX 的步骤手写最简单的 GET 请求。
--------------------
原生实现
function ajax() {let xhr new XMLHttpRequest() //实例化以调用方法xhr.open(get, https://www.google.com) //参数2url。参数三异步xhr.onreadystatechange () { //每当 readyState 属性改变时就会调用该函数。if (xhr.readyState 4) { //XMLHttpRequest 代理当前所处状态。if (xhr.status 200 xhr.status 300) { //200-300请求成功let string request.responseText//JSON.parse() 方法用来解析JSON字符串构造由字符串描述的JavaScript值或对象let object JSON.parse(string)}}}request.send() //用于实际发出 HTTP 请求。不带参数为GET请求
}--------------------
Promise实现
基于Promise封装Ajax
返回一个新的Promise实例创建HMLHttpRequest异步对象调用open方法打开url与服务器建立链接发送前的一些处理监听Ajax状态信息如果xhr.readyState 4表示服务器响应完成可以获取使用服务器的响应了 xhr.status 200返回resolve状态xhr.status 404返回reject状态 xhr.readyState ! 4把请求主体的信息基于send发送给服务器
function ajax(url) {return new Promise((resolve, reject) {let xhr new XMLHttpRequest()xhr.open(get, url)xhr.onreadystatechange () {if (xhr.readyState 4) {if (xhr.status 200 xhr.status 300) {resolve(JSON.parse(xhr.responseText))} else {reject(请求出错)}}}xhr.send() //发送hppt请求})
}let url /data.json
ajax(url).then(res console.log(res)).catch(reason console.log(reason))--------------------
实现JSONP方法 利用script标签不受跨域限制的特点缺点是只能支持 get 请求 创建script标签设置script标签的src属性以问号传递参数设置好回调函数callback名称插入到html文本中调用回调函数res参数就是获取的数据
function jsonp({url,params,callback}) {return new Promise((resolve,reject){let script document.createElement(script)window[callback] function (data) {resolve(data)document.body.removeChild(script)}var arr []for(var key in params) {arr.push(${key}${params[key]})}script.type text/javascriptscript.src ${url}?callback${callback}${arr.join()}document.body.appendChild(script)})
}// 测试用例
jsonp({url: http://suggest.taobao.com/sug,callback: getData,params: {q: iphone手机,code: utf-8},
}).then(data{console.log(data)})设置 CORS: Access-Control-Allow-Origin*postMessage
--------------------
实现async/await
分析
// generator生成器 生成迭代器iterator// 默认这样写的类数组是不能被迭代的缺少迭代方法
let likeArray {0: 1, 1: 2, 2: 3, 3: 4, length: 4}// // 使用迭代器使得可以展开数组
// // Symbol有很多元编程方法可以改js本身功能
// likeArray[Symbol.iterator] function () {
// // 迭代器是一个对象 对象中有next方法 每次调用next 都需要返回一个对象 {value,done}
// let index 0
// return {
// next: (){
// // 会自动调用这个方法
// console.log(index,index)
// return {
// // this 指向likeArray
// value: this[index],
// done: index this.length
// }
// }
// }
// }
// let arr [...likeArray]// console.log(arr, arr)// 使用生成器返回迭代器
// likeArray[Symbol.iterator] function *() {
// let index 0
// while (index ! this.length) {
// yield this[index]
// }
// }
// let arr [...likeArray]// console.log(arr, arr)// 生成器 碰到yield就会暂停
// function *read(params) {
// yield 1;
// yield 2;
// }
// 生成器返回的是迭代器
// let it read()
// console.log(it.next())
// console.log(it.next())
// console.log(it.next())// 通过generator来优化promisepromise的缺点是不停的链式调用
const fs require(fs)
const path require(path)
// const co require(co) // 帮我们执行generatorconst promisify fn{return (...args){return new Promise((resolve,reject){fn(...args, (err,data){if(err) {reject(err)} resolve(data)})})}
}// promise化
let asyncReadFile promisify(fs.readFile)function * read() {let content1 yield asyncReadFile(path.join(__dirname,./data/name.txt),utf8)let content2 yield asyncReadFile(path.join(__dirname,./data/ content1),utf8)return content2
}// 这样写太繁琐 需要借助co来实现
// let re read()
// let {value,done} re.next()
// value.then(data{
// // 除了第一次传参没有意义外 剩下的传参都赋予了上一次的返回值
// let {value,done} re.next(data)
// value.then(d{
// let {value,done} re.next(d)
// console.log(value,done)
// })
// }).catch(err{
// re.throw(err) // 手动抛出错误 可以被try catch捕获
// })// 实现co原理
function co(it) {// it 迭代器return new Promise((resolve,reject){// 异步迭代 需要根据函数来实现function next(data) {// 递归得有中止条件let {value,done} it.next(data)if(done) {resolve(value) // 直接让promise变成成功 用当前返回的结果} else {// Promise.resolve(value).then(data{// next(data)// }).catch(err{// reject(err)// })// 简写Promise.resolve(value).then(next,reject)}}// 首次调用next()})
}co(read()).then(d{console.log(d)
}).catch(err{console.log(err,--)
})整体看一下结构
function asyncToGenerator(generatorFunc) {return function() {const gen generatorFunc.apply(this, arguments)return new Promise((resolve, reject) {function step(key, arg) {let generatorResulttry {generatorResult gen[key](arg)} catch (error) {return reject(error)}const { value, done } generatorResultif (done) {return resolve(value)} else {return Promise.resolve(value).then(val step(next, val), err step(throw, err))}}step(next)})}
}分析
function asyncToGenerator(generatorFunc) {// 返回的是一个新的函数return function() {// 先调用generator函数 生成迭代器// 对应 var gen testG()const gen generatorFunc.apply(this, arguments)// 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的// var test asyncToGenerator(testG)// test().then(res console.log(res))return new Promise((resolve, reject) {// 内部定义一个step函数 用来一步一步的跨过yield的阻碍// key有next和throw两种取值分别对应了gen的next和throw方法// arg参数则是用来把promise resolve出来的值交给下一个yieldfunction step(key, arg) {let generatorResult// 这个方法需要包裹在try catch中// 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误try {generatorResult gen[key](arg)} catch (error) {return reject(error)}// gen.next() 得到的结果是一个 { value, done } 的结构const { value, done } generatorResultif (done) {// 如果已经完成了 就直接resolve这个promise// 这个done是在最后一次调用next后才会为true// 以本文的例子来说 此时的结果是 { done: true, value: success }// 这个value也就是generator函数最后的返回值return resolve(value)} else {// 除了最后结束的时候外每次调用gen.next()// 其实是返回 { value: Promise, done: false } 的结构// 这里要注意的是Promise.resolve可以接受一个promise为参数// 并且这个promise参数被resolve的时候这个then才会被调用return Promise.resolve(// 这个value对应的是yield后面的promisevalue).then(// value这个promise被resove的时候就会执行next// 并且只要done不是true的时候 就会递归的往下解开promise// 对应gen.next().value.then(value {// gen.next(value).value.then(value2 {// gen.next() //// // 此时done为true了 整个promise被resolve了 // // 最外部的test().then(res console.log(res))的then就开始执行了// })// })function onResolve(val) {step(next, val)},// 如果promise被reject了 就再次进入step函数// 不同的是这次的try catch中调用的是gen.throw(err)// 那么自然就被catch到 然后把promise给reject掉啦function onReject(err) {step(throw, err)},)}}step(next)})}
}--------------------
基于Generator函数实现async/await原理 核心传递给我一个Generator函数把函数中的内容基于Iterator迭代器的特点一步步的执行 function readFile(file) {return new Promise(resolve {setTimeout(() {resolve(file);}, 1000);})
};function asyncFunc(generator) {const iterator generator(); // 接下来要执行next// data为第一次执行之后的返回结果用于传给第二次执行const next (data) {let { value, done } iterator.next(data); // 第二次执行并接收第一次的请求结果 dataif (done) return; // 执行完毕(到第三次)直接返回// 第一次执行next时yield返回的 promise实例 赋值给了 valuevalue.then(data {next(data); // 当第一次value 执行完毕且成功时执行下一步(并把第一次的结果传递下一步)});}next();
};asyncFunc(function* () {// 生成器函数控制代码一步步执行 let data yield readFile(a.js); // 等这一步骤执行执行成功之后再往下走没执行完的时候直接返回data yield readFile(data b.js);return data;
})--------------------
实现ES6的const 由于ES5环境没有block的概念所以是无法百分百实现const只能是挂载到某个对象下要么是全局的window要么就是自定义一个object来当容器 var __const function __const (data, value) {window.data value // 把要定义的data挂载到window下并赋值valueObject.defineProperty(window, data, { // 利用Object.defineProperty的能力劫持当前对象并修改其属性描述符enumerable: false,configurable: false,get: function () {return value},set: function (data) {if (data ! value) { // 当要对当前属性进行赋值时则抛出错误throw new TypeError(Assignment to constant variable.)} else {return value}}})}__const(a, 10)console.log(a)delete aconsole.log(a)for (let item in window) { // 因为const定义的属性在global下也是不存在的所以用到了enumerable: false来模拟这一功能if (item a) { // 因为不可枚举所以不执行console.log(window[item])}}a 20 // 报错Vue目前双向绑定的核心实现思路就是利用Object.defineProperty对get跟set进行劫持监听用户对属性进行调用以及赋值时的具体情况从而实现的双向绑定 --------------------
实现一个迭代器生成函数
ES6对迭代器的实现
JS原生的集合类型数据结构只有Array数组和Object对象而ES6中又新增了Map和Set。四种数据结构各自有着自己特别的内部实现但我们仍期待以同样的一套规则去遍历它们所以ES6在推出新数据结构的同时也推出了一套 统一的接口机制 ——迭代器Iterator。 ES6约定任何数据结构只要具备Symbol.iterator属性这个属性就是Iterator的具体实现它本质上是当前数据结构默认的迭代器生成函数就可以被遍历——准确地说是被for...of...循环和迭代器的next方法遍历。 事实上for...of...的背后正是对next方法的反复调用。 在ES6中针对Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都可以通过for...of...进行遍历。原理都是一样的此处我们拿最简单的数组进行举例当我们用for...of...遍历数组时
const arr [1, 2, 3]
const len arr.length
for(item of arr) {console.log(当前元素是${item})
}之所以能够按顺序一次一次地拿到数组里的每一个成员是因为我们借助数组的Symbol.iterator生成了它对应的迭代器对象通过反复调用迭代器对象的next方法访问了数组成员像这样 const arr [1, 2, 3]
// 通过调用iterator拿到迭代器对象
const iterator arr[Symbol.iterator]()// 对迭代器对象执行next就能逐个访问集合的成员
iterator.next()
iterator.next()
iterator.next()丢进控制台我们可以看到next每次会按顺序帮我们访问一个集合成员 而for...of...做的事情基本等价于下面这通操作 // 通过调用iterator拿到迭代器对象
const iterator arr[Symbol.iterator]()// 初始化一个迭代结果
let now { done: false }// 循环往外迭代成员
while(!now.done) {now iterator.next()if(!now.done) {console.log(现在遍历到了${now.value})}
}可以看出for...of...其实就是iterator循环调用换了种写法。在ES6中我们之所以能够开心地用for...of...遍历各种各种的集合全靠迭代器模式在背后给力。 ps此处推荐阅读迭代协议 (opens new window)相信大家读过后会对迭代器在ES6中的实现有更深的理解。
--------------------
实现迭代器生成函数
我们说迭代器对象全凭迭代器生成函数帮我们生成。在ES6中实现一个迭代器生成函数并不是什么难事儿因为ES6早帮我们考虑好了全套的解决方案内置了贴心的 生成器 Generator供我们使用
// 编写一个迭代器生成函数
function *iteratorGenerator() {yield 1号选手yield 2号选手yield 3号选手
}const iterator iteratorGenerator()iterator.next()
iterator.next()
iterator.next()丢进控制台不负众望 写一个生成器函数并没有什么难度但在面试的过程中面试官往往对生成器这种语法糖背后的实现逻辑更感兴趣。下面我们要做的不仅仅是写一个迭代器对象而是用ES5去写一个能够生成迭代器对象的迭代器生成函数解析在注释里
// 定义生成器函数入参是任意集合
function iteratorGenerator(list) {// idx记录当前访问的索引var idx 0// len记录传入集合的长度var len list.lengthreturn {// 自定义next方法next: function() {// 如果索引还没有超出集合长度done为falsevar done idx len// 如果done为false则可以继续取值var value !done ? list[idx] : undefined// 将当前值与遍历是否完毕done返回return {done: done,value: value}}}
}var iterator iteratorGenerator([1号选手, 2号选手, 3号选手])
iterator.next()
iterator.next()
iterator.next()此处为了记录每次遍历的位置我们实现了一个闭包借助自由变量来做我们的迭代过程中的“游标”。
运行一下我们自定义的迭代器结果符合预期 --------------------
实现ES6的extends
function B(name){this.name name;
};
function A(name,age){//1.将A的原型指向BObject.setPrototypeOf(A,B);//2.用A的实例作为this调用B,得到继承B之后的实例这一步相当于调用superObject.getPrototypeOf(A).call(this, name)//3.将A原有的属性添加到新实例上this.age age; //4.返回新实例对象return this;
};
var a new A(poetry,22);
console.log(a);--------------------
实现Object.create Object.create()方法创建一个新对象使用现有的对象来提供新创建的对象的 __proto__ // 模拟 Object.createfunction create(proto) {function F() {}F.prototype proto;return new F();
}--------------------
实现Object.freeze Object.freeze冻结一个对象让其不能再添加/删除属性也不能修改该对象已有属性的可枚举性、可配置可写性也不能修改已有属性的值和它的原型属性最后返回一个和传入参数相同的对象 function myFreeze(obj){// 判断参数是否为Object类型如果是就封闭对象循环遍历对象。去掉原型属性将其writable特性设置为falseif(obj instanceof Object){Object.seal(obj); // 封闭对象for(let key in obj){if(obj.hasOwnProperty(key)){Object.defineProperty(obj,key,{writable:false // 设置只读})// 如果属性值依然为对象要通过递归来进行进一步的冻结myFreeze(obj[key]); }}}
}--------------------
实现Object.is
Object.is不会转换被比较的两个值的类型这点和更为相似他们之间也存在一些区别
NaN在中是不相等的而在Object.is中是相等的0和-0在中是相等的而在Object.is中是不相等的
Object.is function (x, y) {if (x y) {// 当前情况下只有一种情况是特殊的即 0 -0// 如果 x ! 0则返回true// 如果 x 0则需要判断0和-0则可以直接使用 1/0 Infinity 和 1/-0 -Infinity来进行判断return x ! 0 || 1 / x 1 / y;}// x ! y 的情况下只需要判断是否为NaN如果x!x则说明x是NaN同理y也一样// x和y同时为NaN时返回truereturn x ! x y ! y;
};--------------------
实现一个compose函数 组合多个函数从右到左比如compose(f, g, h) 最终得到这个结果 (...args) f(g(h(...args))). 题目描述:实现一个 compose 函数
// 用法如下:
function fn1(x) {return x 1;
}
function fn2(x) {return x 2;
}
function fn3(x) {return x 3;
}
function fn4(x) {return x 4;
}
const a compose(fn1, fn2, fn3, fn4);
console.log(a(1)); // 1432111实现代码如下
function compose(...funcs) {if (!funcs.length) return (v) v;if (funcs.length 1) {return funcs[0]}return funcs.reduce((a, b) {return (...args) a(b(...args)))}
}compose创建了一个从右向左执行的数据流。如果要实现从左到右的数据流可以直接更改compose的部分代码即可实现 更换Api接口把reduce改为reduceRight交互包裹位置把a(b(...args))改为b(a(...args))
--------------------
setTimeout与setInterval实现
setTimeout 模拟实现 setInterval
题目描述: setInterval 用来实现循环定时调用 可能会存在一定的问题 能用 setTimeout 解决吗
实现代码如下:
function mySetInterval(fn, t) {let timerId null;function interval() {fn();timerId setTimeout(interval, t); // 递归调用}timerId setTimeout(interval, t); // 首次调用return {// 利用闭包的特性 保存timerIdcancel:() {clearTimeout(timerId)}}
}// 测试
var a mySetInterval((){console.log(111);
},1000)
var b mySetInterval(() {console.log(222)
}, 1000)// 终止定时器
a.cancel()
b.cancel()为什么要用 setTimeout 模拟实现 setIntervalsetInterval 的缺陷是什么 setInterval(fn(), N);上面这句代码的意思其实是fn()将会在 N 秒之后被推入任务队列。在 setInterval 被推入任务队列时如果在它前面有很多任务或者某个任务等待时间较长比如网络请求等那么这个定时器的执行时间和我们预定它执行的时间可能并不一致 // 最常见的出现的就是当我们需要使用 ajax 轮询服务器是否有新数据时必定会有一些人会使用 setInterval然而无论网络状况如何它都会去一遍又一遍的发送请求最后的间隔时间可能和原定的时间有很大的出入// 做一个网络轮询每一秒查询一次数据。
let startTime new Date().getTime();
let count 0;setInterval(() {let i 0;while (i 10000000); // 假设的网络延迟count;console.log(与原设定的间隔时差了,new Date().getTime() - (startTime count * 1000),毫秒);
}, 1000)// 输出
// 与原设定的间隔时差了 567 毫秒
// 与原设定的间隔时差了 552 毫秒
// 与原设定的间隔时差了 563 毫秒
// 与原设定的间隔时差了 554 毫秒(2次)
// 与原设定的间隔时差了 564 毫秒
// 与原设定的间隔时差了 602 毫秒
// 与原设定的间隔时差了 573 毫秒
// 与原设定的间隔时差了 633 毫秒再次强调 定时器指定的时间间隔表示的是何时将定时器的代码添加到消息队列而不是何时执行代码。所以真正何时执行代码的时间是不能保证的取决于何时被主线程的事件循环取到并执行。 setInterval(function, N)
//即每隔N秒把function事件推到消息队列中上图可见setInterval 每隔 100ms 往队列中添加一个事件100ms 后添加 T1 定时器代码至队列中主线程中还有任务在执行所以等待some event 执行结束后执行 T1定时器代码又过了 100msT2 定时器被添加到队列中主线程还在执行 T1 代码所以等待又过了 100ms理论上又要往队列里推一个定时器代码但由于此时 T2 还在队列中所以 T3 不会被添加T3 被跳过结果就是此时被跳过这里我们可以看到T1 定时器执行结束后马上执行了 T2 代码所以并没有达到定时器的效果 setInterval有两个缺点
使用setInterval时某些间隔会被跳过可能多个定时器会连续执行 可以这么理解 每个setTimeout产生的任务会直接push到任务队列中而setInterval在每次把任务push到任务队列前都要进行一下判断(看上次的任务是否仍在队列中)。因而我们一般用setTimeout模拟setInterval来规避掉上面的缺点 setInterval 模拟实现 setTimeout
const mySetTimeout (fn, t) {const timer setInterval(() {clearInterval(timer);fn();}, t);
};// 测试
// mySetTimeout((){
// console.log(1);
// },1000)--------------------
实现Node的require方法
require 基本原理 require 查找路径 require 和 module.exports 干的事情并不复杂我们先假设有一个全局对象{}初始情况下是空的当你 require 某个文件时就将这个文件拿出来执行如果这个文件里面存在module.exports当运行到这行代码时将 module.exports 的值加入这个对象键为对应的文件名最终这个对象就长这样 {a.js: hello world,b.js: function add(){},c.js: 2,d.js: { num: 2 }
}当你再次 require 某个文件时如果这个对象里面有对应的值就直接返回给你如果没有就重复前面的步骤执行目标文件然后将它的 module.exports 加入这个全局对象并返回给调用者。这个全局对象其实就是我们经常听说的缓存。所以 require 和 module.exports 并没有什么黑魔法就只是运行并获取目标文件的值然后加入缓存用的时候拿出来用就行 手写实现一个require
const path require(path); // 路径操作
const fs require(fs); // 文件读取
const vm require(vm); // 文件执行// node模块化的实现
// node中是自带模块化机制的每个文件就是一个单独的模块并且它遵循的是CommonJS规范也就是使用require的方式导入模块通过module.export的方式导出模块。
// node模块的运行机制也很简单其实就是在每一个模块外层包裹了一层函数有了函数的包裹就可以实现代码间的作用域隔离// require加载模块
// require依赖node中的fs模块来加载模块文件fs.readFile读取到的是一个字符串。
// 在javascrpt中我们可以通过eval或者new Function的方式来将一个字符串转换成js代码来运行。// eval
// const name poetry;
// const str const a 123; console.log(name);
// eval(str); // poetry;// new Function
// new Function接收的是一个要执行的字符串返回的是一个新的函数调用这个新的函数字符串就会执行了。如果这个函数需要传递参数可以在new Function的时候依次传入参数最后传入的是要执行的字符串。比如这里传入参数b要执行的字符串str
// const b 3;
// const str let a 1; return a b;
// const fun new Function(b, str);
// console.log(fun(b, str)); // 4
// 可以看到eval和Function实例化都可以用来执行javascript字符串似乎他们都可以来实现require模块加载。不过在node中并没有选用他们来实现模块化原因也很简单因为他们都有一个致命的问题就是都容易被不属于他们的变量所影响。
// 如下str字符串中并没有定义a但是确可以使用上面定义的a变量这显然是不对的在模块化机制中str字符串应该具有自身独立的运行空间自身不存在的变量是不可以直接使用的
// const a 1;
// const str console.log(a);
// eval(str);
// const func new Function(str);
// func();// node存在一个vm虚拟环境的概念用来运行额外的js文件他可以保证javascript执行的独立性不会被外部所影响
// vm 内置模块
// 虽然我们在外部定义了hello但是str是一个独立的模块并不在村hello变量所以会直接报错。
// 引入vm模块 不需要安装node 自建模块
// const vm require(vm);
// const hello poetry;
// const str console.log(hello);
// wm.runInThisContext(str); // 报错
// 所以node执行javascript模块时可以采用vm来实现。就可以保证模块的独立性了// 分析实现步骤
// 1.导入相关模块创建一个Require方法。
// 2.抽离通过Module._load方法用于加载模块。
// 3.Module.resolveFilename 根据相对路径转换成绝对路径。
// 4.缓存模块 Module._cache同一个模块不要重复加载提升性能。
// 5.创建模块 id: 保存的内容是 exports {}相当于this。
// 6.利用tryModuleLoad(module, filename) 尝试加载模块。
// 7.Module._extensions使用读取文件。
// 8.Module.wrap: 把读取到的js包裹一个函数。
// 9.将拿到的字符串使用runInThisContext运行字符串。
// 10.让字符串执行并将this改编成exports// 定义导入类参数为模块路径
function Require(modulePath) {// 获取当前要加载的绝对路径let absPathname path.resolve(__dirname, modulePath);// 自动给模块添加后缀名实现省略后缀名加载模块其实也就是如果文件没有后缀名的时候遍历一下所有的后缀名看一下文件是否存在// 获取所有后缀名const extNames Object.keys(Module._extensions);let index 0;// 存储原始文件路径const oldPath absPathname;function findExt(absPathname) {if (index extNames.length) {throw new Error(文件不存在);}try {fs.accessSync(absPathname);return absPathname;} catch(e) {const ext extNames[index];findExt(oldPath ext);}}// 递归追加后缀名判断文件是否存在absPathname findExt(absPathname);// 从缓存中读取如果存在直接返回结果if (Module._cache[absPathname]) {return Module._cache[absPathname].exports;}// 创建模块新建Module实例const module new Module(absPathname);// 添加缓存Module._cache[absPathname] module;// 加载当前模块tryModuleLoad(module);// 返回exports对象return module.exports;
}// Module的实现很简单就是给模块创建一个exports对象tryModuleLoad执行的时候将内容加入到exports中id就是模块的绝对路径
// 定义模块, 添加文件id标识和exports属性
function Module(id) {this.id id;// 读取到的文件内容会放在exports中this.exports {};
}Module._cache {};// 我们给Module挂载静态属性wrapper里面定义一下这个函数的字符串wrapper是一个数组数组的第一个元素就是函数的参数部分其中有exportsmodule. Require__dirname, __filename, 都是我们模块中常用的全局变量。注意这里传入的Require参数是我们自己定义的Require
// 第二个参数就是函数的结束部分。两部分都是字符串使用的时候我们将他们包裹在模块的字符串外部就可以了
Module.wrapper [(function(exports, module, Require, __dirname, __filename) {,})
]// _extensions用于针对不同的模块扩展名使用不同的加载方式比如JSON和javascript加载方式肯定是不同的。JSON使用JSON.parse来运行。
// javascript使用vm.runInThisContext来运行可以看到fs.readFileSync传入的是module.id也就是我们Module定义时候id存储的是模块的绝对路径读取到的content是一个字符串我们使用Module.wrapper来包裹一下就相当于在这个模块外部又包裹了一个函数也就实现了私有作用域。
// 使用call来执行fn函数第一个参数改变运行的this我们传入module.exports后面的参数就是函数外面包裹参数exports, module, Require, __dirname, __filename
Module._extensions {.js(module) {const content fs.readFileSync(module.id, utf8);const fnStr Module.wrapper[0] content Module.wrapper[1];const fn vm.runInThisContext(fnStr);fn.call(module.exports, module.exports, module, Require,__filename,__dirname);},.json(module) {const json fs.readFileSync(module.id, utf8);module.exports JSON.parse(json); // 把文件的结果放在exports属性上}
}// tryModuleLoad函数接收的是模块对象通过path.extname来获取模块的后缀名然后使用Module._extensions来加载模块
// 定义模块加载方法
function tryModuleLoad(module) {// 获取扩展名const extension path.extname(module.id);// 通过后缀加载当前模块Module._extensions[extension](module);
}// 至此Require加载机制我们基本就写完了我们来重新看一下。Require加载模块的时候传入模块名称在Require方法中使用path.resolve(__dirname, modulePath)获取到文件的绝对路径。然后通过new Module实例化的方式创建module对象将模块的绝对路径存储在module的id属性中在module中创建exports属性为一个json对象
// 使用tryModuleLoad方法去加载模块tryModuleLoad中使用path.extname获取到文件的扩展名然后根据扩展名来执行对应的模块加载机制
// 最终将加载到的模块挂载module.exports中。tryModuleLoad执行完毕之后module.exports已经存在了直接返回就可以了// 给模块添加缓存
// 添加缓存也比较简单就是文件加载的时候将文件放入缓存中再去加载模块时先看缓存中是否存在如果存在直接使用如果不存在再去重新加载之后再放入缓存// 测试
let json Require(./test.json);
let test2 Require(./test2.js);
console.log(json);
console.log(test2);--------------------