这是我参与「第四届青训营 」笔记创作活动的的第4天
ES6快速入门(1)
变量
let 命令
ES6 新增let 命令,用于声明变量,其用法类似于var,但是所声明的变量只在let命令所在的代码块内有效。
即所谓的块级作用域
<script type="module">
let a = 10;
var b = 1;
</script>
a // ReferenceError : a is not defind
b // 1
在计数器 for 循环的i 变量中 let 的使用
for(let i = 0;i < 10; i ++){
// todo
}
console.log(i);
// ReferenceError : i is not defind
使用var 的经典误区,这也更加验证了在我们日常开发中最好统一使用let,避免出现涉及作用域类型的错误
var a = [];
for(var i = 0; i < 10; i++){
a[i] = function(){
console.log(i);
};
}
// 调用 a[6]
a[6](); // 10
// 如果声明 let i = 0;
a[6](); // 6
解释说明,上面的代码中, 变量i是var声明的,在全局范围内有效,所以全局只有一个变量i,每一次循环,变量i 的值都会发生改变,而在循环内部,被赋给数组a的函数内部的console.log(i) 指向的是全局的i,也就是说,所有数组a的成员中的i指向的都是同一个i,导致运行时输出的最后一轮的i值,也就是10
如果使用let,声明的变量i只在本轮循环有效。所以每一次循环的i其实都是一个新的变量,于是最后输出的是6.
不存在变量提升的问题
var 命令的变量会发生变量提升的现象,即变量可以在声明之前使用, 值为undefined。 这种现象多少是有些奇怪的,按照一般的逻辑, 变量应该在声明语句之后才可以使用, 为了纠正这一现象,let 改变了语法规则,所声明的变量一定要在声明之后使用。
// var 的情况
console.log(foo); // 输出为undefined
var foo = 2;
// let 的情况
console.log(foo); // 输出ReferenceError
let foo = 2;
不允许 重复声明
let 不允许在相同作用域内重复声明同一个变量
// 报错
function(){
let a = 1;
let a = 10;
}
const 命令
const 声明一个只读的常量,一旦声明,常量的值就不能改变,而且const 声明必须赋值。
例如 const pi = 3.14
pi // 3.14
pi = 3; // TypeError: Assignment to constant variable
const foo; // SyntaxError: Missing intitlizer in const declaration
上面的代码表示, 对于const 而言,只声明不赋值就会报错
const 的作用域与let 命令相同,只在声明所在的块级作用域内有效,不存在变量提升,同样存在暂时性死区,只能在声明之后使用,不可重复声明
对于复合类型的数据(对象,数组)而言,直接赋值是错误的,对内部元素赋值是可以的。
const foo = {};
// 为foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将foo指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
const a = [];
a.push("hello");// 可执行
a.length = 0;// 可执行
a = ['11'] // 报错
如果想让对象冻结(内部的属性元素不可变,且不可以添加修改删除元素),应该使用Object. freeze 方法
var constantize = (obj) =>{
Object.freeze(obj);
Object.keys(obj).forEach((key,i) =>{
if(typeof obj[key] === 'object'){
constantize( obj[key] );
}
})
}
原理相当于逐层遍历对象内部,来对内部的对象进行冻结
const dic = {a:11,b:{c:00,d:22}}
constanize(dic);
// 内部过程模拟
// 第一次: 冻结 最外层 的对象
// 第二次: 冻结 dic['b'] 的对象
变量的解构赋值
数组的解构赋值
ES6 允许按照一定模式从数组和对象中提取值, 然后对变量进行赋值, 这被称为 解构
// 以前为变量赋值
let a = 1;
let b = 2;
let c = 3;
// ES6可以写成下面这样
let [a, b, c] = [1,2,3];
只要等号两边的模式相同, 左边的变量就会被赋予对应的值, 下面是一些使用嵌套数组进行解构的例子。
let [foo, [[bar], baz ]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [, , third] = ['foo', 'bar', 'baz']
third // "bar"
let [x, , y] = [1,2,3];
x // 1
y // 3
let [head. ...tail] = [1,2,3,4];
head // 1
tail // [2,3,4] 展开运算符,下面会详细说明
let [x, y, ...z] = ['a'];
x // 'a'
y // undefined
z // []
如果解构不成功,变量的值就等于undefined。
另一种情况就是,左边的模式只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
let [x,y] = [1,2,3];
x // 1
y // 2
如果 等号右边的不是数组(或者严格来说不是可遍历的结构),那么就会报错
// 报错
let [foo] = 1;
// false, NaN, undefined, null, {}
上面语句都会报错,因为等号右边的值或是转化为对象以后不具备Iterator接口,或者本身不具备Iterator接口(最后一个)
对于Set结构,也可以使用数组的解构赋值。
let [x,y,z] = new Set(['a','b','c'])
默认值
解构赋值允许指定默认值
let [foo = true] = [];
foo //true
let [x, y = 'b'] = ['a']; // x = 'a' , y = 'b'
let [x, y = 'b'] = ['a', undefined]; // x = 'a' , y = 'b'
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
ES6 内部使用严格相等运算符 (===) 判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的
上面的代码中,如果一个数组成员是null, 默认值就不会改变,因为null不严格等于undefined。
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到时才会求值。
function f(){
console.log('aaa');
}
let [x = f()] = [1];
上面的代码x能取到值,所以函数f根本不会执行。上面的代码等价于,先声明了一个let 变量的x,然后根据位置判断等号右侧的第一位是不是undefined,如果是,则x = f(); 否则,x = 右侧数组的第一位。
默认值也可以引用其他解构赋值的其他变量,但该变量必须已经声明。
let [x = 1, y = x] = [2]; // x = 2, y = 2
let [x = 1, y = x] = [1, 2] // x = 1, y = 2
let [x = y, y = 1] = []; // ReferenceError
上面最后一个表达式之所以会报错,是因为x用到默认值y时,y还没有声明。