ES6标准入门 学习笔记 (01)

77 阅读4分钟

引入

系统学习ES6各种特性,了解背后的原理。

深入学习了let const的使用,解构赋值的各类用法。

笔记

1. let

ES6新增的letvar基本相同,区别有几点

1.1 let只在代码块内有效

for循环中for()是父级作用域,{}内是子作用域

for( let i =0; i < 10; i++){}的循环中,每次都会生成新的i

( js引擎中会记住上一次的i,用于生成下一次的i )

1.2 let不存在变量提升

var声明的变量可以在声明之前使用(但是还是要声明的),即“变量提升”现象

let变量不允许先用再声明,会报ReferenceError

1.3 暂时性死区

ES6规定,如果区块中存在const或者let,则该区块中对这些变量从一开始就形成了封闭作用

(在区块中声明之前不能使用,不能访问到全局的值)

let i = 1;
if (true){
    console.log(i); //报错
    let i = 2;
    console.log(i);
}
let i = 1;
if (true){ 
    const i = 2;
    console.log(i); //可以正常运行,输出2
}

1.4 相同作用域内不允许重复声明同一个变量

{
    var j = 2; //报错,重复声明
    let j = 1;
}

2. 块级作用域

块级作用域(条件语句等等后面常见)

可以嵌套使用,也可以直接使用{}来形成一个新的块级作用域

2.1 let的使用

使用let的好处是外层作用域无法读取内层作用域的变量,避免变量泄露、避免内层新声明的覆盖外层

2.2 函数声明

ES6规定函数声明也遵循块级作用域,但浏览器中不遵循

(浏览器中为了减少对旧代码产生的不兼容问题,规定函数声明function(){}的效果类似于var

所以若需要这样使用,请使用函数表达式形式let f = function(){};

3. const

const声明的常量在其所有作用域内不能重新声明、不能改变,而且一声明就要赋值

(除了不可变相关的特性,基本和let一致)

不可变的本质是指向的内存地址不可变,所以对于对象、数组还是可以修改其内容的

若想真正冻结一个对象,要使用Object.freeze(obj)

此后的obj.xxx = "xxx"在常规模式下无效,严格模式下报错

(但是想彻底冻结,除了将对象冻结、还要将属性再冻结,有点深拷贝的意思)

const constantize = (obj)=>{
    Object.freeze(obj);
    for (let key in obj){
        if (typeof obj[key] === 'object')
            constantize(obj[key]); //递归冻结
    }
}

4. 顶层对象

在ES5中顶层对象(浏览器中为windownode下是global)的属性等同于全局变量

所以声明的全局变量会添加到顶层对象中

在ES6中为了改变这一点并兼顾兼容性:

var, function声明的全局变量仍然等同于顶层对象的属性

let, const, class声明的不属于(隔离)

var a = 1;
window.a; //1

let b = 1;
window.b; //undefined

5. 变量的解构赋值

ES6允许按照一定模式从数组和对象中提取值,然后对变量进行赋值,称为解构

5.1 数组

let = [a,b,c] = [1,2,3]; 表示从数组中提取值,按位置对变量进行赋值

还有更多解构的用法

let t = [1,2,3]
let [, , c, d] = t;
c; \\3
d; \\undefined 因为解构不成功

let [x,[y],[z]] = [1, [2,3], [4]];
x; \\1
y; \\2 属于部分解构(只匹配了部分)
z; \\4 本质上属于模式匹配,只要两侧模式相同就会赋与对应值

但若右侧是不可迭代的结构(没有iterator接口),那么会报错如let [foo] = {};

甚至能设置默认值(当匹配到的值严格等于undefined时才会取默认值)

let [foo = 1] = [];
let [foo = f()] = [1]; //对于表达式是惰性的,只有结构失败才会调用f()进行表达式计算

5.1对象

对象的解构,按对象属性的名称进行取值(与位置无关)

let {foo, bar, ttt} = { foobar:"ccc", foo:"aaa", bar:"bbb" };
foo; // "aaa"
bar; // "bbb"
ttt; // undefined 解构失败

名字不一致时,要写成这样:

let {foo:ttt} = { foobar:"ccc", foo:"aaa", bar:"bbb" };
ttt; // "aaa" (ttt才是要被赋值的对象,foo只是在右侧中查找的键值)

实际上,之前的解构是let {foo:foo, bar:bar} = {foo:"aaa", bar:"bbb"}的简写。

数组解构和对象解构可以嵌套使用:

let obj = {
    p:['hello', {y:'world'}]
}

let {p:[x, {y:z}]} = obj;
x; // "hello" 
z; // "world" 因为此时p和y都是匹配的模式

// 若还需要两个值,可以这样写
let {p, p:[x, {y, y:z}]} = obj;

let {foo:{bar}} = {foobar:"aaa"} // 报错 因为foo为undefined,再取其子属性时会报错

总之注意模式和变量的使用,可以多次取模式来赋给不同变量不同的值

此外对象的解构也可以使用默认值,注意等号要加在变量的后面

当对象匹配到的属性严格等于undefined时才会取默认值

let {x, y=2, z:t=5} = {x:1, q:666};
x; //1
y; //2
t; //5

由于数组是特殊的对象,所以也可以对数组进行对象解构

let arr = [2,3,4];
let {0:a, 1:b, 2:c} = arr;
a; // 2
b; // 3
c; // 4

5.3 字符串的解构赋值

字符串可以看做数组对象

let [a,b,c] = "你干嘛"
console.log(a,b,c); // 你 干 嘛

5.4 数值和布尔值

解构赋值时,如果等号右侧是数值和布尔值,则先转为对象。

let {toString:foo} = 123;
foo; //f toString(){} 此时123被转化为对象,foo被赋值为对象的toString属性

5.5 函数形参的解构赋值

传入数组或者对象时可以进行解构赋值(微信小程序的api中大量使用这种技巧)

function add([a,b]){return a+b};
add([1,2,666]); // 3

function foobar({foo, bar="ccc"}){
   console.log(foo, bar);
}
foobar({foo:"aaa"}); // aaa ccc

5.6 关于圆括号

解构赋值的编译器解析,不能一开始就知道一个式子到底是模式还是表达式。

所以只要可能导致歧义的解构赋值都不能使用圆括号()

总之,可以使用表达式但建议不要使用任何圆括号

5.7 解构赋值的更多应用

  1. 交换变量的值
let a = 1;
let b = 2;
[a,b] = [b,a]
  1. 取函数返回的多个值
function example(){return {a:1,b:2,c:3}};
let {a:x, b, c} = example();
console.log(x,b,c); // 1 2 3
  1. 遍历Map结构体

Map结构体实现了Iterator接口,可以用for...of进行遍历

但遍历时每次得到的值是一个键值对数组,可以用数组解构进行优化

let map = new Map();
map.set('a', 'foo');
map.set('b', 'bar');

for ( let [key, val] of map ){console.log(key, val)};
// a foo
// b bar
  1. 载入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");