轻松搞定this绑定方法 call apply bind

简介:

先来看一个例子

1
2
3
4
5
6
7
8
var  obj = {};  //创建一个对象
obj.name =  "James" ;   //给对象添加一个属性
obj.say =  function (){   //给对象添加一个方法
     console.log( 'My name is'  this .name);
};
obj.say();  //this指向obj,所以输出"My name is James"
var  fn = obj.say;
fn();  //this指向了window,全局中没有name变量,所以只能输出"My name is",没有name


由于fn里面的this指向了window而不是obj,所以name没有输出。如果也想让fn输出James,也就意味着必须让fn里面的this指向obj,这个时候需要我们强行改变this指向了。


call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数。


1
2
3
4
5
6
7
8
var  obj = {};  //创建一个对象
obj.name =  "James" ;   //给对象添加一个属性
obj.say =  function (){   //给对象添加一个方法
     console.log( 'My name is'  this .name);
};
obj.say();  //this指向obj,所以输出"My name is James"
var  fn = obj.say;
fn.call(obj);  //输出"My name is James"


上面的代码中,fn函数的this指向window,call方法改变了this的指向,让this对象指向了obj,然后在对象obj的作用域中运行函数fn


call方法的参数是一个对象,如果参数为空,null和undefined,则默认传入全局对象。我们把上面的例子做下修改:

1
2
3
4
5
6
7
8
9
10
11
12
var  name =  'Kobe' //定义全局变量
var  obj = {};  //创建一个对象
obj.name =  "James" ;   //给对象添加一个属性
obj.say =  function (){   //给对象添加一个方法
     console.log( 'My name is'  this .name);
};
var  fn = obj.say;
fn.call();  //输出"My name is Kobe"
fn.call( null );  //输出"My name is Kobe"
fn.call(undefined);  //输出"My name is Kobe"
fn.call(window);  //输出"My name is Kobe"
fn.call(obj);  //输出"My name is James"


call方法还可以接受多个参数  func.call(this要指向的那个对象, 参数1, 参数2……),call方法的第一个参数就是this要指向的那个对象,后面的参数则是调用函数时需要所需的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
var  uname = 'Kobe' ;
var  team = 'Lakers' ;
var  num =24;
var  obj = {};
obj.uname = 'Westbook' ;
obj.team = 'Thunder' ;
obj.num =0;
obj.introduce = function (){
     console.log( 'My name is' this .uname+ ', I am from' + this .team+ ',My number is' + this .num);
};
var  fn = obj.introduce;
fn();   //My name is Kobe, I am from Lakers,My number is 24
fn.call(obj,uname,team,num);  //My name is Westbook, I am from Thunder,My number is 0


这段代码中,fn函数的this指向window,执行fn时输出的就是全局变量。通过call方法把this指向了obj对象,然后再obj的作用域中运行函数fn,所以输出的就是Westbook而不再是Kobe了


apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数,使用格式如下:

func.apply(this要指向的那个对象,[参数1,参数2……])


所以上面的例子中,除了使用call方法以外,我们还可以使用apply方法

1
fn.apply(obj,[uname,team,num]);   //My name is Westbook, I am from Thunder,My number is 0


apply方法的实际应用:

1、找出数组中最大的元素

Math对象提供了获得最大值和最小值的API

Math.max(a,b,c...); 获得参数中最大的值

Math.min(a,b,c...);  获得参数中最小的值


问题:max和min不支持获取数组中的最大值,因为Javascript没有提供获取数组最大值的API,解决办法就是:Math.max.apply(null,[a,b,b...]);

wKiom1ggDqmSfPEkAAAujqCjgcc657.png


2、将数组的空元素变为undefined

wKiom1ggD2WBPa8gAAAle7Do_Es449.png

空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined。所以,在遍历内部元素的时候会得到不同的结果

1
2
3
4
5
6
var  arr=[ 'a' , , 'b' ];
function  show(i){
     console.log(i);
};
arr.forEach(show); //a b
Array.apply( null ,arr).forEach(show);  //a undefined b

wKioL1ggEW7RKxjiAAA5zGmNrlc916.png


3、把类数组对象转为真正的数组(call和apply都可以)

Array.prototype.slice.call(obj)-->obj是一个类数组,通过这个函数可以把类数组转换成一个真正的数组(参数位置就是类数组)。  

wKioL1ggGAqSjgcCAABNfmJ-86Y771.png  

上面的apply/call方法中的参数都是对象,返回结果都是数组,这就起到了把对象转成数组的目的。同时可以看出,这个方法起作用的前提是,被处理的对象必须有length属性,以及相对应的数字键


Object.prototype.toString.call(arg)-->arg可以是任意一种类型的值,这个函数最精确的判断出参数的类型。

wKiom1ggGPPDOQeRAABHnmpz1T0418.png


