js-call/apply/bind

文章目录

  • call、apply、bind
    • call
    • apply
    • bind
    • 该怎么选择
    • 总结

参考:
1.https://baijiahao.baidu.com/s?id=1617122883116378490&wfr=spider&for=pc
2.https://www.cnblogs.com/zhazhanitian/p/11400898.html

call、apply、bind

js中的call(), apply()和bind()是Function.prototype下的方法,都是用于改变函数运行时上下文,最终的返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined。
call和apply方法的作用都是改变函数的执行环境,第一个参数传入上下文执行环境,然后传入函数执行所需的参数

let a = {
    value: 1
}
function getValue(name, age) {
    console.log(name, age)
    console.log(this.value)
}
getValue.call(a, 'xql', '18')
getValue.apply(a, ['xql', '18'])

getValue.bind(a, 'xql', '18') 
// getValue.bind(a, 'xql', '18')() 执行该函数

call

使用 apply, 你可以继承其他对象的方法:注意这里apply()的第一个参数是null,在非严格模式下,第一个参数为null或者undefined时会自动替换为指向全局对象,apply()的第二个参数为数组或类数组。
使用方法

function Product(name, price) {
    this.name = name
    this.price = price
}

function Food(name, price) {
    // 调用Product的call方法并将当前作用域对象this传入替换掉Product内部的this作用域对象
    Product.call(this, name, price)
    this.category = 'food'
}

let tempFood = new Food('cheese', 5)

console.log('输出:' + tempFood.name, tempFood.price, tempFood.category)
// 输出:cheese 5 food

这是一个简单使用 call 来实现的继承操作,在上面我们可以看到 Food 这个构造函数它自身并没有定义 name 和 price 这两个属性,重点就在 Product.call(this, name, price) 这一行代码上面,调用 Product 的 call (调用 call 方法会调用一遍自身)方法并将当前作用域对象 this 传入替换掉 Product 内部的 this 作用域对象,都知道当对象作为参数的时候都是地址传递,对任何一个引用修改都会修改到源对象,所以这里最终 new 出来的对象就有了 name 和 price 属性
接下来看一下具体的实现:

Function.prototype.imitateCall = function (context) {
    // 赋值作用域参数,如果没有则默认为 window,即访问全局作用域对象
    context = context || window    
    // 绑定调用函数(.call之前的方法即this,前面提到过调用call方法会调用一遍自身,所以这里要存下来)
    context.invokFn = this    
    // 截取作用域对象参数后面的参数
    let args = [...arguments].slice(1)
    // 执行调用函数,记录拿取返回值
    let result = context.invokFn(...args)
    // 销毁调用函数,以免作用域污染
    Reflect.deleteProperty(context, 'invokFn')
    return result
}

可用imitateCall代替call

apply

call()是apply()的一颗语法糖,作用和apply()一样,同样可实现继承,唯一的区别就在于call()接收的是参数列表,而apply()则接收参数数组。
唯参数形式不同而已,依此只需要稍微改动 imitateCall 方法即可模拟出我们的 imitateApply 方法

Function.prototype.imitateApply = function (context) {
    // 赋值作用域参数,如果没有则默认为 window,即访问全局作用域对象
    context = context || window
    // 绑定调用函数(.call之前的方法即this,前面提到过调用call方法会调用一遍自身,所以这里要存下来)
    context.invokFn = this
    // 执行调用函数,需要对是否有参数做判断,记录拿取返回值
    let result
    if (arguments[1]) {
        result = context.invokFn(...arguments[1])
    } else {
        result = context.invokFn()
    }
    // 销毁调用函数,以免作用域污染
    Reflect.deleteProperty(context, 'invokFn')
    return result
}

bind

bind()的作用与call()和apply()一样,都是可以改变函数运行时上下文,区别是call()和apply()在调用函数之后会立即执行,而bind()方法调用并改变函数运行时上下文后,返回一个新的函数,供我们需要时再调用。

var Preson = {
 name:'person';
 getName:function(){
  return this.name;
 }
}
var boy ={
 name:'twy'
}
//bind返回一个新函数,供以后调,this指向bind里的对象
var getName = Person.getName.bind(boy);
//现在调用
console.log(getName());//输出twy

来看一个例子深入理解:

const people = {
    names: '渣渣逆天',
    getName: function () {
        return this.names
    }
}

const temp = people.getName

console.log('输出:' + temp())
// 输出:undefined

虽然这里的 temp 方法和 getName 方法指向同一个对地址(即同一段代码块),但是这里的 temp 的调用者是 window,然而在 window 对象上找 names 属性就会发现是 undefined

绑定bind后

const people = {
        names: '渣渣逆天',
        getName: function () {
            return this.names
        }
    }

const temp = people.getName,
      context = temp.bind(people)
// temp的this换成people的this
console.log('输出:' + context())
// 输出:渣渣逆天

需要注意的是这里新建并被返回的方法当被执行时,绑定 bind 的原方法(temp)将被调用,并将原方法内部作用域对象替换为绑定 bind 时传入的第一个参数(people),即然如此,应该能联想到 bind 的实现离不开 call 或 apply
看看如何实现

Function.prototype.imitateBind = function (context) {
    // 获取绑定时的传参
    let args = [...arguments].slice(1),
        // 定义中转构造函数,用于通过原型连接绑定后的函数和调用bind的函数
        F = function () {},
        // 记录调用函数,生成闭包,用于返回函数被调用时执行
        self = this,
        // 定义返回(绑定)函数
        bound = function () {
            // 合并参数,绑定时和调用时分别传入的
            let finalArgs = [...args, ...arguments]
            
            // 改变作用域,注:aplly/call是立即执行函数,即绑定会直接调用
            // 这里之所以要使用instanceof做判断,是要区分是不是new xxx()调用的bind方法
            return self.call((this instanceof F ? this : context), ...finalArgs)
        }
    
    // 将调用函数的原型赋值到中转函数的原型上
    F.prototype = self.prototype
    // 通过原型的方式继承调用函数的原型
    bound.prototype = new F()
    
    return bound
}

这是《JavaScript Web Application》一书中对 bind() 的实现:通过设置一个中转构造函数 F,使绑定后的函数与调用 bind() 的函数处于同一原型链上,用 new 操作符调用绑定后的函数,返回的对象也能正常使用 instanceof,因此这是最严谨的 bind() 实现
注:在 Javascript 中,多次 bind() 是无效的,只有第一次有用

该怎么选择

不确定具体有多少参数被传入函数,选用apply();如果确定函数可接收多少个参数,并且想一目了然表达形参和实参的对应关系,用call();如果我们想要将来再调用方法,不需立即得到函数返回结果,则使用bind();

总结

call()、apply()和bind()都是用来改变函数执行时的上下文,可借助它们实现继承;call()和apply()唯一区别是参数不一样,call()是apply()的语法糖;bind()是返回一个新函数,供以后调用,而apply()和call()是立即调用。

你可能感兴趣的:(前端,js,call/apply/bind)