apply和call, bing方法的应用

Clloz · · 114次浏览 ·

前言

JavaScript 中所有函数的构造函数为 ƒ Function() { [native code] },所有函数的 [[prototype]] 默认指向 Function.prototype,在 Function.prototype 上有三个方法 callapplybind,他们共同的作用就是为函数调用指定的执行上下文,也是就是 this 的指向。本文来说一说这三个方法的区别和使用场景。

callapply

callapply 基本上没有什么区别,不同的地方是他们所接受的参数不同。两者的第一个参数都是函数执行时使用的 this 值,后面的参数就有所不同。apply 接受一个数组或类数组对象(比如 arguments),而 call 接受一组参数列表。

//apply
func.apply(thisArg, [argsArray])

//call
function.call(thisArg, arg1, arg2, ...)

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

应用

类数组对象调用数组方法

JavaScript 中有类数组对象,最常见的就是所有非箭头函数中都可以使用的局部变量 arguments,还有 DOM 操作返回的 NodeList 集合,它类似于 Array,但除了 length 属性和索引元素之外没有任何 Array 属性。例如,它没有 pop 方法。但是它可以被转换为一个真正的 Array

var args = Array.prototype.slice.call(arguments);
var args = [].slice.call(arguments);

// ES2015
const args = Array.from(arguments);
const args = [...arguments]; //扩展运算符

实际上我们也可以自己定义类数组对象,只要有索引和 length 即可,{'length': 2, '0': 'eat', '1': 'bananas'}

数组拼接

我们可以用 push 方法为数组添加新的元素,虽然 push 方法接受可变参数,但是如果我们以数组作为参数的话,它只是把这个数组所谓一个元素添加,如果想把两个数组进行拼接,那么可以用 concat,但是 concat 是返回一个新的数组,如果只是想要将两个数组拼接,可以用 apply

var array = ['a', 'b'];
var elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ["a", "b", 0, 1, 2]
用数组替代参数列表

其实从上面的例子就可以看出,对于一些可能需要很长参数列表的函数,apply 都可以让我们用数组来代替参数列表,比如 Math.maxMath.min 等。

/* 找出数组中最大/小的数字 */
var numbers = [5, 6, 2, 3, 7];

/* 应用(apply) Math.min/Math.max 内置函数完成 */
var max = Math.max.apply(null, numbers); /* 基本等同于 Math.max(numbers[0], ...) 或 Math.max(5, 6, ..) */
var min = Math.min.apply(null, numbers);

这样使用的风险就是如果数组非常大,在函数调用的时候可能参数个数会超出引擎的限制(JavaScript 核心中已经做了硬编码 参数个数限制在 65536,具体数值由引擎决定),如果遇到这种情况可以把数组切块循环执行。

继承
function Animal(name){
    this.name = name;
    this.showName = function(){
        console.log(this.name);
    }
}

function Cat(name){
    Animal.call(this, name);
}

bind

bind 和前面两者不一样的是他并不是立即调用函数,而是返回一个新的函数。bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。bind 方法返回一个原函数的拷贝,并拥有指定的 this 值和初始参数。还有一点是 如果 bind 函数的参数列表为空,或者第一个参数是 nullundefined,执行作用域的 this 将被视为新函数的 thisArgapplycall 则是如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

应用

创建绑定函数
this.x = 9;    // 在浏览器中,this 指向全局的 "window" 对象
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX();
// 返回 9 - 因为函数是在全局作用域中调用的

// 创建一个新函数,把 'this' 绑定到 module 对象
// 新手可能会将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
预设函数初始参数

这种用法可以为函数设置一些初始参数,有点类似函数柯里化。

function list() {
  return Array.prototype.slice.call(arguments);
}

function addArguments(arg1, arg2) {
    return arg1 + arg2
}

var list1 = list(1, 2, 3); // [1, 2, 3]

var result1 = addArguments(1, 2); // 3

// 创建一个函数,它拥有预设参数列表。
var leadingThirtysevenList = list.bind(null, 37);

// 创建一个函数,它拥有预设的第一个参数
var addThirtySeven = addArguments.bind(null, 37); 

var list2 = leadingThirtysevenList(); 
// [37]

var list3 = leadingThirtysevenList(1, 2, 3); 
// [37, 1, 2, 3]

var result2 = addThirtySeven(5); 
// 37 + 5 = 42 

var result3 = addThirtySeven(5, 10);
// 37 + 5 = 42 ,第二个参数被忽略
快捷调用

这是一个 mdn 上给出的示例。

var slice = Array.prototype.slice;

// ...

slice.apply(arguments);

// 与前一段代码的 "slice" 效果相同
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.apply.bind(unboundSlice);

// ...

slice(arguments);

一开始没看明白,mdn 的意思是第二段函数不需要调用 apply 方法了。Function.prototype.apply.bind(unboundSlice); 这句其实是把 slice 作为 apply 绑定函数的 this ,一开始我没看明白 slice 作为 apply 的拷贝,是怎么执行的,也没函数调用它啊。后来换了个角度想了想,我们的 a.b() 的调用方式对执行的 b 函数意义就是将 this 指向 a 对象,那么既然这样我们直接用 bind 设置好了 this 也就不需要对象调用函数的形式了,也就是 a.b()b.bind(a) 是等价的。这里把 bindapply 在同一个表达式里面使用,让人感觉有点绕,其实写下来就是 Array.prototype.slicebind 设置为了 Function.prototype.apply 的绑定函数(即 var slice,也就是 apply 的拷贝)this。最后一句语句执行的时候就是,applyarguments 作为了 slicethis

参考文章

  1. MDN

Clloz

人生をやり直す

发表评论

电子邮件地址不会被公开。 必填项已用*标注

我不是机器人*

 

EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00