声明:本人看了冴羽一篇文章后,感受那边文章写的很好,可是不够详细,因此在这里丰富了一下,而后也但愿分享给更多的人,一块儿学习javascript
javascript 的this这个关键字我相信有不少人会像我同样对他既陌生有熟悉。相信每个学习JavaScript的前端工程师都作过这么一个事那就是到某个搜索引擎 key in ( javscript this),而后看过几个博客后感受本身懂了this!html
来来让咱们看看大部分博客会怎么写:前端
反正基本上都第一步,先告诉你一个事实『this 是在函数被调用时发生的绑定,指向上下文对象,即被调用函数所处的环境,也就是说,this 在函数内部指向了调用函数的对象。』;第二步,而后要不经过绑定方式的角度给你从new 绑定,显示绑定等多个角度告诉你this是谁,或者从函数调用状况,构造函数状况,bind调用等多个状况给你分析一波。而后不知道你会不会想我同样深深的记住调用对象是谁,this就是谁,而后把不太明白的特殊状况紧紧的记下,作个笔记什么的。而后以为我终于弄明白this了,而后在真正使用的时候偶尔还会发生,怎么又忘记这种状况了!!!纳尼?what?脸好疼有没有?
而后在去看网上的博客,看看有没有这种状况的解释,看了冴羽大大的这篇《JavaScript深刻之从ECMAScript规范解读this》文章后我以为我能够从一个新的角度再去学习一下,让咱们能够更深一步的去理解this,甚至找出一种方法,找this的时候经过“公式”去找到this的指代。java
PS.在正式开始以前,本人先声明如下的文章会啰嗦一点,若是你还不是很懂this,但愿你有时间去耐心的读下去,但愿也能够给你一种新的认识。git
在深刻了解 JavaScript 中的 this 关键字以前,有必要先退一步,看一下为何 this 关键字很重要。this 容许复用函数时使用不一样的上下文。换句话说,“this” 关键字容许在调用函数或方法时决定哪一个对象应该是焦点。github
【ECMAScript规范 10.4.3节 进入函数代码】是这么解释的: 当控制流根据一个函数对象 F、调用者提供的 thisArg 以及调用者提供的 argumentList,进入 函数代码 的执行环境时,执行如下步骤:面试
由于咱们是研究this,因此咱们着重关注前4条就能够了,简单总结一下就是:当调用函数的时候会建立函数执行上下文,在建立的过程当中有一步就是建立this ,并根据是否在严格模式下,调用者是否是NUll或者undefined,调用者是否是对象决定this的指向。bash
那咱们来看看【ECMAScript规范 11.2.3节 函数调用】:前端工程师
这里的thisValue的值其实就是咱们要找的thisArg了!!app
那咱们看一看MemberExpression的语法:
原来MemberExpression 的结果就是:执行函数名部分表达式的结果(简单理解 MemberExpression 其实就是()左边的部分表达式的结果)。
Reference type:按字面翻译就是引用类型,可是它并非咱们常说的JavaScript中的引用类型,它是一个规范类型(实际并不存在),也就是说是为了解释规范某些行为而存在的,好比delete、typeof、赋值语句等。规范类型设计用于解析命名绑定的(A Reference is a resolved name binding.),它由三部分组成:
基值就是属性所在的对象或者就是 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
};
复制代码
把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就是这个基值对象
}
复制代码
举几个栗子🌰:
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",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());//The Window
复制代码
这个我没有在规范里面找到,因此只能当个问题给你们写出来,若是写的不对,还但愿有大神在评论中指出来。
object.getNameFunc()()的MemberExpression 是object.getNameFunc(),这个函数的运行结果是return的匿名函数的指针,是个Reference可是它的base不是对象,因此是一个环境记录项,没有被bind,this,call改变因此ImplicitThisValue()为undefined 因此非严格模式下就是window