关于变量提升,有很多好玩的特性,让我们来了解一下
基础补充
console.log(foo); // undefined
{
function foo() {}
}
旧版浏览器中没有块级上下文,所以 foo 能提升到全局上下文中,输出函数体。
新版浏览器有块级上下文,块级上下文中声明的函数,会提升自全局上下文,但只声明不赋值,输出 undefined。
console.log(foo); // undefined
{
foo = 1;
}
console.log(foo); // 1
没有关键字 var,也不是函数声明,所以不会进行变量提升,执行到赋值语句时才把 window.foo = 1
图解1 - 函数渣男论
console.log(foo); // undefined
{
console.log(foo); // function foo {...}
foo = 1;
function foo() {}
foo = 2;
console.log('里面', foo); // 2
}
console.log(foo); // 1
首先,ECG入栈,进行变量提升(为了方便,我们这边把 VOG 和 GO 放在了一起),块作用域中的 foo 函数名提升至全局,此时打印 foo 输出 undefined。
然后 EC(BLOCK) 块级上下文入栈,此时就好玩了,首先函数被提升到作用域顶部,并赋值为函数体(0X001),此时打印 foo 为函数体,不过,块作用域中函数提升,会有这样的特性,提升完会在原地留下一行 window.foo = foo 给全局做一次赋值。,然后 foo 赋值 为 1 并给到全局,又把 VO(BLOCK) 中私有的 foo 修改为 2,所以得到代码中的输出结果。

函数渣男论:首先 foo 提升到外面,正在跟全局处对象,然后镜头来到了块级上下文,又跟块级上下文谈对象,并给它买了个包(foo = 1),接下来不幸的是,被全局上下文发现了(函数声明这一行),全局上下文哭着闹着自己也要包并分手,于是 foo 给全局上下文也买了个同样的包(window.a = a), 于是跟全局上下文分手了,foo 和 块级上下文过起了甜蜜的小日子。
试着分析一下如下代码
console.log(foo); // undefined
{
console.log(foo); // function() {2}
function foo() {1}
console.log(foo); // function() { 2 }
foo = 1;
console.log(foo); // 1
function foo() {2}
console.log(foo); // 1
}
console.log(foo); // 1
{
function foo() {
}
foo = 1;
function foo() {
}
foo = 2;
}
console.log(foo); // 1
函数形参默认值
var x = 1;
function func(x, y = function() { x = 2 }) {
x = 3;
y();
console.log(x); // 2
}
func(5);
console.log(x); // 1
图解2 - 形参默认值和 var
var x = 1;
function func(x, y = function() { x = 2 }) {
var x = 3;
y();
console.log(x); // 3
}
func(5);
console.log(x); // 1
两个前置条件:
- 如果函数使用了形参默认值「无论赋什么值,无论实参是否传递,也无理论默认值是否生效」
- 在函数体中,基于 let/const/var 声明过变量「不包括 function」,当然 let 和 const 的特性导致他们不能声明已经存在的变量。
如果两个条件都符合,那么函数执行会产生两个私有上下文
- 函数 AO
- BLOCK 并且块级私有上下文的 "上级上下文",就是函数私有上下文。
如果块级内有 var 声明的变量和函数 AO 中存在的同名变量,则会在块中代码执行之前,首先把函数上下文中的这个变量值,同步给块级上下文一份,块级上下文的作用域链是 <EC(B), EC(FUNC)>,所以块级上下文中有 x = 5,注意,整体函数内代码,会在块级上下文中执行,然后 y 默认值的函数的作用域链是 <EC(Y), EC(FUNC)>,改的是 EC(FUNC) 中的 x。
图解:
var x = 1;
function func(x, y = function() { x = 2 }) {
var x = 3;
var y = function() { x = 4 };
y();
console.log(x); // 4
}
func(5);
console.log(x); // 1