call、apply、bind知识点一条龙服务

276 阅读4分钟

1、定义

Apply()/call()方法调用一个具有给定this值的函数,用数组或列表形式提供的参数。

Bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this指向bind()的第一个参数,其余的参数作为新函数的参数,供调用时使用。

在JS中,call和apply都是为了改变某个函数运行时上下文存在的(即改变函数内部this指向)

JS的一大特点:存在定义时上下文、运行时上下文和函数上下文是可以改变的

2、区别

  1. apply、call、bind都是用来改变this指向
  2. apply、call、bind 中第一个参数都是this要指向的对象,指定的上下文
  3. apply、call、bind都可以利用后续参数传值
  4. bind返回一个绑定上下文的函数,可以立即执行(在bind绑定函数后加括号),也可以稍后执行;apply、call则是立即执行

注意:多次bind()是无效的

3、常用用法

3.1数组之间的追加

Array.prototype.push.apply(array1,array2)

3.2验证是否是数组

Object.prototype.toString.call(obj)==='[object Array]'

3.3伪数组变真数组

Array.prototype.slice.call(arguments)

3.4获取数组中最大值和最小值

Let num = [1,3,5445,289]
Math.max.apply(Math,number)
Math.max.call(Math,3,6,78,1)

3.5 bind的使用

var name = '兔子'
var food = "草"
function Animal(name, food) {
    this.name = name
    this.food = food
    this.eating = function () {
        setTimeout(function () {
            console.log(this)
            console.log(this.name + '吃' + this.food)
        }, 1000)
    }
}
let cat = new Animal('猫', '鱼')
cat.eating()

正常执行这段代码我们想要输出的答案肯定是 “猫吃鱼”,但是实际上确是“兔子吃草”,为什么呢,因为setTimeout在全局环境中执行,所以哦this指向了window,如果要输出“猫吃鱼”,就要改变setTimeout函数执行时的this指向,如下

var name = '兔子'
var food = "草"
function Animal(name, food) {
    this.name = name
    this.food = food
    this.eating = function () {
        let _this = this //保存this
        setTimeout(function () {
            console.log(this)
            console.log(_this.name + '吃' + _this.food)
        }, 1000)
    }
}
let cat = new Animal('猫', '鱼')
cat.eating()//猫吃鱼

或者

var name = '兔子'
var food = "草"
function Animal(name, food) {
    this.name = name
    this.food = food
    this.eating = function () {
        setTimeout(function () {
            console.log(this)
            console.log(this.name + '吃' + this.food)
        }.bind(this), 1000)//通过bind改变this指向
    }
}
let cat = new Animal('猫', '鱼')
cat.eating()//猫吃鱼

4、原理实现

4.1 call实现

4.1.1原理分析:

执行call时实际上是先改变执行函数的this指向,然后再进行函数执行

举例:

var foo = {
    value:1
}
function bar() {
    console.log(this.value)
}
bar.call(foo)//1

call调用后实际如下执行:

var foo = {
    value: 1,
    bar: function () {
        console.log(this.value)
    }
}
foo.bar()//1

这样就将this指向了foo,在真正执行call时用完bar属性还是会把他删除的,下面我们就来模拟实现一下call

4.1.2步骤如下:

  1. 将要执行函数(bar)设为传入对象(foo)的属性;
  2. 执行改函数(foo.bar());
  3. 删除该函数;
  4. 返回结果;

4.1.3实现代码如下:


Function.prototype._call = function (context = window, ...args) {
    if (this === Function.prototype) {
      return undefined; // 用于防止 Function.prototype.call() 直接调用
    }
    context = context;
    const fn = Symbol();//键的唯一性,避免与context中的其他键发生冲突
    context[fn] = this;//很多同学不明白这个this是什么意思,实际上这里符合了this绑定四项原则的隐式绑定原则,this相当于上面🌰中的bar函数,将此函数设为传入对象的属性
    const result = context[fn](...args);//执行函数
    delete context[fn];//删除该函数,避免对空间的浪费以及对执行上下文对象的污染
    return result;
}

4.2 apply实现

apply的原理同call类似,只是apply中第二个参数传入的是一个数组

4.2.1代码实现

Function.prototype._apply = function (context = window, args) {
    if (this == Function.prototype) {
        return undefined
    }
    const fn = Symbol()
    context[fn] = this
    let result
    if (Function.prototype.toString.call(args) === '[Object array]') {//Array.isArray()
        result = context[fn](...args)
    } else {
        result = context[fn]()
    }
    delete context[fn]
    return result
}

4.3 bind 实现

bind的实现和call,apply还不一样

bind特点

  • 改变this指向
  • 返回一个函数
  • 可以传入参数
  • 绑定函数也可以使用new操作符创建对象 接下来一步一步用js实现bind

4.3.1第一版改变this指向并返回函数

Function.prototype._bind = function (context) {
    let fn = this
    if (typeof this !== 'function') {
        return undefined
    } else {
        return function () {
            fn.call(context)
        }
    }
}

4.3.2第二版 传入参数并支持柯里化

Function.prototype._bind = function (context) {
    let fn = this
    if (typeof this !== 'function') {
        return undefined
    }
    let args = Array.prototype.slice.call(arguments, 1)
    return function () {
        let bindArgs = Array.prototype.slice.call(arguments)
        fn.apply(context, args.concat(bindArgs))
    }
}

4.3.2 第三版(终版)

以上的实现即可满足bind的大部分使用场景,但是要搞就要搞明白呗; 仔细阅读MDN中# Function.prototype.bind()发现bind还有另外一个特性

当使用new构造绑定函数时,原来提供的this值会被忽略,提供的参数列表仍然会插入到构造函数调用时的参数列表之前。(以下面代码为参考,如果是new调用,bound函数的this指向实例,如果是普通函数调用this指向bind绑定的context)

Function.prototype._bind = function (context) {
    let fn = this
    if (typeof this !== 'function') {
        return undefined
    }
    let args = Array.prototype.slice.call(arguments, 1)
    let Fn_ = function () { }
    let bound = function () {
        let bindArgs = Array.prototype.slice.call(arguments)
        // this instanceof Fn_ 也可以来判断绑定函数是不是进行new操作
        fn.apply(this.constructor === bound ? this : context, args.concat(bindArgs))
    }
    Fn_.prototype = fn.prototype
    bound.prototype = new Fn_()//如果直接使用bound.prototype=fn.prototype 会直接更改fn的原型函数
    return bound
}

over~  

5、参考地址

www.cnblogs.com/coco1s/p/48… blog.csdn.net/chern1992/a… zhuanlan.zhihu.com/p/94068275 www.muyiy.cn/blog/3/3.4.…