ES6语法快速入门(1)| 青训营笔记

92 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的的第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还没有声明。