JS 的 call、apply、bind 函数

3,365 阅读5分钟

1、三者的区别

  • call()方法接受的是参数列表,需要一个一个的传
  • apply()方法接受的是一个参数数组,
  • bind()call()函数的传参是一样的,但是bind()返回的是一个function,不能立即执行。

2、call

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

call的语法

fun.call(thisArg, arg1, arg2, ...)

参数

  • thisArg

    fun 函数运行时指定的 this 值。if(thisArg == undefined|null) this = window,if(thisArg == number|boolean|string) this == new Number()|new Boolean()| new String()

  • arg1, arg2, ...

    指定的参数列表。

call的应用

使用 call 方法调用父构造函数实现继承

在该例子中,子构造函数Sun()通过调用父构造函数Father()call 方法来实现继承,此时Sun()函数中拥有了Father()函数中的属性。

function Father(name,age){
	this.name = name;
	this.age = age;
	this.book=['数据结构','计算机网络','算法']
	console.log('1.执行到Father---名字:'+this.name+'; 年龄:'+this.age+' 书籍:'+this.book);

}
function Sun(name,age){
	Father.call(this,name,age);
	this.subject = '软件专业';
	console.log('2.执行到Sun---专业:'+this.subject);
}
var sun = new Sun('gxm','20');
	sun.book.push('javaScript');
	console.log('3.改变sun.book:'+sun.book);
	
var sun2 = new Sun('xz','22');

运行结果

1.执行到Father---名字:gxm; 年龄:20 书籍:数据结构,计算机网络,算法
2.执行到Sun---专业:软件专业
3.改变sun.book:数据结构,计算机网络,算法,javaScript

1.执行到Father---名字:xz; 年龄:22 书籍:数据结构,计算机网络,算法
2.执行到Sun---专业:软件专业
3.查看sun2.book:数据结构,计算机网络,算法

从这个运行结果也可以看出,new出来的实例对其继承而来的属性进行改变,对于原函数来说并没有改变。

使用 call 方法调用函数并且指定上下文的 this

function play() {
  var reply = [this.player, '打', this.playName,'打了',this.playTimes].join(' ');
  console.log(reply);
}

var obj = {
  player: 'gxm',playName: '羽毛球', playTimes: '1小时'
};

play.call(obj); //gxm 打 羽毛球 打了 1小时

如果直接运行play()函数,结果就只会是打 打了。但指定了上下文就不一样了,play.call(obj)就是将play()函数的上下文从全局的window指定到obj对象上了。所以运行出来的结果过成为gxm 打 羽毛球 打了 1小时

思考下面的代码之中有几个this:

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1

上面代码之中,只有一个this,就是函数foo的this,所以t1、t2、t3都输出同样的结果。因为所有的内层函数都是箭头函数,都没有自己的this,它们的this其实都是最外层foo函数的this。

使用 call 方法调用函数并且不指定第一个参数(argument)节

在下面的例子中,我们调用了 display 方法,但并没有传递它的第一个参数。如果没有传递第一个参数,this 的值将会被绑定为全局对象。

var sData = 'Wisen';

function display() {
  console.log('sData value is %s ', this.sData);
}

display.call();  // sData value is Wisen

注意:在严格模式下,this 的值将会是 undefined。见下文。

'use strict';

var sData = 'Wisen';

function display() {
  console.log('sData value is %s ', this.sData);
}

display.call(); // Cannot read the property of 'sData' of undefined

3、apply

apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

apply语法

func.apply(thisArg, [argsArray])

参数

  • thisArg

    可选的。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

  • argsArray

    可选的。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 nullundefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

apply的应用

apply实现继承

function superType() {
    this.color = ['red','blue','yellow']
}
function subType() {
    //---继承了superType---
    superType.applay(this);
    //superType.call(this);
}
var instance1 = new subType();
instance1.color.push('green');
console.log('instance1.color:', instance1.color);
//instance1.color: (4) ["red", "blue", "yellow", "green"]

用 apply 将数组添加到另一个数组

我们可以使用push将元素追加到数组中。并且,因为push接受可变数量的参数,我们也可以一次推送多个元素。

但是,如果我们传递一个数组来推送,它实际上会将该数组作为单个元素添加,而不是单独添加元素,因此我们最终得到一个数组内的数组。如果那不是我们想要的怎么办?在这种情况下,concat确实具有我们想要的行为,但它实际上并不附加到现有数组,而是创建并返回一个新数组。

但是我们想要附加到我们现有的阵列......那么现在呢? 写一个循环?当然不是吗?

apply来帮你!

var array = ['a', 'b'];
var elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ["a", "b", 0, 1, 2]

3、bind

语法

function.bind(thisArg[, arg1[, arg2[, ...]]])

参数

  • thisArg

    调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为新函数的 thisArg

  • arg1, arg2, ...

    当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

返回值

返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。

4、手撕call、bind、apply

var foo = {
    value: 1
};
function bar() {
    console.log(this.value);
}
bar.call(foo); // 1
bar.apply(foo); // 1
let a = bar.bind(foo); 
a();

call

Function.prototype._call=function (context, ...arg) {
    context = context || window;
    let fn = Symbol();
    context[fn] = this;//这里的意思是将bar赋给obj.fn属性
    let res = context[fn](...arg);
    delete context[fn];
    return res;
}

apply

Function.prototype._apply=function (context, arg=[]) {
    context = context || window;
    let fn = Symbol();
    context[fn] = this;//这里的意思是将bar赋给obj.fn属性
    let res = context[fn](...arg);
    delete context[fn];
    return res;
}

bind

Function.prototype._bind = function (context, ...arg) {
    context = context || window;
    let fn = this;//这里的意思是将bar赋给fn属性
    return function(...others){
        fn.apply(context, [...arg, ...others])
    }
}

5、类数组转换成数组的方式

  1. Array.prototype.slice.call( arguments );
  2. [...arguments]
  3. Array.from(arguments)
  4. 还有一个方法就是第一个方法的延伸,结合了bind方法 slice 是 Function.prototype (en-US) 的 call() 方法的绑定函数,并且将 Array.prototype 的 slice() 方法作为 this 的值。
// 与前一段代码的 "slice" 效果相同
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.apply.bind(unboundSlice);

// ...

slice(arguments);