一. 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的实现考虑的点还是比较多的。










