循序漸進,越往後越難!!!
實現淺拷貝
如果給一個變量賦值一個對象,那麼兩者的值會是同一個引用,其中一方改變,另一方也會相應改變。針對引用類型我們需要實現數據的拷貝。
- 用
...
實現
const copy = {...{x:1}}
- 用
Object.assign
實現
const copy = Object.assign({}, {x:1})
- 用
slice
實現
let arr = [1, 3, {
x: 1
}];
let copy = arr.slice();
深拷貝
通常淺拷貝就能解決大部分問題,但是隻解決了第一層的問題,如果接下去的值中還有對象的話,那麼我們需要使用深拷貝。
- 通過 JSON 轉換實現
缺點:
- 對於 function、undefined,會丟失這些屬性。
- 對於 RegExp、Error 對象,只會得到空對象
- 對於 date 對象,得到的結果是 string,而不是 date 對象
- 對於 NaN、Infinity、-Infinity,會變成 null
- 無法處理循環引用
const obj = {a: 1, b: {x: 3}}
const copy = JSON.parse(JSON.stringify(obj))
- 乞丐式遞歸
乞丐版的遞歸,針對常用的JS類型(基礎類型、數組、對象),雖然解決了大部分JSON.parse(JSON.stringify(oldObj))
的問題,但依然無法解決循環引用的問題。
function deepClone(obj) {
let copy = obj instanceof Array ? [] : {}
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
copy[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
}
}
return copy
}
- 改良版深拷貝
參考vuex的deepCopy源碼,解決循環引用
function deepCopy (obj, cache = []) {
// typeof [] => 'object'
// typeof {} => 'object'
if (obj === null || typeof obj !== 'object') {
return obj
}
// 如果傳入的對象與緩存的相等, 則遞歸結束, 這樣防止循環
/**
* 類似下面這種
* var a = {b:1}
* a.c = a
* 資料: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value
*/
const hit = cache.filter(c => c.original === obj)[0]
if (hit) {
return hit.copy
}
const copy = Array.isArray(obj) ? [] : {}
// 將copy首先放入cache, 因爲我們需要在遞歸deepCopy的時候引用它
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key => {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
當然: cache 可以使用 new WeakMap()
代替
- 深拷貝再優化
- 深拷貝添加
Map
和Set
相關,當然你可以再添加Date
之類的補充 - 使用
WeakMap
代替[]
function clone(target, map = new WeakMap()) {
// 克隆原始類型
if (!isObject(target)) {
return target;
}
// 初始化
const type = getType(target);
let cloneTarget;
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type);
}
// 防止循環引用
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
// 克隆set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value,map));
});
return cloneTarget;
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value,map));
});
return cloneTarget;
}
// 克隆對象和數組
const keys = type === arrayTag ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}
- 各種兼容版本的深拷貝
兼容對象、數組、Symbol、正則、Error、Date、基礎類型。
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
}
function isObject(target) {
const type = typeof target;
return target !== null && (type === 'object' || type === 'function');
}
function getType(target) {
return Object.prototype.toString.call(target);
}
function getInit(target) {
const Ctor = target.constructor;
return new Ctor();
}
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
function cloneFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
if (param) {
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
function cloneOtherType(targe, type) {
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
case funcTag:
return cloneFunction(targe);
default:
return null;
}
}
function clone(target, map = new WeakMap()) {
// 克隆原始類型
if (!isObject(target)) {
return target;
}
// 初始化
const type = getType(target);
let cloneTarget;
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type);
} else {
return cloneOtherType(target, type);
}
// 防止循環引用
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
// 克隆set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value, map));
});
return cloneTarget;
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value, map));
});
return cloneTarget;
}
// 克隆對象和數組
const keys = type === arrayTag ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}
實現一個 bind 函數
通過閉包調用 call || apply 可以實現 bind 函數。
- 乞丐版
Function.prototype.myapply = function (context, ...preArgs) {
return (...args) => this.call(context, ...preArgs, ...args)
}
- 進階版,做一些異常處理
Function.prototype.mybind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
let _this = this
let arg = [...arguments].slice(1)
return function F() {
// 處理函數使用new的情況
if (this instanceof F) {
return new _this(...arg, ...arguments)
} else {
return _this.apply(context, arg.concat(...arguments))
}
}
}
實現一個 apply 函數
實現 bind
需要調用 apply || call,那麼如何實現?
思路:將要改變this指向的方法掛到目標this上執行並返回
Function.prototype.myapply = function (context) {
if (typeof this !== 'function') {
throw new TypeError('not funciton')
}
context = context || window
context.fn = this
let result
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
實現防抖函數
滾動事件、resize事件、input事件等: 需要做個複雜計算或者實現一個按鈕的防二次點擊操作。這些需求都可以通過函數防抖動來實現。
- 乞丐版
缺點:這個防抖只能在最後調用。一般的防抖會有immediate選項,表示是否立即調用。
const debounce = (func, wait = 50) => {
// 緩存一個定時器id
let timer = 0
// 這裏返回的函數是每次用戶實際調用的防抖函數
// 如果已經設定過定時器了就清空上一次的定時器
// 開始一個新的定時器,延遲執行用戶傳入的方法
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, wait)
}
}
- 改良版
// 這個是用來獲取當前時間戳的
function now() {
return +new Date()
}
/**
* 防抖函數,返回函數連續調用時,空閒時間必須大於或等於 wait,func 纔會執行
*
* @param {function} func 回調函數
* @param {number} wait 表示時間窗口的間隔
* @param {boolean} immediate 設置爲ture時,是否立即調用函數
* @return {function} 返回客戶調用函數
*/
function debounce (func, wait = 50, immediate = true) {
let timer, context, args
// 延遲執行函數
const later = () => setTimeout(() => {
// 延遲函數執行完畢,清空緩存的定時器序號
timer = null
// 延遲執行的情況下,函數會在延遲函數中執行
// 使用到之前緩存的參數和上下文
if (!immediate) {
func.apply(context, args)
context = args = null
}
}, wait)
// 這裏返回的函數是每次實際調用的函數
return function(...params) {
// 如果沒有創建延遲執行函數(later),就創建一個
if (!timer) {
timer = later()
// 如果是立即執行,調用函數
// 否則緩存參數和調用上下文
if (immediate) {
func.apply(this, params)
} else {
context = this
args = params
}
// 如果已有延遲執行函數(later),調用的時候清除原來的並重新設定一個
// 這樣做延遲函數會重新計時
} else {
clearTimeout(timer)
timer = later()
}
}
}
實現一個節流函數
節流的本質和防抖差不多。
- 乞丐版
缺點:這個節流函數缺少首尾調用的開關
/**
* 函數節流方法
* @param Function fn 延時調用函數
* @param Number delay 延遲多長時間
* @param Number atleast 至少多長時間觸發一次
* @return Function 延遲執行的方法
*/
var throttle = function (fn, delay, atleast) {
var timer = null;
var previous = null;
return function () {
var now = Date.now();
if ( !previous ) previous = now;
if ( now - previous > atleast ) {
fn();
// 重置上一次開始時間爲本次結束時間
previous = now;
} else {
clearTimeout(timer);
timer = setTimeout(function() {
fn();
}, delay);
}
}
};
- 改良版:參考
lodash
經典的節流函數
/**
* underscore 節流函數,返回函數連續調用時,func 執行頻率限定爲 次 / wait
*
* @param function func 回調函數
* @param number wait 表示時間窗口的間隔
* @param object options 如果想忽略開始函數的的調用,傳入{leading: false}。
* 如果想忽略結尾函數的調用,傳入{trailing: false}
* 兩者不能共存,否則函數不能執行
* @return function 返回客戶調用函數
*/
const throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 之前的時間戳
var previous = 0;
// 如果 options 沒傳則設爲空對象
if (!options) options = {};
// 定時器回調函數
var later = function() {
// 如果設置了 leading,就將 previous 設爲 0
// 用於下面函數的第一個 if 判斷
previous = options.leading === false ? 0 : Date.now();
// 置空一是爲了防止內存泄漏,二是爲了下面的定時器判斷
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
// 獲得當前時間戳
var now = Date.now();
// 首次進入前者肯定爲 true
// 如果需要第一次不執行函數
// 就將上次時間戳設爲當前的
// 這樣在接下來計算 remaining 的值時會大於0
if (!previous && options.leading === false) previous = now;
// 計算剩餘時間
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果當前調用已經大於上次調用時間 + wait
// 或者用戶手動調了時間
// 如果設置了 trailing,只會進入這個條件
// 如果沒有設置 leading,那麼第一次會進入這個條件
// 還有一點,你可能會覺得開啓了定時器那麼應該不會進入這個 if 條件了
// 其實還是會進入的,因爲定時器的延時
// 並不是準確的時間,很可能你設置了2秒
// 但是他需要2.2秒才觸發,這時候就會進入這個條件
if (remaining <= 0 || remaining > wait) {
// 如果存在定時器就清理掉否則會調用二次回調
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判斷是否設置了定時器和 trailing
// 沒有的話就開啓一個定時器
// 並且不能不能同時設置 leading 和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
};
柯里化函數的實現
柯里化:
- 把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數
- 並且返回接受餘下的參數且返回結果的新函數
實現柯里化
- 固定傳入參數,參數夠了才執行
/**
* 實現要點:柯里化函數接收到足夠參數後,就會執行原函數,那麼我們如何去確定何時達到足夠的參數呢?
* 柯里化函數需要記住你已經給過他的參數,如果沒給的話,則默認爲一個空數組。
* 接下來每次調用的時候,需要檢查參數是否給夠,如果夠了,則執行fn,沒有的話則返回一個新的 curry 函數,將現有的參數塞給他。
*
*/
// 待柯里化處理的函數
let sum = (a, b, c, d) => {
return a + b + c + d
}
// 柯里化函數,返回一個被處理過的函數
let curry = (fn, ...arr) => { // arr 記錄已有參數
return (...args) => { // args 接收新參數
if (fn.length <= (...arr,...args)) { // 參數夠時,觸發執行
return fn(...arr, ...args)
} else { // 繼續添加參數
return curry(fn, [...arr, ...args])
}
}
}
var sumPlus = curry(sum)
sumPlus(1)(2)(3)(4)
sumPlus(1, 2)(3)(4)
sumPlus(1, 2, 3)(4)
- 不固定傳入參數,隨時執行
/**
* 當然了,柯里化函數的主要作用還是延遲執行,執行的觸發條件不一定是參數個數相等,也可以是其他的條件。
* 例如參數個爲0的情況,那麼我們需要對上面curry函數稍微做修改
*/
// 待柯里化處理的函數
let sum = arr => {
return arr.reduce((a, b) => {
return a + b
})
}
let curry = (fn, ...arr) => { // arr 記錄已有參數
return (...args) => { // args 接收新參數
if (args.length === 0) { // 參數爲空時,觸發執行
return fn(...arr, ...args)
} else { // 繼續添加參數
return curry(fn, ...arr, ...args)
}
}
}
var sumPlus = curry(sum)
sumPlus(1)(2)(3)(4)()
sumPlus(1, 2)(3)(4)()
sumPlus(1, 2, 3)(4)()
數組扁平化
const flattenDeep = (arr) => Array.isArray(arr)
? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , [])
: [arr]
flattenDeep([1, [[2], [3, [4]], 5]])
實現一個new操作符
new操作符做了這些事:
- 創建了一個全新的對象。
- 被執行[[Prototype]](也就是proto)鏈接。
- 使this指向新創建的對象。。
- 通過new創建的每個對象將最終被[[Prototype]]鏈接到這個函數的prototype對象上。
- 如果函數沒有返回對象類型Object(包含Functoin, Array, Date, RegExg, Error),那麼new表達式中的函數調用將返回該對象引用。
function New(func) {
var res = {};
if (func.prototype !== null) {
res.__proto__ = func.prototype;
}
var ret = func.apply(res, Array.prototype.slice.call(arguments, 1));
if ((typeof ret === "object" || typeof ret === "function") && ret !== null) {
return ret;
}
return res;
}
var obj = New(A, 1, 2);
實現一個 instanceOf
instanceOf
的內部機制是通過判斷對象的原型鏈中是不是能找到該類型的 prototype
function instanceOf(left,right) {
let proto = left.__proto__;
let prototype = right.prototype
while(true) {
if(proto === null) return false
if(proto === prototype) return true
proto = proto.__proto__;
}
}
Promise 實現
Promise 有3個狀態 pending
、 resolve
和 reject
。因爲狀態的的確定,所以Promise的結果是可靠的。
Promise 還能解決回調地獄的問題。
- 乞丐版
實現了 Promise
的主要功能,缺少異步處理等其他情況
class Promise {
constructor (fn) {
// 三個狀態
this.state = 'pending'
this.value = undefined
this.reason = undefined
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
}
}
let reject = value => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = value
}
}
// 自動執行函數
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}
// then
then(onFulfilled, onRejected) {
switch (this.state) {
case 'fulfilled':
onFulfilled(this.value)
break
case 'rejected':
onRejected(this.value)
break
default:
}
}
}
- 改良版:yck 小冊裏面實現的了Promise的主要功能(沒有catch、finally、靜態調用等)
// 三種狀態
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.3
function 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);
}
}
- 補充版
// 三種狀態
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn) {
let _this = this;
_this.currentState = PENDING;
_this.value = undefined;
_this.resolvedCallbacks = [];
_this.rejectedCallbacks = [];
_this.resolve = function (value) {
if (value instanceof MyPromise) {
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());
}
})
}
try {
fn(_this.resolve, _this.reject);
} catch (e) {
_this.reject(e);
}
}
MyPromise.prototype.then = function (onResolved, onRejected) {
var self = this;
var promise2;
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) {
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 () {
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 {
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.3
function resolutionProcedure(promise2, x, resolve, reject) {
if (promise2 === x) {
return reject(new TypeError("Error"));
}
if (x instanceof MyPromise) {
if (x.currentState === PENDING) {
x.then(function (value) {
resolutionProcedure(promise2, value, resolve, reject);
}, reject);
} else {
x.then(resolve, reject);
}
return;
}
let called = false;
if (x !== null && (typeof x === "object" || typeof x === "function")) {
try {
let then = x.then;
if (typeof then === "function") {
then.call(
x,
y => {
if (called) return;
called = true;
resolutionProcedure(promise2, y, resolve, reject);
},
e => {
if (called) return;
called = true;
reject(e);
}
);
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
// catch方法
MyPromise.prototype.catch = function (rejectFn) {
return this.then(undefined, rejectFn)
}
//finally方法
MyPromise.prototype.finally = function (callback) {
return this.then(
value => MyPromise.resolve(callback()).then(() => value),
reason => MyPromise.resolve(callback()).then(() => { throw reason })
)
}
/*
靜態方法添加
*/
// resolve方法
MyPromise.resolve = function(val){
return new MyPromise((resolve,reject)=>{
resolve(val)
});
}
//reject方法
MyPromise.reject = function(val){
return new MyPromise((resolve,reject)=>{
reject(val)
});
}
//race方法
MyPromise.race = function(promises){
return new MyPromise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve, reject)
};
})
}
//all方法(獲取所有的promise,都執行then,把結果放到數組,一起返回)
MyPromise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}
實現 EventBus
- 乞丐版
實現了主要功能,但不處理異常場景,也不實現 remove
class EventEmitter {
constructor () {
// 存儲事件
this.events = this.events || new Map()
}
// 監聽事件
addListener (type, fn) {
if (!this.events.get(type)) {
this.events.set(type, fn)
}
}
// 觸發事件
emit (type) {
let handle = this.events.get(type)
handle.apply(this, [...arguments].slice(1))
}
}
- 進階版
class EventEmitter{
constructor(){
if(this._events === undefined){
this._events = Object.create(null);//定義事件對象
this._eventsCount = 0;
}
}
emit(type,...args){
const events=this._events;
const handler=events[type];
//判斷相應type的執行函數是否爲一個函數還是一個數組
if(typeof handler==='function'){
Reflect.apply(handler,this,args);
}else{
const len=handler.length;
for(var i=0;li<len;i++){
Reflect.apply(handler[i],this,args);
}
}
return true;
}
on(type,listener,prepend){
var m;
var events;
var existing;
events=this._events;
//添加事件的
if(events.newListener!==undefined){
this.emit('namelessListener',type,listener);
events=target._events;
}
existing=events[type];
//判斷相應的type的方法是否存在
if(existing===undefined){
//如果相應的type的方法不存在,這新增一個相應type的事件
existing=events[type]=listener;
++this._eventsCount;
}else{
//如果存在相應的type的方法,判斷相應的type的方法是一個數組還是僅僅只是一個方法
//如果僅僅是
if(typeof existing==='function'){
//如果僅僅是一個方法,則添加
existing=events[type]=prepend?[listener,existing]:[existing,listener];
}else if(prepend){
existing.unshift(listener);
}else{
existing.push(listener);
}
}
//鏈式調用
return this;
}
removeListener(type,listener){
var list,events,position,i,originalListener;
events=this._events;
list=events[type];
//如果相應的事件對象的屬性值是一個函數,也就是說事件只被一個函數監聽
if(list===listener){
if(--this._eventsCount===0){
this._events=Object.create(null);
}else{
delete events[type];
//如果存在對移除事件removeListener的監聽函數,則觸發removeListener
if(events.removeListener)
this.emit('removeListener',type,listener);
}
}else if(typeof list!=='function'){
//如果相應的事件對象屬性值是一個函數數組
//遍歷這個數組,找出listener對應的那個函數,在數組中的位置
for(i=list.length-1;i>=0;i--){
if(list[i]===listener){
position=i;
break;
}
}
//沒有找到這個函數,則返回不做任何改動的對象
if(position){
return this;
}
//如果數組的第一個函數纔是所需要刪除的對應listener函數,則直接移除
if(position===0){
list.shift();
}else{
list.splice(position,1);
}
if(list.length===1)
events[type]=list[0];
if(events.removeListener!==undefined)
this.emit('removeListener',type,listener);
}
return this;
}
}
最後
- 覺得有用的請點個贊
- 歡迎關注公衆號「前端進階課」認真學前端,一起進階。