this、call 和 apply ----- JavaScript设计模式与开发实践 (二)电子书.pdf

在 JavaScript 编程中,this 关键字老是让初学者感到迷惑,Function.prototype.call 和
Function.prototype.apply 这两个方法也有着普遍的运用。咱们有必要在学习设计模式以前先理解
这几个概念。html

2.1 this
跟别的语言截然不同的是,JavaScript 的 this 老是指向一个对象,而具体指向哪一个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。编程

2.1.1 this的指向
除去不经常使用的 with 和 eval 的状况,具体到实际应用中,this 的指向大体能够分为如下 4 种。
** 做为对象的方法调用。
 做为普通函数调用。
 构造器调用。
 Function.prototype.call 或 Function.prototype.apply 调用。**设计模式

  1. 做为对象的方法调用

当函数做为对象的方法被调用时,this 指向该对象:数组

var obj = { 
 a: 1, 
 getA: function(){ 
 alert ( this === obj ); // 输出:true 
 alert ( this.a ); // 输出: 1 
 } 
}; 
obj.getA();
  1. 做为普通函数调用

当函数不做为对象的属性被调用时,也就是咱们常说的普通函数方式,此时的 this 老是指
向全局对象。在浏览器的 JavaScript 里,这个全局对象是 window 对象。浏览器

window.name = 'globalName'; 
var getName = function(){ 
 return this.name; 
}; 
console.log( getName() ); // 输出:globalName
或者
window.name = 'globalName123'; 
function getName (){ 
 return this.name; 
}; 
console.log( getName() ); // 输出:globalName123

或者:app

window.name = 'globalName'; 
var myObject = { 
 name: 'sven', 
 getName: function(){ 
 return this.name; 
 } 
}; 
var getName = myObject.getName; 
console.log( getName() ); // globalName

注解:由于getName()调用,只是普通函数的调用,因此这里函数内的this是指向全局的。var getName = myObject.getName就是把myObject对象的getName属性方法给getName。由于这里的this 是指向全局,因此结果是globalName。函数

有时候咱们会遇到一些困扰,好比在 div 节点的事件函数内部,有一个局部的 callback 方法,
callback 被做为普通函数调用时,callback 内部的 this 指向了 window,但咱们每每是想让它指向
该 div 节点,见以下代码:学习

<html> 
 <body> 
 <div id="div1">我是一个 div</div> 
 </body> 
 <script> 
 window.id = 'window'; 
 document.getElementById( 'div1' ).onclick = function(){ 
 alert ( this.id ); // 输出:'div1' 
 var callback = function(){ 
 alert ( this.id ); // 输出:'window' 
 } 
 callback(); 
 }; 
 </script> 
</html>

此时有一种简单的解决方案,能够用一个变量保存 div 节点的引用:
图灵社区会员 轩辕 专享 尊重版权
26 第 2 章 this、call 和 applythis

document.getElementById( 'div1' ).onclick = function(){ 
 var that = this; // 保存 div 的引用
 var callback = function(){ 
 alert ( that.id ); // 输出:'div1' 
 } 
 callback(); 
};

在 ECMAScript 5 的 strict 模式下,这种状况下的 this 已经被规定为不会指向全局对象,而
是 undefined:prototype

function func(){ 
 "use strict" 
 alert ( this ); // 输出:undefined 
} 
func();
  1. 构造器调用

JavaScript 中没有类,可是能够从构造器中建立对象,同时也提供了 new 运算符,使得构造
器看起来更像一个类。
除了宿主提供的一些内置函数,大部分 JavaScript 函数均可以看成构造器使用。构造器的外
表跟普通函数如出一辙,它们的区别在于被调用的方式。当用 new 运算符调用函数时,该函数总
会返回一个对象,一般状况下,构造器里的 this 就指向返回的这个对象,见以下代码:

var MyClass = function(){ 
 this.name = 'sven'; 
}; 
var obj = new MyClass(); 
alert ( obj.name ); // 输出:sven

注释:咱们这里要知道new的操做符都为咱们作了哪4件事。
new操做符干了如下三步:
1.先建立了一个新的空对象
2.而后让这个空对象的__proto__指向函数的原型prototype
3.将对象做为函数的this传进去,若是return 出来东西是对象的话就直接返回 return 的内容,没有的话就返回建立的这个对象

对应伪代码:

对于const a = new Foo();,new干了如下事情
 const o = new Object();//建立了一个新的空对象o
 o.__proto__ = Foo.prototype;//让这个o对象的 __proto__指向函数的原型prototype
 Foo.call(o);//this指向o对象
 a = o;//将o对象赋给a对象

但用 new 调用构造器时,还要注意一个问题,若是构造器显式地返回了一个 object 类型的对象,那么这次运算结果最终会返回这个对象,而不是咱们以前期待的 this

var MyClass = function(){ 
 this.name = 'sven'; 
 return { // 显式地返回一个对象
 name: 'anne' 
 } 
}; 
var obj = new MyClass(); 
alert ( obj.name ); // 输出:anne

若是构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会形成上述
问题:
若是构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,就不会形成上述
问题:

var MyClass = function(){ 
 this.name = 'sven' 
 return 'anne'; // 返回 string 类型
}; 
var obj = new MyClass(); 
alert ( obj.name ); // 输出:sven
  1. Function.prototype.call 或 Function.prototype.apply 调用

跟普通的函数调用相比,用 Function.prototype.call 或 Function.prototype.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

call 和 apply 方法能很好地体现 JavaScript 的函数式语言特性,在 JavaScript 中,几乎每一次
编写函数式语言风格的代码,都离不开 call 和 apply。在 JavaScript 诸多版本的设计模式中,也
用到了 call 和 apply。在下一节会详细介绍它们。

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

当调用 obj.getName 时,getName 方法是做为 obj 对象的属性被调用的,根据 2.1.1 节提到的规
律,此时的 this 指向 obj 对象,因此 obj.getName()输出'sven'。

当用另一个变量 getName2 来引用 obj.getName,而且调用 getName2 时,根据 2.1.2 节提到的
规律,此时是普通函数调用方式,this 是指向全局 window 的,因此程序的执行结果是 undefined。

2.2.1 call和apply的区别
Function.prototype.call 和 Function.prototype.apply 都是很是经常使用的方法。它们的做用一模
同样,区别仅在于传入参数形式的不一样。
apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下
标的集合,这个集合能够为数组,也能够为类数组,apply 方法把这个集合中的元素做为参数传
递给被调用的函数:

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

在这段代码中,参数 一、二、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 );

当调用一个函数时,JavaScript 的解释器并不会计较形参和实参在数量、类型以及顺序上的
区别,JavaScript 的参数在内部就是用一个数组来表示的。从这个意义上说,apply 比 call 的使用
率更高,咱们没必要关心具体有多少参数被传入函数,只要用 apply 一股脑地推过去就能够了。
call 是包装在 apply 上面的一颗语法糖,若是咱们明确地知道函数接受多少个参数,并且想一目了然地表达形参和实参的对应关系,那么也能够用 call 来传送参数。

  1. Function.prototype.bind

大部分高级浏览器都实现了内置的 Function.prototype.bind,用来指定函数内部的 this指向,
即便没有原生的 Function.prototype.bind 实现,咱们来模拟一个也不是难事,代码以下:
图灵社区会员 轩辕 专享 尊重版权
32 第 2 章 this、call 和 apply

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。
这是一个简化版的 Function.prototype.bind 实现。

call是改变函数内部的this指向的。

相关文章
相关标签/搜索