一、 变量提升
在当前执行上下文中,代码执行之前,浏览器首先会把所有带var或者function的关键字进行提前声明或定义。
老版本浏览器:带 var 的只是提前声明,带 function 会提前声明 + 定义(赋值)
新版本浏览器(兼容ES5 & ES6):存在块级上下文,function:大括号中的函数(非函数和对象),在变量提升阶段,只声明不赋值。
- 条件判断:在当前上下文中,变量提升阶段,不论条件是否成立,都要进行变量提升。
- var:还是只声明
- function:判断体中的函数,在变量提升阶段,只声明不赋值。
console.log(a) if(!('a' in window)){ var a = 13 } console.log(a) // undefined undefined
let,const就不会引起变量提升,因此在写函数时,最好使用函数表达式的方式创建函数,如:
let fn = function(){}
fn()
举个栗子:
fn()
function fn(){console.log(1)} //(1)
fn()
function fn(){console.log(2)} //(2)
fn()
var fn = function (){console.log(3)} //(3)
fn()
function fn(){console.log(4)} //(4)
fn()
function fn(){console.log(5)} //(5)
fn()
分析: 根据上述知识点可知道,代码执行前遇见function就会提前声明并定义(就是把function指向的堆内存地址赋值给fn),遇见var提前声明,因此:
//变量提升:
(1) => fn = aaafff111 (function的堆内存地址,自定义的)
(2) => aaafff222
(3) =>
(4) => aaafff444
(5) => aaafff555
//代码执行:按照顺序执行fn(),依次打印 5 5 5
//运行到第六行时,因为之前只是声明,所以现在开始执行赋值操作,此时fn 指向 aaafff333 地址
//继续执行,依次打印 3 3 3
结果:
5 5 5 3 3 3
二、 总结
1. 变量提升
- 使用
var
声明变量 会引起变量提升 let
,const
不会。let,const未声明就使用会报错
2. 给全局添加属性
全局上下文中,
- 基于var声明的变量不是存放在VO(G)中,而是直接存放在GO(window)中;「新浏览器」
- 基于let/const声明的变量存放在VO(G)中,不存放在GO中
var x = 1;
console.log(window.x) //1
let y = 1
console.log(y) // 1
console.log(window.y) //undefined
3. 重复声明
var
的是可以重复声明的(词法解析可以审核通过),执行阶段遇到已经声明过的不会再重新声明;- 但
let/const
不可以,词法解析阶段发现在相同上下文中,基于let重复声明变量的操作,则直接报错,所有代码都不会执行。在当前上下文中不论基于什么方式声明了这个变量,再次基于let/const声明都会报重复声明错误
var a = 1
let a = 1
4. 块级作用域
没有ES6之前,作用域只有2种:全局作用域,函数作用域。有了let后,产生了块级作用域,如果在{}中出现let/const/function,则当前{}会成为一个块级私有上下文。「{}排除函数和对象的{}」
let存在块级作用域,var没有。 对于循环来说,let会形成父块作用域。每一轮循环形成子块作用域,创建一个私有变量,值由父块作用域传进来,每执行完一轮循环,循环累计数i加1。
ES6中常写循环的方式:
let arr=[10,20,30]
for(let i = 0;i<arr.length;i++){}
以上会创建出4个块级作用域,每轮循环都会再对arr的length做计算,虽然最终都会被释放掉,但可以有更优写法
// ES6中正常循环,使性能更优的写法
let arr=[10,20,30]
let i = 0,
len = arr.length
for(;i<len;i++){}
5. 暂时性死区
在代码块内,使用let。const命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
a为未定义变量,直接使用不报错
console.log(typeof a) //undefined
在代码块里,用let或const声明了变量再之前使用就会报错
console.log(typeof a) //报错
let a
6. let与const
let和const都是ES6新增的用于创建变量的语法。
let创建的变量是可以更改指针指向(可以重新赋值)。
但const声明的变量是不允许改变指针的指向,不能重新赋值。不代表值不能变
const a = {
name:'hello'
}
a.name = 'world'
console.log(a) // {name:'world'}
补充1:Js代码执行
- 词法解析(AST):我们基于HTTP服务从服务器端拉回来的js代码都是字符串,浏览器首先按照ECMAScript规则,把字符串转换成C++可识别和解析的树结构对象
- 全局上下文
- 变量提升
- 代码执行
- ...