js深入之實現call、apply和bind

奮鬥的小舟發表於2019-07-21

一. 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。所以mybindfn.apply的第一個引數,做了這樣的判斷this instanceof fBound ? this : context

const obj = new bindFoo('18')內部執行到this instanceof fBound ? this : context時,此刻this指向objfBound其實也就是bindFoothis 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的實現考慮的點還是比較多的。

 

參考:

https://github.com/mqyqingfeng/Blog/issues/12

相關文章