前言
本人一枚PHP程序员,最近在学习前端方面的知识,于是淘了本阮一峰老师写的《ES6标准入门》,书中讲的特别好,在此记录下一些get到的知识点,希望对大家也有所帮助。 let和const是此次ES6加入的新命令,大家之前可能或多或少了解它们,但相信大家看完这这篇文章会有更深刻的理解。
1.let命令
1.1 基本用法
ES6新增了let命令,类似于var用于变量的声明,但是不同于var的全局生效,所声明的变量只在let所在的代码块内有效。
{
let a = 1;
var b = 2;
//代码块内部调用
console.log(a); //1
console.log(b); //2
}
//代码块外部调用
console.log(a); //ReferenceError: a is not defined
console.log(b); //2
上面的代码分别在代码块中用let和var声明了2个变量,然后在代码块内外分别调用这两个变量;在内部都可以正确调用;但是在外部,使用let声明的变量报错,var则正确输出。说明let声明的变量仅在块内部有效。
1.2不存在变量提升
var命令会发生“变量提升”的现象(变量声明之前即可以去调用,只不过值是undefined),是有悖于逻辑的,不声明居然还能调用,这就很不可思议。 为了纠正这类现象,let改变了语法行为,它声明的变量要在声明后才能使用,不然就直接报错了;
//var的情况
console.log(foo); //undefined
var foo = 2;
//let的情况
console.log(foo2); //ReferenceError: Cannot access 'foo2' before initialization
let foo2 = 10;
foo用var声明会发生变量提升,所以会输出undefined;但是foo2使用let声明则不会出现变量提升,这表示在声明它之前,变量foo2是不存在的,若使用的话就会抛出错误。
1.3暂时性死区
只要块级作用域内存在let命令,它所声明的变量就绑定这个区域,不再受外部影响。
//暂时性死区
var tmp = 123;
if (true) {
tmp = 'abc'; //ReferenceError: Cannot access 'tmp' before initialization
let tmp;//
}
上面的代码中存在全局变量tmp,但是在块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。
ES6明确规定,如果区块内存在let和const命令,则这个区域对这些命令声明的变量从一开始就形成封闭作用域,只要在声明之前使用这些变量,就会报错。
1.4 不允许重复声明
let不允许在相同作用域内重复声明同一个变量。
//报错
{
let a = 10;
var a = 20; //SyntaxError: Identifier 'a' has already been declared
}
//报错
() => {
let a = 10;
let a = 1; //SyntaxError: Identifier 'a' has already been declared
}
因此不能在函数内部重新声明参数。
//报错
(a) => {
let a = 10; //SyntaxError: Identifier 'a' has already been declared
}
//不报错
(a) => {const PI = 3.1415;
PI = 3; //TypeError: Assignment to constant variable.
{
let a = 10;
}
}
2.const命令
2.1 基本用法
const声明一个只读的常量,一旦声明,常量的值不能被修改。
const PI = 3.1415;
PI = 3; //TypeError: Assignment to constant variable.
从上面的代码可以看出,改变常量的值会报错。
const FOO; //SyntaxError: Missing initializer in const declaration
const声明常量不赋值也会报错。
2.2 本质
const本质上并不是保证变量的值不能改动,而是变量指向的内存地址不得改动。对于简单的数据类型(数值、字符串、布尔值)而言,值就保存在变量指向的内存地址中,因此等同于常量。但对于复合类型的数据(对象和数组),变量指向的内存地址只是一个指针,const只能保证这个指针是固定的,不能完全控制它指向的数据结构是不是可变。
const FOO = {};
//为FOO添加一个name属性,可以成功
FOO.name = 'neo';
console.log(FOO.name); //neo
//将FOO指向另一个对象,报错
FOO = {}; //TypeError: Assignment to constant variable.
上面的代码中,常量FOO存储的是一个地址,这个地址指向一个对象,不可变的只是这个地址,但对象本身是可变的,依然可以给其添加属性。
来看下数组的例子
const A = [];
A.push('neo'); //可执行
A.length = 0; //可执行
A = ['aliy']; //TypeError: Assignment to constant variable.
上面的代码中,A是一个数组,数组本身是可写的,如果把另外一个数组赋值给A则会报错。
PS:那如何冻结对象,让其无法修改呢? 使用Object.freeze方法。
const FOO = Object.freeze({});
//常规模式下,下面这行代码不生效
//严格模式下,下面这行代码会报错
FOO.name = 'neo';
console.log(FOO) //输出为{}
上面的代码中,常量FOO指向一个冻结的对象,所以添加新的属性不起作用,严格模式时还会报错。 除了对象本身冻结,对象的属性也应该冻结,下面是一个将对象彻底冻结的函数。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if( typeof obj[key] === 'object') {
constantize(obj[key]);
}
});
}
const neo = {name:"neo",test:{}};
constantize(neo)
neo.test.age=23;
console.log(neo)
//若使用constantize函数,则输出值为{ name: 'neo', test: {} }
//反之{ name: 'neo', test: { age: 23 } }
个人总结
- 在用if、for循环、函数体内最好使用let和const定义变量和常量,可以防止内存外泄(例如for循环中使用var声明变量进行循环,循环结束后该变量是全局变量,一直存在于内存)和变量提升等问题。
- 使用const定义对象或数组时要注意对应属性的变化。