引入
系统学习ES6各种特性,了解背后的原理。
深入学习了let const的使用,解构赋值的各类用法。
笔记
1. let
ES6新增的let与var基本相同,区别有几点
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中顶层对象(浏览器中为window,node下是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 解构赋值的更多应用
- 交换变量的值
let a = 1;
let b = 2;
[a,b] = [b,a]
- 取函数返回的多个值
function example(){return {a:1,b:2,c:3}};
let {a:x, b, c} = example();
console.log(x,b,c); // 1 2 3
- 遍历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
- 载入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");