let、const
let
- let声明的变量不提升
if(true){
//tmp = 'abc';//ReferenceError
//console.log(tmp);//ReferenceError
let tmp; //暂时死区(TDZ)结束,在let命令声明变量tmp之前,都属于变量tmp的死区
console.log(tmp);//undefined
tmp = 123;
console.log(tmp);//123
}
//typeof x; //ReferenceError "暂时性死区"也意味着typeof不再是一个百分之百安全的操作,
//上面代码中,变量x使用let命令声明,所以在声明之前,都属于x的"死区",只要用到该变量就会报错.因此
//typeof运行时就会抛出一个ReferenceError
//let x;
typeof undeclared_variable; //undefined 在上面代码中,undeclared_variable是一个不存在的变量名,结果返回"undefined",
//所以,在没有let之前,typeof运算符是百分百安全的,永远不会报错.现在这一点不成立了
//var x = x; //不报错
let x = x; //ReferenceError : x is not defined
//使用let变量声明时,只要变量在还没有声明完成前使用,就会报错.在变量x的声明语句还没有执行完成前,就去取x的值,
//导致报错"x未定义"
//报错
function (){
let a = 10;
var a = 1;
}
//报错
function (){
let a = 10;
let a = 1;
}//let不允许在相同的作用域内,重复声明同一个变量.
function func(arg){
let arg; //报错
}
function func(arg){
{
let arg; //不报错,新的作用域
}
}
块级作用域
var tmp = new Date();
function f(){
console.log(tmp); //undefined
if(false){
var tmp = 'hello world';// if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量,但是,函数f执行后,
//输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量
}
}
f();
var s = 'hello';
for(var i = 0 ;i<s.length;i++){
console.log(s[i]);// h e l l o
}
console.log(i);//5 变量i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量
function f1(){
let n = 5;
if(true){
let n = 10;
}
console.log(n); //5
}
f1();
{{{{
let insane = 'hello world';
{
let insane = 'hello world'
}
}}}} //内层作用域可以定义外层作用域的同名变量
块级作用域与函数声明
ES5规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明
//es6中
// function f(){
// console.log('i am outside');
// }
//
// (function (){
// if(false){
// //重复声明一次函数f
// function f(){
// console.log('i am inside');
// }
// }
// f(); //Uncaught TypeError: f is not a function
// }());
// 浏览器的 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
{
let a = 'secret';
function f(){ //函数声明语句
return a;
}
}
{
let a = 'secret';
let f = function (){ //函数表达式
return a;
};
}
//考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数.如果确实需要,
// 也应该写成函数表达式,而不是函数声明语句
const
const PI = 3.1415;
console.log(PI) //3.1415
PI = 3;// TypeError: Assignment to constant variable.
// 上面代码表明改变常量的值会报错.const声明的变量不得改变值,这意味着,const一旦表明变量,
// 就必须初始化,不能留到以后赋值
const foo; // SyntaxError: Missing initializer in const declaration
//const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值
//对于const来说,只声明不赋值,就会报错
if(true){
const MAX = 5;
}
MAX //Uncaught ReferenceError: MAX is not defined
//const的作用域与let命令相同,只在声明所在的块级作用域内有效
if(true){
console.log(MAX); //ReferenceError
const MAX = 5;
} //const命令声明的常量也是不提升,同样存在暂存性死区,只能在声明的位置后面使用
var message = "hello!";
let age = 25;
//一下两行都会报错 const 声明的常量,也与let一样不可重复声明
const message = "goodbye!";
const age = 30;
本质
- const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
- 上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
//常量a是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给a,就会报错。