this是面向对象编程中的一个概念,它通常指向当前方法调用所在的对象,这一点在java、c++这类比较严格的面向对象编程语言里是很是明确的。可是在javascript中,this的定义要灵活许多,若是未准确掌握,很是容易混淆。本人在面试过程当中也发现,面试者不多有由可以回答得很是全面的。本文总结了this的各类状况,并从Ecma规范的角度探讨了this的具体实现,但愿对你们理解this有所帮助。javascript
在javascript里面,this的指向能够概括为如下四种状况。只要能牢记这四种状况,大部分状况下就已经够用了。java
1.在全局代码或者普通的函数调用中,this指向全局对象,在浏览器里面既为window对象。c++
console.log(this);//输出window function foo(){ console.log(this); } foo();//输出window
在浏览器环境里运行上述代码,两处输出结果均为window对象。程序员
2.经过call或apply方法调用函数,this指向方法调用的第一个参数。es6
var obj = {name:'test'}; function foo(){ console.log(this); } foo.call(obj);//输出obj foo.apply(obj);//输出obj
在浏览器环境里执行以上代码,输出结果均为对象obj。call和apply除了参数形式不同外其余都同样,call采用逗号分割,apply采用数组。说到这里,顺便介绍一个小技巧。如何在不生成新数组的状况下实现两个数组的链接?请看下面的代码。面试
var arr1 = [1, 2 , 3], arr2 = [4, 5, 6]; Array.prototype.push.apply(arr1, arr2); console.log(arr1);//输出[1, 2, 3, 4, 5, 6]
执行上述代码后,输出结果为[1, 2, 3, 4, 5, 6]。这是一个很是实用的小技巧,因为apply第二个参数为数组形式,因此咱们能够把push方法“借”过来,从而实现两个数组的链接。编程
3.调用对象的方法,this指向该对象。数组
var obj = {name:'test'}; function foo(){ console.log(this); } obj.foo = foo; obj.foo();//输出obj
执行以上代码后,控制台输出为obj对象。这就是咱们常说的“谁调用,指向谁”。浏览器
4.构造方法中的this,指向新构造的对象。闭包
function C(){ this.name = 'test'; this.age = 18; console.log(this); } var c = new C();//输出 c console.log(c);//输出 c
执行以上代码后,控制台输出均为c所指向的对象。当new操做符用于函数时,会建立一个新对象,并用this指向它。
Ecma规范里面详细介绍了this的实现细节,经过阅读规范,咱们能够更准确的理解上述四种状况究竟是怎么回事。
函数对象有一个叫[[Call]]内部方法,函数的执行实际上是经过[[Call]]方法来执行的。[[Call]]方法接收两个参数thisArg和argumentList,thisArg和this的指向有直接关系,argumentList为函数的实参列表。thisArg又是怎么来的呢?咱们能够和前面讨论的四种状况对应起来:
普通方法调用thisArg为undefined。
经过call或apply调用,thisArg既为第一个参数。
经过对象调用,thisArg指向该对象。
在构造方法中,thisArg为新构造的对象。
thisArg和this是什么关系?规范里的描述是这样的:
If the function code is strict code, set the ThisBinding to thisArg.
Else if thisArg is null or undefined, set the ThisBinding to the global object.
Else if Type(thisArg) is not Object, set the ThisBinding to ToObject(thisArg).
Else set the ThisBinding to thisArg.
在严格模式下,thisArg和this是一一对应的。
function foo(){ 'use strict'; console.log(this); } foo();//输出undefined
该示例输出的结果为undefined。
第二点是说若是thisArg为null或者undefined则this指向全局对象。
function foo(){ console.log(this); } foo.call(null);//输出window
该示例的输出结果为window。
第三点说若是thisArg为非对象类型,则会强制转型成对象类型。
function foo(){ console.log(this); } var aa = 2; console.log(aa);//输出2 foo.call(aa);//输出 Number
这里的输出结果分别为2和Number,它将基本类型转型成了对象包装类型。
第四点说明剩下的状况thisArg和this为一一对应的关系。
规范里面对this指向的描述仍是比较明确的。只要你搞清楚thisArg怎么肯定,thisArg和this的对应关系,那么你就能搞定全部this的状况了。
在实际使用this的过程当中,遇到得最多得一个问题可能就是上下文丢失的问题了。由于javascript中的函数是能够做为参数传递的,那么其余对象在执行回调函数时就可能形成回调函数原来的上下文丢失,也就是this的指向改变了。
var C = function(){ this.name = 'test'; this.greet = function(){ console.log('Hello,I am '+this.name+'!'); }; } var obj = new C(); obj.greet();//输出 Hello,I am test! setTimeout(obj.greet, 1000);//输出 Hello,I am !
可见第二条输出中this的值改变了,其实咱们是但愿this可以指向obj的。解决该问题的方法有两种。
1.bind方法。
bind方法经过闭包巧妙地实现了上下文的绑定,它其实是将原方法包装成了一个新方法。通常的实现以下:
Function.prototype.bind = function(){ var args = arguments, thisArg = arguments[0], func = this; return function(){ var arg = Array.prototype.slice.call(args, 1); Array.prototype.push.apply(args, arguments); return func.apply(thisArg, arg); } }
前面的示例代码咱们只须要加上bind,就可以获得咱们但愿的结果了。
... setTimeout(obj.greet.bind(obj), 1000);//输出 Hello,I am test! ...
2.es6箭头函数。
es6里面提供了一个新的语法糖,箭头函数。箭头函数的this再也不变幻莫测,它永远指向函数定义时的this值。
var C = function(){ this.name = 'test'; this.greet = ()=>{ console.log('Hello,I am '+this.name+'!'); }; } var obj = new C(); obj.greet();//输出 Hello,I am test! setTimeout(obj.greet, 1000);//输出 Hello,I am test!
咱们将前面的示例该成箭头函数后,两处的输出结果同样了。this的值再也不改变了,这是咱们想要的。
this看起来是个很是小的知识点,其实挖起来仍是有不少细节的,特别是规范里面的一些定义,本人以为对于一个js程序员来讲是很是重要的。本人后面也准备找些ecma规范里面的知识点和你们分享,但愿能对你们有所帮助。因为本人能力有限,若有理解错误的地方还望指出。