变量提升(预解析):
在
当前作用域
中 js代码自上而下执行之前,浏览器首先会把带‘var’或者 ‘function’
关键字的进行提前的‘声明或定义’
。声明(declare):var num;在当前作用域声明
定义(defined):num=12;给声明的变量附一个值
页面加载时只把全局作用域中的变量提升,不管私有作用域里的变量
,当函数执行时才把函数内的私有作用域下的变量提升
带var关键字的只是提前声明一下 并没有赋值;
带function关键字的在变量提升阶段把声明和定义都完成了
js代码执行时遇到创建函数的代码直接跳过(不在声明也不进行赋值操作)因为变量提升时 声明和定义都做过了
1 | console.log(num) //=> undefined 此时由于变量提升只是 var num; |
声明过的变量不再重复声明,遇到再次声明直接跳过
1 | //=>1.全局下的变量提升:var x;var y; var z; fn=aaafff111; |
变量提升特殊情况
1.全局作用域下定义变量时带var 和不带var的区别
(1)如果当前是全局作用域,带var的
声明了一个变量
,同时也给window全局作用域加了一个属性
(2)在全局作用域如果不带var
只是给window加了一个属性
,只不过在使用时把window.
省略了(严格意义上不是一个变量)
1 | //带var |
2. 私有作用域下定义变量时带var 和不带var的区别
- (1)在私有作用域里,
带var的重新开始变量提升
,声明为私有变量,和外界没有任何关系 - (2)
不带var不是私有变量
,会向他的上级作用域查找,看是否是上级的变量,是的话,操作的就是上级作用域的变量, - (3)不是上级的变量,向上继续查找,一直找到window为止(我们把这种查找机制叫做:
“作用域链”
),window有,操作的是window的变量(属性) - (4)
window没有,如果带等号就是给window添加一个属性
,如果不带等号,是undefined,也就是不带var
我们在私有作用域中操作的一直是别人的
1 | //带var |
作用域链
函数执行形成一个私有作用域(保护变量),
进入私有作用域中,首先变量提升
(声明的变量是私有的),接下来才是代码执行
1、 执行的时候遇到一个变量,如果这个变量是私有的,那么按照私有变量处理即可
1 | function fn(){ |
2、 如果当前这个变量不是私有的,需要向它的上级作用域进行查找,上级如果没有,则继续向上查找,一直找到window全局作用域为止,我们把这种查找机制叫做 作用域链
1)如果上级作用域有,我们当前操作的都是上级作用域中的变量(如果我们在当前作用域把值改了,相当于把上级作用域中的这个值给修改了)
2)如果找到window也没有这个变量;变量=值:相当于给window设置了一个属性
以后再操作window下就有了;但是在赋值操作之前alert(变量):想要输出这个变量,此时是没有的,所以会报错
1 | function fn(){ |
例题
1 | //变量提升 var a;var b; fn=AAAFFF111 |
只对等号左边的进行变量提升
=: 赋值,左边为变量,
右边永远是值
(右边不管是啥都要先计算出值
再赋值给左边)
1 | //之前: |
匿名函数:函数表达式(
把函数当做一个值赋值给变量或者其他内容
比如事件)
1 | oDiv.onclick=function(){} |
只对等号左边进行变量提升,右边是值不会提前声明(就算是函数也不会变量提升)
1 | console.log(fn) => undefined 变量提升 var fn;=右边是值不进行变量提升 |
声明函数 最好用函数表达式
的方式 var fn=function(){}
因为只能对等号左边的进行变量提升,所以变量提升完成后,当前函数只是声明了,没有定义,要想执行
只能放在赋值的代码之后执行
(放在前面执行相当于让undefined执行,会报错的)这样让我们的
代码逻辑更加的严谨
,以后想知道一个执行的函数做了什么功能只需要向上查找定义的部分即可
1 | sum(); //=> 因为 sum 是undefined 会报错 sum is not a function |
不管条件是否成立都要进行预解析
不管条件是否成立,判断体中出现的var/function都会进行变量提升
在新版浏览器
中,function
声明的变量只声明不能定义
(前提:函数必须在判断体中)
老版本浏览器
低版本ie 不管条件成不成立 function变量提升并定义
1 | //新版浏览器 |
代码执行到条件判断地方
1. 【条件不成立时
】 进入不到判断体中,赋值的代码不执行
,此时依然是undefined
2. 【条件成立时
】 进入判断体中的第一件事不是代码执行
,而是把之前变量提升没有定义的函数首先定义了
(进入判断体中函数就定义了
,为了迎合es6的块级作用域)
3. 老版本浏览器低版本ie 不管条件成不成立 function变量提升并定义
1 | //类似于es6的块级作用域 |
案例
1 | //=>变量提升:没有 |
变量提升重名的处理
在变量提升阶段,如果名字重复了,不会重新的进行声明
,但是会重新进行定义
(后面赋的值把前边赋的值给替换掉)
函数变量提升并定义后,代码至上而下执行,遇到声明定义函数的代码时不再声明和定义
1 | //=>变量提升: |
var 变量提升 之所以为 undefined
不是因为赋值为了undefined
而是因为不进行赋值操作才是undefined
只声明不赋值默认值为undefined
只有在全局作用域中
我们声明一个变量,相当于给全局对象window增加了一个属性名
ES6中的let不存在变量提升
在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测(语法检测)
在ES6中基于LET/CONST等方式创建的变量或者函数,不存在变量提升机制
:切断了全局变量和WINDOW属性的映射机制
在相同的作用域中,基于let不能声明相同名字的变量(不管用什么方式在当前作用域下声明了变量,再次使用let创建都会报错
(不同作用域下可以重复互不影响)
ES6同样具有作用域链
虽然没有变量提升机制,但是在
当前作用域代码自上而下执行之前,浏览器会做一个重复性检测(语法检测)
:自上而下查找当前作用域下所用所有变量,一旦发现有重复的,直接抛出异常,代码也不会再执行了(虽然没有把变量提前声明定义,但是浏览器已经记住了当前作用域下有哪些let创建的变量,
在let之前使用就会报错
,在let之前进行=赋值也报错
)检测语法规范,看一下是否是基于新语创建的变量,如果是按照新语法规范来解析
下边有let声明的这个变量,那么在let代码之前 赋值和获取都是报错
1 | //console.log(a) 报错 |
1 | var a=12; |
1 | let a=10; |
JS中的暂时性死区
在代码块内,
使用let命令声明变量之前
,该变量都是不可用的
。这在语法上,称为“暂时性死区
”(temporal dead zone,简称 TDZ)ES6明确规定,如果
区块中
存在let
和const
命令,这个区块对这些命令声明的变量
,从一开始就形成了封闭作用域
。凡是在
声明之前
就使用
这些变量,就会报错
。在原有浏览器渲染机制下(es5),
基于typeof等逻辑运算符检测一个未被声明过的变量,不会报错,返回'undefined'
;如果当前变量是基于ES6语法处理,如果在
没有声明(let之前)之前使用typeof检测会直接报错
;基于let创建变量,会把大部分
{}
当做一个私有的块级作用域
(类似于函数的私有作用域),在这里也是重新检测语法规范,看一下是否是基于新语创建的变量,如果是按照新语法规范来解析
ES6
中if
后面的{}
形成块级私有作用域
上级块级作用域不是上级作用域(上级作用域只能由函数执行形成)
1 | var a=12; |