1、实现instanceof运算符
instanceof运算符用于检测构造函数的 prototype属性是否出现在某个实例对象的原型链上,运算符左侧是实例对象,右侧是构造函数。
const iInstanceof = function(left, right) {
let proto = Object.getPrototypeOf(left);
while (true) {
if (proto === null) return false;
if (proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
};
这是常见的实现,我们也可以用 isPrototypeOf实现
const iInstanceof = function (left, right) {
return right.prototype.isPrototypeOf(left)
};
2、实现new操作符
new执行过程如下:
-
创建一个新对象;
-
新对象的[[prototype]]特性指向构造函数的prototype属性;
-
构造函数内部的this指向新对象;
-
执行构造函数;
-
如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象;
const iNew = function (fn, ...rest) {
let instance = Object.create(fn.prototype);
let res = fn.apply(instance, rest);
return res !== null && (typeof res === 'object' || typeof res === 'function') ? res : instance;
};
3、实现bind方法
改变函数内 this 的值并且传参,返回一个函数
const iBind = function(thisArg, ...args) {
const originFunc = this;
const boundFunc = function(...args1) {
// 解决 bind 之后对返回函数 new 的问题
if (new.target) {
if (originFunc.prototype) {
boundFunc.prototype = originFunc.prototype;
}
const res = originFunc.apply(this, args.concat(args1));
return res !== null && (typeof res === 'object' || typeof res === 'function') ? res : this;
} else {
return originFunc.apply(thisArg, args.concat(args1));
}
};
// 解决length 和 name 属性问题
const desc = Object.getOwnPropertyDescriptors(originFunc);
Object.defineProperties(boundFunc, {
length: Object.assign(desc.length, {
value: desc.length < args.length ? 0 : (desc.length - args.length)
}),
name: Object.assign(desc.name, {
value: `bound ${desc.name.value}`
})
});
return boundFunc;
};
// 保持 bind 的数据属性一致
Object.defineProperty(Function.prototype, 'iBind', {
value: iBind,
enumerable: false,
configurable: true,
writable: true
});
实现函数的 bind 方法核心是利用 call 绑定 this 指向,同时考虑了一些其他情况,例如
-
bind 返回的函数被 new 调用作为构造函数时,绑定的值会失效并且改为 new 指定的对象
-
定义了绑定后函数的 length 属性和 name 属性(不可枚举属性)
-
绑定后函数的 prototype 需指向原函数的 prototype(真实情况中绑定后的函数是没有 prototype 的,取而代之在绑定后的函数中有个 内部属性 [[TargetFunction]] 保存原函数,当将绑定后函数作为构造函数时,将创建的实例的 __proto__ 指向 [[TargetFunction]] 的 prototype,这里无法模拟内部属性,所以直接声明了一个 prototype 属性)
4、实现call方法
用指定的 this 值和参数来调用函数
const iCall = function(thisArg, ...args) {
thisArg = (thisArg === undefined || thisArg === null) ? window : Object(thisArg);
let fn = Symbol('fn');
thisArg[fn] = this;
let res = thisArg[fn](...args);
delete thisArg[fn];
return res;
};
// 保持 call 的数据属性一致
Object.defineProperty(Function.prototype, 'iCall', {
value: iCall,
configurable: true,
enumerable: false,
writable: true
});
原理就是将函数作为传入的上下文参数(context)的属性执行,这里为了防止属性冲突使用了 ES6 的 Symbol 类型
5、函数柯里化
将一个多参数函数转化为多个嵌套的单参数函数。
const curry = function(targetFn) {
return function fn(...rest) {
if (targetFn.length === rest.length) {
return targetFn.apply(null, rest);
} else {
return fn.bind(null, ...rest);
}
};
};
// 用法
function add(a, b, c, d) {
return a + b + c + d;
}
console.log('柯里化:', curry(add)(1)(2)(3)(4));
// 柯里化:10
6、发布订阅
class EventBus {
constructor() {
Object.defineProperty(this, 'handles', {
value: {}
});
}
on(eventName, listener) {
if (typeof listener !== 'function') {
console.error('请传入正确的回调函数');
return;
}
if (!this.handles[eventName]) {
this.handles[eventName] = [];
}
this.handles[eventName].push(listener);
}
emit(eventName, ...args) {
let listeners = this.handles[eventName];
if (!listeners) {
console.warn(`${eventName}事件不存在`);
return;
}
for (const listener of listeners) {
listener(...args);
}
}
off(eventName, listener) {
if (!listener) {
delete this.handles[eventName];
return;
}
let listeners = this.handles[eventName];
if (listeners && listeners.length) {
let index = listeners.findIndex(item => item === listener);
listeners.splice(index, 1);
}
}
once(eventName, listener) {
if (typeof listener !== 'function') {
console.error('请传入正确的回调函数');
return;
}
const onceListener = (...args) => {
listener(...args);
this.off(eventName, listener);
};
this.on(eventName, onceListener);
}
}
7、深拷贝
const deepClone = function(source) {
if (source === null || typeof source !== 'object') {
return source;
}
let res = Array.isArray(source) ? [] : {};
for (const key in source) {
if (source.hasOwnProperty(key)) {
res[key] = deepClone(source[key]);
}
}
return res;
};
这个是深拷贝的很基础版本,其中存在一些问题,比如循环引用,比如递归爆栈,后面我会专门写一篇文章来展开讨论。
8、实现ES6的Class
用构造函数模拟,class 只能用 new 创建,不可以直接调用,另外注意一下属性的描述符
const checkNew = function(instance, con) {
if (!(instance instanceof con)) {
throw new TypeError(`Class constructor ${con.name} cannot be invoked without 'new'`);
}
};
const defineProperties = function(target, obj) {
for (const key in obj) {
Object.defineProperty(target, key, {
configurable: true,
enumerable: false,
value: obj[key],
writable: true
});
}
};
const createClass = function(con, proto, staticAttr) {
proto && defineProperties(con.prototype, proto);
staticAttr && defineProperties(con, staticAttr);
return con;
};
// 用法
function Person(name) {
checkNew(this, Person);
this.name = name;
}
var PersonClass = createClass(Person, {
getName: function() {
return this.name;
}
}, {
getAge: function() {}
});
9、实现ES6的继承
ES6 内部使用寄生组合式继承,首先用 Object.create 继承原型,并传递第二个参数以将父类构造函数指向自身,同时设置数据属性描述符。
然后用 Object.setPrototypeOf 继承静态属性和静态方法。
const inherit = function(subType, superType) {
// 对 superType 进行类型判断
if (typeof superType !== "function" && superType !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
configurable: true,
enumerable: false,
value: subType,
writable: true
}
});
// 继承静态方法
superType && Object.setPrototypeOf(subType, superType);
};
// 用法
function superType(name) {
this.name = name;
}
superType.staticFn = function() {
console.log('staticFn');
}
superType.prototype.getName = function() {
console.log('name: ' + this.name);
}
function subType(name, age) {
superType.call(this, name);
this.age = age;
}
inherit(subType, superType);
// 必须在继承之后再往 subType 中添加原型方法,否则会被覆盖掉
subType.prototype.getAge = function() {
console.log('age: ' + this.age);
}
let subTypeInstance = new subType('Twittytop', 29);
subType.staticFn();
subTypeInstance.getName();
subTypeInstance.getAge();
10、使用reduce实现数组flat方法
const selfFlat = function (depth = 1) {
let arr = Array.prototype.slice.call(this)
if (depth === 0 ) return arr
return arr.reduce((pre, cur) => {
if (Array.isArray(cur)) {
return [...pre, ...selfFlat.call(cur, depth - 1)]
} else {
return [...pre, cur]
}
}, [])
}
因为 selfFlat 是依赖 this 指向的,所以在 reduce 遍历时需要指定 selfFlat 的 this 指向,否则会默认指向 window 从而发生错误
原理通过 reduce 遍历数组,遇到数组的某个元素仍是数组时,通过 ES6 的扩展运算符对其进行降维(ES5 可以使用 concat 方法),而这个数组元素可能内部还嵌套数组,所以需要递归调用 selfFlat
同时原生的 flat 方法支持一个 depth 参数表示降维的深度,默认为 1 即给数组降一层维度
传入 Inifity 会将传入的数组变成一个一维数组
11、CO(协成)实现
function co(gen) {
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen();
if (!gen || typeof gen.next !== 'function') return resolve(gen);
onFulfilled();
function onFulfilled(res) {
let ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
function onRejected(err) {
let ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
function next(ret) {
if (ret.done) return resolve(ret.value);
let val = Promise.resolve(ret.value);
return val.then(onFulfilled, onRejected);
}
});
}
使用方法:
// 用法
co(function*() {
let res1 = yield Promise.resolve(1);
console.log(res1);
let res2 = yield Promise.resolve(2);
console.log(res2);
let res3 = yield Promise.resolve(3);
console.log(res3);
return res1 + res2 + res3;
}).then(value => {
console.log('add: ' + value);
}, function(err) {
console.error(err.stack);
});
co 接受一个生成器函数,当遇到 yield 时就暂停执行,交出控制权,当其他程序执行完毕后,将结果返回并从中断的地方继续执行,如此往复,一直到所有的任务都执行完毕,最后返回一个 Promise 并将生成器函数的返回值作为 resolve 值。
我们将 * 换成 async,将 yield 换成 await 时,就和我们经常用的 async/await 是一样的,所以说 async/await 是生成器函数的语法糖。