4、绑定回调函数的对象

1
< button  id = "button" >Button</ button >
1
2
3
4
5
6
7
8
9
10
11
var  obj =  new  Object();
obj.fun =  function (){
     console.log( this ===obj);  //this对象指向obj
};
var  fun =  function (){  //全局函数fun,this指向window
     obj.fun.apply(obj);  //强行把this指向obj (这里也可以用call)
};
var  button = document.getElementById( 'button' );
button.onclick= function (){
     fun();
}

wKiom1ggG5zQR-F6AAAcwuEi5Wo365.png

点击按钮以后,控制台将会显示true。由于apply方法(或者call方法)不仅绑定函数执行时所在的对象,还会立即执行函数,因此不得不把绑定语句写在一个函数体内。更简洁的写法是采用下面介绍的bind方法。


bind方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。

1
2
3
4
5
6
7
var  obj = {};
obj.name =  "Kobe" ;
obj.say =  function (){  //this指向obj
     console.log( "My name is" + this .name);
};
var  fn = obj.say.bind(obj);  //全局函数fn,this指向window
fn();  //My name is Kobe


obj的say方法赋值给一个全局变量fn以后,fn的this指向了window,所以是无法输出name的。现在通过bind方法强行改变让this指向obj对象。


bind方法比call/apply方法更进一步的是,除了绑定this以外,还可以绑定原函数的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var  add =  function  (x, y) {
     console.log (x *  this .m + y *  this .n);
};
 
var  obj = {
     m: 2,
     n: 3
};
 
var  newAdd = add.bind(obj, 5);
 
newAdd(6);  //28    5*2+6*3
newAdd(8);  //34    5*2+8*3
newAdd(10);  //40   5*2+10*3


上面代码中,bind不仅绑定了this对象,还将add函数的第一个参数x绑定成5,然后返回一个新的函数newAdd,这个函数只要再接受一个参数y就能运行了


为了进一步验证,我们可以把上面的代码稍微改变一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var  add =  function  (x, y) {
     console.log (x *  this .m + y *  this .n);
};
 
var  obj = {
     m: 2,
     n: 3
};
 
var  newAdd = add.bind(obj, 5, 6);
 
newAdd(6);      //28
newAdd(8);      //28
newAdd(10);     //28
newAdd(11,12);  //28

现在是同时绑定了参数x为5,y为6,后续无论怎么往newAdd函数中传值,都不能改变输出结果


如果bind方法的第一个参数是null或者undefined,那么,this就绑定到了全局对象window

1
2
3
4
5
6
7
function  add(x, y) {
     console.log(x + y);
}
 
var  plus = add.bind( null , 5);  //this指向window,x绑定成5
plus(10);      // 15 y=10 5+10
plus(10,10);   //15 x已经绑定成5,无法改成10

上面代码中,函数add内部并没有this,使用bind方法的主要目的是绑定参数x,以后每次运行新函数plus,就只需要提供另一个参数y就够了。而且因为add内部没有this,所以bind的第一个参数是null,不过这里如果是其他对象,也没有影响。


对于那些不支持bind方法的老式浏览器,可以自行定义bind方法。

1
2
3
4
5
6
7
8
9
10
if (!( 'bind'  in  Function.prototype)){
     Function.prototype.bind =  function (){
         var  fn =  this ;
         var  context = arguments[0];
         var  args = Array.prototype.slice.call(arguments, 1);
         return  function (){
             return  fn.apply(context, args);
         }
     }
}


bind方法使用注意点:

1、每次返回一个新函数

bind方法每运行一次,就返回一个新函数,这会产生一些问题。比如:事件监听时,不能写成下面这样

1
2
element.addEventListener( 'click' , o.m.bind(o));
element.removeEventListener( 'click' , o.m.bind(o));


上面代码中,click事件绑定bind方法生成的一个匿名函数。这样会导致无法取消绑定,所以,上面的代码是无效的。正确的写法:

1
2
3
4
var  listener = o.m.bind(o);
element.addEventListener( 'click' , listener);
//  ...
element.removeEventListener( 'click' , listener);


2、结合回调函数使用

回调函数是Javascript最常用的模式之一,但是一个常见的错误是,将包含this的方法直接当回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var  counter = {
     count: 0,
     inc:  function  () {
         'use strict' ;   //注意:严格模式下this对象是undefined
         this .count++;   //this对象不是counter
         alert( this );
     }
};
 
function  callIt(callback) {  //全局函数callIt的this对象在严格模式下也不是window了
     callback();
}
 
callIt(counter.inc);  //Uncaught TypeError: Cannot read property 'count' of undefined

上面代码中,counter.inc方法被当作回调函数,传入了callIt,调用时其内部的this指向callIt运行时所在的对象,即顶层对象window,所以得不到预想结果。注意,上面的counter.inc方法内部使用了严格模式,在该模式下,this指向顶层对象时会报错,一般模式不会。  


