JavaScript的this老是指向一个对象,至于指向哪一个对象,是在运行时基于函数的执行环境的动态绑定的,而非函数被声明时的环境。设计模式
this的指向大体能够分为如下4类:数组
做为对象的方法调用浏览器
做为普通函数调用app
构造器调用函数
Function.prototype.call
或Function.prototype.apply
调用this
1.做为对象的方法调用prototype
当函数做为对象的方法被调用时,this指向该对象:设计
// 声明obj对象 var obj = { a: 'a属性的值', // a 属性 getA: function(){ // getA()方法 console.log(this === obj); // 输出:true console.log(this.a); // 输出: a属性的值 } }; obj.getA();
2.做为普通函数调用code
当函数不做为对象的属性被调用时,也就是以普通函数方式,this指向全局对象。在浏览器的JavaScript里,全局对象是window对象。对象
window.name = 'globalName'; // 声明全局对象的name属性 var getName = function(){ // 定义getName()函数 return this.name; }; // 调用函数 console.log(getName()); //输出: globalName
window.name = 'globalName'; // 声明全局对象的name属性 var myObject = { // 声明myObject对象 name: 'objectName'; getName: function(){ // 定义getName()方法 return this.name; } } var getName = myObject.getName; // 将getName()方法赋给变量getName console.log(getName()); // 输出: globalName
3.构造器调用
JavaScript没有类,但能够从构造器中建立对象,也提供了new运算符用于调用构造器。
大部分JavaScript函数均可以看成构造器使用。构造器的外表跟普通函数同样,他们的区别在于被调用的方式。即,使用new运算符建立对象时,就是将函数看成构造器调用。当用new运算符调用函数时,该函数总会返回一个对象,此时,构造器里的this指向返回的这个对象。
var myClass = function(){ this.name = 'className'; }; var obj = new myClass(); console.log(obj.name); // 输出:seven
但,若是构造器显式地返回了一个object类型的对象,那么此函数将返回这个object类型的对象,而不是函数自己所定义的对象,例如:
var myClass = function(){ this.name = 'className'; return { //显式地返回一个对象 name: 'anne' } }; var obj = new myClass(); console.log(obj.name); // 输出:anne
而,若是构造器不显式地返回任何数据,或返回一个非对象类型的数据,就不会形成上述情形。
var myClass = function(){ this.name = 'className'; return 'anne'; // 返回string类型 }; var obj = new myClass(); console.log(obj.name); // 输出:className
4.Function.prototype.call 或 Function.prototype.apply调用
跟普通函数调用相比,用 Function.prototype.call
或 Function.prototype.apply
能够动态地改变传入函数的this。
var A = { name: 'ObjectA', getName: function(){ return this.name; } }; var B = { name: 'ObjectB' }; console.log(A.getName()); // 做为对象的方法调用,输出:ObjectA console.log(A.getName.call(B)); // 输出:ObjectB
咱们常常会由于this的指向与咱们的期待不一样,而出现undefined的状况,例如:
var obj = { name: 'objName'; getName: function(){ return this.name; } }; // 做为对象的方法调用,指向obj对象 console.log(obj.getName()); // 输出:objName // 做为普通函数调用,指向全局对象window,name属性还没有定义 var getName2 = obj.getName; console.log(getName2()); // 输出:Lundefined
ECAMScript3给Function的原型定义了两个方法,分别是Function.prototype.call 或 Function.prototype.apply。在一些函数式风格的代码编写中,call和apply方法尤其有用。
Function.prototype.call 或 Function.prototype.apply的做用如出一辙,区别仅在于传入参数形式的不一样。
apply接受两个参数,第一个参数制定了函数体内this对象的指向,第二个函数为一个带下标的集合,这个集合能够是数组,也能够是类数组。apply方法把这个集合中的元素做为参数传递给被调用的函数。
var func = function(a, b, c){ console.log([a, b, c]); // 输出:[1,2,3] }; func.apply(null, [1, 2, 3]);
call传入的参数数量不固定,第一个参数也是表明了函数体内的this指向,从第二个参数开始日后,每一个参数依次被传入函数:
var func = function(a, b, c){ console.log([a, b, c]); // 输出:[1,2,3] }; func.call(null, 1, 2, 3);
当调用一个函数时,JavaScript的解释器并不会计较形参和实参在数量、类型、以及顺序上的区别,JavaScript的参数在内部就是用一个数组来表示的。从这个意义上说,apply比call的使用率更高,咱们没必要关心具体有多少参数被传入函数,只要用apply一股脑地推过去就能够了。
当使用call或apply的时候,若是咱们传入的第一个参数为null,函数体内的this会指向默认的宿主对象,在浏览器中则是window:
var func = function(a, b, c){ console.log(this); }; func.apply(null, [1, 2, 3]); //输出:window对象 func.call(null, 1, 2, 3); //输出:window对象
改变this指向
Function.prototype.bind
借用其余对象的方法
1.改变this指向
call和apply最多见的用途是改变函数内部的this指向:
var A = { name: 'nameA'; }; var B = { name: 'nameB'; }; window.name = 'nameWindow'; var getName = function(){ conlole.log(this.name); }; getName(); // 以普通函数调用,指向了window对象,输出:nameWindow getName.call(A); // 改变了this的指向,指向了传入的对象,输出:nameA getName.call(B); // 改变了this的指向,指向了传入的对象,输出:nameB
2.Function.prototype.bind
大部分高级浏览器都实现了内置的Function.prototype.bind,用来指定函数内部的this指向。
若没有原生的Function.prototype.bind实现,能够经过模拟一个:
Function.prototype.bind = function(context){ var self = this; // 保存原函数 return function(){ // 返回一个新的函数 return self.apply(context, arguments); // 执行新函数的时候,会把以前传入的context看成新函数体内的this } }; var obj = { name: "objName" }; var func = function(){ console.log(this.name); // 输出:objName }.bind(obj); func();
咱们经过Function.prototype.bind来“包装”func函数,而且传入一个对象context看成参数,这个context对象就是咱们想要修正的this对象,即让函数内部的this指向这个对象。
3.借用其余对象的方法
咱们知道,杜鹃即不会筑巢,也不会孵雏,而是把本身的蛋寄托给云雀等其余鸟类,让它们代为孵化和养育。在JavaScript中也存在相似的借用现象。
场景一:借用构造函数
经过这种技术,可以实现一些相似继承的效果:
var A = function(name){ this.name = name; }; var B = function(){ // 借用A的构造函数 A.apply(this, arguments); }; B.prototype.getName = function(){ return this.name; }; var b = new B('baby'); console.log(b.getName()); // 输出:baby
场景二:类数组对象的操做
函数的参数列表arguments是一个类数组对象,虽然它也有下标,但它并不是真正的数组,因此也不能像数组同样,进行排序操做或者往集合里添加一个新的元素。这时,能够借用Array.prototype对象上的方法。
好比,想往arguments中添加一个新的元素,能够借用Array.prototype.push:
(function(){ Array.prototype.push.call(arguments, 3); console.log(arguments); // 输出:[1,2,3] })(1, 2);
想把arguments转成真正的数组的时候,能够借用Array.prototype.slice方法;想截去arguments列表中的头一个元素时,能够借用Array.prototype.shift方法。
PS:本节内容为《JavaScript设计模式与开发实践》第二章 笔记。