# JS-This (二)
🐴
# 前言
在上一篇文章中我们从this
指向的多种情况来认识了一下this
, 这篇文章中我们从ECMAScritp
规范来看看this
是如何确定的。
在JavaScript
中,this
是一直讨论的东西,并且在每个上下文创建时,this
就被创建了,另外还有三个重要的属性也会被创建:变量对象、作用域链。最近抽出了部分时间将JavaScript
中this
的知识汇总了一下。
ECMAScript 镜像版规范 (opens new window)
# ECMAScript 中的数据类型
在js
中的数据类型分为语言类型和规范类型:
语言类型
ECMAScript
语言类型 是ECMAScript
程序员使用ECMAScript
语言直接操作的值对应的类型。ECMAScript
语言类型包括 未定义 (Undefined)、 空值 (Null)、 布尔值(Boolean)、 字符串 (String)、 数值 (Number)、 对象 (Object)。规范类型
规范类型 是描述 ECMAScript
语言构造与 ECMAScript
语言类型语意的算法所用的元值对应的类型。规范类型包括 引用(Reference) 、 列表(List) 、 完结(Completion) 、 属性描述式(Property Descriptor) 、 属性标示(Property Identifier)、 词法环境(Lexical Environment)、 环境纪录(Environment Record)。规范类型的值是不一定对应 ECMAScript
实现里任何实体的虚拟对象。规范类型可用来描述 ECMAScript
表式运算的中途结果,但是这些值不能存成对象的变量或是 ECMAScript
语言变量的值。
# 引用类型(Reference)
注意
该引用类型是规范中的数据类型,并不是javascript实际代码中的引用类型(因为我们常说Object是引用类型,这里的Reference
并不是指的它)
上面说了ECMAScript
中的数据类型,其中规范类型中的引用类型Reference
(opens new window)和this
有这亲密的关系。引用类型Reference
是规范中的类型,在js中并不存在,只是存在于ECMAScript
底层。引用类型Reference
包含有三个重要的属性:
- 基值(base):属性所在的环境对象,可以是
undefined
,Object
,Boolean
,String
,Number
,environment record
中的任意一个,当所处的环境是全局环境(在全局中,或setTimeout
或setInterval
),或执行环境中时值为environment record (opens new window) - 引用名称(referenced name):该引用名称,是一个字符串
- 严格引用标志 (strict reference):是否是严格模式,
true
为严格模式
例如:
var a = 1;
// a的引用类型Reference, a处于全局环境
AReferenceObj = {
base:EnvironmentRecord,
name:'a',
strict: false,
}
// 第二种基值为EnvironmentRecord情况
//f执行时处于函数fun的执行环境中
function fun(){
function f(){}
f()
}
fun()
//函数f的引用类型Reference
FReferenceObj = {
base:EnvironmentRecord,
name:'f',
strict: false,
}
在例如:
var obj = {
fun:function(){
}
}
// fun对应的Reference是:
var FunReferenceObj = {
base: obj,
name: 'fun',
strict: false
};
另外引用类型Reference
除了拥有三个重要的属性外,还提供了一些方法比如:
GetBase(V)
。 返回引用值V
的基值, base的值。GetReferencedName(V)
。 返回引用值V
的引用名称。IsStrictReference(V)
。 返回引用值V
的严格引用。HasPrimitiveBase(V)
。 如果基值是Boolean
,String
,Number
,那么返回true
。IsPropertyReference(V)
。 如果基值是个对象或HasPrimitiveBase(V)
是true
,那么返回true
;否则返回false
。IsUnresolvableReference(V)
。 如果基值是undefined
那么返回true
,否则返回false
。
# GetValue
另外ECMAScript
还提提供了一个GetValue(v) (opens new window) 方法,它的作用就是获取Reference
中的具体值。当使用GetValue(v)
方法时,返回的是具体值,不在是一个引用类型Reference
例如:
var a = 1;
// a的引用类型Reference
AReferenceObj = {
base:EnvironmentRecord, // 全局对象
name:'a',
strict: false,
}
GetValue(AReferenceObj) // 返回值为1 ,1可不再是引用类型
# 函数中This的确定
# 函数执行中的规范
在ECMAScript
规范中,函数的调用 (opens new window)解释了this
是如何确定的
- 令
ref
为解释执行MemberExpression
的结果...
- 如果
Type(ref)
为Reference
,那么 如果IsPropertyReference(ref)
为true
,那么 令thisValue
为GetBase(ref)
. 否则 ,ref
的基值是一个环境记录项 令thisValue
为调用GetBase(ref)
的ImplicitThisValue
具体方法的结果- 否则 , 假如
Type(ref)
不是Reference
. 令thisValue
为undefined
. 返回调用func
的 [[Call]] 内置方法的结果 , 传入thisValue
作为this
值和列表argList
作为参数列表
上面函数调用时确定this
的值说清楚点就是:
- 将
MemberExpression
解析后的结果赋值给ref
(MemberExpression (opens new window)就是函数调用时括号左边的部分,如:foo
()中的MemberExpression
为foo
) - 使用
Type(ref)
判断ref
是不是Reference
类型,判断时有如下几种情况
- 如果
ref
是 引用类型Reference
,并且 基值是否是对象(IsPropertyReference(ref)
是true
), 那么this
的值为 基值GetBase(ref)
- 如果
ref
是 引用类型Reference
,并且base value
值是Environment Record
, 那么this
的值为ImplicitThisValue(ref)
(js中为window
) - 如果
ref
不是 引用类型Reference
,那么this
的值为undefined
,(js中严格模式下为undefined
,非严格模式下为window
,可以通过IsStrictReference(ref)
来确定是否是严格模式)
上面我们说了函数调用时MemberExpression (opens new window)就是括号左边的,如:
function f(){}
f() // MemberExpression 为f
var obj = {
fun:function(){}
}
obj.fun() // MemberExpression 为obj.fun
function fun(){
return function(){}
}
fun()()// MemberExpression 为fun()
obj['funName']()// MemberExpression 为obj['funName']
第二步我们很重要的就是需要判断ref
是否是引用类型,一般ref
是引用类型的情况有两种:
- 函数标示符
- 属性访问器(点
.
属性访问器或中括号[]
属性访问器)
# 实际例子
下面我们来使用实际列子看看函数调用时this
的值是如何确定的
var foo = {
bar: function () {
alert(this);
}
};
function fun(){
console.log(this)
}
foo.bar(); // 例子1
(foo.bar)(); // 例子2
(foo.bar = foo.bar)(); // 例子3
(false || foo.bar)(); // 例子4
(foo.bar, foo.bar)(); // 例子5
fun() // 例子6
foo.bar()
在执行时,MemberExpression
为foo.bar
, 并将MemberExpression
赋值给了ref
;然后通过Type(ref)
判断foo.bar
是不是引用类型,因为foo.bar
是属性访问器形式,所以foo.bar
是引用类型; 然后判断基值是否是对象IsPropertyReference(ref)
,foo.bar
是对象所以this
确定为foo
对象(通过GetBase(ref)
获取this = GetBase(ref)
)(foo.bar)()
在执行时()
并没有使用GetValue
对MemberExpression
进行处理,所以MemberExpression
为foo.bar
, 接下来的情况和例一就一样了。(foo.bar = foo.bar)()
在执行时,在左边括号中foo.bar = foo.bar
因为使用了赋值运算=
,此时调用了GetValue
方法,将foo.bar
最终的值不在是引用类型(上面说了调用GetValue
方法返回的是具体值,不在是一个引用类型Reference
), 所以this
为undefined
,之后还会通过IsStrictReference(V)
判断是否在严格模式下,如果不是this
为undefined
时,this
将隐士转换为全局对象window
或global
(false || foo.bar)()
在执行时,逻辑运算符也会调用GetValue
方法,所以最终this
结果和例三一样(foo.bar, foo.bar)()
在执行时,逗号运算符也会调用GetValue
方法,所以最终this
结果和例三一样fun()
在执行时,因为fun
是一个标识符所以ref
会是引用类型;但是引用类型的基值是EnvironmentRecord
,它并不是一个对象,通过IsPropertyReference(ref)
判断返回false
,此时this
值为ImplicitThisValue(ref)
,ImplicitThisValue(ref)
函数通常会返回undefined
(声明式环境 (opens new window),对象式环境 (opens new window));之后还会通过IsStrictReference(V)
判断是否在严格模式下,如果不是this
为undefined
时,this
将隐士转换为全局对象window
或global
# 构造函数中的This
在构造函数中this
还是很好确定的,在构造函数中this
永远指向通过构造函数创建的对象。
function people(){
this.name = "king";
this.age = 52
}
var one = new People()
var two = new People()
上面在构造函数中的this
会指向通过new
创建出来的对象。第一种情况下people
中的this
指向 one
对象,第二种情况下people
中的this
指向 two
对象
tom大叔
针对上面的构造函数new
运算符调用people
函数的内部的[[Construct]]
方法,接着,在对象创建后,调用内部的[[Call]]
方法。 所有相同的函数people
都将this
的值设置为新创建的对象。
# 改变This指向的方式
在js
我们可以使用call
、apply
、bind
、方法可以来改变this
的指向的。下面我们来看看着几个方法的使用。
# call 方法
call
方法可以执行一个函数,并改变一个函数中this
的指向,call
方法中有多个参数,第一个参数是this
指向对象,后面的参数就是调用函数时传入的参数。
var obj = {
name: "King",
age:23
}
var name = "global";
function fun(age,sum){
console.log(this.name)
console.log(age,sum)
}
fun.call(obj,50,30) //King; 50 30
我们会发现通过call
调用函数fun
后,fun
函数内部的this
指向了对象obj
,并且传入了两个参数50 30
。
# apply 方法
apply
方法也是可以执行一个函数,并改变一个函数中this
的指向,和call
方法不同的是,apply
方法的第二个参数传入的是一个数组。
var obj = {
name: "King",
age:23
}
var name = "global";
function fun(age,sum){
console.log(this.name)
console.log(age,sum)
}
fun.call(obj,[50,30]) //King; 50 30
# bind 方法
js
中bind
方法可以给函数中this
绑定一个固定的对象,并返回一个新函数
var obj = {
name: "King",
age:23
}
var name = "global";
function fun(age,sum,a){
console.log(this.name)
}
var newFun = fun.bind(obj)
newFun() //King
setTimeout(newFun,2000) // King
通过例子我们可以看到,通过bind
方法处理后,返回的新函数中的this
永远都会指向同一个对象,而不会随着函数的调用方式不同而改变。
# DOM事件中This
DOM
事件中绑定的函数中的this
指向触发该事件的DOM
元素,这点毋庸置疑。
DOM事件中This
关于DOM
事件中This
虽然知道指向触发该事件的DOM
元素,但是在ECMAScript
规范中是如何实现的,我目前不太确定,希望知道的同学可以提供一下帮助。
参考链接:
← JS-This(一) JS-闭包 →