一. call和apply
1. 程式碼完整實現
Function.prototype.mycall = function (context, ...argus) { if (typeof this !== 'function') { throw new TypeError('not funciton') } const fn = this let result = null context = context || window context.fn = fn result = context.fn(...argus) delete context.fn return result } Function.prototype.myapply = function (context, ...argus) { if (typeof this !== 'function') { throw new TypeError('not funciton') } const fn = this let result = null context = context || window argus = argus && argus[0] || [] context.fn = fn result = context.fn(...argus) delete context.fn return result }
2. 先來溜溜
- 案例一
class Member { constructor (options) { const {name, sex, age} = options this.name = name this.sex = sex this.age = age } introduce () { console.log(`I'm ${this.name}, ${this.age}, ${this.sex}`) } } const member1 = new Member({ name: 'gina', sex: 'girl', age: 23 }) const member2 = new Member({ name: 'gun', sex: 'boy', age: 24 }) member2.introduce.mycall(member1) // I'm gina, 23, girl member2.introduce.myapply(member1) // I'm gina, 23, girl
- 案例二
Math.max.myapply(null, [1,2,3,4]) // 4 Math.max.mycall(null, 1,2,3,4) // 4
3. 注意要點
- 開頭需要做一個型別判斷:
if (typeof this !== 'function') { throw new TypeError('not funciton') }
- 獲取原始函式: 比如執行
Math.max.mycall(null, 1,2,3,4)
的時候,mycall函式內部的this指向了Math.max函式,所以我們可以通過const fn = this
獲取到要執行的函式,然後將該函式繫結到傳入的context
物件(context.fn = fn
),然後再把它刪除掉delete context.fn
。
總體來說,call和apply的實現還是比較簡單的。
二. bind
1. 完整程式碼實現
Function.prototype.mybind = function (context, ...argus) { if (typeof this !== 'function') { throw new TypeError('not funciton') } const fn = this const fBound = function (...argus2) { return fn.apply(this instanceof fBound ? this : context, [...argus, ...argus2]) } fBound.prototype = Object.create(this.prototype) return fBound }
2. 邊溜邊說
- 案例一
const foo = { v: 1 }; function bar() { return this.v; } const bindFoo = bar.mybind(foo); bindFoo() // 1
bind
函式返回的是一個可執行函式,所以return
了一個函式。此刻返回的函式,按正常來說,在執行的時候,this是指向執行處的當前上下文。但該案例中, mybind
需要滿足bar在執行中返回值時,this
依然是指向 foo,所以我們在mybind
返回的函式中需要使用fn.apply
來保持上下文和執行mybind
的時候一致。
- 案例二
const foo = { v: 1 }; function bar(name, age) { console.log(this.v); console.log(name); console.log(age); } const bindFoo = bar.bind(foo, 'daisy'); bindFoo('18'); // 1 // daisy // 18
mybind
需要做到可以接受傳參,並且將引數給到bar
函式,後面再執行bindFoo
再傳的引數,會接在之前傳參的後面。所以mybind
原始碼中使用了[...argus, ...argus2]
來進行引數整合。
- 案例三
const value = 2; const foo = { value: 1 }; function bar(name, age) { this.habit = 'shopping'; console.log(this.value); console.log(name); console.log(age); } bar.prototype.friend = 'kevin'; const bindFoo = bar.bind(foo, 'daisy'); const obj = new bindFoo('18'); // undefined // daisy // 18 console.log(obj.habit); console.log(obj.friend); // shopping // kevin
在執行const obj = new bindFoo('18')
這一 new
操作的時候,此刻this
應該指向當前物件obj
。所以mybind
在fn.apply
的第一個引數,做了這樣的判斷this instanceof fBound ? this : context
。
在const obj = new bindFoo('18')
內部執行到this instanceof fBound ? this : context
時,此刻this
指向obj
,fBound
其實也就是bindFoo
,this instanceof fBound
判斷了obj
是不是繼承自bindFoo
,也就是進行了構建函式new
操作。
- 案例4
function bar() {} bar.prototype.value = 2 const bindFoo = bar.mybind(null); bindFoo.prototype.value = 1; console.log(bar.prototype.value) // 2
mybind
執行後返回的函式fBound
修改prototype
的時候,不應該影響到fn.prototype
,兩者應該是獨立的。所以原始碼使用了fBound.prototype = Object.create(this.prototype)
, 而不是fBound.prototype = this.prototype
。
總得來說,bind
的實現考慮的點還是比較多的。