this是面向对象编程中的一个概念,它一般指向当前方法调用所在的对象,这一点在java、c++这类比较严格的面向对象编程语言里是非常明确的。但是在javascript中,this的定义要灵活许多,如果未准确掌握,非常容易混淆。本人在面试过程中也发现,面试者很少有由能够回答得非常全面的。本文总结了this的各种情况,并从Ecma规范的角度探讨了this的具体实现,希望对大家理解this有所帮助。

this指向的四中情况

在javascript里面,this的指向可以归纳为以下四种情况。只要能牢记这四种情况,大部分情况下就已经够用了。

1.在全局代码或者普通的函数调用中,this指向全局对象,在浏览器里面既为window对象。

    console.log(this);//输出window

    function foo(){
console.log(this);
}
foo();//输出window

在浏览器环境里运行上述代码,两处输出结果均为window对象。

2.通过call或apply方法调用函数,this指向方法调用的第一个参数。

    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规范

Ecma规范里面详细介绍了this的实现细节,通过阅读规范,我们可以更准确的理解上述四种情况到底是怎么回事。
函数对象有一个叫[[Call]]内部方法,函数的执行其实是通过[[Call]]方法来执行的。[[Call]]方法接收两个参数thisArg和argumentList,thisArg和this的指向有直接关系,argumentList为函数的实参列表。thisArg又是怎么来的呢?我们可以和前面讨论的四种情况对应起来:

  1. 普通方法调用thisArg为undefined。

  2. 通过call或apply调用,thisArg既为第一个参数。

  3. 通过对象调用,thisArg指向该对象。

  4. 在构造方法中,thisArg为新构造的对象。

thisArg和this是什么关系?规范里的描述是这样的:

  1. If the function code is strict code, set the ThisBinding to thisArg.

  2. Else if thisArg is null or undefined, set the ThisBinding to the global object.

  3. Else if Type(thisArg) is not Object, set the ThisBinding to ToObject(thisArg).

  4. 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的指向

在实际使用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规范里面的知识点和大家分享,希望能对大家有所帮助。由于本人能力有限,如有理解错误的地方还望指出。

原文:https://segmentfault.com/a/1190000003906484

从Ecma规范深入理解js中的this的指向的更多相关文章

  1. 理解JS中的this的指向

    原文地址:https://www.cnblogs.com/pssp/p/5216085.html#1 首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到 ...

  2. 深入理解JS中的对象(二):new 的工作原理

    目录 序言 不同返回值的构造函数 深入 new 调用函数原理 总结 参考 1.序言 在 深入理解JS中的对象(一):原型.原型链和构造函数 中,我们分析了JS中是否一切皆对象以及对象的原型.原型链和构 ...

  3. 怎么理解js中的事件委托

    怎么理解js中的事件委托 时间 2015-01-15 00:59:59  SegmentFault 原文  http://segmentfault.com/blog/sunchengli/119000 ...

  4. 如何更好的理解js中的this,分享2段有意思的代码

    关于js中this的浅析,大家可以点击[彻底理解js中this的指向,不必硬背]这篇博客了解. 今天遇到2段比较有意思的代码. ----------------第一段----------------- ...

  5. 图文结合深入理解 JS 中的 this 值

    图文结合深入理解 JS 中的 this 值 在 JS 中最常见的莫过于函数了,在函数(方法)中 this 的出现频率特别高,那么 this 到底是什么呢,今天就和大家一起学习总结一下 JS 中的 th ...

  6. 深度理解js中var let const 区别

    首先要理解js中作用域的概念 作用域:指的是一个变量的作用范围 1.全局作用域 直接写在script中的js代码,在js中,万物皆对象,都在全局作用域,全局作用域在页面打开时创建,在全局作用域中有一个 ...

  7. 如何理解js中的this和实际应用中需要避开哪些坑

    this是什么 this就是函数内部的关键字 看下面例子理解js中的this // 例子1 function fnOne () { console.log(this) } 'use strict' f ...

  8. 深入理解JS中的对象(三):class 的工作原理

    目录 序言 class 是一个特殊的函数 class 的工作原理 class 继承的原型链关系 参考 1.序言 ECMAScript 2015(ES6) 中引入的 JavaScript 类实质上是 J ...

  9. 深入理解Js中的this

    深入理解Js中的this JavaScript作用域为静态作用域static scope,但是在Js中的this却是一个例外,this的指向问题就类似于动态作用域,其并不关心函数和作用域是如何声明以及 ...

随机推荐

  1. 技术之美[程序人生]我在IBM实习的日子

    写这篇文章的时候,我已经在IBM正式工作了,看看上一篇博文的发布日期,才发现,我已经将近三个月没有更新博客了,多么惊人!为什么这么久?期间发生了很多事情.最重要的一件就是我大学毕业了!毕业的那么平淡, ...

  2. 基于redis AE异步网络架构

    最近的研究已redis源代码,redis高效率是令人钦佩. 在我们的linux那个机器,cpu型号, Intel(R) Pentium(R) CPU G630 @ 2.70GHz  Intel(R) ...

  3. Velocity.js发布:更快的动画切换速度

    Velocity.js是一款动画切换的jQuery插件,它重新实现了jQuery的$.animate()方法从而加快动画切换的速度.Velocity.js只有7k的大小,它不仅包含了$.animate ...

  4. putty保持连接不自动段开

    经常在网上看到有人说自己利用putty工具登录服务器总是连接不上,这样的情况自己在刚接触putty时也遇到过.在 Connection 里面有个 Seconds between keepaliaves ...

  5. Uva 10652 Board Wrapping(计算几何之凸包+点旋转)

    题目大意:给出平面上许多矩形的中心点和倾斜角度,计算这些矩形面积占这个矩形点形成的最大凸包的面积比. 算法:GRAHAM,ANDREW. 题目非常的简单,就是裸的凸包 + 点旋转.这题自己不会的地方就 ...

  6. H - Ones

    Description Given any integer 0 <= n <= 10000 not divisible by 2 or 5, some multiple of n is a ...

  7. C++程序设计实践指导1.3求任意整数降序数改写要求实现

    改写要求1:动态生成单链表存储 #include <cstdlib> #include <iostream> using namespace std; struct LinkN ...

  8. 关于serialVersionUID的说明

    1.为什么要使用serialVersionUID (1)对于实现了Serializable接口的类,可以将其序列化输出至磁盘文件中,同时会将其serialVersionUID输出到文件中. (2)然后 ...

  9. XML新手入门 创建构造良好的XML(1)

    XML新手入门 创建构造良好的XML(1) 2009-03-19 09:24 Kay Whatley IBM Developerworks 我要评论(0) 字号:T | T 本文描述了构建良好的XML ...

  10. Java编程思想——类型信息(RTTI)

    一.概念 编译时已知的到所有的类型:就是在写代码阶段就确定是这个类型了,当运行程序的时候,类型是不可改变的 举例:List<String> str = new ArrayList();   ...