一. 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的實現考慮的點還是比較多的。
