实现千位分隔符
// 保留三位小数parseToMoney(1234.56); // return '1,234.56'parseToMoney(123456789); // return '123,456,789'parseToMoney(1087654.321); // return '1,087,654.321'
function parseToMoney(num) { num = parseFloat(num.toFixed(3)); let [integer, decimal] = String.prototype.split.call(num, '.'); integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,'); return integer + '.' + (decimal ? decimal : '');}
正则表达式(使用了正则的前向申明和反前向申明):
function parseToMoney(str){ // 仅仅对地位进行匹配 let re = /(?=(?!\b)(\d{3})+$)/g; return str.replace(re,','); }
实现filter办法
Array.prototype.myFilter=function(callback, context=window){ let len = this.length newArr = [], i=0 for(; i < len; i++){ if(callback.apply(context, [this[i], i , this])){ newArr.push(this[i]); } } return newArr;}
实现节流函数(throttle)
节流函数原理:指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的工夫才会执行回调函数。总结起来就是: 事件,依照一段时间的距离来进行触发 。
像dom的拖拽,如果用消抖的话,就会呈现卡顿的感觉,因为只在进行的时候执行了一次,这个时候就应该用节流,在肯定工夫内屡次执行,会晦涩很多
手写简版
应用工夫戳的节流函数会在第一次触发事件时立刻执行,当前每过 wait 秒之后才执行一次,并且最初一次触发事件不会被执行
工夫戳形式:
// func是用户传入须要防抖的函数// wait是等待时间const throttle = (func, wait = 50) => { // 上一次执行该函数的工夫 let lastTime = 0 return function(...args) { // 以后工夫 let now = +new Date() // 将以后工夫和上一次执行函数工夫比照 // 如果差值大于设置的等待时间就执行函数 if (now - lastTime > wait) { lastTime = now func.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
) - 计算鼠标挪动的间隔(
mousemove
) Canvas
模仿画板性能(mousemove
)- 监听滚动事件判断是否到页面底部主动加载更多
- 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
- 缩放场景:监控浏览器
resize
- 动画场景:防止短时间内屡次触发动画引起性能问题
总结
- 函数防抖 :将几次操作合并为一次操作进行。原理是保护一个计时器,规定在delay工夫后触发函数,然而在delay工夫内再次触发的话,就会勾销之前的计时器而从新设置。这样一来,只有最初一次操作能被触发。
- 函数节流 :使得肯定工夫内只触发一次函数。原理是通过判断是否达到肯定工夫来触发函数。
实现instanceOf
思路:
- 步骤1:先获得以后类的原型,以后实例对象的原型链
步骤2:始终循环(执行原型链的查找机制)
- 获得以后实例对象原型链的原型链(
proto = proto.__proto__
,沿着原型链始终向上查找) - 如果 以后实例的原型链
__proto__
上找到了以后类的原型prototype
,则返回true
- 如果 始终找到
Object.prototype.__proto__ == null
,Object
的基类(null
)下面都没找到,则返回false
- 获得以后实例对象原型链的原型链(
// 实例.__ptoto__ === 类.prototypefunction _instanceof(example, classFunc) { // 因为instance要检测的是某对象,须要有一个前置判断条件 //根本数据类型间接返回false if(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)) // falseconsole.log('test', _instanceof([], Array)) // trueconsole.log('test', _instanceof('', Array)) // falseconsole.log('test', _instanceof({}, Object)) // true
实现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 指向 context return self.apply( this instanceof fBound ? this : context, args.concat(innerArgs) ); } // 如果绑定的是构造函数,那么须要继承构造函数原型属性和办法:保障原函数的原型对象上的属性不失落 // 实现继承的形式: 应用Object.create fBound.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') // undefinedvar a = new bindFun(10) // Person name: poetry1、Person age: 10、Person this: fBound {}a.say() // person say// 再测试作为一般函数调用var bindNormalFun = normalFun.myBind(obj, 'poetry2') // undefinedbindNormalFun(12) // 一般函数name: poetry2 一般函数 age: 12 一般函数 this: {name: 'poetries', age: 18}
留神:bind
之后不能再次批改this
的指向,bind
屡次后执行,函数this
还是指向第一次bind
的对象
实现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/javascript' script.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
实现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六大状况用法
// 状况1Promise.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); // undefined return 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); // undefined return 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); // undefined return 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]可能不是一个promise Promise.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.7,then 必须返回一个新的 promise var 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,保障 onFulfilled,onRjected 异步执行 // 所以用了 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 () { // 异步执行onRejected try { 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.3function resolutionProcedure(promise2, x, resolve, reject) { // 标准 2.3.1,x 不能和 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 // 把值传给下个 then resolutionProcedure(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,就 reject try { // 标准 2.3.3.1 let then = x.then; // 如果 then 是函数,调用 x.then if (typeof then === "function") { // 标准 2.3.3.3 then.call( x, y => { if (called) return; called = true; // 标准 2.3.3.3.1 resolutionProcedure(promise2, y, resolve, reject); }, e => { if (called) return; called = true; reject(e); } ); } else { // 标准 2.3.3.4 resolve(x); } } catch (e) { if (called) return; called = true; reject(e); } } else { // 标准 2.3.4,x 为根本类型 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所有apiconst promisifyAll = target=>{ Reflect.ownKeys(target).forEach(key=>{ if(typeof target[key] === 'function') { target[key+'Async'] = 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, rejectconst resolvePromise = (x, promise2, resolve, reject)=>{ // 解析promise的值解析promise2是胜利还是失败 传递到上层then if(x === promise2) { reject(new TypeError('类型谬误')) } // 这里的x如果是一个promise的话 可能是其余的promise,可能调用了胜利 又调用了失败 // 避免resolve的时候 又throw err抛出异样到reject了 let called // 如果x是promise 那么就采纳他的状态 // 有then办法是promise if(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.value then.call(x, y=>{// 胜利 // y值可能也是一个promise 如resolve(new Promise()) 此时的y==new Promise() // 递归解析y,直到拿到一般的值resolve(x进来) if(called) return; called = true; resolvePromise(y, promise2, resolve, reject) },r=>{// 一旦失败间接失败 if(called) return; called = true; reject(r) }) } else { // 一般对象不是promise resolve(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()) 须要解析value if(value instanceof myPromise) { // 不停的解析 直到值不是promise return value.then(resolve,reject) } if(this.status === EMUM.PENDING) { this.status = EMUM.FULFILLED this.value = value this.onResolvedCallbacks.forEach(fn=>fn()) } } const reject = reason =>{ if(this.status === EMUM.PENDING) { this.status = EMUM.REJECTED this.reason = reason this.onRejectedCallbacks.forEach(fn=>fn()) } } 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(v=>v).then(v=>v).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 => v onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err} // 调用then 创立一个新的promise let promise2 = new myPromise((resolve,reject)=>{ // 依据value判断是resolve 还是reject value也可能是promise if(this.status === EMUM.FULFILLED) { setTimeout(() => { try { // 胜利回调后果 let x = onFulFilled(this.value) // 解析promise resolvePromise(x, promise2,resolve,reject) } catch (error) { reject(error) } }, 0); } if(this.status === EMUM.REJECTED) { setTimeout(() => { try { let x = onRejected(this.reason) // 解析promise resolvePromise(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) // 解析promise resolvePromise(x, promise2,resolve,reject) } catch (error) { reject(error) } }) this.onRejectedCallbacks.push(()=>{ try { let x = onRejected(this.reason) // 解析promise resolvePromise(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(d=>console.log('success',d)).catch(er=>console.log('faild',er)) // Promise.reject(100).finally(()=>new Promise()).then(d=>console.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.all static all(values) { return new myPromise((resolve,reject)=>{ let resultArr = [] let orderIndex = 0 const processResultByKey = (value,index)=>{ resultArr[index] = value // 解决齐全部 if(++orderIndex === values.length) { resolve(resultArr) // 解决实现的后果返回去 } } for (let i = 0; i < values.length; i++) { const value = values[i]; // 是promise if(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(d=>console.log('finally success',d))// .catch(er=>console.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 abort let newPromsie = new myPromise((resolve,reject)=>{ abort = reject }) // 只有管制newPromsie失败,就能够管制被包装的promise走向失败 // Promise.race 任何一个先胜利或者失败 就能够取得后果 let p = myPromise.race([promise, newPromsie]) p.abort = abort return 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 = resolve dfd.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
实现事件总线联合Vue利用
Event Bus
(Vue、Flutter 等前端框架中有出镜)和Event Emitter
(Node中有出镜)出场的“剧组”不同,然而它们都对应一个独特的角色—— 全局事件总线 。
全局事件总线,严格来说不能说是观察者模式,而是公布-订阅模式。它在咱们日常的业务开发中利用十分广。
如果只能选一道题,那这道题肯定是 Event Bus/Event Emitter
的代码实现——我都说这么分明了,这个知识点到底要不要把握、须要把握到什么水平,就看各位本人的了。
在Vue中应用Event Bus来实现组件间的通信
Event Bus/Event Emitter
作为全局事件总线,它起到的是一个沟通桥梁的作用。咱们能够把它了解为一个事件核心,咱们所有事件的订阅/公布都不能由订阅方和公布方“私下沟通”,必须要委托这个事件核心帮咱们实现。
在Vue中,有时候 A 组件和 B 组件中距离了很远,看似没什么关系,但咱们心愿它们之间可能通信。这种状况下除了求助于 Vuex
之外,咱们还能够通过 Event Bus
来实现咱们的需要。
创立一个 Event Bus
(实质上也是 Vue 实例)并导出:
const EventBus = new Vue()export default EventBus
在主文件里引入EventBus
,并挂载到全局:
import bus from 'EventBus的文件门路'Vue.prototype.bus = bus
订阅事件:
// 这里func指someEvent这个事件的监听函数this.bus.$on('someEvent', func)
公布(触发)事件:
// 这里params指someEvent这个事件被触发时回调函数接管的入参this.bus.$emit('someEvent', params)
大家会发现,整个调用过程中,没有呈现具体的发布者和订阅者(比方下面的PrdPublisher
和DeveloperObserver
),全程只有bus
这个货色一个人在疯狂刷存在感。这就是全局事件总线的特点——所有事件的公布/订阅操作,必须经由事件核心,禁止所有“私下交易”!
上面,咱们就一起来实现一个Event Bus
(留神看正文里的解析):
class EventEmitter { constructor() { // handlers是一个map,用于存储事件与回调之间的对应关系 this.handlers = {} } // on办法用于装置事件监听器,它承受指标事件名和回调函数作为参数 on(eventName, cb) { // 先检查一下指标事件名有没有对应的监听函数队列 if (!this.handlers[eventName]) { // 如果没有,那么首先初始化一个监听函数队列 this.handlers[eventName] = [] } // 把回调函数推入指标事件的监听函数队列里去 this.handlers[eventName].push(cb) } // emit办法用于触发指标事件,它承受事件名和监听函数入参作为参数 emit(eventName, ...args) { // 查看指标事件是否有监听函数队列 if (this.handlers[eventName]) { // 如果有,则一一调用队列里的回调函数 this.handlers[eventName].forEach((callback) => { callback(...args) }) } } // 移除某个事件回调队列里的指定回调函数 off(eventName, cb) { const callbacks = this.handlers[eventName] const index = callbacks.indexOf(cb) if (index !== -1) { callbacks.splice(index, 1) } } // 为事件注册单次监听器 once(eventName, cb) { // 对回调函数进行包装,使其执行结束主动被移除 const wrapper = (...args) => { cb.apply(...args) this.off(eventName, wrapper) } this.on(eventName, wrapper) }}
在日常的开发中,大家用到EventBus/EventEmitter
往往提供比这五个办法多的多的多的办法。但在面试过程中,如果大家可能残缺地实现出这五个办法,曾经十分能够阐明问题了,因而楼上这个EventBus
心愿大家能够熟练掌握。学有余力的同学
实现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时,返回true return 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)); // 1+4+3+2+1=11
实现代码如下
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))
实现every办法
Array.prototype.myEvery=function(callback, context = window){ var len=this.length, flag=true, i = 0; for(;i < len; i++){ if(!callback.apply(context,[this[i], i , this])){ flag=false; break; } } return flag; } // var obj = {num: 1} // var aa=arr.myEvery(function(v,index,arr){ // return v.num>=12; // },obj) // console.log(aa)
实现Ajax
步骤
- 创立
XMLHttpRequest
实例 - 收回 HTTP 申请
- 服务器返回 XML 格局的字符串
- JS 解析 XML,并更新部分页面
- 不过随着历史进程的推动,XML 曾经被淘汰,取而代之的是 JSON。
理解了属性和办法之后,依据 AJAX 的步骤,手写最简略的 GET 申请。
实现一个双向绑定
defineProperty 版本
// 数据const data = { text: 'default'};const input = document.getElementById('input');const span = document.getElementById('span');// 数据劫持Object.defineProperty(data, 'text', { // 数据变动 --> 批改视图 set(newVal) { input.value = newVal; span.innerHTML = newVal; }});// 视图更改 --> 数据变动input.addEventListener('keyup', function(e) { data.text = e.target.value;});
proxy 版本
// 数据const data = { text: 'default'};const input = document.getElementById('input');const span = document.getElementById('span');// 数据劫持const handler = { set(target, key, value) { target[key] = value; // 数据变动 --> 批改视图 input.value = value; span.innerHTML = value; return value; }};const proxy = new Proxy(data, handler);// 视图更改 --> 数据变动input.addEventListener('keyup', function(e) { proxy.text = e.target.value;});
实现redux-thunk
redux-thunk
能够利用redux
中间件让redux
反对异步的action
// 如果 action 是个函数,就调用这个函数// 如果 action 不是函数,就传给下一个中间件// 发现 action 是函数就调用const thunk = ({ dispatch, getState }) => (next) => (action) => { if (typeof action === 'function') { return action(dispatch, getState); } return next(action);};export default thunk
实现迭代器生成函数
咱们说迭代器对象全凭迭代器生成函数帮咱们生成。在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.length return { // 自定义next办法 next: function() { // 如果索引还没有超出汇合长度,done为false var 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()
此处为了记录每次遍历的地位,咱们实现了一个闭包,借助自在变量来做咱们的迭代过程中的“游标”。
运行一下咱们自定义的迭代器,后果合乎预期:
实现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(a+b) console.log(this.name)}let obj={ name:1}f.myCall(obj,1,2) //否则this指向window
实现ES6的extends
function B(name){ this.name = name;};function A(name,age){ //1.将A的原型指向B Object.setPrototypeOf(A,B); //2.用A的实例作为this调用B,失去继承B之后的实例,这一步相当于调用super Object.getPrototypeOf(A).call(this, name) //3.将A原有的属性增加到新实例上 this.age = age; //4.返回新实例对象 return this;};var a = new A('poetry',22);console.log(a);
实现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]
实现一个迭代器生成函数
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.lengthfor(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中的实现有更深的了解。
数组去重办法汇总
首先:我晓得多少种去重形式
1. 双层 for 循环
function distinct(arr) { for (let i=0, len=arr.length; i<len; i++) { for (let j=i+1; j<len; j++) { if (arr[i] == arr[j]) { arr.splice(j, 1); // splice 会扭转数组长度,所以要将数组长度 len 和下标 j 减一 len--; j--; } } } return arr;}
思维: 双重for
循环是比拟蠢笨的办法,它实现的原理很简略:先定义一个蕴含原始数组第一个元素的数组,而后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不反复则增加到新数组中,最初返回新数组;因为它的工夫复杂度是O(n^2)
,如果数组长度很大,效率会很低
2. Array.filter() 加 indexOf/includes
function distinct(a, b) { let arr = a.concat(b); return arr.filter((item, index)=> { //return arr.indexOf(item) === index return arr.includes(item) })}
思维: 利用indexOf
检测元素在数组中第一次呈现的地位是否和元素当初的地位相等,如果不等则阐明该元素是反复元素
3. ES6 中的 Set 去重
function distinct(array) { return Array.from(new Set(array));}
思维: ES6 提供了新的数据结构 Set,Set 构造的一个个性就是成员值都是惟一的,没有反复的值。
4. reduce 实现对象数组去反复
var resources = [ { name: "张三", age: "18" }, { name: "张三", age: "19" }, { name: "张三", age: "20" }, { name: "李四", age: "19" }, { name: "王五", age: "20" }, { name: "赵六", age: "21" }]var temp = {};resources = resources.reduce((prev, curv) => { // 如果长期对象中有这个名字,什么都不做 if (temp[curv.name]) { }else { // 如果长期对象没有就把这个名字加进去,同时把以后的这个对象退出到prev中 temp[curv.name] = true; prev.push(curv); } return prev}, []);console.log("后果", resources);
这种办法是利用高阶函数reduce
进行去重, 这里只须要留神initialValue
得放一个空数组[],不然没法push