虽然现在大家写代码多数用ES6的方式声明变量,如let/const。当然也就不存在变量提升问题了,但是还是把变量提升的机制总结一下。因为工作中,而且还是大多数的工作中,都需要维护之前的代码。当然如果有一天,进步的很快,都换成新的,那么这可能就是一篇无用的文章了。废话不多说,上菜喽!
定义
变量提升: 在 当前上下文 中(全局/私有/块级),JS代码自上而下执行之前,浏览器会提前处理一些事情(可以理解为词法解析的一个环节,词法解析一定发生在代码执行之前),浏览器会把当前上下文中所有带var/function关键字的进行提前的声明或者定义。
var a = 10
// 声明 declare: var a;
// 定义 defined: a = 10;
// 带var的只会提前的声明
// 带function会提前的声明和定义
// 因为变量提升,所以可以在fn代码之前,调用fn
fn()
function fn(){}
// 真实项目建议大家,定义function用 表达式的方式定义。执行在下面在执行。(这样的顺序看起来更舒服)
var func = function(){}
func()
变量提升-块作用域下-新版本浏览器的特殊点
通过两段代码,分析运行机制,说明特殊点。
第一段代码:
{
function foo(){}
foo = 1
}
console.log(foo)
// 以上是一个代码块,在代码块,循环体,判断体中,如果存在let/const/function,则会产生块级私有上下文。
代码执行之前,变量提升:当前上下文中(EC(G)),出现在“非函数和对象”的大括号中的function,只声明不定义。代码执行:如果大括号中出现了function/let/const等关键字,则会形成一个全新的块级私有上下文。
全局上下文EC(G)
-
代码执行之前:
变量提升:(特点是只声明不定义) 声明foo
-
代码执行:
形成块级上下文 EC(Block)
-
代码执行之前:
作用域链:<EC(Block),EC(G)>
没有this和形参赋值以及arguments
变量提升:也是把当前上下文中所有带var/function声明或者定义。以上代码 -> 声明+定义 foo
-
代码执行 function foo(){} (当前上下文中,这一步已经处理过了,所以不再重新执行;但是因为这一步操作和全局上下文也有关系,所以:把当前这一行代码之前对foo的操作,都“映射/同步”给全局一份,但是“之后”对foo的操作都认为是改自己私有的,和全局没有关系了! )那么全局上下文中之前只声明过,这一步赋值完,同步到全局,之后操作与全局没有关系。
-
第二段代码
{
function foo(){}
foo = 1
function foo(){}
}
console.log(foo) // 1
全局上下文EC(G)
-
代码执行之前:
VO(G): foo=> undefined
变量提升:function foo;只声明:因为出现在大括号中了
-
代码执行:
形成块级上下文 EC(Block)
-
代码执行之前:
VO(BLOCK): foo => 0X000-> 0X001
作用域链:<EC(Block),EC(G)>
没有this和形参赋值以及arguments
变量提升:
function foo(){} 函数堆 0X000 [[scope]]:EC(BLOCK) function foo(){} 不再声明,但是会创建 函数堆 0X001 [[scope]]:EC(BLOCK) -
代码执行
function foo(){} 忽略,但是会在此映射到全局上下文 foo = 1 私有的foo改成1 function foo(){} 忽略,但是会把他之前的操作映射到全局上下文中的foo 将全局的改成最新的foo
-
出现这种机制的原因:
-
向后兼容ES3,ES5的语法规范
-
向前兼容ES6的新语法规范(块级上下文)
项目中,千万不要把function这个操作放在除了函数和对象的大括号中。
变量提升---形参加默认值的变态机制
只要符合以下条件:
- 形参赋值有默认值,不管执行不执行,只要有默认值。
- 函数体中声明过变量(let/const/var), function声明的变量得和形参中的一个保持一致,才会生效。即function的名字是 fn 那么形参也得有个fn。 就会触发一个全新的“变态机制”,函数执行不仅形成了一个私有的上下文,而且会把函数体当作一个块级私有上下文,并且块级上下文的作用域链的上级上下文就是上一个私有上下文 如果块级私有上下文中声明的变量,也出现在函数的形参变量中,则默认把私有上下文中的形参变量值,赋值给块级上下文中一份。[ 这个变量不能基于let/const声明, 因为let/const 不能重复定义变量 ]
执行方式: 私有上下文中形参赋值成功后,接下来的操作,都是在块级上下文中处理的,(他是把函数的大括号当作块级上下文) 如果块级上下文中的某个私有变量,和当前私有上下文中的形参变量的名字一样,还会把形参变量的值,默认给块级上下文....
项目中,尽可能少用形参默认值。
注意点
基于“var 或者 function”在 “全局上下文” 中声明的变量(全局变量) 会“映射”到 GO(全局对象window) 上一份,作为他的属性;而且接下来一个修改,另外一个也会跟着修改。
var a =12
console.log(a) // 12 全局变量
console.log(window.a) // 12 映射到GO上的属性 a
eg:
// EC(G):全局上下文的变量提升 变量提升的同时,也会映射。都在代码执行之前。
// 不论条件是否成立,都要进行变量提升(细节点:条件中带function在新版本浏览器中只会提前声明,不会提前赋值了)
// IE10(包含)以下都是老版本 新版本 ie10以上 和 其他的浏览器
if(!("a" in window)){
var a = 1
}
console.log(a) //undefined
复习知识点
-
哪种情况下会产生块级上下文
块级作用域(块级私有上下文): 除了 对象/函数(let obj={} function fn(){})的大括号之外的其他大括号,有例如:判断体,循环体,代码块,如果存在let/const/function,则会产生块级私有上下文。
注: let/const/function会产生块级私有上下文,而var不会。
-
||和&&用法
A||B : A 为真 返回 A的值,B不再判断; A 为假 返回 B的值
A&&B : A 为真 返回 B的值; A 为假 返回 A的值,B不再判断
两者同时出现:&& 优先级高于 ||