从Ecma规范深入理解js中的this的指向

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

时间: 2024-12-24 12:01:24

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

理解JS中的call、apply、bind方法

理解JS中的call.apply.bind方法(*****************************************************************) 在JavaScript中,call.apply和bind是Function对象自带的三个方法,这三个方法的主要作用是改变函数中的this指向. call.apply.bind方法的共同点和区别:apply . call .bind 三者都是用来改变函数的this对象的指向的:apply . call .bind 三者

理解js中的作用域以及初探闭包

前言 对于js中的闭包,无论是老司机还是小白,我想,见得不能再多了,然而有时三言两语却很难说得明白,反正在我初学时是这样的,脑子里虽有概念,但是却道不出个所以然来,在面试中经常会被用来吊自己的胃口,考察基础,虽然网上自己也看过不少相关闭包的文章,帖子,但貌似这玩意,越看越复杂,满满逼格高,生涉难懂的专业词汇常常把自己带到沟里去了,越看越迷糊,其实终归结底,用杨绛先生的一句话就是:"你的问题在于代码写得太少,书读得不够多",其实在我看来前者是主要的,是后者的检验, 自知目标搬砖20年(还

转:彻底理解js中this的指向,不必硬背

转:http://www.cnblogs.com/pssp/p/5216085.html 首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象(这句话有些问题,后面会解释为什么会有问题,虽然网上大部分的文章都是这样说的,虽然在很多情况下那样去理解不会出什么问题,但是实际上那样理解是不准确的,所以在你理解this的时候会有种琢磨不透的感觉),那么接下来我会深入的探讨这个问题. 为什么要学习this?

理解JS中的prototype

JS中的phototype是JS中比较难理解的一个部分 本文基于下面几个知识点: 1 原型法设计模式 在.Net中可以使用clone()来实现原型法 原型法的主要思想是,现在有1个类A,我想要创建一个类B,这个类是以A为原型的,并且能进行扩展.我们称B的原型为A. 2 javascript的方法可以分为三类: a 类方法 b 对象方法 c 原型方法 例子: function People(name) { this.name=name; //对象方法 this.Introduce=function

彻底理解js中this的指向,不必硬背。

原文链接:http://www.cnblogs.com/pssp/p/5216085.html 首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象(这句话有些问题,后面会解释为什么会有问题,虽然网上大部分的文章都是这样说的,虽然在很多情况下那样去理解不会出什么问题,但是实际上那样理解是不准确的,所以在你理解this的时候会有种琢磨不透的感觉),那么接下来我会深入的探讨这个问题. 为什么要学习th

理解js中的自由变量以及作用域的进阶

如果你不知道什么是作用域,建议你先看什么是作用域链,什么是原型链.这篇文章,因为这些内容都是有关联性的. 什么是自由变量? 如我在全局中定义了一个变量a,然后我在函数中使用了这个a,这个a就可以称之为自由变量,可以这样理解,凡是跨了自己的作用域的变量都叫自由变量. var a = "追梦子"; function b(){ console.log(a); //追梦子 } b(); 上面的这段代码中的变量a就是一个自由变量,因为在函数b执行到console.log(a)的时候,发现在函数中

终于理解JS中的闭包了

之前看到一个观点是  闭包是走向高级Javascript的必经之路,之前看过很多关于闭包的讲解帖子,一直没有理解透彻,模棱两可. 现在终于可以讲出来了. 检验自己有没有掌握一个知识,最好的方式是讲给一个不懂的人 ,给Ta讲懂了.我做到了. 请有心读者检阅我的知识点有么有错误. 一:什么闭包 首先要理解 js特殊的作用域机制:只能按照作用域链向上访问,而不能访问Ta下级域中的变量. 闭包:就是能够读取其他函数内部变量的函数. (*^__^*) 一切函数某种环境下都可以当做闭包. 手写一个demo:

详细理解JS中的继承

正式说继承之前,有两个相关小点: JS只支持实现继承,即继承实际的方法,不支持接口继承(即继承方法的签名,但JS中函数没签名) 所有对象都继承了Object.prototype上的属性和方法. 说继承之前还要再说一下原型.原型之所以很重要,原因之一就是可以利用它来实现JavaScript的继承.重写一个函数的原型对象,将其指定为另一个函数的实例,这样便实现了一个简单的原型链继承. 看一个利用原型链来实现继承的基础例子: 1 function Super(){ 2 this.name='super

轻松理解JS中的面向对象,顺便搞懂prototype和__proto__的原理介绍

这篇文章主要讲一下JS中面向对象以及 __proto__,ptototype和construcator,这几个概念都是相关的,所以一起讲了. 在讲这个之前我们先来说说类,了解面向对象的朋友应该都知道,如果我要定义一个通用的类型我可以使用类(class).比如在java中我们可以这样定义一个类: public class Puppy{ int puppyAge; public Puppy(age){ puppyAge = age; } public void say() { System.out.