shawlocke / es-blog Goto Github PK
View Code? Open in Web Editor NEWBlog about EcmaScript
Blog about EcmaScript
The reference record type is used to explain the behaviour of such operators as
typeof
, the assignment operators and other language features. E.g. the left-hand operand of an assignment is expected to produce a reference record.Reference record 是一个抽象概念,可以将其理解为一个 plain object.
Spec 用 specification type, 比如 reference record. 我们编写的 JS 代码用 language type,比如 Boolean, Number, String 等。
Reference record 用于 2 个地方:
如何确定 identifier 的 reference record 的[[Base]]请参考《Chapter 4 中的 identifier resolution》
会用到 spec 中的 EvaluatePropertyAccessWithIdentifierKey 或 EvaluatePropertyAccessWithExpressionKey 抽象运算
Reference record 的属性有:
undefined
or null
. (见示例 2 和 3)对 unresolvable 的 reference record 应用 GetValue 抽象运算会导致 ReferenceError
true
if in strict mode code, false
otherwise.// 示例 1
// foo的reference record:
// { [[Base]]: globalEnvRec, [[ReferencedName]]: 'foo', [[Strict]]: false }
const foo = 10;
// 示例 2
const bar = { x: 20 };
// 整个bar.x的解析过程如下:
// bar的reference record:
// { [[Base]]: globalEnvRec, [[ReferencedName]]: 'bar', [[Strict]]: false }
// 然后应用 GetValue ,得到一个对象
// bar.x的reference record:
// { [[Base]]: bar指向的对象(上面的对象), [[ReferencedName]]: 'x', [[Strict]]: false }
bar.x;
// 示例 3
const baz = null;
// 无法成功生成baz.toString的Reference record: EvaluatePropertyAccessWithIdentifierKey
// 会调用RequireObjectCoercible, `undefined`或`null`会导致TypeError
baz.toString; // TypeError: Cannot read properties of null
// 示例 4
// notFound的reference record:
// { [[Base]]: unresolvable, [[ReferencedName]]: 'notFound', [[Strict]]: false }
notFound; // ReferenceError: notFound is not defined (应用 GetValue 时导致的ReferenceError)
如错误或者不严谨的地方,请斧正,感谢。
In JavaScript, variables don't have types, but values do.
A primitive value is a member of one of the following built-in types: Undefined, Null, Boolean, Number, String, Symbol, and BigInt.
BigInt 用得很少,就没列出来
undefined
null
true
, false
{}
[]
function foo() {}
new Set()
new Map()
new Error('msg')
new Date()
JSON.stringify
JSON.floor
/^foo$/gmi
, g: global, m: multiline, i: case-insensitivetypeof
operator检查值的类型,结果是字符串。
当 operand (运算子)无法 resolve 时,结果是
"undefined"
,如何判断是否能 resolve 请参考《Chapter 2. Reference record》
typeof
和 spec 中的 Type(x)不同:Type(x)的结果是 Undefined, Null, Boolean, Number, String, or Object 等,不会有 Function 之类的。
Type of operand | Result | Note |
---|---|---|
Undefined | "undefined" | |
Null | "object" | Oops! 历史遗留错误:在面向对象编程中,想 unset 对象时,往往将其赋值为null . |
Boolean | "boolean" | |
Number | "number" | |
String | "string" | |
Symbol | "symbol" | |
Object (does not implement [[Call]]) | "object" | 比如typeof {} , typeof [] . 又比如typeof JSON , typeof Math , 不能用作函数. |
Object (implements [[Call]]) | "function" | 比如 typeof function(){} . 又比如typeof String , typeof Object , typeof Array , typeof Function , 能用作函数. |
undefined
,可以用 foo === undefined
null
,可以用 foo === null
undefined
或 null
,可以用 foo == null
foo === true
或 foo === false
typeof foo == "number"
typeof foo == "string"
typeof
对 Object 的判断很粗放),可以用 Object.prototype.toString.call(foo).slice(8, -1).toLowerCase()
Array.isArray(foo)
typeof foo == "function"
我们经常看到的 coercion 说的就是 type conversion,但 spec 中并没有 coercion 这个术语。
spec 中算法所用符号的说明:
?表示紧跟的运算可能会抛出异常。
!表示紧跟的运算不会抛出异常 (我都省略掉了。注意:这里的!不是逻辑非运算符)
[] 表示可选。
NewTarget 用来区分
new Boolean()
和Boolean()
(这里以 Boolean 类型 举例,后一种方式调用时,NewTarget 是 undefined)
以Boolean(value)
方式被调用时 (前面没有new
)
相当于
!!value
,注意是 2 个逻辑非运算符,通常用!!value
而不是Boolean(value)
, 写起来更简短如果参数不存在,结果是
false
ToBoolean(argument) (抽象运算,运算结果是true
或者false
)
Type of argument | Result |
---|---|
Undefined | Return false . |
Null | Return false . |
Boolean | Return argument. |
Number | If argument is 0 , -0 , or NaN , return false ; otherwise return true . |
String | If argument is "" (空字符串), return false ; otherwise return true . |
Symbol | Return true . |
Object | Return true . |
以Number(value)
方式被调用时 (前面没有new
)
相当于
+value
,不建议使用,影响可读性如果参数不存在,结果是
0
ToNumber(argument) (抽象运算,运算结果是 Number 类型值或者 Exception)
Type of argument | Result | Note |
---|---|---|
Undefined | Return NaN . |
|
Null | Return 0 . |
Oops! 如果是NaN 会更合适。 |
Boolean | If argument is true , return 1 . If argument is false , return 0 . |
|
Number | Return argument. (十进制数字) | |
String | 去掉首尾的空白(包括空格,tab,换行等)后,如果是空字符串,return 0 , 如果看起来是数字(可能是二进制 Binary,八进制 Octal,十六进制 heXadecimal),return 十进制 decimal 数字 (输入是科学记数法的字符串,输出可能是十进制数字,也可能是科学计数法数字),否则 return NaN . |
"-0" ->-0 . |
Symbol | Throw a TypeError exception. | |
Object | Apply the following steps: 1. Let primValue be ? ToPrimitive(argument, number). (指定了 preferredType) 2. Return ? ToNumber(primValue). |
ToPrimitive(input[, preferredType]) (抽象运算)
该运算追求得到 primitive value,如果得不到就会抛出 Exception。
这很好解释了:如果@@toPrimitive 返回的是 Object 类型,直接抛出 Exception 了 (步骤 1-2-6)。
Symbol.toPrimitive
属性的值,如果属性不存在或属性值是undefined
或null
,返回undefined
,见示例 1;如果是函数,返回函数,见示例 2;否则抛出 TypeError,见示例 3)这里可以看出 number 的权重比 string 高
When ToPrimitive is called with no preferredType, then it generally behaves as if the preferredType were number.
如果没有 preferredType,那大部分情况下 preferredType 被看成是 number
However, objects may over-ride this behaviour by defining a @@toPrimitive method.
对象通过定义@@toPrimitive 方法可重写这一行为
Date objects over-ride the default ToPrimitive behaviour. Date objects treat no preferredType as if the preferredType were string.
Date 重写了 ToPrimitive 的默认行为。Date 将没有 preferredType 看成是 string。ES spec 中搜索
Date.prototype [ @@toPrimitive ]
就能看出来怎么回事。见下面的示例const foo = new Date(); // ToPrimitive(foo, 没有 preferredType) // => "Tue Jun 27 2023 08:49:53 GMT+0800 (China Standard Time)" + 1 foo + 1;const foo = new Date(); // ToPrimitive(foo, preferredType 是 number) // => 1687826993053 > 1 foo > 1;
OrdinaryToPrimitive(o, preferredType) (抽象运算)
该运算追求得到 primitive value,如果得不到就会抛出 Exception。
这很好解释了:最后一步直接抛出 Exception 了 (步骤 6)。
// 示例 1
// 1. 调用ToNumber(arg), 参数是一个Object类型
// 2. 调用ToPrimitive(arg, number), 虽然定义了Symbol.toPrimitive,但其值是undefined
// 3. 调用OrdinaryToPrimitive(arg, number),先调用valueOf(),结果是对象本身,忽略;
// 再调用toString(),结果是"[object Object]",返回该字符串。
// 4. 调用ToNumber("[object Object]")
Number({
// 或者这个属性不存在
[Symbol.toPrimitive]: undefined,
}); // NaN
Number({
[Symbol.toPrimitive]: null,
}); // 结果同上,原因参考上面
// 示例 2
Number({
[Symbol.toPrimitive]: () => {
return 123;
},
}); // 123
// 示例 3
Number({
[Symbol.toPrimitive]: 123, // Symbol.toPrimitive属性不是一个函数导致TypeError
}); // TypeError: xxx is not a function
// 示例 4
Number({
[Symbol.toPrimitive]: (hint) => {
if (hint === "string") {
return "abc";
} else {
// hint is "number" or "default"
return 123;
}
},
}); // 123. 如果是String(...), 结果就是abc
// 示例 5
Number({
[Symbol.toPrimitive]: () => {
return {}; // 返回值不是原始类型导致TypeError
},
}); // TypeError: Cannot convert object to primitive value
// 示例 6
// 123. 原因:OrdinaryToPrimitive先调用valueOf,结果是一个数组,它是Object类型,忽略;
// 再调用toString,结果是" 123 ",返回该字符串,再调用ToNumber,最终结果是123
Number({
// 6.a
valueOf() {
return [];
},
// 6.b
toString() {
return " 123 ";
},
}); // 123
// 示例 7
// TypeError. 原因:valueOf和toString都不是函数,会到最后一步
Number({
valueOf: 123,
toString: "abc",
}); // TypeError: Cannot convert object to primitive value
// 示例 8
// TypeError. 原因:这个对象没有valueOf和toString属性,会到最后一步
Number(Object.create(null)); // TypeError: Cannot convert object to primitive value
以String(value)
方式被调用时 (前面没有new
):
注意与
`${value}`
,value + ""
之间的比较,三者异同点请看后面如果参数不存在,结果是
""
undefined
,结果是"Symbol()"
,否则是"Symbol(" + value.[[Description]] + ")"
)ToString(argument) (抽象运算,运算结果是 String 类型值或者 Exception)
Type of argument | Result | Note |
---|---|---|
Undefined | Return "undefined" . |
|
Null | Return "null" . |
|
Boolean | If argument is true , return "true" . If argument is false , return "false" . |
|
Number | Return NumberToString(argument). (结果是十进制数字字符串,数字非常大时会是科学记数法的字符串) | Oops! -0 ->"0" |
String | Return argument. | |
Symbol | Throw a TypeError exception. | |
Object | Apply the following steps: 1. Let primValue be ? ToPrimitive(argument, string). (指定了 preferredType) 2. Return ? ToString(primValue). |
// String(value) vs. value + "" vs. `${value}`?
// 相同:三者都用到ToString这个抽象运算
// String(value)单独处理了Symbol类型的值,不会抛异常,后两者都会抛TypeError
String(Symbol()); // "Symbol()"
`${Symbol()}`; // TypeError
Symbol() + ""; // TypeError
const foo = {
toString() {
return "abc";
},
valueOf() {
return 123;
},
};
// 调用了ToString
String(foo); // "abc"
// 调用了ToString
`${foo}`; // "abc"
// 逻辑:先对左右运算子分别调用ToPrimitive,
// 未指定preferredType (其被视为number),valueOf被调用,得到值123;
// 然后应用+运算符,由于右运算子是个字符串,所以会进行string-concatenation
// 首先对左运算子应用ToString,得到"123","123"+""最终结果是"123"
// 具体请参考下面的二元运算符
foo + ""; // "123"
注意:和其他语言大不相同,因为结果不一定是 Boolean 类型的值
&&
(logical and, 逻辑与), ||
(logical or, 逻辑或)
leftOperand && rightOperand
或leftOperand || rightOperand
如果是
||
,上面的false 改为 true.
直接返回左运算子的值,这是 short circuit.
!
(logical not, 逻辑非)
!operand
? :
ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
spec 中并没有 arithmetic operators 这个术语,个人猜测原因是:
+
还能串接字符串
+
(addition), -
(subtraction), *
(multiplication), /
(division), %
(remainder)
会调用:ApplyStringOrNumericBinaryOperator(lval, opText, rval) (抽象运算,简化)
++
(increment operator, 分为 postfix increment operator 和 prefix increment operator), --
(decrement operator,分为 postfix 和 prefix)
以 postfix increment operator 为例:
LeftHandSideExpression++
let i = "abc";
// 不要简单的将i++看成是i = i + 1
i++; // i is NaN
<
(less than), <=
(less than or equals), >
(greater than), >=
(greater than or equals)
会调用:Abstract Relational Comparison (抽象运算,简化)
undefined
视为false
)==
(double equals), !=
, ===
(triple equals), !==
==
和===
如何选择?
==
可以考虑用在:typeof == "string"
(typeof
运算结果一定是 String 类型),foo == null
(用来检查 foo 是null
或者undefined
)。其他情况推荐使用===
==
和!=
会用到 Abstract Equality Comparison (抽象运算,简化)
===
和!==
会用到 Strict Equality Comparison (抽象运算,简化)
0xa
和10
是一样的)SameValueNonNumber(x, y) (抽象运算)
const foo = null;
const bar = 0;
foo >= bar; // true
foo > bar; // false
foo == bar; // false
// 数学常识:如果左运算子大于或者等于右运算子,那么前者要么大于后者,要么等于后者。
// 注意:左右运算子都是数字。
// 粗看上述代码,会觉得它们违背了上述数学常识,但只要熟悉 >= > == 三个运算符的算法,
// 就不会对上述结果感到惊讶。
// 如果一开始将foo和bar都赋值为数字,就和上述数学常识匹配了。
"" == 0; // true,对''进行了ToNumber转换,变成0
0 == " "; // true,对' '进行了ToNumber转换,变成0
"" == " "; // false,都是String类型,会进行 ===
const a = new String("foo");
const b = "foo";
const c = new String("foo");
// true,对a进行了ToPrimitive转换(注意:valueOf不是对象本身,而是"foo",参考`String.prototype.valueOf`),
// 变成"foo", 然后进行 ===
a == b;
b == c; // true,对c进行了ToPrimitive转换,变成"foo", 然后进行 ===
a == c; // false,进行 ===
// 数学常识:如果a=b, b=c,那么a=c,这称为等式的传递性。注意:左右运算子都是数字。
// 粗看上述2段代码,会觉得它们违背了上述数学常识,但只要熟悉 == 运算符的算法,
// 就不会对上述结果感到惊讶。
SameValue(x, y) (抽象运算,Object.is(x, y)
会用到它)
SameValueZero(x, y) (抽象运算,String.prototype.includes
, Array.prototype.includes
, Map
, Set
会用到它)
ISO 8601 是表达日期和时间的一种国际标准,完整体格式: YYYY-MM-DDTHH:mm:ss.SSS[UTC offset]
- YYYY: Year, 4 位
- MM: Month, 2 位, 01 - 12
- DD: Day, 2 位, 01 - 31
- T 或者 空格: date 和 time 的分隔符, T 意味着 Time 开始,可以用空格替代 T,但建议用 T
- HH: Hour, 2 位, 00 - 23. 注意: ISO 8601 用的是小写 hh, 因为它是 24 小时制 (dayjs 中 大写 HH 表示 24 小时制, 小写 hh 表示 12 小时制,我采用 HH)
- mm: Minute, 2 位, 00 - 59
- ss: Second, 2 位, 00 - 59
- SSS: milliSecond, 3 位, 000 - 999. 注意: ISO 8601 没有规定必须用 S (我采用 dayjs 的大写 S, 与小写 s 进行区分), 也没有规定必须 3 位 (我采用 EcmaScript 的 3 位)
- UTC offset: 准确来说是 Time Zone Designator (TDZ) 时区标识, 可以不存在, 可以是 Z, 可以是 +hh:mm 或者 -hh:mm, 一共有 24 个时区: -11 ... -1 Z +1 ... +12
注意:以下几个 time 都是 string
值
2023-07-01T02:03:04.567
0
, 用 Z
表示
2023-07-01T02:03:04.567Z
2023-07-01T02:03:04.567+08:00
Date
object原生 Date
object 内部存储的永远是 UTC
整数值 (精确到 3 位毫秒), 起始点是 1970-01-01T00:00:00.000Z
. e.g. new Date('1970-01-01T08:00:00.123+08:00')
这个对象的 valueOf()
结果是 123
非常重要:虽然内部存储的是 UTC 日期时间,但我们使用的是转换后的 local 日期时间
string
值 -> Date
对象
new Date(stringArg)
, 记得让 stringArg 符合 ISO 8601 format, 请勿使用 date-only forms (比如 "2023-07-01"), 会被看成是 UTC time, 而不是 local time, 而且不同 EcmaScript 实现还不相同Date
对象 -> string
值
toISOString()
, 结果是 UTC timeDayjs
对象是原生 Date
对象的 wrapper.
非常重要:虽然内部存储的是 UTC 日期时间,但我们使用的是转换后的 local 日期时间
string
值 -> Dayjs
对象
dayjs(stringArg)
, stringArg 符合 ISO 8601 format 时用dayjs(stringArg, <customParseFormat>)
, stringArg 不符合 ISO 8601 format 时用,使用前需要引入 customParseFormat
, 请看《实战 - 问题 1 和 2》Dayjs
对象 -> string
值
toISOString()
, 结果是 UTC timeformat([template])
, 结果是 UTC offset time 或 UTC time, 可以不输入 template, 默认 template 是 YYYY-MM-DDTHH:mm:ssZ
, 特别注意:这里的Z
并不是去获取 UTC time,它说的是要保留时区信息,具体看《实战 - 问题 3》问题 1: 后端返回一个 UTC time 或 UTC offset time, 要求前端把它看成是 local time,如何处理?e.g. 后端返回的是 2023-07-01T02:03:04Z
,前端应该将看成是 2023-07-01T02:03:04
思路 1: 要点是如何丢掉时区?因为后端返回的字符串可能是Z
结尾,也可能是+02:00
之类的结尾
答案 1: dayjs(backendString, 'YYYY-MM-DDTHH:mm:ss')
, 需要特别注意的是:需要引入 customParseFormat
, 不然 dayjs 会进行时区换算, 那样的话结果就不对了
问题 2: 后端返回08:00
, 前端需要在 antd 时间组件中显示出来,如何做?
答案 2: dayjs('08:00', 'HH:mm')
, 需要特别注意的是:需要引入 customParseFormat
, 不然无法得到一个有效的 Dayjs 对象
问题 3: dayjs('2023-07-01T02:03:04+01:00').format('YYYY-MM-DD HH:mm')
和 dayjs('2023-07-01T02:03:04+01:00').format('YYYY-MM-DD HH:mmZ')
有什么区别?
思路 3: 要点是dayjs('2023-07-01T02:03:04+01:00')
到底是什么?format 中的结尾处一个没有 Z,一个有 Z,有什么区别?
答案 3: 具体过程如下
dayjs('2023-07-01T02:03:04+01:00')
得到一个 Dayjs 对象2023-07-01T01:03:04Z
这个 UTC time 的整数值2023-07-01T09:03:04+08:00
YYYY-MM-DD HH:mm
,从左到右解析分别得到 2023
-
07
-
01
(空格) 09
:
03
(没有ss
丢掉秒) (没有SSS
丢掉毫秒) 没有Z
丢掉时区,结果就是 2023-07-01 09:03
YYYY-MM-DD HH:mmZ
,从左到右解析分别得到 2023
-
07
-
01
(空格) 09
:
03
(没有ss
丢掉秒) (没有SSS
丢掉毫秒) +08:00
,结果就是 2023-07-01 09:03+08:00
Environment record 对应的是 scope
Environment record is used to explain the behavior of name resolution in nested functions and blocks.
Environment record 是一个抽象概念,可以将其理解为一个 plain object.
name resolution 也就是 identifier resolution. name 包括 variable name, function name, parameter name, class name 等。
Spec 经常用到 binding 这个词,a binding is the association between an identifier and a value.
根据代码类型 (比如 global code, module code, function code 等)将 environment record 粗略分为:
Global environment record (由 object environment record + declarative environment record 组成)
[[OuterEnv]] is null
.
bindings for the top-level declarations. (全局中最上层声明的所有绑定)
object environment record 关联一个 binding object,即 the global object, 它包括:global built-in bindings (比如
console
)和 VariableDeclaration (指的是用var
声明), FunctionDeclaration (函数声明), AsyncFunctionDeclaration (异步函数声明)等。declarative environment record 包括: LexicalDeclaration (指的是用
let
或const
声明), ClassDeclaration (类声明).
// Legacy variable using `var`, added to object environment record.
var x = 10;
// Modern variable using `const`, added to declarative environment record.
const y = 20;
console.log(x, y); // 10 20
// in global environment, `this` is the global object
// which is the binding object of object environment record.
console.log(this.x, this.y); // 10 undefined
this.x = 1;
this.y = 2;
console.log(x, y); // 1 20
this
is the global object.
window
for browsers, self
for web workersglobal
for node.jsglobalThis
for allGlobal environment record is used to represent the outer most scope that is shared by all of the ECMAScript script elements.
所有 script 元素共享 global environment record。
下面这段代码说明:一个 script 中出现 exception 只会阻断该 script 中后续代码的执行,不会阻断其他 script 代码的执行。所有 script 共享 global environment record,随着 script 的执行,global environment record 在变化。
<script>
console.log(x); // ReferenceError: x is not defined
console.log(123); // 不会打印123
</script>
<script>
let x = 1;
const y = 10;
</script>
<script>
console.log(x); // 1
x = 2;
console.log(x); // 2
console.log(y); // 10
y = 20; // TypeError: Assignment to constant variable.
</script>
Module environment record (一种 declarative environment record)
[[OuterEnv]] is global environment record.
bindings for the top-level declarations. (模块中最上层声明的所有绑定)
包括 VariableDeclaration (指的是用
var
声明), FunctionDeclaration, AsyncFunctionDeclaration, LexicalDeclaration (指的是用let
或const
声明), ClassDeclaration 等.
this
is undefined
.
Function environment record (一种 declarative environment record)
每次执行函数会生成新的environment record.
[[OuterEnv]]的情况:
创建函数时会确定它的[[Environment]](即:将函数声明所在的 environment record 存入它的[[Environment]],请参考 spec 中的 OrdinaryFunctionCreate。[[Environment]]是函数对象的一个 internal slot)。执行函数时,新生成的 environment record 的[[OutEnv]]被设置为[[Environment]]。
bindings for the top-level declarations. (函数中最上层声明的所有绑定)
包括 VariableDeclaration (指的是用
var
声明), FunctionDeclaration, AsyncFunctionDeclaration, LexicalDeclaration (指的是用let
或const
声明), ClassDeclaration 等.
this
的情况:
创建函数时会确定它的[[ThisMode]](请参考 spec 中的 OrdinaryFunctionCreate,[[ThisMode]]是函数对象的一个 internal slot),其值是 lexical, strict, or gloabl.
- lexical means that this refers to the this value of a lexically enclosing function. (这里用 environment 更恰当,比如 module 中的 arrow function)
arrow function 的[[ThisMode]]是 lexical.
- strict means that this value is used exactly as provided by an invocation of the function.
strict mode 下的 function declaration 和 function expression 的[[ThisMode]]是 strict.
- global means that a this value of undefined or null is interpreted as a reference to the global object, and any other this value is first passed to ToObject.
non-strict mode 下的 function declaration 和 function expression 的[[ThisMode]]是 global.
确定this
的具体算法会用到 EvaluateCall(func, ref, arguments, tailPosition) (抽象运算):
The abstract operation EvaluateCall takes arguments func (an ECMAScript language value), ref (an ECMAScript language value or a Reference Record), arguments (a Parse Node), and tailPosition (a Boolean).
以 property accessor 形式调用函数,thisValue 是 ref.[[Base]],见示例 1 和 2
以 identifier 形式调用函数,thisValue 是
undefined
(注意这是排除了 with statement 的情况,排除原因是其不被推荐使用),见示例 3
undefined
)以其他形式调用函数,比如:IIFE,thisValue 是
undefined
,见示例 4 和 5
这里会调用 Function Object 的[[Call]],它又会调用 OrdinaryCallBindThis(F, calleeContext, thisArgument),它会根据 Function Object 的[[ThisMode]]来绑定 this value
[[ThisMode]]的值:
- 如果是 lexical,直接返回 (意思是不进行最后一步的绑定操作,所以说 arrow function environment record 不绑定 this value)
- 如果是 strict,thisValue 就是 thisArgument
- 其他,即:global,如果 thisArgument 是
undefined
或null
,thisValue 就是 the global object, 否则 thisValue 就是 ToObject(thisArgument)- ... (最后一步操作是为 environment record 绑定 this value)
const foo = {
bar: function () {
console.log(this);
},
};
// 示例 1
// Reference record:
// {[[Base]]: foo指向的对象, [[ReferencedName]]: 'bar', [[Strict]]: false}
foo.bar(); // foo, the dot notation
foo['bar'](); // foo, the bracket notation
// 示例 2
// Reference record:
// {[[Base]]: 临时对象, [[ReferencedName]]: 'bar', [[Strict]]: false}
({
x: 1,
bar: function () {
console.log(this.x); // 1
},
}).bar();
const bar = foo.bar;
// 示例 3
// Reference record:
// {[[Base]]: globalEnvRec, [[ReferencedName]]: 'bar', [[Strict]]: false}
bar(); // the global object (如果是在strict mode下,结果是undefined)
// 示例 4
// 没有Reference record
// the global object (如果是在strict mode下,结果是undefined)
(false || foo.bar)();
// 示例 5
// 没有Reference record
// the global object (如果是在strict mode下,结果是undefined)
(function () {
console.log(this);
})();
// 示例 6
this.x = 10;
const qux = {
x: 20,
bar: () => console.log(this.x),
baz() {
let arrow = () => console.log(this.x);
return arrow();
},
};
// qux.bar指向的函数的[[ThisMode]]是lexical, 它的this会refer to外部环境的this,
// 外部环境是global environment record,它的this是the global object
qux.bar(); // 10
// arrow指向的函数的[[ThisMode]]是lexical,它的this会refer to外部环境的this,
// 也就是baz环境的this,接着看baz环境怎么生成的?Reference record:
// {[[Base]]: qux指向的对象, [[ReferencedName]]: 'baz', [[Strict]]: false}
qux.baz(); // 20
浏览器的setTimeout
, setInterval
调用 handler 的方式是handler.call(window)
,而不是handler()
. (参考:https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers).
'use strict';
const user = {
firstName: 'John',
sayHi() {
console.log(`Hi, I am ${this.firstName}.`);
},
};
// 如果setTimeout不做特殊处理,这里会报TypeError: cannot read properties of undefined.
setTimeout(user.sayHi, 1000); // Hello, I am undefined.
// 如何让`this`是user指向的对象呢?
const boundSayHi = user.sayHi.bind(user);
setTimeout(boundSayHi, 1000); // Hello, I am John.
生成 environment record 实际上是在 instantiation (实例化),这个过程是在代码执行前进行的。步骤如下:
基于 spec 的 GlobalDeclarationInstantiation 和 FunctionDeclarationInstantiation 的修改
parse 阶段 (参考《Misc》)会收集所有 FunctionDeclaration, ParameterName, VariableDeclaration, LexicalDeclaration 等集合
- LexicalDeclaration 集合自身内有重名,直接报 SyntaxError
- LexicalDeclaration 集合与其他集合有重名,直接报 SyntaxError
弄清楚以下几个步骤,hoisting 就很容易理解,spec 中并不存在 hoisting 这个术语。
FunctionDeclaration list (AsyncFunctionDeclaration 等属于这一类),针对每个元素:
FormalParameterName list,针对每个元素:
function environment record 才会进行这个步骤
undefined
,否则初始化为实参VariableDeclaration list (指的是用var
声明),针对每个元素:
undefined
LexicalDeclaration list (指的是用let
或const
声明,ClassDeclaration 属于这一类),针对每个声明:
function test(a, b) {
var x = 10;
let y;
const z = 20;
function foo() {}
var bar = function _bar() {};
(function baz() {});
}
test(1);
图片显示不出来,请看原文最下方:https://github.com/ShawLocke/ES-blog/blob/main/posts/Chapter%203.%20Environment%20record%20(scope).md
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.