Skip to content

[Issues#3]关于js中的apply()和call()两个方法

ckinmind edited this page Nov 7, 2016 · 1 revision

参考资料

[知乎: 如何理解和熟练运用js中的call及apply?](https://www.zhihu.com/question/20289071) js笔记——call,apply,bind使用笔记


1. 方法定义

1.1 call()方法

语法:call(thisObj, arg1, arg2, ...) 
定义:调用一个对象的一个方法,以另一个对象替换当前对象。 
说明: 
call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 
如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj

1.2 apply()方法

语法:apply([thisObj,[arg1,arg2,....]) 
定义:应用某一对象的一个方法,用另一个对象替换当前对象。 
说明: 
如果第二个参数不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。 
如果没有提供任何一个参数,那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数

2. 作用,相似,区别

  • 两者作用一致,都是把obj(即this)绑定到thisObj,这时候thisObj具备了obj的属性和方法。或者说thisObj『继承』了obj的属性和方法。绑定后会立即执行函数

  • 简单说就是用对象的实例去替换原来的this,这样的话就可以用其他对象里的方法了

  • 唯一区别是apply接受的是数组参数,call接受的是连续参数

  • 当你的参数是明确知道数量时,用 call,而不确定的时候,用 apply,然后把参数 push 进数组传递进去。当参数数量不确定时,函数内部也可以通过 arguments 这个数组来便利所有的参数

  • call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向。因为 JavaScript 的函数存在「定义时上下文」和「运行时上下文」以及「上下文是可以改变的」这样的概念

3. 使用例子

3.1

function add(a, b){console.dir(this);}

function sub(a, b){console.dir(this);}

add(1,2);
"Window"

sub(1,2);
"Window"

add.call(sub, 1, 2);
"sub(a, b)"

sub.apply(add, [1, 2]);
"add(a, b)"

console.dir()直接将该DOM结点以DOM树的结构进行输出,可以详细查对象的方法发展等

3.2

function add(j, k){
    return j+k;
}

function sub(j, k){
    return j-k;
}

我们在控制台运行:
add(5,3); //8
add.call(sub, 5, 3); //8
add.apply(sub, [5, 3]); //8

sub(5, 3); //2
sub.call(add, 5, 3); //2
sub.apply(add, [5, 3]); //2

结合3.1理解

3.3 调用原生对象的方法

var a = {0:1, 1:"yjc", length: 2}; 

a.slice(0,1); //TypeError: a.slice is not a function

Array.prototype.slice.call(a,0,1);//[1]
Array.prototype.slice.apply(a,[0,1]);//[1]
  1. slice()方法可从已有的数组中返回选定的元素,第一个参数是起始位置,第二个参数是结束为止
  2. 对象a类似array,但不具备array的slice等方法。使用call绑定,这时候就可以调用slice方法

3.4 实现继承

var Parent = function(){
    this.name = "yjc";
    this.age = 22;
}

var child = {};

console.log(child);//Object {} ,空对象

Parent.call(child);

console.log(child); //Object {name: "yjc", age: 22}

通过call和apply,我们可以实现对象继承

3.5 bind的使用

obj.bind(thisObj, arg1, arg2, ...);
把obj绑定到thisObj,这时候thisObj具备了obj的属性和方法。与call和apply不同的是,bind绑定后不会立即执行
同样是add()和sub():
add.bind(sub, 5, 3); //不再返回8
add.bind(sub, 5, 3)(); //8
如果bind的第一个参数是null或者undefined,等于将this绑定到全局对象

3.6

function changeStyle(attr, value){
    this.style[attr] = value;
}
var box = document.getElementById('box');
window.changeStyle.call(box, "height", "200px");

call中的第一个参数用于指定将要调用此函数的对象,在这里,changeStyle函数将被box对象调用,this指向了box对象,如果不用call的话,程序报错,因为window对象中没有style属性

apply的用法:
window.changeStyle.apply(box, ['height', '200px']);

如果call或apply的第一参数是null的话, this指向window

3.7

举个更常规的例子吧,比如人类,当被问到:你爸爸叫什么名字的时候,每个人都会调用一个返回父亲名字的方法

function fatherName(){
    return this.father.name;
}
你直接调用它会出事,因为这时候的this指代window,系统抛出异常说: Cannot read property 'name' of undefined
但这不代表fatherName这个函数没有存在的意义,要看是谁来调用它。比如有一个人:

var people = {
    father: {
       name: 'ck'
    },
    'getMyFatherName': fatherName
};
people.getMyFatherName(); //ck

还有另一个人:
var people2 = {
    father: {
       name: 'ck2'
    },
    'getMyFatherName': fatherName
};
people2.getMyFatherName(); //ck2

其实,中国还有很多人,写那……么多fatherName: fatherName 好烦呐
其实你可以这样:
var people = {
    father: {
       name: 'ck'
    }
};
var people2 = {
    father: {
       name: 'ck2'
    }
};
[people,people2].map(function(who){
    return fatherName.call(who);
}); //["guodegang", "stone"]

map方法对数组的所有成员依次调用一个函数,根据函数结果返回一个新数组 方法接受一个函数作为参数。该函数调用时,map方法会将其传入三个参数,分别是当前成员、当前位置和数组本身