Appearance
promise 篇
如何判断是否时 promise
什么是 promise?
只要里面有 then 函数,那么就是 promise,传统做法:value instanceof Promise
,但 这样会失去互操作性
function isPromiseLike(value){
if(typeof value === 'object' || typeof value === 'function'){
return typeof value.then === 'function';
}
return false;
}
实现 promise
分析一下基本原理:
- Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行
- Promise 会有三种状态
- Pending 等待
- Fulfilled 完成
- Rejected 失败
- 状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便 不可二次修改;
- Promise 中使用 resolve 和 reject 两个函数来更改状态;
- then 方法内部做的事情就是状态判断
- 如果状态是成功,调用成功回调函数
- 如果状态是失败,调用失败回调函数
实现代码
// MyPromise.js
// 先定义三个常量表示状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
// 新建 MyPromise 类
class MyPromise {
constructor(executor) {
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject);
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// resolve和reject为什么要用箭头函数?
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 用箭头函数就可以让this指向当前实例对象
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 存储成功回调函数
// onFulfilledCallback = null;
onFulfilledCallbacks = [];
// 存储失败回调函数
// onRejectedCallback = null;
onRejectedCallbacks = [];
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// ==== 新增 ====
// resolve里面将所有成功的回调拿出来执行
while (this.onFulfilledCallbacks.length) {
// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
this.onFulfilledCallbacks.shift()(value);
}
}
};
// 更改失败后的状态
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// ==== 新增 ====
// resolve里面将所有失败的回调拿出来执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
then(onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
} else if (this.status === PENDING) {
// ==== 新增 ====
// 因为不知道后面状态的变化,这里先将成功回调和失败回调存储起来
// 等待后续调用
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
}
module.exports = MyPromise;
实现结果
const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000);
})
promise.then(value => {
console.log(1)
console.log('resolve', value)
})
promise.then(value => {
console.log(2)
console.log('resolve', value)
})
promise.then(value => {
console.log(3)
console.log('resolve', value)
})
// 1
// resolve success
// 2
// resolve success
// 3
// resolve success
实现 promise.all
思路:
- 判断传入的是数组类型
- 定义数组保存每一个 promise 的返回结果,同时开始计数,当数量等于传入的数组长度 ,则执行完成
- 如果遇到 reject,直接返回 reject 的失败原因,如果全部成功,则返回结果数组
实现方法
function PromiseAll(promises) {
return new Promise((resolve, reject) => {
// 判断传入的必须是数组
if (!Array.isArray(promises)) {
throw new TypeError("promises must be an array")
}
let result = [] // 保存结果
let count = 0 // 用于判断是否全部执行完
promises.forEach((promise, index) => {
promise.then((res) => {
console.log("res", res);
result[index] = res
count++
count === promises.length && resolve(result) //全部执行完后输出结果
}, (err) => {
reject(err)
})
})
})
}
module.exports = PromiseAll;
实现结果
const PromiseAll = require('./promiseAll')
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success1')
}, 5000);
})
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success2')
}, 1000);
})
const promise3 = new Promise((resolve, reject) => {
reject('error')
})
PromiseAll([promise1, promise2, promise3]).then((res) => {
console.log("全部执行完成", res);
}).catch((error) => {
console.log("执行异常", error)
})
//执行结果,如果没有 reject,输出 ['success1','success1'],reject 输出 error
实现 promise.finally
实现代码
// 由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,
// 它仅用于无论最终结果如何都要执行的情况
// 并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then
Promise.prototype.myFinally = function (cb) {
// 此处的 this 是一个 pending状态的 promise,resolve(cb())会执行 finally 的代码
// promise后有可能还是一个promise,finally需要原封不动的返回 return结果
return this.then((value) => {
return Promise.resolve(cb()).then(function () {
return value
})
}, (err) => {
return Promise.resolve(cb()).then(function () {
throw err
})
})
}
const promise = new Promise((resolve, reject) => {
resolve('success')
})
promise.then((res) => {
return new Promise((resolve) => {
resolve('success1')
})
}).myFinally(() => {
console.log("执行finally");
}).then((res) => {
console.log("finally后,原封不动返回值", res);
})
// 输出:执行finally finally后,原封不动返回值 success1
实现 promise.allSettled
实现代码
function allSettled(promises) {
if (promises.length === 0) return Promise.resolve([])
const _promises = promises.map(
item => item instanceof Promise ? item : Promise.resolve(item)
)
return new Promise((resolve, reject) => {
const result = []
let unSettledPromiseCount = _promises.length
_promises.forEach((promise, index) => {
promise.then((value) => {
result[index] = {
status: 'fulfilled',
value
}
unSettledPromiseCount -= 1
// resolve after all are settled
if (unSettledPromiseCount === 0) {
resolve(result)
}
}, (reason) => {
result[index] = {
status: 'rejected',
reason
}
unSettledPromiseCount -= 1
// resolve after all are settled
if (unSettledPromiseCount === 0) {
resolve(result)
}
})
})
})
}
module.exports = allSettled;
// 思路:
// 当所有的 promise 都执行完了把结果组成数组,包含状态以及 value。
// 首先判断传入的 promise 数组长度,为0直接返回空数组
// 如果 promise 中没有 reslove 或 reject 的,我们只处理 reslove 或 reject 的,所以没有会直接跳出不执行,停止
// 通过计数 -1 ,以及包装结果,返回全部正常结束的最终结果
实现结果
const promiseAllSettled = require('./promiseAllSettled')
const promise1 = new Promise((resolve, reject) => {
resolve('success1')
})
const promise2 = new Promise((resolve, reject) => {
// resolve('success2')
console.log("sdf");
})
const promise3 = new Promise((resolve, reject) => {
reject('error')
})
promiseAllSettled([promise1, promise2, promise3]).then((res) => {
console.log("AllSettled全部执行完成", res);
})
// 执行结果
// 全部执行完成 [
// { status: 'fulfilled', value: 'success1' },
// { status: 'fulfilled', value: 'success2' },
// { status: 'rejected', reason: 'error' }
// ]
实现 promise.any
实现方法
function promiseAny(promiseArr) {
let index = 0
return new Promise((resolve, reject) => {
if (promiseArr.length === 0) return
promiseArr.forEach((p, i) => {
Promise.resolve(p).then(val => {
resolve(val)
}, err => {
index++
if (index === promiseArr.length) {
reject(new AggregateError('All promises were rejected'))
}
})
})
})
}
module.exports = promiseAny;
// 思路:
// 计数,当遇到成功状态,就返回这个结果,当全部都失败,就返回失败信息
实现结果
// 执行 promise 数组,当遇到一个 成功状态,就返回这个成功结果,如果全部失败,就返回 失败提示// 执行所有的 promise,返回所有的 promise 状态与结果,reject不影响过程
const promiseAny = require('./promiseAny')
const promise1 = new Promise((resolve, reject) => {
resolve('success1')
})
const promise2 = new Promise((resolve, reject) => {
resolve('success2')
})
const promise3 = new Promise((resolve, reject) => {
reject('error')
})
promiseAny([promise1, promise2, promise3]).then((res) => {
console.log("promiseAny结果", res);
})
// 执行结果
// promiseAny结果 success1
array 数组篇
实现数组去重
//ES6 提供的 Set 去重
function unique(arr) {
const result = new Set(arr);
return [...result];
// return Array.from(result); //不兼容ie
//使用扩展运算符将Set数据结构转为数组
}
// filter 配合 indexOf,
//使用 indexOf 获取当前内容的下标,这个下标必须跟当前 index 相等,不想等说明出现在别的地方,是重复的需过滤
function unique(arr) {
return arr.filter(function (item, index, arr) {
return arr.indexOf(item) === index;
})
}
// reduce
let arr = [1, 2, 2, 4, null, null].reduce((accumulator, current) => {
return accumulator.includes(current) ? accumulator : accumulator.concat(current);
}, []);
foreach
Array.prototype.myForEach = function (callbackFn) {
// 判断this是否合法
if (this === null || this === undefined) {
throw new TypeError("Cannot read property 'myForEach' of null");
}
// 判断callbackFn是否合法
if (Object.prototype.toString.call(callbackFn) !== "[object Function]") {
throw new TypeError(callbackFn + ' is not a function')
}
// 取到执行方法的数组对象和传入的this对象
var _arr = this, thisArg = arguments[1] || window;
for (var i = 0; i < _arr.length; i++) {
// 执行回调函数
callbackFn.call(thisArg, _arr[i], i, _arr);
}
}
reduce
先理解 reduce 的用法:
array.reduce(function(accumulator, currentValue, currentIndex, array), initialValue)
接受四个参数:
- accumulator 累加器,当前执行的结果
- currentValue 当前执行的值
- currentIndex 当前执行的 index
- array 正在被执行的数组
- initialValue 初始值
必须要 retuen,这样后续才能获取之前的值,继续进行操作
看例子:
// 计数
// 无初始值
let total = [1, 2, 3, 4].reduce((accumulator, current) => accumulator += current); // 10
// 有初始值
let total = [1, 2, 3, 4].reduce((accumulator, current) => accumulator += current, 90); // 100
// 数组去重
let arr = [1, 2, 2, 4, null, null].reduce((accumulator, current) => {
return accumulator.includes(current) ? accumulator : accumulator.concat(current);
}, []);
实现代码
Array.prototype.myReduce = function (callbackFn) {
var _arr = this, accumulator = arguments[1];
var i = 0;
// 判断是否传入初始值
if (accumulator === undefined) {
// 没有初始值的空数组调用reduce会报错
if (_arr.length === 0) {
throw new Error('initVal and Array.length>0 need one')
}
// 初始值赋值为数组第一个元素
accumulator = _arr[i];
i++;
}
for (; i < _arr.length; i++) {
// 计算结果赋值给初始值
accumulator = callbackFn(accumulator, _arr[i], i, _arr)
}
return accumulator;
}
// 思路:
// 判断是否传入初始值,如果没有传入初始值并且,第一个数组还是空的,就返回异常,否则将数组的第一项作为初始值
// 开始循环数组,迭代执行传入的方法,传入四个值
// 接受四个参数:
// - accumulator 累加器,当前执行的结果
// - currentValue 当前执行的值
// - currentIndex 当前执行的index
// - array 正在被执行的数组
// - initialValue 初始值
节流防抖
节流: 多次触发同一个函数,同一段时间内只执行一次,所以节流会稀释函数的执行频率
可以看出节流的主要原理就是利用时间差(当前和上次执行)来过滤中间过程触发的函数 执行。控制是否在开始时会立即触发一次,及最后一次触发是否执行,添加取消的功能
// 节流: 多次触发同一个函数,同一段时间内只执行一次,所以节流会稀释函数的执行频率。触发后只在乎时间,时间到了才执行
// 如果事件不停的触发,它会在规定的时间内一直触发
// fn:执行的方法,wait:等待的时间,immediate:第一次进入是否执行
// 设置上次执行毫秒数初始值 0 ,设置 当前毫秒数 now,通过 now - previous 是否大于传入的等待毫秒数控制方法执行
// 每次点击进入
function throttle(fn, wait, immediate = false) {
let timer; // 定时器
let previous = 0; // 上次执行毫秒数初始化 0
const throttled = function (...args) {
// args 为执行函数传入的参数
// 清除上一个 timer
if (timer) clearTimeout(timer);
const now = +new Date();
// 如果第一次进来:previous === 0 并且不需要立即执行 immediate === false
// 设置当当前毫秒数等于上次执行毫秒数,相减等于0,肯定小于等待时间 wait,所以不能立即执行。
if (immediate === false && !previous) previous = now; // 控制不能立即执行
// now - previous 是否大于传入的等待毫秒数控制方法执行
if (now - previous > wait) {
fn.apply(this, args);
previous = now; // 执行完将上次执行毫秒数设置为当前毫秒数
}
}
// 提供马上停止的方法
throttled.cancel = () => {
clearTimeout(timer);
timer = null;
previous = 0;
}
return throttled;
}
防抖: 按最后一次算。比如说“停止输入 5s 后才发送请求”,防止多次点击 (比较常用)
可以看出 debounce 函数的实现原理就是通过计时器延迟函数执行,短时间内再次触发时 重置并添加新计时器。
// 防抖: 按最后一次算。比如说“停止输入5s后才发送请求”,防止多次点击 (比较常用)
// 你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行
// 总之,就是要等你触发完事件 n 秒内不再触发事件,我才执行。
// 关键点就是,进来清除上一次的定时器,重新设置定时器
// 可以看出debounce函数的实现原理就是通过计时器延迟函数执行,短时间内再次触发时重置并添加新计时器。
//第一次触发可以立即执行,并且有取消功能
function debounce(fn, wait = 1000, immediate = false) {
let timer = null;
function debounced(...args) {
// 每次进来都重置计时器,当没有触发了,才真正执行,
// 或者是 延迟时间 比 触发间隔短 执行
timer && clearTimeout(timer);
// 首次立即执行
if (immediate && !timer) {
fn.apply(this, ...args);
// 立即执行完,再过 wait 就不执行了
timer = setTimeout(() => {
timer = null;
}, wait);
return;
}
// 新计时器
timer = setTimeout(() => {
fn.apply(this, ...args);
timer = null;
}, wait);
}
debounced.cancel = () => {
clearTimeout(timer);
timer = null;
};
return debounced;
}
深拷贝
深拷贝,只拷贝内容,不拷贝地址,不会造成互相影响
简单易懂版本
//递归的形式
function copyObj( obj ){
if( Array.isArray(obj) ){
var newObj = [];
}else{
var newObj = {};
}
for( var key in obj ){
if( typeof obj[key] == 'object' ){
newObj[key] = copyObj(obj[key]);
}else{
newObj[key] = obj[key];
}
}
return newObj;
}
console.log( copyObj(obj5) );
const deepClone = (target) => {
// 判断是否是对象并且不为null
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;
}
}
const object1 = {
a: 1,
b: {
c: 2
},
d: ['1', '2']
}
const object2 = deepClone(object1) // 这里两个的a为1 和 3
// const object2 = object1 // 这里两个的a都将输出3
object2.a = 3
console.log('object1', object1);
console.log('object2', object2);
实现 new
function createObject(Con) {
// 创建新对象obj
// var obj = {};也可以
var obj = Object.create(null);
// 将obj.__proto__ -> 构造函数原型
// (不推荐)obj.__proto__ = Con.prototype
Object.setPrototypeOf(obj, Con.prototype);
// 执行构造函数,并接受构造函数返回值
const ret = Con.apply(obj, [].slice.call(arguments, 1));
// 若构造函数返回值为对象,直接返回该对象
// 否则返回obj
return typeof(ret) === 'object' ? ret: obj;
}
Function 篇
Call
Function.prototype.myCall = function (thisArg) {
thisArg = thisArg || window;
thisArg.func = this;
const args = []
for (let i = 1; i<arguments.length; i++) {
args.push('arguments['+ i + ']')
}
const result = eval('thisArg.func(' + args +')')
delete thisArg.func;
return result;
}
Apply
Function.prototype.myApply = function (thisArg, arr) {
thisArg = thisArg || window;
thisArg.func = this;
const args = []
for (let i = 0; i<arr.length; i++) {
args.push('arr['+ i + ']')
}
const result = eval('thisArg.func(' + args +')')
delete thisArg.func;
return result;
}
Bind
Function.prototype.sx_bind = function (obj, ...args) {
obj = obj || window
const fn = Symbol()
obj[fn] = this
const _this = this
const res = function (...innerArgs) {
console.log(this, _this)
if (this instanceof _this) {
this[fn] = _this
this[fn](...[...args, ...innerArgs])
delete this[fn]
} else {
obj[fn](...[...args, ...innerArgs])
delete obj[fn]
}
}
res.prototype = Object.create(this.prototype)
return res
}