JavaScript设计模式笔记-this,call,apply

2.1 this

JavaScript中的this总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的.

2.1.1 this的指向

this的指向大致分为以下4种

  • 作为对象的方法调用
  • 作为普通函数调用
  • 构造器调用
  • Function.prototype.call或者Function.prototype.apply调用

1.作为对象的方法调用当作为对象的方法调用,this指向该对象

var obj={ 
   a:1, 
   getA:function(){ 
       console.log(this===obj);//true 
       console.log(this.a);//1 
   } 
}; 
obj.getA() 

2.作为普通函数调用当函数不作为对象的属性调用,也就是我们常说的用普通函数方法,此时的this总是指向全局对象,浏览器中是window

window.name='globalname'; 
var getName=function(){ 
   return this.name 
} 
console.log(getName());//globalname 

但是在事件类部函数里定义一个函数并且调用,因为普通函数方法this是永远指向window的,比如下例的问题

<div id='div1'></div> 

$('div1').onclick=function(){ 
   console.log(this.id);//div1 
   var callback=function(){ 
       console.log(this.id) 
   } 
   callback();//window 
} 

那么可以一个变量保存一下div1中的变量

$('div1').onclick=function(){ 
   console.log(this.id);//div1 
   var that=this 
   var callback=function(){ 
       console.log(that.id) 
   } 
   callback();//div1 
} 

ES5严格模式中,this指向的是undefined

3.构造器调用当用new运算符调用函数时,函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象

var myClass=function(){ 
   this.name='sven' 
} 
    
var obj=new Myclass() 
console.log(obj.name);//sven 

但是如果显式的返回了一个object那么就会返回这个object而不是我们之前期待的this,返回一个非对象类型的数据是不会影响的

var myClass=function(){ 
   this.name='sven' 
   return{ 
       name:'anne' 
   } 
} 
var obj=new Myclass() 
console.log(obj.name);//anne 
---- 

var myClass=function(){ 
   this.name='sven' 
   return 'anne' 
} 
var obj=new Myclass() 
console.log(obj.name);//sven 

4.Function.prototype.call或者Function.prototype.apply调用call和apply可以动态的改变this指向.

var obj1={ 
   name:'sven', 
   getName:function(){ 
       return this.name 
   } 
}; 
var obj2={ 
   name:'anne' 
} 
console.log(obj1.getName());//sven 
console.log(obj1.getName.call(obj2));//anne 

2.1.2丢失的this

//这是一个经常遇到的问题,我们先看下面的代码: 
var obj = { 
   myName: 'sven', 
   getName: function(){ 
       return this.myName; 
   } 
}; 

console.log( obj.getName() ); // 输出:'sven' 

var getName2 = obj.getName; 
console.log( getName2() ); // 输出:undefined 

注意getName2并不是执行后赋值,而是把这个函数赋值给他,执行的时候参考第二条,普通函数的this指向.所以执行后的结果是undefined下例这个例子可以更加清楚的说明问题所在

var getId=document.getElementById; 
getId('div1');//error 

默认的document.getElementById方法运行的时候this指向的是document这个object,但是这里赋值后再运行相当于window.getId()所以会报错.不过可以利用apply来修复这个问题

document.getElementById=function(){ 
   return function(){ 
       return document.getElementById.apply(document,arguments) 
   } 
}(document.getElementById) 
var getId = document.getElementById; 
var div = getId( 'div1' ); 
alert (div.id); // 输出: div1 

2.2 call与apply

2.2.1 call与apply区别

简单来说,call和apply仅仅是传递的调入参数不同.

//apply 方法把这个集合中的元素作为参数传递给被调用的函数: 
   var func = function( a, b, c ){ 
       alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ] 
   }; 

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


//从第二个参数开始往后,每个参数被依次传入函数: 
   var func = function( a, b, c ){ 
       alert ( [ a, b, c ] ); // 输出 [ 1, 2, 3 ] 
   }; 

   func.call( null, 1, 2, 3 ); 

apply会扁平化参数罢了在浏览器中如果我们第一个参数传递的为null,那么函数体内的this会指向默认的宿主对象,在浏览器中是window,但是在严格模式下this还是指向null

//当使用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 ] ); 

平时传递null的条件一般都是借用一些方法.

Math.max.apply(null,[1,2,3]);//3 

2.2.2 call与apply用途

1.改变this的指向

var obj1={ 
   name:'sven' 
}; 
var obj2={ 
   name:'anne' 
} 
window.name='window' 
var getName=function(){ 
   console.log(this.name) 
} 
getName.apply(obj1);//sven 
getName.apply(obj2);//anne 
getName.apply(window);//window 

2. Function.prototype.bind

虽然ES5添加了bind,但是ES5以下的版本并没有这个函数,那么我们可以手动写一个

Function.prototype.bind=function(context){ 
   var self=this;//保存原函数 
   return function(){ 
       return self.apply(context,arguments) 
   } 
} 
var obj1={ 
   name:'sven' 
} 
var func=function(){ 
   console.log(this.name);//sven 
}.bind(obj1); 
func(); 

分析下这段代码.首先调用Bind会触发第一个function(context)把context传递进去 上例的context是obj1,执行后返回内部function,那么问题来了 如果不保存self=this 直接写 this.apply这个this是什么? 参考this第二条 如果不保存那么this指向的就是window.保存后self指向的就是调用者也就是func函数,这段代码翻译过来就是func.apply(context,arguments)因为arguments是类数组,没有那些方法所以需要利用apply和call上例是一个简化版的,下例基本上就符合bind函数实现了.

Function.prototype.bind=function(context){ 
   var self=this,//保存原函数 
       context=[].shift.call(arguments),//需要绑定this的上下文,shift会操作原数组 
       args=[].slice.call(arguments),//原函数的参数,奇淫技巧,利用数组的特性借用slice把arguments转为数组 

   return function(){ 
       return self.apply(context,[].concat.call(args,[].slice.call(arguments))) 
       //组合两次传递的参数 
   } 
} 
var func=function(a,b,c,d){ 
   console.log(this.name);//sven 
   console.log([a,b,c,d])//[1,2,3,4] 
}.bind(obj1,1,2); 
func(3,4); 

首先在调用bind函数时,会先保存原函数的参数和上下文也就是obj1和1,2然后调用func函数的时候,会把3,4与1,2组合起来也就是这条语句self.apply(context,[].concat.call(args,[].slice.call(arguments)))

3. 借用其他对象的方法

最常见的例子就是arguments,上述例子已经可以说明了.JavaScript设计模式与开发实践中一书在这里说到了Array.prototype.push 这里不做笔记.因为要表达的意思是一样的.