Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

重学js —— 函数块级作用域坑点(一道题目引发的思考) #98

Open
lizhongzhen11 opened this issue Apr 15, 2020 · 0 comments
Labels
js基础 Good for newcomers 重学js 重学js系列 规范+MDN

Comments

@lizhongzhen11
Copy link
Owner

lizhongzhen11 commented Apr 15, 2020

关于函数块级作用域基础的一道题

今天看到高级前端进阶推送的一篇文章,关于js基础的,我答错了,但是看了他的讲解,并没有让我理解这个问题,所以我自己记录并理解下。

// 原题
var a = 0;
if (true) {
  a = 1;
  function a(){};
  a = 21;
  console.log('inside', a); // 21
}
console.log('outside', a); // 1

看到这题,我下意识地认为全都输出 21,但错了,错在 全局环境下的打印输出!

// 1.看看没有 if 的情况
var a = 0;
a = 1;
function a(){};
a = 21;
console.log('inside', a); // 21
console.log('outside', a); // 21

// 2.改变下打印输出顺序
var a = 0;
a = 1;
function a(){};
console.log('inside', a); // 1
a = 21;
console.log('outside', a); // 21

// 3.再变下
var a = 0;
console.log('inside', a); // 0
a = 1;
function a(){};
a = 21;
console.log('outside', a); // 21

// 4.再变下
var a;
console.log('inside', a); // func
a = 1;
function a(){};
a = 21;
console.log('outside', a); // 21

有点经验的前端应该都知道,函数声明存在变量提升。所以上述代码第4种变型就很好理解,虽然函数声明在第四行才写出来,但是js引擎执行时会将其声明提升并覆盖第一行的 var a。而第3种变型区别在于赋值,所以js引擎声明变量并赋值本质顺序是下面这样的:

var a;
function a(){};
a = 0;

var a = 0 被拆开成先声明 a ,等变量提升声明完成后才会进行赋值,到这一步应该很好理解。再看第1种和第2种变型,其实就是对全局变量 a 的赋值与打印罢了。由于这里是全局作用域下的代码,结合 GlobalDeclarationInstantiation ( script, env ) 可知,varfunction 声明的变量会成为全局对象的属性,而赋值运算看 重学js —— 赋值运算符 就能理解。

接下来继续看变型代码:

var a = 0;
if (true) {
  function a() {};
  console.log('inside', a, window.a); // func 和 func
}
console.log('outside', a, window.a); // func 和 func

为什么?这里看MDN的解释:非严格模式下的块级函数,MDN直接坦言:不要用! 当然,在日常工作中我几乎没这么用过,但是不用不代表不需要懂。也见:有条件的创建函数

var a = 0;
if (true) {
  console.log('...', a, window.a) // func 和 0
  function a() {};
  console.log('inside', a, window.a); // func 和 func
}
console.log('outside', a, window.a); // func 和 func

这又是什么情况呢?不是说好的函数声明变量提升吗?那第一个输出的 window.a 为何是 0 而不是 function a(){} 呢???其实这个继续看 有条件的创建函数 就能理解了,把MDN的示例稍微改下:

var hoisted = "foo" in this;
console.log(`'foo' name ${hoisted ? "is" : "is not"} hoisted. typeof foo is ${typeof foo}`); // undefined
if (true) {
  function foo(){ return 1; }
}
console.log(typeof foo) // 'function'

回顾上述代码,大致可以看出,虽然 if 判断内函数声明提升了,但是只有在函数代码后的打印输出才会影响到全局对象,这里跟全局环境下不同。继续看代码变型:

var a = 0;
if (true) {
  console.log('...', a, window.a) // func 和 0
  a = 1;
  console.log('+++', a, window.a) // 1 和 0
  function a() {};
  console.log('inside', a, window.a); // 1 和 1
}
console.log('outside', a, window.a); // 1 和 1

这里就很奇怪了!疑惑点在于 a = 1 后立即打印 window.a 没变,而函数后的打印却改变了!!!

难道这里 a = 1a,指的是 function a() {} 这个函数名引用,而不是全局环境 var a 的引用???

是不是意味着这里先有个保存函数的作用域,此时函数声明提升,函数名 a 保存在该作用域内,而函数之前的代码 a = 1 也在该作用域内,只是对保存函数引用的变量 a 进行赋值更改?然后当真正执行到函数代码后,再把该作用域内的 a 变量映射到全局环境,覆盖原先的值?

那原题在函数后面的 a = 21 如何解释呢?

var a = 0;
if (true) {
  console.log('...', a, window.a) // func 和 0
  a = 1;
  console.log('+++', a, window.a) // 1 和 0
  function a() {};
  a = 21
  console.log('inside', a, window.a); // 21 和 1
}
console.log('outside', a, window.a); // 1 和 1

个人猜测,可能 a = 21 这里的 a 依然是那个特殊作用域内的 a 变量(即原先指向函数的),此时赋值改变没有问题,但是其重新赋值不会重新映射到全局环境了,这里的 a 与全局环境的 a 是两个不同环境的变量,只不过中间有个 function a() {} 曾经将它们两的值映射为同一个,可能通过下面代码说明更好理解:

var a1 = 'a1'
var a2 = a1
a2 = 'aa'
a1 // a1

即上述代码,其实全局环境的 a 与含有 function a() {}if 条件内的 a 应该算是两个变量,但是引擎会把 function a() {} 之前的关于 a 变量的代码映射到全局环境上,但是之后的不会。可以看下面代码:

if (true) {
  function b(){};
  b = 1;
  console.log('inside', b); // 1
}
console.log('outside', b); // function

这种代码主要了解即可,工作中不要用!

@lizhongzhen11 lizhongzhen11 added js基础 Good for newcomers 重学js 重学js系列 规范+MDN labels Apr 15, 2020
@lizhongzhen11 lizhongzhen11 changed the title 重学js —— 函数块级作用域坑点 重学js —— 函数块级作用域坑点(一道题目引发的思考) Apr 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
js基础 Good for newcomers 重学js 重学js系列 规范+MDN
Projects
None yet
Development

No branches or pull requests

1 participant