变量提升
下面这段代码没有按照:先声明,后应用,但是并没有报错
b();//b is called
console.log(a);//undefined
var a=1;
function b(){
console.log("b is called");
}
函数b正常执行了,但a是undefined。
JavaScript 引擎会将变量的声明提升,但赋值还是原来位置,可以理解为:
function b(){
console.log("b is called");
}
var a;//declare
b();//b is called
console.log(a);//undefined
a=1;//set value
function b(){
console.log("b is called");
}
将所有的变量声明提到它所在执行环境的最前面(全局执行环境或者函数执行环境),声明之后就再内存中开辟了一块空间,放入‘undefine’ 。赋值之后这块内存里才是我们想要的值。
而函数声明会被整个提到最上面,在执行时,这块内存中已经时我们写的代码了。
用var关键词声明的变量都有声明部分的提升,即:在运行时之前将变量的声明提到当前作用域的最顶部。(注意:提升的只有声明,赋值还是在写下的地方)
但let和const声明的变量没有提升,必须先声明后使用
// console.log('inside code block ' + a);//ReferenceError: a is not defined
// console.log('inside code block ' + c)//ReferenceError: c is not defined
// console.log('inside code block ' + b);//inside code block !!!undefined!!!!
let a = 'let';
var b = 'var';
const c = 'const'
块作用域
在ES6之前,变量的声明都使用var,JS中没有“块作用域”的概念。只存在全局作用域和函数作用域。
但用let和const声明的变量都只在块作用域内生效,即生命周期从进入块开始({),到出块结束(})。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
在第一个demo中,i使用var声明,作用域是全局的,最后数组a中保存的10个函数中的i都是指向这同一个i。
在第二个demo中,i使用let声明,for循环实际上是10个代码块,数组a中保存的函数中的i是指向十个不同的i。(算闭包吗?)
块作用域防止了变量对外层覆盖
var tmp = 'outside';
function f() {
console.log(tmp);
if (false) {
var tmp = 'inside';
}
}
f();//undefined
上面这段代码因为变量提升,实际运行之前的状态是
var tmp = 'outside';
function f() {
var tmp;//js引擎会为所有未赋值的变量赋值为undefined
console.log(tmp);
if (false) {
tmp = 'inside';
}
}
f();//undefined
但如果对if代码块内部的变量声明使用let
var tmp = 'outside';
function f() {
console.log(tmp);
if (false) {
let tmp = 'inside';
}
}
f();//outside
块作用域防止了对全局环境的污染
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
上面代码中,变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
var s = 'hello';
for (let i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // ReferenceError: i is not defined
块作用域改变了函数声明的提升
下面的domo
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); }
if (false) {
}
f();
}());
在ES5下
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); }
if (false) {
}
f();
}()); // I am inside
在ES6浏览器下
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}()); //Uncaught TypeError: f is not a function
避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
不允许重复声明
var可以重复声明变量,而let和const都不可以。
不可修改
const声明的变量不可修改。因为不可修改,所以const声明的变量也必须立即赋值。
首先:const声明的变量是在对应的栈内存中不可修改。很多文章都提到了这一点,如果是用const声明的对象,因为栈中存储的是指针,真正的值在堆内存中,这个对象属性是可以被修改,添加的。
但栈内存的特点是不可修改(即:即使使用var声明的原始类型变量,在修改变量时,实际也不是在原有内存空间上做改动,而是去重新开辟一块新的内存,存储新的值)。const声明的变量本质应该是:对应的栈内存空间不可改变。
for (const i = 0; i < 3; i++) {
console.log(i);
}
// TypeError: Assignment to constant variable.
上面这段代码在i++操作试图修改i的值,实际是给i去重新分配一块栈内存,const不允许这种操作。
但如果是 const in语句是可以的,效果同var in
obj = {
first:'1',
second:'2'
}
for (const pro in obj) {
console.log(pro);
}
//first
//second
因为const in 操作并没有试图改变原有值,而是每次循环都是一个代码块,都有一个const声明在当前代码块中的变量。没有出现过在栈中重新分配内存的操作。
for循环与新特性
使用var时:
for (var i = 0; i < 3; i++) {
var i = 'abc';
console.log(i);// 一次abc
}
console.log('abc'==3)
因为var存在变量提升和重复声明,上面的代码可以看作
var i;
for (i = 0; i < 3; i++) {
i = 'abc';
console.log(i);
}
而'abc'<3返回false,for代码块只循环依次
使用let时:
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);// 三次abc
}
前面说过let不允许重复声明变量,这里也说明了for括号内声明的变量有一个隐式的代码块。可以看成
{
let i = 0;
{
let i = 'abc';
console.log(i);
}
}
{
let i = 1;
{
let i = 'abc';
console.log(i);
}
}
{
let i = 2;
{
let i = 'abc';
console.log(i);
}
}
暂时性死区
在JS中,变量可以不声明,直接赋值,这时候被赋值的变量会被绑定到全局对象上。
if (true) {
tmp = 'abc'; // 这句必须要,没有声明可以,但一定要赋值
console.log(tmp); // ReferenceError
}
console.log(this)
//window{
...
tem:'abc',
}
但在使用了let之后,产生暂时性死区,在let存在的块作用域内,let关键词之前都是暂时性死区。
if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
}
顶层对象
顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。
浏览器下var
var a = 1;
console.log(this.a) // 1
浏览器下var
let a = 1;
console.log(this.a) // undefine
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。
ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。