js速记--call、apply与bind

63 阅读2分钟

一、异同

相同点:改变this指向

不同点:

  • call:参数为参数列表,立即执行
  • apply:参数为数组,立即执行
  • bind:返回一个新的函数,需要再次进行调用

二、使用场景

1、将类数组转为数组操作

类数组如果没有实现数组方法,是不具备数组操作的能力的,比如获取一个元素集合,要使用数组方法操作

var element = document.querySelectorAll('*')
// 获取所有元素的标签名
const tags = Array.prototype.map.call(element, function(e) {return e.tagName})

2、调用原始的toString()方法

Object对象原型上实现的toString()方法可以用来判断当前对象的类型,但是每个对象自身可能又重写了toString()方法,所以通常要获取对象的类型,都使用的是Object原型上的toString()

var d = new Date()
// 返回的是 [object Date]
console.log(Object.prototype.toString.call(d))
// 返回的是时间格式的字符串
console.log(d.toString())

3、实现继承时调用父类构造

在实现js继承时,通常使用call或者apply来实现对父类的属性的继承

function Parent(name) {
    this.name = name
}

function Child(name, age) {
    Parent.call(this, name)
    this.age = age
}

var child = new Child('张三', 12)

三、实现

1、实现call

Function.prototype.call = function(context) {
    if(!context) {
        // 没有传入指定的this对象,默认指向全局对象
        context = typeof window === 'undefined' ? global : window
    }
    // this指向的是调用call函数的那个Function实例
    context.fn = this
    // 获取到函数参数
    var rest = [...arguments].slice(1)
    var res = context.fn(...rest)
    delete context.fn
    return res
}

2、实现apply

apply和call的实现类似,唯一不同的就是参数不一样

Function.prototypr.apply = function(context, rest) {
    if(!context) {
        context = typeof window === 'undefined' ? global : window
    }
    context.fn = this
    if(!rest) {
        rest = []
    }
    var res = context.fn(...rest)
    delete context.fn
    return res
}

3、实现bind

函数调用bind不会立即执行,而是返回一个新的函数,执行新的函数,this指向的是bind()传入的第一个对象; 通过bind()返回的新函数,可以当做构造函数被new,此时内部的this指向new出来的实例,不会指向bind()时绑定的对象,但是传入的参数依然有效,所以实现bind需要注意这两点

Function.prototype.bind = function(context, ...args1) {
    if(typeof this !== 'function') {
        throw new Error('不是通过函数调用')
    }
    var _this = this
    function F() {}
    // ①让返回的实例继承原来那个函数的原型
    F.prototype = this.prototype
    function bound(...args2) {
        // 这里指定this,需要判断是new还是直接执行返回函数,如果是new的,this指向实例自身,直接执行,this指向context
        // 判断是否为new,可以通过 instance 或者 contructor,由于返回的bound可以修改原型上的contructor,所以不直接判断bound上的构造函数,一般都是创建一个新的构造,然后让bound原型基础新的构造的原型
        _this.apply(this instanceof F ? this : context, [...args1, args2])
    }
    // 结合①实现继承
    bound.prototype = new F()
    return bound
}