在ES6之前,只有使用`var`来进行变量的声明,而在ES6 不仅增加了 `let `和` const` 两个关键字,而且还让这两个关键字压倒性地超越 `var` 成为首选。
6.1 var
1.使用 var 的函数作用域声明
在使用 var 声明变量时,变量会被自动添加到最接近的上下文。在函数中,最接近的上下文就是函数的局部上下文。在 with 语句中,最接近的上下文也是函数上下文。如果变量未经声明就被初始化了,那么它就会自动被添加到全局上下文
function add(num1, num2) {
var sum = num1 + num2; //加上var声明,变量为局部变量
return sum;
}
let result = add(10, 20); // 30
console.log(sum); // 报错:sum 在这里不是有效变量
- 在此区域内,var声明的变量根据所在的
{}
自动分配为该范围内变量,即局部变量、 - 局部变量在外部时销毁,无法使用
- var所谓的局部概念仅限于函数!!!
当去掉var
时,定义的变量虽然在函数内,但会转换成全局变量,可以在全局访问
function add(num1, num2) {
sum = num1 + num2;
return sum;
}
let result = add(10, 20); // 正常输出的结果,由sum返回给函数在外部
console.log(sum); // 30 去除var后变为全局变量
- 去掉
var
后声明的变量虽然依然在函数内,却转换成全局变量 - 未经声明就初始化变量是一种错误的用法,虽然不会报错但也尽量不要使用!!
2. var声明变量提升
- var 声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前。这个现象叫作“提升(hoisting)。
- 提升让同一作用域中的代码不必考虑变量是否已经声明就可以直接使用。
可是在实践中,提 升也会导致合法却奇怪的现象,即在变量声明之前使用变量。
console.log(nun); var num=10 //变量提升情况下 var num; console.log(nun); num=10;
虽然声明的变量提升到了当前范围内的最顶上,但是变量赋值依然在原处
- 此时在执行到
console.log(nun);
时,打印输出的是undefined - 通过在声明之前打印变量,可以验证变量会被提升。声明的提升意味着会输出
undefined
而不是Reference Error
6.2 let
1.使用 let 的块级作用域声明
let
是ES6的新增的关键字,它和var
有些类似,但是它不是在函数区域作用,而是在块级作用区域- 所谓的块级作用区域即是此声明变量最近的一对
{}
来界定,换句话说,if 块、while 块、function 块,甚至连单独 的块也是 let 声明变量的作用域。if (true) { let a; // a 只在if里面生效 } console.log(a); // ReferenceError: a 在外部没有声明 while (true) { let b; // b 只在while里面生效 } console.log(b); // ReferenceError: b 在while外部没有被定义 function foo() { let c; // c仅在函数内定义 ,和var在函数内相同 } console.log(c); // ReferenceError: c 没有定义 // JavaScript 解释器会根据其中内容识别出它来 { let d; //就算只有花括号 ,也只在内部生效 } console.log(d); // ReferenceError: d 没有定义
let 与 var 的另一个不同之处是在同一作用域内不能声明两次。重复的 var 声明会被忽略,而重 复的 let 声明会抛出 SyntaxError
。
var a;
var a;
// 不会报错 但后面相同的变量会覆盖前面的变量
{
let b;
let b;
}
// SyntaxError: 标识符 b 已经声明过了
let 的行为非常适合在循环中声明迭代变量。使用 var 声明的迭代变量会泄漏到循环外部,这种情 况应该避免。
for (var i = 0; i < 10; ++i) {}
console.log(i); // 10 内部定义可在外部使用
for (let j = 0; j < 10; ++j) {}
console.log(j); // ReferenceError: j 没有定义仅在for内生效
严格来讲,let 在 JavaScript 运行时中也会被提升,但由于“暂时性死区”(temporal dead zone)的 缘故,实际上不能在声明之前使用 let 变量。因此,从写 JavaScript 代码的角度说,let 的提升跟 var 是不一样的
// name 会被提升
console.log(name); // undefined
var name = 'Matt';
// age 不会被提升
console.log(age); // ReferenceError:age 没有定义
let age = 26;
在解析代码时,JavaScript 引擎也会注意出现在块后面的 let 声明,只不过在此之前不能以任何方式来引用未声明的变量。在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出 ReferenceError。
6.3 const
1. 使用const常量声明
const
也是在ES6出现的新声明,在进行const变量声明时必须同时对变量进行赋值初始化操作,并且在其生命周期内无法再重新赋值。
const a; //错误! 常量声明时没有初始化
const b = 3;
console.log(b); // 3
b = 4; // 错误! 给常量赋值
//给复杂类型赋值时,地址无法修改,但里面数据可以修改
const ary = [100, 200];
ary[0] = 'a';
ary[1] = 'b';
console.log(ary); // ['a', 'b'];
- 使用const时必须在声明时赋值
- const在其活动周期内简单数据类型无法改变值,复杂数据类型无法改变地址
- 其他方面于
let
相同
由于 const 声明暗示变量的值是单一类型且不可修改,JavaScript 运行时编译器可以将其所有实例都替换成实际的值,而不会通过查询表进行变量查找。谷歌的 V8 引擎就执行这种优化。
注意
- 开发实践表明,如果开发流程并不会因此而受很大影响,就应该尽可能地多使用 const 声明
- 除非确实需要一个将来会重新赋值的变量。这样可以从根本上保证提前发现 重新赋值导致的 bug。
6.4 作用域
内部函数可以调用外部函数的变量,优先最进的函数
function f1() {
var num = 123;
function f2() {
console.log( num );
}
f2();
}
var num = 456;
f1();
- 值为123,由于f2没有num变量,只能调用就近函数此变量,按照从小到大,从上到下的顺序
- 优先向外层函数寻找,层层递进
6.5 预解析
在JavaScript
中,JavaScript 解析器在运行 JavaScript 代码的时候分为两步:预解析和代码执行。
- 预解析:在当前作用域下, JS 代码执行之前,浏览器会默认把带有 var 和 function 声明的变量在内存中进行提前声明或者定义。(声明时函数排在变量的前面)
- 代码执行:在解析完后从上到下执行代码
- 预解析将在代码执行前把变量和函数声明完
1. 变量预解析
在使用var声明时,变量的声明会被提到当前作用域的最顶端,但变量赋值不会
console.log(num); //为undefined
var num=10;
解析完为:
var num;
coonsole.log(num);
num=10
2. 函数预解析
函数的声明会被提到作用域的最顶端,但不会调用函数
f1();
function f1(){
console.log('能显示吗?')
}
函数能够正常显示,解析后为:
function f1(){
console.log('能显示吗?')
}
f1();
函数声明被提到最顶端,然后按照代码执行顺序正确执行
但当使用函数表达式声明函数时,由于此时只是将函数结果赋值给变量,即函数未声明,所以只有变量提升,函数报错
fn();
var fn = function() {
console.log('想不到吧');
}
var fn; //只有变量声明被提到顶端
fn();
function() { //函数名为空,直接报错
console.log('想不到吧');
}
- 函数表达式只是将函数返回值赋予变量,没有函数声明提升