浅谈Javascript中的call和apply

2018-10-18  本文已影响0人  指尖的宇宙

ECMAScript3给Function的原型定义了两个方法,他们是 Function.prototype.callFunction.prototype.apply 在实际开发中特别是在一些函数式风格的代码书写中,call和apply方法尤其重要。

call和apply的区别

Function.prototype.callFunction.prototype.apply都是非常常用的方法,他们的作用一模一样,区别仅仅是传入的参数形式不同。

apply接收两个参数,第一个参数指定了函数体内部this对象的指向,第二个参数为一个带下标的集合,这个集合可以是数组,也可以为类数组,apply方法把这个集合中的元素作为参数传递给被调用的函数。

var func = function (a, b, c) {
    alert([a, b, c]); // 输出[1,2,3]
}
func.apply(null, [1, 2, 3]);

在这段代码中,参数1,2,3被放在一个数组中一起传递给func函数,他们分别对应func参数列表中的a, b, c

call传入的参数数量不固定,跟apply相同的是,第一个参数也是代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数:

var func = function (a, b, c) {
    alert([a, b, c]); // 输出 [1,2,3]
}
func.call(null, 1, 2, 3);

当调用一个函数时候,js的解析器并不会计较形参和实参的数量、类型以及顺序上的区别,js的参数在内部就是用一个数组来表示的,从这个意义上面来说,call比apply的使用率更高,我们不必关心具体有多少参数被传入函数,只要使用call一股脑的推进去就可以了。

apply是包装在call上面的一颗语法糖,如果我们明确的知道了函数接收多少个参数,而且想一目了然的表达形参和实参的对应关系,那么就可以使用apply来传递参数。

当我们使用call或者apply的时候,如果我们传入的第一个参数为null,函数体内部的this会指向默认的宿主对象,在浏览器中则是window:

var func = function (a, b, c) {
    alert(this === window); // true
}
func.apply(null, [1, 2, 3])

但是在严格模式下面,函数体内部的this还是null

var func = function (a, b, c) {
  "use strict";
  alert(this === null);     // 输出true
}

func.apply(null, [1, 2, 3]);

有时候我们使用 call或者apply的目标并不是在于指定this指向而是另有用途
比如借用其他对象的方法,那么我们可以传入null来代替某一个具体的对象;

Math.max.apply(null, [1, 2, 4, 5]) // 输出5

call和apply的用途

1.改变this指向:

call和apply最常见的用途就是改变函数内部的this指向,我们看个例子:

var obj1 = {
  name: "louis"
}
var obj2 = {
  name: "jack"
}
window.name = 'window';
var getName = function () {
  alert(this.name);
}
getName(); //输出 window
getName.call(obj1); // 输出 louis
getName.call(obj2); // 输出 jack

当执行getName.call(obj1)这句代码的时候,getName函数体内的this指向obj1对象,所以此处的

var getName = function () {
  alert(this.name);
}

实际上相当于:

var getName = function () {
  alert(obj1.name); // 输出louis
}

实际开发中,我们会经常遇到this。指向被不经意改变的场景,比如有一个div节点,div节点的onclick事件中的this指向本来是指向这个div的:

document.getElementById('div1').onclick = function () {
  alert(this.id); // div1
}

假如该事件中有一个内部函数func,在事件内部调用func的时候,func函数体内部的this就指向了window而不是我们预期的div,见如下代码;

document.getElementById('div1').onclick = function () {
  alert(this.id); // 输出:div1
  var func = function () {
    alert(this.id); // 输出:undefined
  }
  func();
};

这个时候我们可以使用call来修正func函数内部的this,使其依然指向div:

document.getElementById('div1').onclick = function () {
  var func = function () {
    alert(this.id); // 输出:div1
  }
  func.call(this);
};

2.Function.prototype.bind

大部分的高级浏览器都实现了内置的 Function.prototype.bind, 用来指定函数内部的this指向即使没有原生的 Function.prototype.bind 实现,我们来模拟一个也不是难事。

Function.prototype.bind = function (context) {
  var self = this;    // 保存原函数
  return function () { // 返回一个新的函数
    return self.apply(context, arguments);    
// 执行新的函数的时候,会把之前传入的context 当作新函数体内的this
  }
};

var obj = {
  name: 'sven'
};

var func = function () {
  alert(this.name); // 输出:sven
}.bind(obj);
func();

我们通过Function.prototype.bind来包装'func'函数,并且传入一个对象context当做参数,这个context就是我们想要修正的this对象。

Function.prototype.bind的内部实现中,我们先把func函数的引用保存起来,然后返回一个新的函数。当我们在将来执行func函数时,实际上先执行的是这个刚刚返回的新函数。在新函数内部,self.apply(context,arguments)这句代码才是执行原来的func函数,并且指定context对象为func函数体内的this。

3.借用其他对象的方法

我们知道,杜鹃既不会筑巢,也不会孵雏,而是把自己的蛋寄托给云雀等其他鸟类,让它们代为孵化和养育。同样,在JavaScript中也存在类似的借用现象。

借用方法的第一种场景是“借用构造函数”,通过这种技术,可以实现类似于继承的效果:

var A = function () {
  this.name = name;
}

var B = function () {
  A.apply(this, arguments);
}

B.prototype.getName = function () {
  return this.name;
}

var b = new B('sven');
console.log(b.getName()); // 输出 sven

借用方法的第二种运用场景跟我们的关系更加紧密。

函数的参数列表arguments是一个类数组对象,虽然它也有"下标",但是它并非真正的数组,所以也不能像数组一样进行排序操作或者往集合里面添加一个新的元素,这种情况下,我们常常使用

Array.prototype对象上面的方法,比如想王auguments中添加一个新的元素,通常会借用Array.prototype.push:

(function () {
  Array.prototype.push.call(arguments, 3);
  console.log(arguments); // [1,2,3]
})(1, 2)

在操作arguments的时候,我们经常非常频繁地找 Array.prototype 对象借用方法。

想把arguments转成真正数组的时候,可以借用Array.prototype.slice 方法;想要截取arguments列表中的头一个元素的时候,又可以借用Array.prototype.shift方法,

上一篇下一篇

猜你喜欢

热点阅读