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. 让你的javascript函数拥有记忆功能,降低全局变量的使用

    考虑例如以下场景:假如我们须要在界面上画一个圆,初始的时候界面是空白的.当鼠标移动的时候,圆须要尾随鼠标移动.鼠标的当前位置就是圆心.我们的实现方案是:假设界面上还没有画圆,那么就新创建一个:假设已经 ...

  2. SQLServer查询逻辑读最高的语句

    select top 25 p.name as [SP Name], deps.total_logical_reads as [TotalLogicalReads], deps.total_logic ...

  3. Android 四大组件之 BroadcastReceiver

    0  简介        BroadcastReceiver也就是“广播接收者”的意思,顾名思义,它就是用来接收来自系统和应用中的 广播.        在Android系统中,广播体现在方方面面,例 ...

  4. Android开发最佳学习路线图

          为了帮助大家更好的学习Android开发的相关知识,尚观4G智能操作系统研究室(www.up4g.com)为大家制作下面学习路线图:希望能帮助到广大的android爱好者. 在開始之前我们 ...

  5. Makefiles 介绍

    http://www-personal.umich.edu/~ppannuto/writings/makefiles.html Makefiles Makefiles (or, the GNU aut ...

  6. JQ动画 show hide

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  7. Kill命令模拟1

    #include<sys/types.h> #include<signal.h> #include<stdio.h> #include<stdlib.h> ...

  8. node.js中favicon.ico请求两次问题

    var http=require("http"); var server=http.createServer(); server.on("request",fu ...

  9. Git commit template 模板设定

    多人协作开发一个项目时,版本控制工具是少不了的,git是linux 内核开发时引入的一个优秀代码管理工具,利用它能很好使团队协作完成一个项目.为了规范团队的代码提交,也方便出版本时的release n ...

  10. Android动画的深入分析

    一.AnimationDrawable的使用 详见:Drawable类及XMLDrawable的使用 补充:通过Animation的setAnimationListener()可以给View动画添加监 ...