1.抛砖引玉
声明:本人看了冴羽一篇文章后,感受那边文章写的很好,可是不够详细,因此在这里丰富了一下,而后也但愿分享给更多的人,一块儿学习javascript
javascript 的this这个关键字我相信有不少人会像我同样对他既陌生有熟悉。相信每个学习JavaScript的前端工程师都作过这么一个事那就是到某个搜索引擎 key in ( javscript this),而后看过几个博客后感受本身懂了this!html
来来让咱们看看大部分博客会怎么写:前端
- MDN
- Javascript 的 this 用法--阮一峰
- [译] this(他喵的)究竟是什么 — 理解 JavaScript 中的 this、call、apply 和 bind -- 掘金
- The JavaScript this Keyword
.....
此外不就不列举了。
反正基本上都第一步,先告诉你一个事实『this 是在函数被调用时发生的绑定,指向上下文对象,即被调用函数所处的环境,也就是说,this 在函数内部指向了调用函数的对象。』;第二步,而后要不经过绑定方式的角度给你从new 绑定,显示绑定等多个角度告诉你this是谁,或者从函数调用状况,构造函数状况,bind调用等多个状况给你分析一波。而后不知道你会不会想我同样深深的记住调用对象是谁,this就是谁,而后把不太明白的特殊状况紧紧的记下,作个笔记什么的。而后以为我终于弄明白this了,而后在真正使用的时候偶尔还会发生,怎么又忘记这种状况了!!!纳尼?what?脸好疼有没有?
而后在去看网上的博客,看看有没有这种状况的解释,看了冴羽大大的这篇《JavaScript深刻之从ECMAScript规范解读this》文章后我以为我能够从一个新的角度再去学习一下,让咱们能够更深一步的去理解this,甚至找出一种方法,找this的时候经过“公式”去找到this的指代。java
PS.在正式开始以前,本人先声明如下的文章会啰嗦一点,若是你还不是很懂this,但愿你有时间去耐心的读下去,但愿也能够给你一种新的认识。git
2.this的前世此生
在深刻了解 JavaScript 中的 this 关键字以前,有必要先退一步,看一下为何 this 关键字很重要。this 容许复用函数时使用不一样的上下文。换句话说,“this” 关键字容许在调用函数或方法时决定哪一个对象应该是焦点。github
2.1 this 是在函数被调用时发生的绑定,那么函数被调用的时候,JavaScript引擎都干了啥?
【ECMAScript规范 10.4.3节 进入函数代码】是这么解释的: 当控制流根据一个函数对象 F、调用者提供的 thisArg 以及调用者提供的 argumentList,进入 函数代码 的执行环境时,执行如下步骤:web
- 若是函数代码是严格模式下的代码 ,设 this 绑定为 thisArg(调用者)。(严格等于调用者)
- 若是不是严格模式下的代码, 断定thisArg 是否是 null 或 undefined,是则设 this 绑定为 全局对象 。
- 不然若是 Type(thisArg) 的结果不为 Object,则设 this 绑定为 ToObject(thisArg)。
- 不然设 this 绑定为 thisArg。
- 以 F 的 [[Scope]] 内部属性为参数调用 NewDeclarativeEnvironment,并令 localEnv 为调用的结果。
- 设词法环境为 localEnv。
- 设变量环境为 localEnv。
- 令 code 为 F 的 [[Code]] 内部属性的值。
- 按 10.5 描述的方案,使用 函数代码 code 和 argumentList 执行定义绑定初始化步骤。
由于咱们是研究this,因此咱们着重关注前4条就能够了,简单总结一下就是:当调用函数的时候会建立函数执行上下文,在建立的过程当中有一步就是建立this ,并根据是否在严格模式下,调用者是否是NUll或者undefined,调用者是否是对象决定this的指向。面试
2.2 咱们搞清楚在调用的时候的this的指向规则,里面有个很扎眼的词 thisArg,它是什么鬼?
那咱们来看看【ECMAScript规范 11.2.3节 函数调用】:bash
- 令 ref 为解释执行 MemberExpression 的结果 .
- 令 func 为 GetValue(ref).
- 令 argList 为解释执行 Arguments 的结果 , 产生参数值们的内部列表 (see 11.2.4).
- 若是 Type(func) is not Object ,抛出一个 TypeError 异常 .
- 若是 IsCallable(func) is false ,抛出一个 TypeError 异常 .
- 若是 Type(ref) 为 Reference,那么 若是 IsPropertyReference(ref) 为 true,那么 令 thisValue 为 GetBase(ref). 若是 Type(ref) 不是 Reference , ref 的基值是一个环境记录项,令 thisValue 为调用 GetBase(ref) 的 ImplicitThisValue 具体方法的结果
- 不然 , 假如 Type(ref) 不是 Reference. 令 thisValue 为 undefined.
- 返回调用 func 的 [[Call]] 内置方法的结果 , 传入 thisValue 做为 this 值和列表 argList 做为参数列表
这里的thisValue的值其实就是咱们要找的thisArg了!!前端工程师
那么根据规范咱们就要先去找MemberExpression 的结果是什么了?
那咱们看一看MemberExpression的语法:
- PrimaryExpression // 原始表达式 能够参见《JavaScript权威指南第四章》
- FunctionExpression // 函数定义表达式
- MemberExpression [ Expression ] // 属性访问表达式
- MemberExpression . IdentifierName // 属性访问表达式
- new MemberExpression Arguments // 对象建立表达式
原来MemberExpression 的结果就是:执行函数名部分表达式的结果(简单理解 MemberExpression 其实就是()左边的部分表达式的结果)。
下面咱们要看看Reference是什么了?
Reference type:按字面翻译就是引用类型,可是它并非咱们常说的JavaScript中的引用类型,它是一个规范类型(实际并不存在),也就是说是为了解释规范某些行为而存在的,好比delete、typeof、赋值语句等。规范类型设计用于解析命名绑定的(A Reference is a resolved name binding.),它由三部分组成:
- 基 (base) 值,
- 引用名称(referenced name)
- 布尔值 严格引用 (strict reference) 标志。
基值就是属性所在的对象或者就是 EnvironmentRecord,基值是 undefined, 一个 Object, 一个 Boolean, 一个 String, 一个 Number, 一个 environment record 中的任意一个。基值是 undefined 表示此引用能够不解决一个名字的绑定(A base value of undefined indicates that the reference could not be resolved to a binding.)。
PS.最后一句话个人理解是这个引用不须要一个名字和他关联起来,好比咱们建立的匿名函数,他的base应该就是undefined。
举个栗子🌰:
var foo = 1;
// 对应的Reference是: var fooReference = { base: EnvironmentRecord, name: 'foo', strict: false };
var foo = { bar: function () { return this; } };
foo.bar(); // foo
复制代码// bar对应的Reference是: var BarReference = { base: foo, propertyName: 'bar', strict: false }; 复制代码复制代码
IsPropertyReference(ref),GetBase(ref)和ImplicitThisValue()是什么?
- IsPropertyReference(V)。 若是引用的基值是个对象或 HasPrimitiveBase(V) 是 true,那么返回 true;不然返回 false。(Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.)
- GetBase(V)。 返回引用值 V 的基值组件。(Returns the base value component of the reference V.)
- ImplicitThisValue()。 环境记录分为声明式环境记录和对象式环境记录,不一样的略有不一样。
- 声明式环境记录项永远将 undefined 做为其 ImplicitThisValue 返回。
- 对象式环境记录项的 ImplicitThisValue 一般返回 undefined,除非其 provideThis 标识的值为 true。
- 令 envRec 为函数调用时对应的声明式环境记录项。
- 若是 envRec 的 provideThis 标识的值为 true,返回 envRec 的绑定对象。
- 不然返回 undefined。
2.3 this指代计算伪代码
把2.1 和2.2的知识进行总结能够写出以下伪代码:
FUNCTION.this = {
ref = MemberExpression 的结果; //'()'左边的表达式的结果
IF Type(ref) == Reference THEN //若是左边的表达式的结果是引用
IF IsPropertyReference(ref) THEN //断定引用的基值是否是一个对象
thisArg = ref.base //若是是,thisArg是这个引用的基值
ELSE
thisArg = ref.base.ImplicitThisValue() //若是引用的基值不是对象,说明是个环境记录,thisArg是
//它的ImplicitThisValue,绝大部分状况下是Undefined
ELSE
thisArg = undefined; //若是左边的表达式的结果不是引用,thisArg是undefined
复制代码IF thisArg == undefined
IF 'using strict' THEN //thisArg若是是undefined return undefined //在严格模式下,函数的this就是undefined ELSE return 全局对象 //在严格模式下,函数的this就是undefined ELSE return thisArg //thisArg不是undefined,函数的this就是这个基值对象 } 复制代码复制代码
3.复习巩固
举几个栗子🌰:
var value = 1;
function foo() { console.log(this.value) }
复制代码var fooObj = { value: 2, bar: function () { return this.value; }, fooIn:{ value:3, foo:foo } } //示例1 console.log(foo()); //示例2 console.log(fooObj.bar()); //示例3 console.log((fooObj.bar)()); //示例4 console.log((fooObj.bar = fooObj.bar)()); //示例5 console.log((false || fooObj.bar)()); //示例6 console.log((fooObj.bar, fooObj.bar)()); //示例7 console.log(fooObj.fooIn.foo()); //输出结果是:1 2 2 1 1 1 3 复制代码复制代码
-
foo() 的MemberExpression 是 foo,他的base是声明式环境记录,声明式环境记录的隐性的this值(ImplicitThisValue)是undefined ,在非严格模式下因此this指向window。
-
fooObj.bar()的MemberExpression 是fooObj.bar是个引用,他的base 值是 fooObj,因此this指向 fooObj。
-
(fooObj.bar)()的MemberExpression 是(fooObj.bar) 一个分组表达式,查看11.1.6分组表达式的规范:
The production PrimaryExpression : ( Expression ) is evaluated as follows: Return the result of evaluating Expression. This may be of type Reference
咱们要这个表达式的结果,结果是:fooObj.bar,因此和2是同样的。
-
(fooObj.bar = fooObj.bar)()的MemberExpression 是(fooObj.bar = fooObj.bar) 咱们分组表达式的返回值是 fooObj.bar = fooObj.bar,咱们就要计算这个表达式的结果,查看11.13.1简单赋值的规范:
令 lref 为解释执行 LeftH 和 SideExpression 的结果 .
令 rref 为解释执行 AssignmentExpression 的结果 .
令 rval 为 GetValue(rref).
抛出一个 SyntaxError 异常,当如下条件都成立 : Type(lref) 为 Reference IsStrictReference(lref) 为 true Type(GetBase(lref)) 为环境记录项 GetReferencedName(lref) 为 "eval" 或 "arguments"
调用 PutValue(lref, rval).
返回 rval.由蓝色部分能够知道(fooObj.bar = fooObj.bar) 不是一个Reference,因此thisArg是一个undefined,非严格模式是window.
-
(false || fooObj.bar)()的MemberExpression 是(false || fooObj.bar) ,类比4查看 11.11 二元逻辑运算符 ,咱们知道(false || fooObj.bar) 是个值不是Reference因此this也是指向window
-
(fooObj.bar, fooObj.bar)()的MemberExpression 是(false || fooObj.bar) ,类比4查看11.14 逗号运算符,,咱们知道(false || fooObj.bar) 是个值不是Reference因此this也是指向window。
-
fooObj.fooIn.foo()的MemberExpression 是fooObj.fooIn.foo,他是个Reference,基值是fooObj.fooIn,因此this指向fooObj.fooIn
总结:
看了本篇文章后应该能够解决绝大多数的this的指向问题,还有传说中的 构造函数调用模式 , 箭头函数调用模式 , call、apply、bind 调用模式,你们应该也是均可以经过规范解释的清楚,在这里再也不一一列举。
问题
var name = "The Window";
var object = {
name : "My Object",
<span class="hljs-attr">getNameFunc</span> : <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.name;
};
}
复制代码
}; alert(object.getNameFunc()());//The Window
复制代码<span class="hljs-attr">getNameFunc</span> : <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
<span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
<span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.name;
};
}
复制代码复制代码复制代码
这个我没有在规范里面找到,因此只能当个问题给你们写出来,若是写的不对,还但愿有大神在评论中指出来。
object.getNameFunc()()的MemberExpression 是object.getNameFunc(),这个函数的运行结果是return的匿名函数的指针,是个Reference可是它的base不是对象,因此是一个环境记录项,没有被bind,this,call改变因此ImplicitThisValue()为undefined 因此非严格模式下就是window