JavaScript深入之从ECMAScript规范解读this

1,113 阅读4分钟

JavaScript深入之从ECMAScript规范解读this

(深入js系列读后感> github.com/mqyqingfeng…)

Types

es5.github.io/#x8

ECMAScript 有两种类型,语言类型和规范类型

语言类型 就是常说的数据类型,比如 Undefined、Null、String 等等

规范类型 是用来用算法描述 ECMAScript 语言结构和 ECMAScript 语言类型的。规范类型包括:Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record。

总结:这些都不重要,只需要知道 ECMAScript 还有一种只存在与规范中的类型,用来描述语言底层行为逻辑

Reference

es5.github.io/#x8.7

Reference 是规范类型的一种,用来解释语言的底层行为逻辑才存在的,比如 delete、typeof 以及复制等操作行为。

(上面这句话有点难以理解,只需要知道有这么回事就行,记住 Reference 是规范类型的一种)

Reference 的构成:

  • base value (base):属性所在的对象或者 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。
  • referenced name (name): 属性名
  • strict reference (strict): 是否是严格模式

举个例子:

var foo = 1

// 对应的 Reference :
var fooReference = {
	base: EnviromentRecord,
	name: 'foo',
	strict: false
}

再举个例子

var foo = {
	bar: function () {
		return this
	}
}

foo.bar()

// bar 对应的 Reference :
var BarReference = {
	base: foo,
	name: 'bar',
	strict: false
}

介绍几个和 Reference 相关的方法:GetBase、IsPropertyReference、GetValue

  1. GetBase: 获取 Reference 中 base 的值

  2. IsPropertyReference: 判断 Reference 中 base 是否为对象,是 返回 true, 否 返回 false

  3. GetValue: 返回的将是属性具体的值,而不是一个 Reference ,这个很关键

    简单模拟 GetValue 的使用

    var foo = 1
    
    // 对应的 Reference :
    var fooReference = {
    	base: EnviromentRecord,
    	name: 'foo',
    	strict: false
    }
    GetValue(fooReference) // 1
    

重点记忆一下这三个方法,后面判断 this 时,会用到!!!

确定 this 的值

es5.github.io/#x11.2.3

ES5 的规范中讲了当函数调用当时候,如何确定 this 的值。主要看1、6、7点。

主要意思:

  1. 计算 MemberExpression 的结果赋值给 ref

什么是 MemberExpression ?举个例子:

function foo() {
	console.log(this)
}
foo()  // MemberExpression 是 foo

function foo() {
	return function() {
		console.log(this)
	}
}

foo()() // MemberExpression 是 foo()

var foo = {
    bar: function () {
        return this;
    }
}

foo.bar(); // MemberExpression 是 foo.bar

简单来说,函数调用的时候,() 左边的部分就是 MemberExpression 的值

  1. 根据 ES5 规范,判断 ref 是不是一个 Reference 类型

  2. 如果 ref 是 Reference,并且 IsPropertyReference(ref) (判断 Reference 中 base 是否为对象,上面有解释) 是 true,那么 this 的值为 GetBase(ref) (获取 Reference 中 base 的值,上面有解释)

  3. 如果 ref 是 Reference,并且 GetBase(ref)EnvironmentRecord,那么 this 的值为 ImplicitThisValue(ref) (查看规范 10.2.1.2.6,ImplicitThisValue 方法的介绍:该函数始终返回 undefined)

  4. 如果 ref 不是 Reference,那么 this 的值为 undefined

小试牛刀

var value = 1;

var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}

function foo1() {
	console.log(this)
}

// 示例1
console.log(foo.bar())
// 示例2
console.log((foo.bar)())
// 示例3
console.log((foo.bar = foo.bar)())
// 示例4
console.log((false || foo.bar)())
// 示例5
console.log((foo.bar, foo.bar)())
// 示例6
foo1()
  1. 示例1

    1. ref = foo.bar

    2. 判断 ref 是否为 Reference,根据 ES5 规范 11.2.1 (Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.) 判断,当以 . 的方式访问属性时,返回 Reference。结论 ref 是 Reference 类型

      var Reference = {
        base: foo,
        name: 'bar',
        strict: false
      }
      
    3. this 的值为 GetBase(ref) 就是 foo

  2. 示例2

    1. ref = (foo.bar)
    2. 根据 ES5 规范 11.1.6 (Return the result of evaluating Expression. This may be of type Reference.) 判断,() 并没有对 ref 进行计算,所以和 示例1 的结果一致
  3. 示例3

    1. ref = (foo.bar = foo.bar)
    2. 根据 ES5 规范 11.13.1 判断,等号操作返回值为 GetValue('等号右边属性') ,根据上面 GetValue 介绍,调用 GetValue 返回的将是属性具体的值,而不是 Reference
    3. 按照判断 this 的逻辑,ref 不是 Reference ,那么 this 的值为 undefined,在非严格模式下,this 的值为 undefined 的时候,其值会被隐式转换为全局对象
  4. 示例4

    1. ref = (foo.bar || foo.bar)
    2. 根据ES5规范 11.11 判断,二元逻辑操作返回值会调用 GetValue,所以和 示例三 一致
  5. 示例5

    1. ref = (foo.bar, foo.bar)
    2. 根据ES5规范 11.14 判断,逗号操作返回值会调用 GetValue,所以和 示例三 一致
  6. 示例6

    1. ref = foo1

    2. 根据ES5规范 10.3.1 判断,标识符的引用,返回一个 Reference 类型的值

      var foo1Reference = {
      	base: EnvironmentRecord,
      	name: 'foo',
      	strict: false
      }
      
    3. 按照判断 this 的逻辑,ref 是 Reference,判断 IsPropertyReference(ref) 是 true 还是 false,根据 IsPropertyReference 的描述 (判断 Reference 中 base 是否为对象),这里的 base 为 EnvironmentRecord,不是 Object 类型

    4. 因为 IsPropertyReference(ref) 为 false,再判断 GetBase(ref) 的值,正是 environment record ,所以 this 的值为 ImplicitThisValue(ref),根据上面介绍,该函数返回 undefined