解决方法就是使用bind方法,将counter.inc绑定counter。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var  counter = {
     count: 0,
     inc:  function  () {
         'use strict' ;   //注意:严格模式下this对象是undefined
         console.log( this .count++);   //this对象不是counter
     }
};
 
function  callIt(callback) {  //全局函数callIt的this对象在严格模式下也不是window了
     callback();
}
 
callIt(counter.inc.bind(counter));  //0 注意前++和后++的区别,前++返回新值,后++返回旧值
callIt(counter.inc.bind(counter));  //1


还有一种情况比较隐蔽,就是某些数组方法可以接受一个函数当作参数。这些函数内部的this指向,很可能也会出错。

1
2
3
4
5
6
7
8
9
10
11
var  obj = {
     name:  '张三' ,
     times: [1, 2, 3],
     print:  function  () {  //this指向obj
         this .times.forEach( function  (n) { //this指向window
             console.log( this .name);
         });
     }
};
 
obj.print();

只需要把this的指向划分出来,很容易就能判断不会有输出结果,因为全局没有name。解决这个问题也是通过bind绑定this

1
2
3
4
5
6
7
8
9
10
11
12
var  obj = {
     name:  '张三' ,
     times: [1, 2, 3],
     print:  function  () {  //this指向obj
         this .times.forEach( function  (n) {
             console.log( this .name);
             }.bind( this //遍历数组的同时绑定了this
         );
     }
};
 
obj.print();  //张三*3


除此之外,还可以用留住this的方法,具体可以参照“扑朔迷离的this关键字”一文

1
2
3
4
5
6
7
8
9
10
11
12
var  obj = {
     name:  '张三' ,
     times: [1, 2, 3],
     print:  function  () {
         var  me= this //留住this
         this .times.forEach( function  (n) {
             console.log(me.name);
         });
     }
};
 
obj.print(); 张三*3


3、结合call方法使用

利用bind方法,可以改写一些JavaScript原生方法的使用形式,以数组的slice方法为例

1
2
3
4
5
6
7
[1, 2, 3].slice(0, 1);  //从第0位开始,截取1位,返回一个数组
// [1]
 
// 等同于
 
Array.prototype.slice.call([1, 2, 3], 0, 1);
// [1]


上面的代码中,数组的slice方法从[1, 2, 3]里面,按照指定位置和长度切分出另一个数组。这样做的本质是在[1, 2, 3]上面调用Array.prototype.slice方法,因此可以用call方法表达这个过程,得到同样的结果。  


call方法实质上是调用Function.prototype.call方法,因此上面的表达式可以用bind方法改写。

1
2
3
var  slice = Function.prototype.call.bind(Array.prototype.slice);
 
slice([1, 2, 3], 0, 1)  // [1]


可以看到,利用bind方法,将[1, 2, 3].slice(0, 1)变成了slice([1, 2, 3], 0, 1)的形式。这种形式的改变还可以用于其他数组方法。


1
2
3
4
5
6
7
var  push = Function.prototype.call.bind(Array.prototype.push);
var  pop = Function.prototype.call.bind(Array.prototype.pop);
var  a = [1 ,2 ,3];
push(a, 4)   //在数组末尾添加一个元素4
// [1, 2, 3, 4]
pop(a)   //删除数组最后一个元素
// [1, 2, 3]


如果再进一步,将Function.prototype.call方法绑定到Function.prototype.bind对象,就意味着bind的调用形式也可以被改写。

1
2
3
4
5
6
function  f() {
   console.log( this .v);
}
var  o = { v: 123 };
var  bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)()  // 123


上面代码表示,将Function.prototype.call方法绑定Function.prototype.bind以后,bind方法的使用形式从f.bind(o),变成了bind(f, o)。



参考链接:阮一峰老师  http://javascript.ruanyifeng.com/oop/this.html#toc3


本文转自   frwupeng517   51CTO博客,原文链接:http://blog.51cto.com/dapengtalk/1870278


相关文章
|
7月前
new bind apply call instanceof 等笔记
new bind apply call instanceof 等笔记
18 0
|
1月前
call\apply\bind详解
call\apply\bind详解
12 0
|
8月前
bind、call、apply 区别
bind、call、apply 区别
51 0
|
10月前
call、apply、bind笔记
call、apply、bind笔记
46 0
|
10月前
|
JavaScript 前端开发
关于 this 指向、如何实现 new call apply bind 我所知道的
关于 this 指向、如何实现 new call apply bind 我所知道的
58 0
apply、bind和call
apply、bind和call
61 0
|
JavaScript 前端开发
一文搞定this、apply、call、bind
一文搞定this、apply、call、bind