判断“this 指向谁”是个老大难的问题。

网络上有许多文章教我们如何判别,但大多艰涩复杂,难以理解。

那么这里介绍一个非常简单实用的判别规则:

1)在函数【调用】时,“this”总是指向小数点左侧的那个对象

2)如果没有小数点,那么“this”指向全局作用域(比如 Window,严格模式为 undefined)

3)有几个可以改变“this”指向的函数——bind,call 和 apply

4)关键字 “new” 将 “this” 绑定到那个新创建的对象上

好了,我们已经学会了基本的理论知识,是时候运用一波了。请判断下面的 this 指向:

var foo = {bar: function () {return this}}
var b = foo.bar foo.bar();
b();
var c = new someFunction();

相信你已经完全掌握了,下面我们再进行一点巩固练习:

(f = foo.bar)();
(1, foo.bar)();
(foo.bar)();

并不是上面的判别规则除了差错,而是新增的操作符/运算符增加了代码复杂度。为了解释上述代码的行为,我们需要理解 Reference 和 函数调用。

Reference Specification Type

在 ES5 中,除了基本的 6 种类型(string,number,boolearn,null,undefined,object),还有 reference 类型,不过它对使用者屏蔽,作为开发者,我们也不用关心它。

但是,了解它能够提升我们对 ECMAScript 的认识。

Reference 是什么

ECMAScript 将 Reference 定义为“被解析的命名绑定(resolved name binding)”,它由三部分组成——base,name, and strict flag。

有两种创建 Reference 的途径:

  • 标识符解析
  • 属性访问

比如,foo 和 foo.bar 创建了一个 Reference ,而字面量(1,“foo”,[1,3]等)或函数表达式——(function(){})却不会。

参考下图:

每创建一个 Reference 都会为其对应的 base,name,strict 设置相应的值。"strict "对应代码是否开启了严格模式;"name"设置为标识符或属性名;"base"设置为 property 对象或环境记录(environment record)。

可以认为 Reference 是一个不带原型、有且只有 3 个属性的对象。譬如说:

'use strict';
var foo; // 标识符解析会产生 Reference
var Reference = Object.create(null);
Reference.base = EnvironmentRecord;
Reference.name = 'foo';
Reference.strict = true; // or
foo.bar; // 属性访问会产生 Reference
var Reference = Object.create(null);
Reference.base = foo;
Reference.name = 'bar';
Reference.strict = true; // or 使用未声明的变量
a;
var Reference = Object.create(null);
Reference.base = undefined;
Reference.name = 'a';
Reference.strict = true;

函数调用

当函数调用的时候,会发生什么?Function Calls

