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. 实现 Object.assign 办法
浅拷贝办法,只会拷贝源对象本身的且可枚举的属性(包含以 Symbol 为 key 的属性)到指标对象
const iAssign = function (target, ...source) { if (target === null || target === undefined) { throw new TypeError('Cannot convert undefined or null to object'); } let res = Object(target); for (let i = 0; i < source.length; i++) { let src = source[i]; let keys = [...Object.keys(src), ...Object.getOwnPropertySymbols(src)]; for (const k of keys) { if (src.propertyIsEnumerable(k)) { res[k] = src[k]; } } } return res;};// 放弃 assign 的数据属性统一Object.defineProperty(Object, 'iAssign', { value: iAssign, configurable: true, enumerable: false, writable: true});
4. 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});
5. 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});
6. 函数柯里化
将一个多参数函数转化为多个嵌套的单参数函数。
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
7. 函数防抖 debounce 办法
const debounce = function (func, wait = 0, options = { leading: true, context: null}) { let timer; let res; const _debounce = function (...args) { options.context || (options.context = this); if (timer) { clearTimeout(timer); } if (options.leading && !timer) { timer = setTimeout(() => { timer = null; }, wait); res = func.apply(options.context, args); } else { timer = setTimeout(() => { res = func.apply(options.context, args); timer = null; }, wait); } return res; }; _debounce.cancel = function () { clearTimeout(timer); timer = null; }; return _debounce;};
leading 示意进入时是否立刻执行,如果在wait 工夫内触发事件,则会将上一个定时器革除,并从新再设置一个 wait 工夫的定时器。
8. 函数节流 throttle 办法
const throttle = function (func, wait = 0, options = { leading: true, trailing: false, context: null}) { let timer; let res; let previous = 0; const _throttle = function (...args) { options.context || (options.context = this); let now = Date.now(); if (!previous && !options.leading) previous = now; if (now - previous >= wait) { if (timer) { clearTimeout(timer); timer = null; } res = func.apply(options.context, args); previous = now; } else if (!timer && options.trailing) { timer = setTimeout(() => { res = func.apply(options.context, args); previous = 0; timer = null; }, wait); } return res; }; _throttle.cancel = function () { previous = 0; clearTimeout(timer); timer = null; }; return _throttle;};
函数节流就像水龙头滴水一样,距离 wait 工夫就会触发一次,这里相比函数防抖新增了 trailing 选项,示意是否在最初额定触发一次。
9. 事件公布订阅(EventBus 事件总线)
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); }}
自定义事件的时候用到,留神一些边界的查看
10. 深拷贝
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;};
这个是深拷贝的很根底版本,其中存在一些问题,比方循环援用,比方递归爆栈,前面我会专门写一篇文章来展开讨论。
11. 实现 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 () {}});
12. 实现 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();
13. 图片懒加载
// 获取窗口高度function getWindowHeight () { return window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;}function getTop (e) { let t = e.offsetTop; while (e = e.offsetParent) { t += e.offsetTop; } return t;}const delta = 30;let count = 0;function lazyLoad (imgs) { const winH = getWindowHeight(); const s = document.documentElement.scrollTop || document.body.scrollTop; for (let i = 0, l = imgs.length; i < l; i++) { if (winH + s + delta > getTop(imgs[i]) && getTop(imgs[i]) + imgs[i].offsetHeight + delta > s) { if (!imgs[i].src) { imgs[i].src = imgs[i].getAttribute('data-src'); count++; } if (count === l) { window.removeEventListener('scroll', handler); window.removeEventListener('load', handler); } } } }const imgs = document.querySelectorAll('img');const handler = function () { lazyLoad(imgs);};window.addEventListener('scroll', handler);window.addEventListener('load', handler);
当然你也能够用 getBoundingClientRect 办法:
// 获取窗口高度function getWindowHeight () { return window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;}const delta = 30;let count = 0;function lazyLoad (imgs) { const winH = getWindowHeight(); for (let i = 0, l = imgs.length; i < l; i++) { const rect = imgs[i].getBoundingClientRect(); if (winH + delta > rect.top && rect.bottom > -delta) { if (!imgs[i].src) { imgs[i].src = imgs[i].getAttribute('data-src'); count++; } if (count === l) { window.removeEventListener('scroll', handler); window.removeEventListener('load', handler); } } } }const imgs = document.querySelectorAll('img');const handler = function () { lazyLoad(imgs);};window.addEventListener('scroll', handler);window.addEventListener('load', handler);
当然你也能够用 IntersectionObserver 办法:
function lazyLoad (imgs) { let options = { rootMargin: '30px' }; let count = 0; let observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.intersectionRatio > 0) { entry.target.src = entry.target.getAttribute('data-src'); count++; observer.unobserve(entry.target); if (count === imgs.length) { window.removeEventListener('load', handler); } } }); }, options); for (let i = 0; i < imgs.length; i++) { observer.observe(imgs[i]); }}const imgs = document.querySelectorAll('img');const handler = function () { lazyLoad(imgs);};window.addEventListener('load', handler);
14. 实现Object.is 办法
Object.is() 和 === 的区别是 Object.is(0, -0) 返回 false, Object.is(NaN, NaN) 返回 true。
const iIs = function (x, y) { if (x === y) { return x !== 0 || 1 / x === 1 / y; } else { return x !== x && y !== y; }}// 放弃 is 的数据属性统一Object.defineProperty(Function.prototype, 'iIs', { value: iIs, configurable: true, enumerable: false, writable: true});
15. 工夫切片
把长工作切割成多个小工作,应用场景是避免一个工作执行工夫过长而阻塞线程
function ts (gen) { if (typeof gen === 'function') gen = gen(); if (!gen || typeof gen.next !== 'function') return; (function next() { const start = performance.now(); let res = null; do { res = gen.next(); } while(!res.done && performance.now() - start < 25) if (res.done) return; setTimeout(next); })();}// 用法ts(function* () { const start = performance.now(); while (performance.now() - start < 1000) { yield; } console.log('done!');});
16. 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 是生成器函数的语法糖。
17. 单例模式
const getSingleton = function (fn) { let instance; return function () { return instance || (instance = new (fn.bind(this, ...arguments))); };};// 用法function Person (name) { this.name = name;}let singleton = getSingleton(Person);let instance1 = new singleton('Twittop1');let instance2 = new singleton('Twittop2');console.log(instance1 === instance2); // true
当然你也能够用 ES6 的 Proxy 实现:
const getSingleton = function (fn) { let instance; const handler = { construct (target, argumentsList) { return instance || (instance = Reflect.construct(target, argumentsList)); } } return new Proxy(fn, handler);};// 用法function Person (name) { this.name = name;}let singleton = getSingleton(Person);let instance1 = new singleton('Twittop1');let instance2 = new singleton('Twittop2');console.log(instance1 === instance2); // true
18. Promise
function isFunction (obj) { return typeof obj === 'function';}function isObject (obj) { return !!(obj && typeof obj === 'object');}function isPromise (obj) { return obj instanceof Promise;}function isThenable (obj) { return (isFunction(obj) || isObject(obj)) && 'then' in obj;}function transition (promise, state, result) { // 一旦变成非 pending 状态,就不可逆 if (promise.state !== 'pending') return; promise.state = state; promise.result = result; setTimeout(() => promise.callbacks.forEach(callback => handleCallback(callback, state, result)));}function resolvePromise (promise, result, resolve, reject) { if (promise === result) { return reject(new TypeError('Chaining cycle detected for promise')); } if (isPromise(result)) { return result.then(resolve, reject); } if (isThenable(result)) { try { let then = result.then; if (isFunction(then)) { return new Promise(then.bind(result)).then(resolve, reject); } } catch (error) { return reject(error); } } resolve(result);}function handleCallback (callback, state, result) { let { onFulfilled, onRejected, resolve, reject } = callback; try { if (state === 'fulfilled') { isFunction(onFulfilled) ? resolve(onFulfilled(result)) : resolve(result); } else if (state === 'rejected') { isFunction(onRejected) ? resolve(onRejected(result)) : reject(result); } } catch (e) { reject(e); }}class Promise { constructor (executor) { this.state = 'pending'; this.result = undefined; this.callbacks = []; let onFulfilled = value => transition(this, 'fulfilled', value); let onRejected = reason => transition(this, 'rejected', reason); // 保障 resolve 或 reject 只有一次调用 let flag = false; let resolve = value => { if (flag) return; flag = true; resolvePromise(this, value, onFulfilled, onRejected); }; let reject = reason => { if (flag) return; flag = true; onRejected(reason); }; try { executor(resolve, reject); } catch (e) { reject(e); } } then (onFulfilled, onRejected) { return new Promise((resolve, reject) => { let callback = { onFulfilled, onRejected, resolve, reject }; if (this.state === 'pending') { this.callbacks.push(callback); } else { setTimeout(() => { handleCallback(callback, this.state, this.result); }); } }); } catch (onRejected) { this.then(undefined, onRejected); } // 无论胜利还是失败都会执行,个别都会传递前一个 promise 的状态,只有在 onFinally 抛出谬误(显示抛出或 reject)的时候才会返回一个 rejected 的 promise finally (onFinally) { return this.then( val => Promise.resolve(onFinally()).then(() => val), rea => Promise.resolve(onFinally()).then(() => { throw rea; }) ); } static resolve (value) { if (isPromise(value)) return value; return new Promise ((resolve, reject) => resolve(value)); } static reject (reason) { return new Promise ((resolve, reject) => reject(reason)); } // 当所有 promise 都返回 fulfilled 的时候,它才会返回一个 fulfilled 的 promise,外面蕴含了对应后果的数组,否则只有一个 promise 返回 rejected,它就会返回一个 rejected 的 promise,其中蕴含第一个 rejected 的 promise 抛出的错误信息 static all (iterable) { return new Promise ((resolve, reject) => { let count = 0; let arr = []; for (let i = 0, l = iterable.length; i < l; i ++) { iterable[i].then(val => { count++; arr[i] = val; if (count === l) { reresolve(arr); } }, reject); } }); } // 只有有一个 promise 返回 fulfilled 或 rejected,它就会返回一个 fulfilled 或 rejected 的 promise static race (iterable) { return new Promise ((resolve, reject) => { for (const p of iterable) { p.then(resolve, reject); } }); } // 当所有 promise 都 fulfilled 或 rejected 后,返回一个蕴含对应后果的数组 static allSettled (iterable) { return new Promise ((resolve, reject) => { let count = 0; let arr = []; function handle (state, index, result) { arr[index] = { status: state, [state === 'fulfilled' ? 'value' : 'reason']: result }; count++; if (count === iterable.length) { resolve(arr); } } for (let i = 0, l = iterable.length; i < l; i ++) { iterable[i].then(val => handle ('fulfilled', i, val), rea => handle ('rejected', i, rea)); } }); } // 只有有一个 promise 胜利,就会返回一个胜利的 promise,否则返回一个 AggregateError 类型实例的失败 promise static any (iterable) { return new Promise ((resolve, reject) => { let count = 0; let arr = []; for (let i = 0, l = iterable.length; i < l; i ++) { iterable[i].then(resolve, rea => { count++; arr[i] = rea; if (count === l) { reject(new AggregateError(arr)); } }); } }); }}
Promise 有三种状态 pending、fulfilled 和 rejected,pending 是最后的状态,一旦落定为 fulfilled 或 rejected 状态,就不可逆。且一旦执行 resolve 或 reject,前面的 resolve 或 reject 就不会失效。then 传入的回调函数有可能提早执行,所以需放到 callbacks 数组中,等状态变更的时候再取出执行。
参考资料
https://juejin.cn/post/684490...
https://github.com/berwin/tim...
CO 模块
100 行代码实现 Promises/A+ 标准