系列文章
平凡前端之路_01.HTML与 HTML5
平凡前端之路_05.CSS与CSS3
平凡前端之路_14.ES与ES6
平凡前端之路_15.let 和 const声明
平凡前端之路_16.变量的解构赋值
作用域与let和const声明
ES6有两种新的声明变量的方式(let 和 const),再加上过去使用的方式(var)共三种。
提示:以下是本篇文章正文内容,下面案例可供参考
作用域
作用域定义了一个变量的生命周期和访问权限,可以是任意一个模块,一个函数,或者一个代码块。
let 和 const 所声明的变量在指定块的作用域外无法被访问。
let 关键字声明
let不允许在相同作用域内,重复声明同一个变量。
let x = '收手吧阿组'
let x = '长得帅不用交房组' // Identifier 'x' has already been declared
let 在当前作用域中声明并可以选择初始化变量,声明的变量可以再重新赋值。
let x = '靓仔啊下个月涨房组';
x = '房东告诉我下个月涨房组';
x //=> '房东告诉我下个月涨房组'
let 在当前作用域中声明并可以选择初始化变量,未被初始化的变量的值是 undefined;
let x;
let y = '如来佛组'
x //=> undefind
y //=> '如来佛组'
下面这段代码强调了 let 的块级作用域:
let x = '关东组';
x //=> '关东组'
{
let x = '斗地组';
x //=> '斗地组'
}
x //=> '关东组'
相反,使用 var 声明的变量则不具备块及作用域:
var x = '光宗耀组';
x //=> '光宗耀组'
{
var x = '欺师灭组';
x //=> '欺师灭组'
}
x //=> '欺师灭组'
let不存在变量提升。
变量提升(变量可以在声明之前使用,提升到当前代码块的顶部,不会报错,值为undefined)。
在一个作用域中如果存在let,这个区块对声明的变量从一开始就会形成封闭作用域,如果在声明前使用这些变量就会报错,这称为“暂时性死区TDZ”。
console.log(x); // x is not defined
let x = '败事有余,成事不组';
if(true){ // 不管代码块是否被执行
let y = '比下有余,比上不组'
}
console.log(y); // y is not defined
相反,使用 var 声明的变量存在变量提升。
变量提升(变量可以在声明之前使用,提升到当前代码块的顶部,不会报错,值为undefined):
console.log(x); // undefind
var x = '兵精粮组';
if(false){ // 不管代码块是否被执行
var y = '鼎分三组'
}
console.log(y); // undefind
const 关键字声明
const不允许在相同作用域内,重复声明同一个变量。
const y = '收手吧阿组'
const y = '长得帅不用交房组' // Identifier 'y' has already been declared
const 在当前作用域中声明并可以选择初始化变量,声明的变量不能再重新赋值。
不可变的值是指那些一旦创建,就不能再改变的值。
const x = '靓仔啊下个月涨房组';
x = '房东告诉我下个月涨房组'; // Assignment to constant variable.
x //=> '靓仔啊下个月涨房组'
const 声明变量必须强制进行初始化操作。
const x ; // Missing initializer in const declaration
const y = '如来佛组'
y //=> '如来佛组'
本质上const是让变量指向的那个内存地址保存的数据不得改动,对于基本类型数据是不可变的, 对于复合类型的数据(对象Object、Map、Set和数组)是可变的,const变量指向的内存地址保存的只是一个指向实际数据的指针,const只能保证这个指针固定,对象或数组本身是可变的。
const foo = {};
foo //=> {}
// 为 foo 添加一个属性,可以成功
foo.prop = '贩夫走组';
foo //=> { prop: '贩夫走组'}
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
const a = [];
a //=> []
a.push('画蛇添组'); // 可执行
a //=> ['画蛇添组']
a.length = 0; // 可执行
a //=> []
a = ['身先士组']; // 报错TypeError: "a" is read-only
声明一个不变的常量,可以使用Object.freeze()方法来冻结变量 ,如:
const obj = {
name: '白雪公组'
}
Object.freeze(obj) // 此时对象obj被冻结,返回被冻结的对象
obj //=> { name: '白雪公组'}
需要注意的是,被冻结后的对象不仅仅是不能修改值,同时也
- 不能向这个对象添加新的属性
- 不能修改其已有属性的值
- 不能删除已有属性
- 不能修改该对象已有属性的可枚举性、可配置性、可写性
下面这段代码强调了 const 的块级作用域:
const x = '关东组';
x //=> '关东组'
{
const x = '斗地组';
x //=> '斗地组'
}
x //=> '关东组'
相反,使用 var 声明的变量则不具备块及作用域:
const x = '光宗耀组';
x //=> '光宗耀组'
{
const x = '欺师灭组';
x //=> '欺师灭组'
}
x //=> '欺师灭组'
const 不存在变量提升。
变量提升(变量可以在声明之前使用,提升到当前代码块的顶部,不会报错,值为undefined)。
在一个区块中如果存在const ,这个区块对声明的变量从一开始就会形成封闭作用域,如果在声明前使用这些变量就会报错,这称为“暂时性死区TDZ”。
console.log(x); // x is not defined
const x = '败事有余,成事不组';
if(true){ // 不管代码块是否被执行
const y = '比下有余,比上不组'
}
console.log(y); // y is not defined
相反,使用 var 声明的变量存在变量提升(变量可以在声明之前使用,值为undefined):
console.log(x); // undefind
var x = '兵精粮组';
if(false){ // 不管代码块是否被执行
var y = '鼎分三组'
}
console.log(y); // undefind
循环与作用域
在循环中,循环体不在同一个作用域内(创建了多个作用域)
(function run(){
for(let i = 0; i < 5; i++){
let k = 5; // 如果循环体在同一个作用域,会报错 Identifier 'k' has already been declared
console.log(k);
}
})();
在循环中,循环条件和循环体不在同一个作用域内
(function run(){
for(let i = 0, k = 1; i < 5; i++){
let k = 5; // 如果循环条件和循环体在同一个作用域内,会报错 Identifier 'k' has already been declared
console.log(k);
}
})();
在循环中,循环条件在同一个作用域内
(function run(){
for(const i = 0; i < 5; i++){ // 报错 Assignment to constant variable.
console.log(i); // 同一个作用域内 const 声明的变量不能再重新赋值
}
})();
由于const 在同一作用域中声明的变量不能再重新赋值,const不能用于循环条件中作为迭代变量。
闭包与作用域
在 ES5 及之前,如果循环内有产生闭包,闭包内访问闭包外的变量,会产生问题:
循环中闭包内访问闭包外同一个 i 变量(变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i,每一次循环,变量i的值都会发生改变),所有的闭包函数都会访问同一个全局的 i 变量最终的值。
(function run(){
for(var i = 0; i < 5; i++){
let j = i
var k = i
setTimeout(function log(){
console.log(i); // 5 5 5 5 5 变量i 自增6次,i值最终为5,最后一次不符合循环条件并结束循环
console.log(j); // 0 1 2 3 4
console.log(k); // 4 4 4 4 4 使用同一个变量k,k值最终为4
}, 0);
}
})();
在 ES6及之后,可以在循环条件中使用 let 声明变量,在循环中创建多个块级作用域,循环中闭包内访问闭包外当前块级作用域中的变量(只在本轮循环有效)。
(function run(){
for(let i = 0; i < 5; i++){
let j = i
var k = i
setTimeout(function log(){
console.log(i); // 0 1 2 3 4
console.log(j); // 0 1 2 3 4
console.log(k); // 4 4 4 4 4 使用同一个变量k,k值最终为4
}, 100);
}
})();
在 ES5及之前,闭包解决方案:
将每次循环 i 的值保存到函数作用域里,作为参数传递
(function run(){
for(var i = 0; i < 5; i++){
(function(i) {
setTimeout(function log(){
console.log(i);
}, 0);
})(i)
}
})();
在 ES6及之后,闭包解决方案:
(function run(){
for(let i = 0; i < 5; i++){
setTimeout(function log(){
console.log(i);
}, 0);
}
})();
总结
以上就是今天要讲的内容,本文简单介绍了两种新的声明变量的方式(let 和 const)与作用域关系。JS知识题
问题 | 答案 |
---|---|
const 声明的变量不能再重新赋值 | const 在同一作用域中 声明的变量不能再重新赋值,在不同作用域或不同块级作用域中是可以重新声明赋值的 |
let 声明的变量不能再重新声明 | let 在同一作用域中 声明的变量,在不同作用域或不同块级作用域中是可以重新声明 |
let 关键字定义的变量声明时必须进行初始化再使用 | let 声明的变量只需先声明再使用,无需进行初始化,const 关键字定义的变量声明时必须进行初始化再使用,var 关键字定义的变量可以先使用后声明。 |
const 能重新声明赋值var已声明的变量 | const 在同一作用域中 不能重新声明赋值var已声明的变量,在不同作用域或不同块级作用域中const是可以重新声明赋值var已声明的变量 |
let 能重新声明赋值var已声明的变量 | let 在同一作用域中 不能重新声明赋值var已声明的变量,在不同作用域或不同块级作用域中let 是可以重新声明赋值var已声明的变量 |
var 能重新声明赋值var已声明的变量 | var 在任何地方都可以重新声明赋值var已声明的变量 |
const能用于循环条件中作为迭代变量 | const 在同一作用域中声明的变量不能再重新赋值,所以const不能用于循环条件中作为迭代变量。 |