先看题目:以下代码输出分别是什么
var a = 10
function fn() {
a()
try {
a()
console.log(a, ' try')
a = 100
} catch (e) {
a = 1
console.log(a, ' catch')
}
console.log(a, ' inner function fn')
function a() {
console.log(a, ' inner function a')
a = 1000
}
}
fn()
console.log(a, 'outer result')
题目简单先试着预想一下执行结果
.
.
.
输出
// [Function] inner function a
// 1 catch
// 1 inner function fn
// 10 outer result
是否和预想的一致呢?
先来了解几个概念:
下面开始分析一下执行结果
首先 fn 会获得变量提升然后声明并且赋值全局变量 var a = 10
执行 fn:
function fn() {
function a() {
console.log(a, ' inner function a')
a = 1000
}
a()
}
var a = function () {}
所以此处 fn 内部的 a 相当于局部变量不在和全局上下文的 a 关联,fn 内部改变不会影响到全局fn 内部输出:
[Function] inner function a
,然后将 fn 内部的 a 重新赋值为 1000a is not a function
被 catch 语句捕获,当前 try 语句终端不在继续向下执行
a = 100
只是简单的赋值操作,没有关键字声明所以没有变量提升a = 1
,输出 a catch
1 inner function fn
fn 执行结束继续执行全局上下文环境中的代码,上面说了 fn 内部的 a 相当于局部变量不在和全局上下文的 a 关联,fn 内部改变不会影响到全局, 所以全局上下文中 a 依旧是 10,输出 10 运行结束
var a = { n: 1 }
var b = a
a.x = a = { n: 2 }
alert(a.x) // --> undefined
alert(b.x) // --> { n: 2 }
理解该问题需要知道以下两点:
如赋值表达式 A = B
GetValue(refB)
得到 valueBenvironment records
(这里理解是 refA 是一个声明的变量或者是对象的某个属性)GetValue(refB)
是通过一系列判断得出 value 值,具体步骤参考GetValue(refB)
具体参考:
所谓结合性,是指表达式中同一个运算符出现多次时,是左边的优先计算还是右边的优先计算。
赋值表达式是右结合的。这意味着:A1 = A2 = A3 = A4
等价于 A1 = (A2 = (A3 = A4))
总的简单 s 来说就是:先从左到右解析各个引用,然后计算最右侧的表达式的值,最后把值从右到左赋给各个引用
a.x = a = { n: 2 }
首先得到 a.x 和 a 的两个引用
得到右表达式是一个对象 { n: 2 }
a.x 和 a 的两个引用并且判断属于 environment records
(a.x 属于对象的某个属性,a 属于当前上下文的变量 a)
将 { n: 2 }
赋值给 当前上下文变量 a 的 refA,a 已被重新赋值为 { n: 2 }
将 { n: 2 }
赋值给 当前上下文变量 a.x 的 ref(a.x)ref(a.x)与 ref(b.x)是同一个, refA 已经与 { n: 2 }
重新绑定,所以此时 b 为 { n: 1, x: { n: 2 } }
所以最终结果:a 为 { n:2 }
,b 为 { n: 1, x: { n: 2 } }
, 并且由于是同一个对象 { n: 2 }
赋值给 b.x 以及 a 获得的引用相同,所以 b.x === a
所以 a.x
为 undefined
, b.x
为 { n: 2 }
Link:
;(function A() {
console.log(A) // [Function A]
A = 1
console.log(window.A) // undefined
console.log(A) // [Function A]
})()
上面立即执行函数中直接将 1 赋值给一个未声明的变量,正常逻辑下我们知道会将他绑定的全局作为全局变量,但是上面的输出显然不是如此,原因在于匿名执行函数有了名字且和赋值的变量 A 同名
有了名字的函数(NFE)有两个特性:
创建 NFE 的机制:
The production FunctionExpression : function Identifier ( FormalParameterListopt ) { FunctionBody } is evaluated as follows:
注意步骤 3 和 5,分别调用了 createImmutableBinding 和 InitializeImmutableBinding 内部方法,创建的是不可更改的绑定
要理解这两个特性,最重要的是搞清楚标识符 A 的绑定记录保存在哪里。让我们问自己几个问题:
标识符 A 与 该 NFE 是什么关系? 两层关系:首先,该 NFE 的 name 属性是 字符串 ‘A’;更重要的是,A 是该 NFE 的一个自由变量。在函数体内部,我们引用了 A,但 A 既不是该 NFE 的形参,又不是它的局部变量,那它不是自由变量是什么!解析自由变量,要从函数的 [[scope]] 内部属性所保存的词法环境 (Lexical Environment) 中查找变量的绑定记录。
标识符 A 保存在全局执行环境(Global Execution Context)的词法环境(Lexical Environment)中吗? 答案是否。如果你仔细看过 ES5 Section 13 这一节,会发现创建 NFE 比创建 匿名函数表达式 (Anonymous Function Expression, AFE) 和 函数声明 (Function Declaration) 的过程要复杂得多
那么为何创建 NFE 要搞得那么复杂呢?就是为了实现 NFE 的只能从函数内部访问 A,而不能从外部访问这一特性!咋实现的? 创建 NFE 时,创建了一个专门的词法环境用于保存 A 的绑定记录(见上面步骤 1~3)!对于 NFE, 有如下关系:
A.[[scope]]
---> Lexical Environment {'environment record': {A: function ...}, outer: --}
---> Lexical Environment of Global Context {'environment record': {...}, outer --}
---> null
可见,A 的绑定记录不在全局执行上下文的词法环境中,故不能从外部访问
但是有个疑问:如果内部输出的时候进行赋值呢?
;(function A() {
console.log((A = 100)) // 100
})()
却可以打印出 100,JS 引擎对赋值表达式的处理过程中我们知道赋值表达式最终结果是返回这个值,如果 NFE 内部没有成功赋值为何可以打印出 100 呢?
Link: