JavaScript 手写call apply bind【保姆级注释】

101 阅读3分钟

call、bind、apply手写怎么写,争取一遍过。 call和bind和apply的作用都能够改变函数调用时的this,并且能够传递参数。他们的区别也很简单,call和bind都能够传递很多个参数,apply必须传递数组。而apply和call都是立即执行,但是bind返回的确是一个参数。

1. call

    Function.prototype.Mycall = function (context) {
    debugger
    // 1. 获取参数 传递进来的参数第一个是函数 不要,我们要后面的参数 转化为数组
    var args = [...arguments].slice(1)
    // 2.声明一个result值
    let result = null

    // 3. 如果传递了this指向的某个对象 context就会有值 如果没有传 就让他指向 window
    context = context || window
    // 4. 给context对象【此时是从传入的sub函数】添加fn方法,赋值为this,this指向add函数,因为原本是add.call() 所以this指向add
    context.fn = this
    // 5. 调用函数
    // 这里由context调用了fn函数,所以在add函数里面打印this 就是context这个对象
    result = context.fn(...args)
    // 6. 将属性删除
    delete context.fn
    return result
    }

测试用例

function add(num1, num2) {
    console.log(this);
    console.log(num1 + num2);
}
function sub(num1, num2) {
    console.log(num1 - num2);
}
add.Mycall(sub, 6, 3)

2. apply

Function.prototype.myApply = function (context) {
    debugger
    // 2. 结果变量
    var result = null
    // 3. 判断是否传入了this指向对象
    context = context || window
    // 4. 给上下文对象 赋值 值就是add函数
    context.fn = this
    // 5. 判断是否有传入参数 注意 apply传入的是一个数组
    if (arguments[1]) {
        // 6. 如果传入,就利用扩展运算符 展开 注意所以是1,
        result = context.fn(...arguments[1])
    } else {
        // 7. 如果没有传入参数,就直接调用
        result = context.fn()
    }
    // 8. 删除增加的属性
    delete context.fn
    // 9. 返回结果
    return result
}

function add(num1, num2) {
    console.log(this);
    console.log(num1 + num2);
}
function sub(num1, num2) {
    console.log(num1 - num2);
}
// add.myApply(sub, [6, 3]) // 9
// add.myApply(sub) // NaN
// 我原来疑惑 如果没有传入第一个参数 那么返回的是NaN 是不是有问题 测试了一下 apply的原始方法也是这样
// add.myApply([6, 3]) // NaN
// add.apply([9, 3]) // NaN

测试用例

function add(num1, num2) {
    console.log(this);
    console.log(num1 + num2);
}
function sub(num1, num2) {
    console.log(num1 - num2);
}
// add.myApply(sub, [6, 3]) // 9
// add.myApply(sub) // NaN
// 我原来疑惑 如果没有传入第一个参数 那么返回的是NaN 是不是有问题 测试了一下 apply的原始方法也是这样
// add.myApply([6, 3]) // NaN
// add.apply([9, 3]) // NaN

3. bind

Function.prototype.myBind = function (context) {
    debugger
    // 2 为什么要保存当前函数的引用? 获取其余传入的参数
    let args = [...arguments].slice(1)
    // 3 保存当前函数的引用 比如这里是add函数
    let fn = this
    // 4 创建一个函数
    function Fn() {
        // 5 根据调用方式 传入不同的绑定值
        //   如果fn是构造函数,不改变this指向, this此时指向构造函数Person【利用instanceof进行判断】
        //   如果普通函数,就把context传给fn执行,也就是普通函数add执行时,this改为context“sub函数”
        // 6. args.concat(arguments)是为了合并参数 合并返回的函数()和调用的函数 传递的参数
        // console.log('33行this', this); // 是Fn函数的实例
        return fn.apply(
            this instanceof Fn ? this : context,
            args.concat(...arguments)
        )
    }
    // 7. 为了使得新创建的函数实例能够继承调用函数的原型上面的属性和方法
    // console.log(this);
    // console.log(this.prototype);
    Fn.prototype = Object.create(this.prototype)
    return Fn
}

测试用例

function add(num1, num2) {
    debugger
    console.log(this);
    console.log(num1 + num2);
}
add.prototype.laugh = function () {
    console.log('laugh');
}
function sub(num1, num2) {
    console.log(num1 - num2);
}
let add1 = add.myBind(sub)
let re1 = add1(1, 2)
// re1.laugh()

function Person(a, b, c) {
    this.a = a
    this.b = b
    console.log('this', this); // 打印的是person函数 person函数就是Fn函数
    console.log('a', a);
    console.log('b', b);
    console.log('c', c);
    this.add = function () {
        console.log('111');
    }
}
Person.prototype.say = function () {
    console.log('say');
}
let person = Person.myBind(sub, 200)
let re = new person(10, 20)
re.add()
re.say() // 能够打印say