# 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-闭包 →