资讯专栏INFORMATION COLUMN

从Ecma规范深入理解this

rottengeek / 1839人阅读

摘要:本文总结了的各种情况,并从规范的角度探讨了的具体实现,希望对大家理解有所帮助。规范规范里面详细介绍了的实现细节,通过阅读规范,我们可以更准确的理解上述四种情况到底是怎么回事。由于本人能力有限,如有理解错误的地方还望指出。

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又是怎么来的呢?我们可以和前面讨论的四种情况对应起来:

普通方法调用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的指向

在实际使用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://www.ucloud.cn/yun/91544.html

相关文章

  • ECMA 规范解读 Javascript 可执行上下文概念

    摘要:不包括作为其嵌套函数的被解析的源代码。作用域链当代码在一个环境中执行时,会创建变量对象的一个作用域链。栈结构最顶层的执行环境称为当前运行的执行环境,最底层是全局执行环境。无限制函数上下文。或者抛出异常退出一个执行环境。 前言 其实规范这东西不是给人看的,它更多的是给语言实现者提供参考。但是当碰到问题找不到答案时,规范往往能提供想要的答案 。偶尔读一下能够带来很大的启发和思考,如果只读一...

    daryl 评论0 收藏0
  • 简单易懂的ECMA规范导读1 that's this

    摘要:本文不是标准的中文翻译,也不是的入门教程,本文虽然以的常见问题切入,但并不适合想要快速了解这些问题的人才是快速了解问题的正解。尽量以英文原版为基础,为了流畅,可能会使用某些名词的中文翻译,但会将匹配的英文名词以此种样式中出现一次以避免误解。 简单易懂的ECMA规范导读1 序 最近混SF,恰巧又逢工作方面有了NodeJS的机会,迫切地有教别人怎么写JS的需求, 我发现JS这个东西其实...

    yintaolaowanzi 评论0 收藏0
  • 怎样阅读 ECMAScript 规范

    摘要:另一方面,我不建议初次接触的开发人员阅读规范。在维护语言的最新规范。在这一点上,我想指出的是,绝对没有人从上到下阅读规范。拓展阅读由于的定义,中的细节如冒泡错误,直到块在规范中不存在。换句话说,会转发中抛出的错误,并终止其余的步骤。 翻译自:How to Read the ECMAScript Specification Ecmascript 语言规范 The ECMAScr...

    lpjustdoit 评论0 收藏0
  • ECMA-262-3详解(2)变量对象

    摘要:不同执行上下文中的变量对象对于所有的执行上下文类型,变量对象的一些操作比如变量实例化和行为是共通的。从这点看,将变量对象看作是抽象的基础的东西很便利。全局上下文的变量对象现在,应该先给出全局对象的定义。 原文地址 作者的话 有很多文章已经对ECMAScript的核心概念做了详尽解读。本系列文章翻译自Dmitry Soshnikov的个人网站,相信不少人已经看过原文或者译文。原文简洁易懂...

    MAX_zuo 评论0 收藏0
  • 深入理解Javascript原型关系

    摘要:如下所示在规范中,已经正式把属性添加到规范中也可以通过设置和获取对象的原型对象对象之间的关系可以用下图来表示但规范主要介绍了如何利用构造函数去构建原型关系。 前言 在软件工程中,代码重用的模式极为重要,因为他们可以显著地减少软件开发的成本。在那些主流的基于类的语言(比如Java,C++)中都是通过继承(extend)来实现代码复用,同时类继承引入了一套类型规范。而JavaScript是...

    ethernet 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<