手寫Promise、call、apply、bind和new
一、Promsie
promise翻譯過來是承諾的意思,這個承諾會在未來有一個確切的答覆,並且該承諾有三種狀態,這個承諾一旦從等待狀態變成為其他狀態就永遠不能更改狀態了。
- pedding等待中
- resolved完成了
- rejected拒絕了
Promise還實現了then鏈式呼叫,解決回撥地獄的問題
實現第一步
搭建建構函式的大體框架
const PENDING = 'pending' const RESOLVE = 'resolved' const REJECTED = 'rejected' function MyPromise(fn){ const that = this; that.state =PENDING that.value = null that.resolveCallbacks = [] that.rejectCallbacks = [] //resolve與reject函式 //fn函式 }
- 首先建立三個常量,用於表示Promise的三個狀態
- 在函式內部體內宣告that,因為程式碼可能會非同步執行,用於獲取正確的this指向
- value用於儲存resolve和reject傳入的值
- resolveCallbacks 、rejectCallbacks 用於儲存then中的回撥,因為執行完Promise時狀態可能還是等待中,需要把then中的回撥儲存起來,當狀態改變時呼叫
第二步,完善resolve和reject函式
function resolve(value) { if(that.state === PENDING) { that.state = RESOLVED that.value = value that.resolvedCallbacks.map(cb => cb(that.value)) } } function reject(value) { if(that.state === PENDING){ that.state = REJECTED that.value = value; that.rejectedCallbacks.map(cb => cb(that.value)); } }
第三步,執行Promise傳入的函式
try { fn(resolve,reject) } catch(e) { reject(e) }
- 執行傳入的函式,並將上一步寫的resolve,reject函式作為引數傳進去
- 要注意,在執行函式過程中可能會報錯,需要捕獲錯誤並且執行reject
第四步,實現then函式
MyPromise.prototype.then = function (onFulfilled,onRejected) { const that = this; onFulfilled = typeof onFulfilled=== 'function' ?onFulfilled :v => v onRejected = typeof onRejected === 'function' ?onRejected :r =>throw r } if(that.state === PENDING) { that.resolvedCallbacks.push(onFulfilled) that.rejectedCallbacks.push(onRejected) } else if(that.state === RESOLVED) { onFulfilled(that.value) } else { onRejected(that.value) }
- 首先判斷兩個引數是否為函式型別,因為這兩個引數是可選的。當引數不是函式型別時,需要建立一個函式賦值給對應的引數
- 接下來進行一系列的邏輯判斷,當狀態不是等待態時,就去執行相對應得函式。如果是等待態,則將函式push進回撥函式中
Promise完整程式碼
const PENDING = 'pending' const RESOLVED = 'resolved' const REJECTED = 'rejected' function MyPromise(fn){ const that = this that.state = PENDING that.value = null that.resolvedCallbacks = [] that.rejectedCallbacks = [] function resolve(value) { if(that.state === PENDING) { that.state = RESOLVED that.value = value that.resolvedCallbacks.map(cb => cb(that.value)) } } function reject(value) { if(that.state === PENDING){ that.state = REJECTED that.value = value; that.rejectedCallbacks.map(cb => cb(that.value)); } } try { fn(resolve, reject) } catch (e) { reject(e) } } MyPromise.prototype.then = function(onFulfilled, onRejected) { const that = this //對傳入的兩個引數做判斷,如果不是函式將其轉為函式 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v// onFulfilled = v => v onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r } if(that.state === PENDING) { that.resolvedCallbacks.push(onFulfilled) that.rejectedCallbacks.push(onRejected) } else if(that.state === RESOLVED) { onFulfilled(that.value) } else { onRejected(that.value) } } new MyPromise((resolve, reject) => { setTimeout(() => { resolve('成功的回撥資料') }, 1000) }).then(value => { console.log('Promise.then:', value) })
二、call
除了第一個引數外,call 可以接收一個引數列表,apply 只接受一個引數陣列。
call的實現思路:
- 不傳入引數,預設為window
- 給新的物件新增一個函式,執行完後刪除
(其實call的實現非常簡單,總共8行程式碼)
Funtion.prototype.myCall = function (context){ //設定預設引數為window var context = context || window //給新物件新增方法fn //getValue.call(a,1,2) =>a.fn = getValue //此處this.指向呼叫myCall的物件 context.fn = this //取出除第一個以外的引數 var args = [...arguments].slice(1) // var result = context.fn(...args) deletecontext.fn return result }
三、apply
apply則更簡單,七行即可
Function.prototype.myApply = function (context){ var context = context || window context.fn = this //判斷是否有第二個引數,有則展開 var result = arguments[1]? context.fn(...arguments[1]) : context.fn() delete context.fn return result }
三、bind
bind和其他兩個方法類似,只是會返回一個函式,可以通過bind實現函式柯里化
Function.prototype.myBind = function (context) { if (typeof this !== 'function') { throw new TypeError('Error') } var _this = this var args = [...arguments].slice(1) // 返回一個函式 return function F() { // 因為返回了一個函式,我們可以 new F(),所以需要判斷 if (this instanceof F) { return new _this(...args, ...arguments) } return _this.apply(context, args.concat(...arguments)) } }
四、new
- 新生成了一個物件
- 連結到原型
- 繫結到this
- 返回新物件
function create() { // 建立一個空的物件 let obj = new Object() // 獲得建構函式 let Con = [].shift.call(arguments) // 連結到原型 obj.__proto__ = Con.prototype // 繫結 this,執行建構函式 let result = Con.apply(obj, arguments) // 確保 new 出來的是個物件 return typeof result === 'object' ? result : obj }