1. 实现 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;
};
2. 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
});
3. 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
});
4. 函数防抖方法
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 时间的定时器。
5. 函数节流方法
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;
};
函数节流间隔一段 时间就会触发一次,这里相比函数防抖新增了 trailing 选项,表示是否最后额外触发一次。
6. 实现 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)
};
7. 实现 Object.assign 方法
浅拷贝方法,只会拷贝源对象自身的且可枚举的属性到目标对象
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
});
8. 函数柯里化
将一个多参数函数转化为多个嵌套的单参数函数
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
9. 事件发布订阅
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. 图片懒加载
非传统实现方式,性能最优
/**
* 懒加载
* @description 可加载`<img>`、`<video>`、`<audio>`等一些引用资源路径的标签
* @param {object} params 传参对象
* @param {string?} params.lazyAttr 自定义加载的属性(可选)
* @param {"src"|"background"} params.loadType 加载的类型(默认为`src`)
* @param {string?} params.errorPath 加载失败时显示的资源路径,仅在`loadType`设置为`src`中可用(可选)
*/
function lazyLoad(params) {
const attr = params.lazyAttr || "lazy";
const type = params.loadType || "src";
/** 更新整个文档的懒加载节点 */
function update() {
const els = document.querySelectorAll(`[${attr}]`);
for (let i = 0; i < els.length; i++) {
const el = els[i];
observer.observe(el);
}
}
/**
* 加载图片
* @param {HTMLImageElement} el 图片节点
*/
function loadImage(el) {
const cache = el.src; // 缓存当前`src`加载失败时候用
el.src = el.getAttribute(attr);
el.onerror = function () {
el.src = params.errorPath || cache;
}
}
/**
* 加载单个节点
* @param {HTMLElement} el
*/
function loadElement(el) {
switch (type) {
case "src":
loadImage(el);
break;
case "background":
el.style.backgroundImage = `url(${el.getAttribute(attr)})`;
break;
}
el.removeAttribute(attr);
observer.unobserve(el);
}
/**
* 监听器
* [MDN说明](https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver)
*/
const observer = new IntersectionObserver(function(entries) {
for (let i = 0; i < entries.length; i++) {
const item = entries[i];
if (item.isIntersecting) {
loadElement(item.target);
}
}
})
update();
return {
observer,
update
}
}
在vue中使用指令去使用
import Vue from "vue";
/** 添加一个加载`src`的指令 */
const lazySrc = lazyLoad({
lazyAttr: "vlazy",
errorPath: "./img/error.jpg"
})
Vue.directive("v-lazy", {
inserted(el, binding) {
el.setAttribute("vlazy", binding.value); // 跟上面的对应
lazySrc.observer.observe(el);
}
})
/** 添加一个加载`background`的指令 */
const lazyBg = lazyLoad({
lazyAttr: "vlazybg",
loadType: "background"
})
Vue.directive("v-lazybg", {
inserted(el, binding) {
el.setAttribute("vlazybg", binding.value); // 跟上面的对应
lazyBg.observer.observe(el);
}
})
实际使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="maximum-scale=1.0, minimum-scale=1.0, user-scalable=0, initial-scale=1.0, width=device-width"/>
<meta name="format-detection" content="telephone=no, email=no, date=no, address=no">
<title>图片懒加载</title>
<style>
*{ padding: 0; margin: 0; }
li{ list-style: none;}
img{ width: 100%; display: block; }
.wrap{ max-width: 500px; margin: auto;}
.wrap h2{ font-size: 28px; line-height: 50px; font-weight: 400; text-align: center;}
.wrap .photos{ width: 100%; }
.wrap .photos li{ margin-bottom: 20px; }
.scroll_box{ height: 150px; width: 100%; overflow-x: auto; }
.scroll_box .scroll_list{ height: 100%; width: 500%; overflow: auto; }
.scroll_box .scroll_list .scroll_item{ float: left; width: 18%; height: 100%; margin: 0 1%; background-size: cover; background-position: center center; background-image: url('./img/loading.gif'); }
</style>
</head>
<body>
<div class="wrap">
<h2>图片懒加载</h2>
<div class="scroll_box">
<div class="scroll_list">
<div class="scroll_item" lazy-bg="https://muse-ui.org/img/img1.35d144b4.png"></div>
<div class="scroll_item" lazy-bg="https://muse-ui.org/img/img2.9bd96df4.png"></div>
<div class="scroll_item" lazy-bg="https://muse-ui.org/img/img3.6e264e66.png"></div>
<div class="scroll_item" lazy-bg="https://muse-ui.org/img/sun.a646a52d.jpg"></div>
<div class="scroll_item" lazy-bg="https://muse-ui.org/img/breakfast.f1098290.jpg"></div>
</div>
</div>
<ul class="photos">
<li><img class="lazy" src="./img/loading.gif" lazy="https://hbimg.huabanimg.com/17191c535fac4dd42468d4eef17d478fd1424ff39d345-f1RFNR_fw658" alt=""></li>
<li><img class="lazy" src="./img/loading.gif" lazy="https://hbimg.huabanimg.com/bc9ada499581fdb21ff97749864daa448d3dc8bc131e46-FQHHuP_fw658" alt=""></li>
<li><img class="lazy" src="./img/loading.gif" lazy="https://hbimg.huabanimg.com/53396c4eff4b2e8f47fcfcf6f99a1141841ac52e77a05-lei29j_fw658" alt=""></li>
<li><img class="lazy" src="./img/loading.gif" lazy="https://hbimg.huabanimg.com/b0e1bc5f888c1914e3dee58daa6d2f280b2067ca3bba4-zTcIVk_fw658" alt=""></li>
<li><img class="lazy" src="./img/loading.gif" lazy="https://hbimg.huabanimg.com/3267dafea30ce345cafb1cde13d1bb8740208e08d85c-SrwkCv_fw658" alt=""></li>
<li><img class="lazy" src="./img/loading.gif" lazy="https://hbimg.huabanimg.com/4605ddb853da07009567669af2eb84c221bb629cc72b-pWJHyQ_fw658" alt=""></li>
<li><img class="lazy" src="./img/loading.gif" lazy="https://img3.duitang.com/uploads/item/201505/10/20150510002613_MmRsY.jpeg" alt=""></li>
<li><img class="lazy" src="./img/loading.gif" lazy="https://hbimg.huabanimg.com/cee52123b60708636ada19d00c35984029724ec990d7d-FXfMwf_fw658" alt=""></li>
<li><img class="lazy" src="./img/loading.gif" lazy="https://hbimg.huabanimg.com/d35531927f54fea2d3b305da33dd600ab17746851108a-QYdRWf_fw658" alt=""></li>
<li><img class="lazy" src="./img/loading.gif" lazy="http://attachments.gfan.com/forum/attachments2/201304/24/134256oe4lamvbxm7bb2hq.jpg" alt=""></li>
<li><img class="lazy" src="./img/loading.gif" lazy="http://pic1.win4000.com/wallpaper/4/5489069c8aa80.jpg" alt=""></li>
<li><img class="lazy" src="./img/loading.gif" lazy="https://hbimg.huabanimg.com/3fec949384472f6423b3b9f7a9e300df22d16548f0ae-3DPPlz_fw658" alt=""></li>
</ul>
</div>
<script src="./js/lazyImg.js"></script>
</body>
</html>
另一种
// 获取窗口高度
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. 单例模式
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
17. 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 数组中,等状态变更的时候再取出执行。