图片 4

JS核心系列

JS宗旨类别:浅谈 call apply 与 bind

2016/03/01 · JavaScript
· apply,
bind,
call

原稿出处: 一像素   

在JavaScript中,call、apply和bind
是Function对象自带的多个形式,那多少个方法的首要性成效是改造函数中的this指向,进而得以高达接花移木的效果。本文将对那五个措施进行详细的上书,并列出多少个优良应用项景。

 

call(thisArgs [,args…])


该方式能够传递八个thisArgs参数和一个参数列表,thisArgs钦定了函数在运行期的调用者,也正是函数中的this对象,而参数列表会被流传调用函数中。thisArgs的取值有以下4种情况:

(1卡塔 尔(阿拉伯语:قطر‎ 不传,大概传null,undefined, 函数中的this指向window对象

(2卡塔尔国 传递另叁个函数的函数名,函数中的this指向那几个函数的援用

(3卡塔尔传递字符串、数值或布尔类型等底工项目,函数中的this指向其相应的包裹对象,如
String、Number、Boolean

(4卡塔 尔(英语:State of Qatar) 传递三个目的,函数中的this指向那一个目的

JavaScript

function a(){ console.log(this); //输出函数a中的this对象 } function
b(){} //定义函数b var obj = {name:’onepixel’}; //定义对象obj a.call();
//window a.call(null); //window a.call(undefined);//window a.call(1);
//Number a.call(”); //String a.call(true); //Boolean a.call(b);//
function b(){} a.call(obj); //Object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function a(){
    console.log(this); //输出函数a中的this对象
}
function b(){} //定义函数b
 
var obj = {name:’onepixel’}; //定义对象obj
 
a.call(); //window
a.call(null); //window
a.call(undefined);//window
a.call(1); //Number
a.call(”); //String
a.call(true); //Boolean
a.call(b);// function b(){}
a.call(obj); //Object

那是call的中坚职能,它同意你在一个对象上调用该目的未有概念的艺术,并且那些方法能够访问该对象中的属性,至于那样做有怎么着平价,笔者待会再讲,我们先看多个回顾的事例:

JavaScript

var a = { name:’onepixel’, //定义a的属性 say:function(){ //定义a的方法
console.log(“Hi,I’m function a!”); } }; function b(name){
console.log(“Post params: “+ name); console.log(“I’m “+ this.name);
this.say(); } b.call(a,’test’); >> Post params: test I’m onepixel
I’m function a!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var a = {
 
    name:’onepixel’, //定义a的属性
 
    say:function(){ //定义a的方法
        console.log("Hi,I’m function a!");
    }
};
 
function b(name){
    console.log("Post params: "+ name);
    console.log("I’m "+ this.name);
    this.say();
}
 
b.call(a,’test’);
>>
Post params: test
I’m onepixel
I’m function a!

当执行b.call时,字符串test用作参数字传送递给了函数b,由于call的功能,函数b中的this指向了对象a,
因而一定于调用了目的a上的函数b,而实际a中绝非定义b 。

 

apply(thisArgs[,args[]])


apply和call的唯意气风发分歧是第三个参数的传递格局各异,apply的第叁个参数必得是三个数组,而call允许传递二个参数列表。值得您放在心上的是,尽管apply选用的是三个参数数组,但在传递给调用函数时,却是以参数列表的样式传递,大家看个大约的例证:

JavaScript

function b(x,y,z){ console.log(x,y,z); } b.apply(null,[1,2,3]); // 1 2
3

1
2
3
4
5
function b(x,y,z){
    console.log(x,y,z);
}
 
b.apply(null,[1,2,3]); // 1 2 3

apply的这一个特点很要紧,大家会在底下的选用途景中涉及这些特点。

 

bind(thisArgs [,args…])


bind是ES5新扩张的四个办法,它的传参和call相像,但又和call/apply有着鲜明的例外,即调用call或apply都会自动实践相应的函数,而bind不会举办相应的函数,只是再次回到了对函数的引用。粗略大器晚成看,bind就像是比call/apply要走下坡路一些,那ES5为什么还要引进bind呢?

骨子里,ES5引进bind的的确目标是为了弥补call/apply的阙如,由于call/apply会对指标函数自动施行,进而变成它无法在事变绑定函数中利用,因为事件绑定函数不必要大家手动实施,它是在事变被触发时由JS内部自行实践的。而bind在落到实处转移函数this的还要又不会自动推行对象函数,由此能够完备的消除上述难点,看多少个例证就能够知晓:

JavaScript

var obj = {name:’onepixel’}; /** *
给document加多click事件监听,并绑定onClick函数 *
通过bind方法设置onClick的this为obj,并传递参数p1,p2 */
document.addEventListener(‘click’,onClick.bind(obj,’p1′,’p2′),false);
//当点击网页时接触并举行 function onClick(a,b){ console.log( this.name,
//onepixel a, //p1 b //p2 ) }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var obj = {name:’onepixel’};
 
/**
* 给document添加click事件监听,并绑定onClick函数
* 通过bind方法设置onClick的this为obj,并传递参数p1,p2
*/
document.addEventListener(‘click’,onClick.bind(obj,’p1′,’p2′),false);
 
//当点击网页时触发并执行
function onClick(a,b){
    console.log(
            this.name, //onepixel
            a, //p1
            b  //p2
    )
}

当点击网页时,onClick被触发施行,输出onepixel p1 p2,
表明onClick中的this被bind订正成了obj对象,为了对bind举行深切的接头,大家来看一下bind的polyfill实现:

JavaScript

if (!Function.prototype.bind) { Function.prototype.bind = function
(oThis) { var aArgs = Array.prototype.slice.call(arguments, 1), fToBind
= this, //this在那针对的是目的函数 fBound = function () { return
fToBind.apply( //假设外界施行var obj = new
fBound(),则将obj作为最终的this,放弃选拔oThis this instanceof fToBind ?
this //当时的this正是new出的obj : oThis || this,
//倘使传递的oThis无效,就将fBound的调用者作为this
//将通过bind传递的参数和调用时传递的参数举办统风度翩翩,并视作最后的参数字传送递
aArgs.concat(Array.prototype.slice.call(arguments))); };
//将指标函数的原型对象拷贝到新函数中,因为目的函数有一点都不小恐怕被当作构造函数使用
fBound.prototype = this.prototype; //再次来到fBond的引用,由外界按需调用
return fBound; }; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
        var aArgs = Array.prototype.slice.call(arguments, 1),
            fToBind = this, //this在这里指向的是目标函数
            fBound = function () {
                return fToBind.apply(
                    //如果外部执行var obj = new fBound(),则将obj作为最终的this,放弃使用oThis
                    this instanceof fToBind
                            ? this  //此时的this就是new出的obj
                            : oThis || this, //如果传递的oThis无效,就将fBound的调用者作为this
 
                    //将通过bind传递的参数和调用时传递的参数进行合并,并作为最终的参数传递
                    aArgs.concat(Array.prototype.slice.call(arguments)));
            };
 
        //将目标函数的原型对象拷贝到新函数中,因为目标函数有可能被当作构造函数使用
        fBound.prototype = this.prototype;
 
        //返回fBond的引用,由外部按需调用
        return fBound;
    };
}

动用场景生龙活虎:世袭


大家通晓,JavaScript中并未有诸如Java、C#等高等语言中的extend
关键字,由此JS中从不继续的定义,假诺必必要继续的话,call和apply能够兑现这一个作用:

JavaScript

function Animal(name,weight){ this.name = name; this.weight = weight; }
function Cat(){ Animal.call(this,’cat’,’50’);
//Animal.apply(this,[‘cat’,’50’]); this.say = function(){
console.log(“I am ” + this.name+”,my weight is ” + this.weight); } } var
cat = new Cat(); cat.say();//I am cat,my weight is 50

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Animal(name,weight){
   this.name = name;
   this.weight = weight;
}
 
function Cat(){
    Animal.call(this,’cat’,’50’);
  //Animal.apply(this,[‘cat’,’50’]);
 
   this.say = function(){
      console.log("I am " + this.name+",my weight is " + this.weight);
   }
}
 
var cat = new Cat();
cat.say();//I am cat,my weight is 50

当通过new运算符产生了cat时,Cat中的this就针对了cat对象(关于new运算符的授课,请参谋:),而一而再的要紧是介于Cat中实行了Animal.call(this,’cat’,’50’)
那句话,在call上校this作为thisArgs参数字传送递,于是Animal方法中的this就对准了Cat中的this,而cat中的this指向的是cat对象,所以Animal中的this指向的正是cat对象,在Animal中定义了name和weight属性,就一定于在cat中定义了这么些属性,由此cat对象便享有了Animal中定义的性质,进而完结了世袭的目标。

 

选拔场景二:狡兔三窟


在讲上面包车型地铁剧情前边,大家先是来认知一下JavaScript中的一个非标准专门的工作术语:ArrayLike(类数组/伪数组)

ArrayLike
对象即具有数组的风流倜傥部分行为,在DOM中曾经显示出来,而jQuery的崛起让ArrayLike在JavaScript中山大学放异彩。ArrayLike对象的精致在于它和JS原生的Array相像,然而它是随意创设的,它出自开采者对JavaScript对象的恢宏,也正是说:对于它的原型(prototype)大家得以轻便定义,而不会传染到JS原生的Array。

ArrayLike对象在JS中被分布应用,比如DOM中的NodeList,
函数中的arguments皆以类数组对象,那个指标像数组同样存款和储蓄着每二个因素,但它从不操作数组的法门,而大家能够透过call将数组的一些方法移接到ArrayLike对象,进而实现操作其成分的指标。比如大家能够这么遍历函数中的arguments:

JavaScript

function test(){ //检查实验arguments是或不是为Array的实例 console.log( arguments
instanceof Array, //false Array.isArray(arguments) //false );
//决断arguments是还是不是有forEach方法 console.log(arguments.forEach);
//undefined // 将数组中的forEach应用到arguments上
Array.prototype.forEach.call(arguments,function(item){
console.log(item); // 1 2 3 4 }); } test(1,2,3,4);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function test(){
    //检测arguments是否为Array的实例
    console.log(
            arguments instanceof Array, //false
            Array.isArray(arguments)  //false
    );
    //判断arguments是否有forEach方法
    console.log(arguments.forEach); //undefined
 
    // 将数组中的forEach应用到arguments上
    Array.prototype.forEach.call(arguments,function(item){
        console.log(item); // 1 2 3 4
    });
 
}
test(1,2,3,4);

而外,对于apply来说,大家地点提到了它只有的二个特色,即apply选择的是数组,在传递给调用函数的时候是以参数列表传递的。
这些特点让apply看起来比call
后起之秀,比方有与上述同类一个场景:给定一个数组[1,3,4,7],然后求数组中的最大因素,而你知道,数组中并从未拿走最大值的方式,日常景观下,你供给通过编写制定代码来达成。而笔者辈通晓,Math对象中有贰个获得最大值的办法,即Math.max(),
max方法需求传递三个参数列表,然后再次回到这几个参数中的最大值。而apply不仅能够将Math对象的max方法运用到此外对象上,还是能够将叁个数组转变为参数列表传递给max,看代码就能够可想而知:

JavaScript

var arr = [2,3,1,5,4]; Math.max.apply(null,arr); // 5

1
2
3
var arr = [2,3,1,5,4];
 
Math.max.apply(null,arr); // 5

如上正是call和apply比较精粹的多少个利用项景,熟谙精通这么些才具,并把这个特征应用到你的骨子里项目中,会使您的代码看起来更为风趣!

2 赞 12 收藏
评论

图片 1

多个函数都是Function对象自带的八个点子,首要作用是改换函数中this的针对性。

call()

语法 fun.call(thisArg[, arg1[, arg2[, …]]])

该措施能够传递一个thisArgs参数和一个参数列表,thisArgs钦点了函数在运营期的调用者,也正是函数中的this对象,而参数列表会被传到调用函数中。

通过 call
方法,你能够在多少个目的上借用另二个对象上的法子,比方Object.prototype.toString.call([]),便是三个Array对象借用了Object对象上的秘诀。

thisArgs的取值有以下4种景况:

(1卡塔 尔(阿拉伯语:قطر‎ 不传,只怕传null,undefined, 函数中的this指向window对象
(2卡塔尔 传递另一个函数的函数名,函数中的this指向那个函数的援引
(3卡塔 尔(英语:State of Qatar)传递字符串、数值或布尔类型等基本功项目,函数中的this指向其相应的包装对象,如
String、Number、Boolean
(4卡塔尔国 传递二个对象,函数中的this指向那么些指标

function a(){
console.log(this); //输出函数a中的this对象
}
function b(){} //定义函数b
var obj = {name:'onepixel'}; //定义对象obj
a.call(); //window
a.call(null); //window
a.call(undefined);//window
a.call(1); //Number
a.call(''); //String
a.call(true); //Boolean
a.call(b);// function b(){}
a.call(obj); //Object

那是call的大旨效用,它同意你在四个对象上调用该对象未有定义的格局,并且这些法子能够访问该对象中的属性。

示例:

A、

var a = {
    name:'onepixel', //定义a的属性
    say:function(){ //定义a的方法
        console.log("Hi,I'm function a!");
    }
};
function b(name){
    console.log("Post params: "+ name);
    console.log("I'm "+ this.name);
    this.say();
}
b.call(a,'test');     //将b的this指向a,参数是b的

输出:

//Post params: test
//I'm onepixel
//I'm function a!

B、

行使call方法调用无名氏函数

在下例中的for循环体内,大家创立了一个佚名函数,然后通过调用该函数的call方法,将每一种数组成分作为钦命的this值实行了这几个无名函数。那几个无名函数的显要指标是给每种数组成分对象增添一个print方法,这几个print方法能够打字与印刷出各要素在数组中的精确索引号。当然,这里不是必需得让数组成分作为this值传入那些无名函数(普通参数就能够卡塔 尔(阿拉伯语:قطر‎,目标是为着演示call的用法。

var animals = [
  {species: 'Lion', name: 'King'},
  {species: 'Whale', name: 'Fail'}
];

for (var i = 0; i < animals.length; i++) {
  (function (i) { 
    this.print = function () { 
      console.log('#' + i  + ' ' + this.species + ': ' + this.name); 
    } 
    this.print();
  }).call(animals[i], i);
}
//#0 Lion: King
//#1 Whale: Fail

另:

图片 2

图片 3

apply()

语法:fun.apply(thisArg[, argsArray])

apply和call的唯意气风发差距是首个参数的传递情势各异,apply的第4个参数必须是三个数组,而call允许传递贰个参数列表。值得您放在心上的是,固然apply选拔的是四个参数数组,但在传递给调用函数时,却是以参数列表的格局传递。

专心:这里的argsArray能够是一个数组恐怕类数组对象,假如该参数的值为null
或 undefined,则代表不要求传入任何参数。

function b(x,y,z){
    console.log(x,y,z);
}
b.apply(null,[1,2,3]); // 1 2 3

采取apply和松手函数

聪明的apply用法允许你在少数本来须要写成遍历数组变量的任务中应用内建的函数。在收受里的事例中我们会动用Math.max/Math.min来搜索一个数组中的最大/最小值。

//里面有最大最小数字值的一个数组对象
var numbers = [5, 6, 2, 3, 7];

/* 使用 Math.min/Math.max 在 apply 中应用 */
var max = Math.max.apply(null, numbers);
// 一般情况是用 Math.max(5, 6, ..) 或者 Math.max(numbers[0], ...) 来找最大值
var min = Math.min.apply(null, numbers);

//通常情况我们会这样来找到数字的最大或者最小值
//比对上面的栗子,是不是下面的看起来没有上面的舒服呢?
max = -Infinity, min = +Infinity;
for (var i = 0; i < numbers.length; i++) {
  if (numbers[i] > max)
    max = numbers[i];
  if (numbers[i] < min) 
    min = numbers[i];
}

call()与apply()应该选哪二个可以吗?

当参数是扎眼知晓多少时用call;不明确的时候用apply,然后把参数push进数组传递步入,也足以经过arguments那一个数组来遍历全数的参数。

bind()

语法:fun.bind(thisArg[, arg1[, arg2[, …]]])

bind是ES5新扩充的二个方式,它的传参和call相仿,但又和call/apply有着显然的两样,即调用call或apply都会活动实践相应的函数,而bind不会实施相应的函数,只是重临了对函数的引用。

图片 4

粗略风度翩翩看,bind就像是比call/apply要走下坡路一些,那ES5怎么还要引入bind呢?

实在,ES5引入bind的的确指标是为了弥补call/apply的阙如,由于call/apply会对指标函数自动施行,进而造成它不可能在事变绑定函数中动用,因为事件绑定函数无需大家手动实施,它是在事变被触发时由JS内部自行实施的。而bind在得以达成转移函数this的还要又不会自动实行对象函数,因而可以完备的缓和上述难题,看二个例证就能够明白:

当点击网页时,伊芙ntClick被触发施行,输出JSLite.io p1 p2,
表明伊芙ntClick中的this被bind退换成了obj对象。假若你将EventClick.bind(obj,’p1′,’p2′)
变成 EventClick.call(obj,’p1′,’p2′) 的话,页面会直接出口 JSLite.io p1 p2

var obj = {name:'JSLite.io'};
/**
 * 给document添加click事件监听,并绑定EventClick函数
 * 通过bind方法设置EventClick的this为obj,并传递参数p1,p2
 */
document.addEventListener('click',EventClick.bind(obj,'p1','p2'),false);
//当点击网页时触发并执行
function EventClick(a,b){
    console.log(
            this.name, //JSLite.io
            a, //p1
            b  //p2
    )
}
// JSLite.io p1 p2

兼容

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, // this在这里指向的是目标函数
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP
                 ? this //此时的this就是new出的obj
                 : oThis || this,//如果传递的oThis无效,就将fBound的调用者作为this

               //将通过bind传递的参数和调用时传递的参数进行合并,并作为最终的参数传递
               aArgs.concat(Array.prototype.slice.call(arguments)));
        };
    fNOP.prototype = this.prototype;
    //将目标函数的原型对象拷贝到新函数中,因为目标函数有可能被当作构造函数使用
    fBound.prototype = new fNOP();
    //返回fBond的引用,由外部按需调用
    return fBound;
  };
}

选择场景后生可畏:世襲
世家知道,JavaScript中绝非诸如Java、C#等高档语言中的extend
关键字,因而JS中尚无持续的概念,假设一定要持续的话,call和apply能够兑现这一个意义:

function Animal(name,weight){
   this.name = name;
   this.weight = weight;
}
function Cat(){
    Animal.call(this,'cat','50');
  //Animal.apply(this,['cat','50']);
   this.say = function(){
      console.log("I am " + this.name+",my weight is " + this.weight);
   }
}
var cat = new Cat();
cat.say();//I am cat,my weight is 50

当通过new运算符发生了cat时,Cat中的this就针对了cat对象(关于new运算符的讲课,请参考:)
那句话,在call中将this作为thisArgs参数字传送递,于是Animal方法中的this就本着了Cat中的this,而cat中的this指向的是cat对象,所以Animal中的this指向的正是cat对象,在Animal中定义了name和weight属性,就一定于在cat中定义了那一个属性,因而cat对象便具备了Animal中定义的天性,从而完成了一连的指标。

选用途景二:冯谖三窟(原型扩大)

在讲上面包车型大巴剧情前边,大家首先来认知一下JavaScript中的三个非规范职业术语:ArrayLike(类数组/伪数组)

ArrayLike 对象即具有数组的风流罗曼蒂克部分行为,在DOM中已经展现出来,而jQuery的隆起让ArrayLike在JavaScript中山高校放异彩。ArrayLike对象的精致在于它和JS原生的Array相同,不过它是随意营造的,它出自开辟者对JavaScript对象的扩张,约等于说:对于它的原型(prototype)大家得以轻松定义,而不会传染到JS原生的Array。

ArrayLike对象在JS中被广大应用,举例DOM中的NodeList, 函数中的arguments都是类数组对象,那一个指标像数组相似存款和储蓄着每二个因素,但它未有操作数组的法子,而大家得以经过call将数组的一点方法移接到ArrayLike对象,进而达到操作其成分的目标。例如我们得以这么遍历函数中的arguments:

function test(){
    //检测arguments是否为Array的实例
    console.log(
            arguments instanceof Array, //false
            Array.isArray(arguments)  //false
    );
    //判断arguments是否有forEach方法
    console.log(arguments.forEach); //undefined

    // 将数组中的forEach应用到arguments上
    Array.prototype.forEach.call(arguments,function(item){
        console.log(item); // 1 2 3 4
    });

}
test(1,2,3,4);

除了那么些之外,对于apply来说,大家地点提到了它独有的三个特色,即apply接受的是数组,在传递给调用函数的时候是以参数列表传递的。
这些天性让apply看起来比call 后起之秀,例如有那样多个境况:给定三个数组[1,3,4,7],然后求数组中的最大因素,而你通晓,数组中并未赢得最大值的点子,日常景况下,你须求经过编写制定代码来兑现。而大家清楚,Math对象中有贰个获得最大值的章程,即Math.max(), max方法需求传递三个参数列表,然后回到那些参数中的最大值。而apply既能够将Math对象的max方法应用到别的对象上,还足以将三个数组转变为参数列表传递给max,看代码就会看清:

var arr = [2,3,1,5,4];

Math.max.apply(null,arr); // 5

 

 

 

 

 

 

 

参考:

 

发表评论

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