1. Let ref be the result of evaluating MemberExpression.
2. Let func be GetValue(ref).
3. Let argList be the result of evaluating Arguments, producing an internal list of argument values ([see 11.2.4](https://es5.github.io/#x11.2.4)).
4. If Type(func) is not Object, throw a TypeError exception.
5. If IsCallable(func) is false, throw a TypeError exception.
6. If Type(ref) is Reference, then
* If IsPropertyReference(ref) is true, then Let thisValue be GetBase(ref).
* Else, the base of ref is an Environment Record, Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).
7. Else, Type(ref) is not Reference.
* Let thisValue be undefined.
8. Return the result of calling the *Call* internal method on func, providing thisValue as the this value and providing the list argList as the argument values.

ES5 标准告诉我们一个事实——只有在函数真正调用的时候,才能判断 this 的值。

赋值,逗号和分组操作符

有了以上的准备,我们可以解答(f = foo.bar)()、(1, foo.bar)()和(foo.bar)()的 this 指向问题了。

简单赋值(=)操作

诸如 a = 1, g = function(){} 等都属于Simple Assignment,和函数调用一样,在赋值发生之前,JS 也会做一些准备工作:

  1. Let lref be the result of evaluating LeftHandSideExpression.
  2. Let rref be the result of evaluating AssignmentExpression.
  3. Let rval be GetValue(rref).
  4. Throw a SyntaxError exception if the following conditions are all true:

    a. Type(lref) is Reference is true

    b. IsStrictReference(lref) is true

    c. Type(GetBase(lref)) is Environment Record

    d. GetReferencedName(lref) is either "eval" or "arguments"
  5. Call PutValue(lref, rval).
  6. Return rval.

注意,在赋值前,等号右侧的表达式的值会经过内部函数 GetValue 进行转化。

在我们的例子中,GetValue 将 foo.bar 的引用转化成那个实际的函数。赋值完成后,和调用(function(){})()没有什么分别。现在我们可以使用前面定义的规则来判别 this 指向了,显然这符合第二条规则—— this 指向全局。

逗号操作符

上面的过程也适用于逗号操作符 Comma Operator ( , )

  1. Let lref be the result of evaluating Expression.
  2. Call GetValue(lref).
  3. Let rref be the result of evaluating AssignmentExpression.
  4. Return GetValue(rref).

GetValue 将 foo.bar 的引用转化成那个实际的函数。逗号操作符计算完成后,和调用(function(){})()没有什么分别。

分组操作符

Grouping Operator会使用 GetValue 计算表达式吗?

Return the result of evaluating Expression. This may be of type Reference.

This algorithm does not apply GetValue to the result of evaluating Expression.

由于分组操作符不会对表达式做额外的操作,所以(foo.bar)() 和 foo.bar()没有差别,this 指向 foo。

(完)

如果你想知道更多细节,不妨点击Annotated ECMAScript 5.1

虽然不是标准文档,但更容易阅读。

参考

know-thy-reference/

【JavaScript】从 this 指向到 reference 类型的更多相关文章

  1. javaScript事件(八)事件类型之变动事件

    DOM2级的变动(mutation)事件能在DOM中某一部分发送变化时给出提示.变动事件为XML或HTML DOM设计的,并不特定于某种语言.DOM2级定义了如下变动事件. DOMSubtreeMod ...

  2. 图解javascript中this指向

    JavaScript 是一种脚本语言,支持函数式编程.闭包.基于原型的继承等高级功能.JavaScript一开始看起来感觉会很容易入门,但是随着使用的深入,你会发JavaScript其实很难掌握,有些 ...

  3. JavaScript 中的数字和日期类型

    本章节介绍如何掌握Javascript里的数字和日期类型 数字EDIT 在 JavaScript 里面,数字都是双精度浮点类型的 double-precision 64-bit binary form ...

  4. javascript的this指向

    JavaScript 是一种脚本语言,支持函数式编程.闭包.基于原型的继承等高级功能.JavaScript一开始看起来感觉会很容易入门,但是随着使用的深入,你会发现JavaScript其实很难掌握,有 ...

  5. javaScript事件(六)事件类型之滚轮事件

    滚轮事件其实就是一个mousewheel事件,这个事件跟踪鼠标滚轮,类似Mac的触屏版. 一.客户区坐标位置 鼠标事件都是在浏览器视口的特定位置上发生的.这个位置信息保存在事件对象的clientX和c ...

  6. javascript学习笔记(四) Number 数字类型

    数字格式化方法toFixed().toExponential().toPrecision(),三个方法都四舍五入 toFixed() 方法指定小数位个数  toExponential() 方法 用科学 ...

  7. JavaScript对象的指向问题

    JavaScript对象的指向问题 标签(空格分隔): JavaScript 对象 在接触了JavaScript之后,我们常听到一句话就是一切皆对象,意思是说除了object以外,JavaScript ...

  8. javaScript事件(九)事件类型之触摸与手势事件

    一.触摸事件 touchstart:当手指触摸屏幕时触发:即使已经有一个手指放在了屏幕上也会触发. touchmove:当手指在屏幕上滑动时连续地触发.在这个世界发生期间,调用preventDefau ...

  9. javaScript事件(七)事件类型之键盘与文本事件

    键盘事件如下: keydown:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件. keypress:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件. k ...

随机推荐

  1. Content-Type与MIME

    http://www.cnblogs.com/jsean/articles/1610265.html 首先,我们要了解浏览器是如何处理内容的.在浏览器中显示的内容有 HTML.有 XML.有 GIF. ...

  2. Generative Adversarial Networks overview(2)

    Libo1575899134@outlook.com Libo (原创文章,转发请注明作者) 本文章会先从Gan的简单应用示例讲起,从三个方面问题以及解决思路覆盖25篇GAN论文,第二个大部分会进一步 ...

  3. Linux安全加固(二)禁止普通用户su到root/设置SSH终端接入白名单/修改history条数

    一.禁止普通用户su到root管理员.设置可以su到root的白名单 1.首先看一下正常情况 2.可以看到普通用户使用su root命令,输入密码即可登录到root用户 3.下面开始配置禁止所有普通用 ...

  4. 使用jpillora/dnsmasq 提供可视化管理的dns server

    实际开发中dns 是一个比较重要的组件,一般大家可能会选择使用dnsmasq 但是缺少UI可视化,有些人可能会选择powerdns jpillora/dnsmasq 是一个对于dnsmasq 的包装, ...

  5. JavaScript == 与 === 区别

    1.对于 string.number 等基础类型,== 和 === 是有区别的 a)不同类型间比较,== 之比较 "转化成同一类型后的值" 看 "值" 是否相等 ...

  6. 2019 NOIP 夏令营(模拟赛1)

    一来到夏令营,第一天上机就考试, 哎,简直不让人活了 这难道是给我们的见面礼??? A https://www.luogu.org/problemnew/show/P1197 #include< ...

  7. centos服务器升级nodejs, pm2

    突然抽风想升级服务器的nodejs版本,原服务器版本运行的8.x,而目前(2019年5月30日)nodejs官方最新版本已经更新到了12.x了,稳定版本也更新到了10.x. 然后就折腾了一把去升级服务 ...

  8. 关于conda和jupyter使用

    conda建立虚拟环境 常用指令 conda env list # 查看环境 conda create -n env_name python=3.6.8 # 安装对应python的虚拟环境 conda ...

  9. linux中SIGHUP与nohup的关系

    SIGHUP信号与控制终端   UNIX中进程组织结构为 session (会话)包含一个前台进程组及一个或多个后台进程组,一个进程组包含多个进程.一个session可能会有一个session首进程, ...

  10. Mac Mini 2014 更换SSD 升级SSD

    将自己的Mac Mini 2014版升级成固态硬盘 亲自动手, 还算顺利, 参考网络教程, 并改进了里面的关键步骤, 下面是原文链接 Mac Mini 2014 升级成SSD Mac Mini 拆机图 ...