let a = 0;
function addone() { let a = 10; addtwo(); }
function addtwo() { let a = 20; addthree(); }
function addthree() { console.log(a); }
addone();
结果为 0,为什么不是 20 呢?不是说一层一层从调用上下文查找直到全局上下文吗?
1
YadongZhang 2020-05-04 11:48:55 +08:00 via Android
log 的是全局的 a,let 和 var 是不是有区别
|
2
Aloehuang OP |
3
YadongZhang 2020-05-04 11:51:22 +08:00 via Android
@Aloehuang #2 javascript.info 了解一下,有图解
|
4
Aloehuang OP @YadongZhang var 和 let 在这里没有区别,结果都是 0.
你给的网站内容太多了,找不到具体要查找的问题 |
5
rabbbit 2020-05-04 11:54:07 +08:00
js 的作用域是词法作用域,不是动态作用域.
|
6
secondwtq 2020-05-04 11:54:25 +08:00 via iPhone 1
楼主所期望的行为恰恰是 Lexical Scoping 的反面,即 Dynamic Scoping
把这两个的定义都找来,对比一下就能明白了 |
7
YadongZhang 2020-05-04 11:56:39 +08:00 via Android
@Aloehuang #4 有搜索框,关键词 Closure,addThree() 调用的是全局 a,函数里的变量只能在函数块里使用,log 找不到的
|
8
ljpCN 2020-05-04 11:57:05 +08:00 via Android
这个例子应该没有涉及闭包吧,闭包主要是把一些局部作用域的变量保持着不被回收掉。
然后,这个你只看词法作用域就行了,只有 this 那种东西才要看运行时的调用吧。 |
10
rabbbit 2020-05-04 11:57:23 +08:00
词法作用域: 在哪定义,在哪往上找变量
静态作用域: 在哪调用,在哪往上找变量 |
11
rabbbit 2020-05-04 11:58:21 +08:00
静态作用域 -> 动态作用域
|
12
rabbbit 2020-05-04 12:00:11 +08:00 2
上边那个"静态作用域"写错了
词法(静态)作用域: 在哪定义,在哪往上找变量 动态作用域: 在哪调用,在哪往上找变量 |
13
Dyon 2020-05-04 12:13:53 +08:00 via Android 1
全局有一个 a,前两个函数又在自己的作用于中定义了一个新的 a,当他们调用名为 a 的变量的时候会现在自己的作用域中找,找到了就调用它,这时候全局的那个 a 就被忽略了。第三个函数的作用域中没有名为 a 的变量,于是它调用了全局的 a 。这里用 let 还是 var 结果是一样的因为这里都是函数,如果是在 for 等块级语法中,let 和 var 才会不一样。
|
16
mrcode 2020-05-04 12:18:52 +08:00
JavaScript 是词法作用域,不是动态作用域
|
17
YadongZhang 2020-05-04 12:21:48 +08:00 via Android
@Aloehuang #15 全局 a 注释掉,其他两个函数体都加一个 console.log(a) 就有答案了
|
18
Dyon 2020-05-04 12:36:31 +08:00 via Android
@Aloehuang 哦刚刚误解楼主的问题了,楼主应该是想问动态作用域与静态作用域,不过即使不了解这些,根据运行结果也能推断出来 js 的作用域链是根据定义的位置确定而非调用的位置,是静态的
|
19
huangbangsheng 2020-05-04 13:47:21 +08:00 via iPhone
高性能 JavaScript 开篇解释了你的问题
|
20
zhengjian 2020-05-04 13:58:30 +08:00
@YadongZhang 全局 a 注释掉就报错了
|
21
YadongZhang 2020-05-04 14:00:40 +08:00 via Android
@zhengjian #20 所以说 addThree() 函数里的 a 和其他两个函数体的 a 没有关系
|
23
whoami9894 2020-05-04 15:48:48 +08:00
你对比一下
let a = 0; let addone = () => { let a = 10; addtwo(); } let addtwo = () => console.log(a); addone() /*===*/ let a = 0; let addone = () => { let a = 10; let addtwo = () => console.log(a); addtwo(); } addone() |
24
xiaoming1992 2020-05-04 16:13:24 +08:00 via Android
为什么要研究这样的东西?明明一个 use strict 就能解决的问题
|
25
ZehaiZhang 2020-05-04 17:08:05 +08:00
面试题吧,考察对 js 作用域的理解=
|
26
leihongtao1230 2020-05-04 17:31:20 +08:00 via Android
作用域和执行栈不是一个概念啊
|
27
iMusic 2020-05-04 18:15:17 +08:00
词法作用域是在写代码时,函数的位置确定的
|
30
zackwan95 2020-05-04 20:20:06 +08:00 via iPhone 4
你把代码写成这样在我们组 code review 都过不去,我最不明白国内的一点就是拿这些要么是错误要么是极差的代码当作考题。唯一的答案不应该是永远别写成这样么
|
31
xieranmaya 2020-05-05 09:51:02 +08:00
函数里对变量的访问仅仅取决于函数的定义位置,不取决于函数的调用位置(这才是词法作用域的关键)。
具体来说,你的 addthree 函数不管在哪调用(就算是被引擎调用),他都之访问到外面这个 a 。 |
32
Aloehuang OP @whoami9894 谢谢,上午抽空继续学习了下,理解了。我其实是把变量查找的原理(词法作用域)和执行栈、执行上下文混在一起了。
你上面那段代码在 addtwo 中没有找到 a,于是从父级执行上下文寻找,但这个父级执行上下文有两种理解方式:第一种,执行上下文栈层面上的父级,也就是从哪个函数调用那么该函数就是父级执行上下文;第二种,作用域链层面上的父级(或者词法层面上的父级)。虽然有执行上下文栈,每调用一个函数就生成新的执行上下文并压入栈中,但是作用域链并不是和执行上下文栈一一对应的,作用域链由词法作用域导出,通常作用域链长度小于等于执行上下文栈的长度。 上面那段代码在 addtwo 中没有找到 a,就从父级执行上下文找,但 addone 和 addtwo 实际上在词法层面上是同级的,所以这个父级执行上下文就是全局执行上下文。自然输出 0 。 下面的父级执行上下文是 addone 函数执行时创建的执行上下文,所以输出 10 。 |
33
Aloehuang OP @xiaoming1992 不是 use strict 的问题
|
34
Aloehuang OP @leihongtao1230 对啊,我搞混了,现在清晰了很多。大佬一针见血说出了我迷惑的地方。
|
35
Aloehuang OP @zackwan95 真写代码肯定不会这样写啊,只是面对和自己预期不相符的结果时,是不是应该探究一下为什么和预期不符呢。要是真写代码这样写自己都会疯掉,无时无刻不要注意 js 语言的规则。
|
37
lbw 2020-05-05 14:43:52 +08:00
词法作用域是 compiling/parsing 期间确定,而不是运行时
|
38
xiaoming1992 2020-05-06 01:25:05 +08:00 via Android
|