前言
JavaScript ES6 虽是我熟知的知识,但在实战中常有疏漏。部分 API 的运用细节总是记不牢,导致写出不够优雅的代码。因此,我决心再次系统温故 ES6,夯实基础知识。
这一次,我着重独立思考,思索 API 设计初衷及实战场景下的运用。毕竟,语法知识终须内化,才能够更灵活的运用。我将全面盘点 ES6 主要的语法特性,剖析示例。
ES6 知识点很多,本文着重核心部分。其他如 Promise、Class 等知识点另启专篇。希望通过这次温故知新,提高我 JavaScript 的技术能力,重塑 JavaScript 基石,稳健前行。
本文并不是一字一句的解释每个 API 的用法,而是主要展示我在复习这些 API 的个人思考以及相关 API 使用示例。
字面量增强
// 定义一个变量
let age = 18;
// {} 为字面量语法
// let obj = {}
let obj = {
// age: age
// 属性简写
age,
// getName: function() {}
// 函数简写
getName() {},
// 计算属性名
[age + 222]: 222,
};
obj[age + 123] = 123;
console.log(obj);
数组解构
let ageList = [12, 18, 25];
// 1. 数组解构(按顺序解构)
let [a1, a2, a3] = ageList;
console.log(a1, a2, a3); // 12 18 25
// 2. 解构后面数据, 用空逗号分割前面的数据
let [, , a4] = ageList;
console.log(a4); // 25
// 3. 解构时将后续数据放一个数组
let [b1, ...otherList] = ageList;
console.log(b1, otherList); // 12 [18, 25]
// 4. 解构时的默认值, ageList 只有 3 个数据,所以 c4 本来为 undefined,这里赋值默认值
let [c1, c2, c3, c4 = 88] = ageList;
console.log(c1, c2, c3, c4);
对象解构
let obj = {
name: 'coder',
age: 18,
};
// 属性名和变量名相同的简写形式;且是根据属性名来匹配的,所以和解构的顺序无关
let { age, name } = obj;
console.log(name, age); // coder 18
// 变量重命名
let { name: newName } = obj;
console.log(newName);
// 赋默认值
// 这个用法应该用的很少,我在工作中重来没用过
let { title: newTitle = '默认值' } = obj;
console.log(newTitle); // 默认值
var / let / const
// FIXME var
// 1. 可以重复声明
// 2. 会作用域提升,虽然访问到的数据是 undefined
console.log(a);
var a = 1;
var a = 2;
// ReferenceError: Cannot access 'a' before initialization
// console.log(b);
let b = 1;
// let b = 2;
// SyntaxError: Identifier 'b' has already been declared
// 此 {} 为一个块级作用域
{
// var 不能在块级作用域中生效
var a1 = 1;
// let/const/function/class 才能在块级作用域中生效
let a2 = 2;
}
console.log(a1);
// console.log(a2);
// if / switch / for 有块级作用域
// FIXME if
if (true) {
var aIf = 1;
let bIf = 1;
}
// console.log(aIf); // 1
// console.log(bIf); // ReferenceError: bIf is not defined
// FIXME switch
// let color = 'red';
// switch (color) {
// case 'red':
// var aSwitch = 1;
// let bSwitch = 1;
// }
// console.log(aSwitch); // 1
// console.log(bSwitch); // ReferenceError: bSwitch is not defined
// FIXME for
const btns = document.getElementsByTagName('button');
// var
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function () {
// 函数内部找不到 i,则向上层作用域查找
// 但是 var 不形成块级作用域
// 因此找到的 i 为全局的变量,即执行完 for 循环的数值
console.log(i); // 一直是 3
};
}
for (let i = 0; i < btns.length; i++) {
// let 形成块级作用域
btns[i].onclick = function () {
console.log(i); // 根据点击不同,分别展示 0 1 2
};
}
// console.log('外部:', i); // 10
// let
// for (let i = 0; i < 10; i++) {
// console.log('内部:', i);
// }
// console.log('外部:', i); // ReferenceError: i is not defined
模板字符串
const age = 18;
console.log('年龄' + ' ' + age);
console.log(`年龄 ${age}`);
函数的默认参数
// ES5 写法
// 缺点:
// 1. 可读性不高
// 2. 麻烦
// 3. 存在漏洞,如 0 和 '' 都被识别为 false,导致入参数据不对
function fn(m, n) {
m = m || 'aaa';
n = n || 'bbb';
console.log(m, n);
}
fn(0, '');
// ES6
function bar(m = 2, n = 1) {
console.log(m, n);
}
bar(); // 2 1
bar(0, ''); // 0 ''
// 函数参数为对象默认值写法
function barObject({ name, age } = { name: '123', age: 18 }) {
console.log(name, age);
}
barObject(); // '123' 18
barObject({ name: 'abc', age: 20 }); // 'abc' 20
// 此处右侧必须写 {} 为整个入参对象的默认值, 否则无法给对应的 name 和 age 赋初始值
// 不写右侧 {} 报错:TypeError: Cannot read properties of undefined (reading 'name')
function barObject1({ name = '123', age = 20 } = {}) {
console.log(name, age);
}
barObject1(); // '123' 20
barObject1({ name: 'abc', age: 22 }); // 'abc' 22
// 默认参数最好放后面,这样在调用时就不用传入多余的 undefined 占位
function test(m, n = 20) {}
function test1(m = 20, n) {
console.log(m, n);
}
test1(undefined, 2);
函数的默认参数
// ...省略号在此处为剩余参数的意思,后续的数据都放作为数据放入该数组中
function fn(m, ...list) {
console.log(m, list);
}
fn(1, 2, 3); // 1 [2, 3]
// 剩余参数必须放最后一个
// SyntaxError: Rest parameter must be last formal parameter
function foo(...m, n) {
console.log(m, n)
}
foo(1, 2, 3);
function bar(n, ...m) {
console.log(n, m);
}
bar(1, 23, 4, 5, 5); // 1 [23, 4, 5, 5]
展开语法
const names = ['abc', 'bbb', 'aaa'];
const name = 'abc';
function fn(x, y, z) {
console.log(x, y, z);
}
// 此处将数组依次展开, 不够的地方直接显示 undefined
fn(...names);
// 将字符串依次拆分,不够的地方直接显示 undefined
fn(...name);
// 数组浅拷贝,拷贝的是引用数据的引用地址
let newNames = [...names];
console.log(newNames); // ['abc', 'bbb', 'aaa]
// 数组合并
let mergeList = [...newNames, ...['test']];
console.log(mergeList); // ['abc', 'bbb', 'aaa', 'test']
// 对象浅拷贝,拷贝的是引用数据的引用地址
let obj = {
name: 'abc',
age: 18,
test: {
name: 'aaa',
},
};
let newObj = {
...obj,
class: 'english',
};
console.log(newObj); // { name: 'abc', age: 18, test: { name: 'aaa' }, class: 'english' }
newObj.test.name = 'newAAA';
// 因为修改的是 obj 内的 test 对象,此时 ...浅拷贝只是拷贝了 test 的引用地址
// 所以修改 newObj.test.name 时会影响 obj.test.name
// 因为实际上 newObj.test 和 obj.test 是同一个引用地址(内存中表现),所以其实就是同一个对象
console.log(obj); // { name: 'abc', age: 18, test: { name: 'newAAA' } }
数值的表示方式
// 十进制
let ten = 100;
// 二进制
let two = 0b100;
// 八进制
let eight = 0o100;
// 十六进制
let sixteen = 0x100;
console.log(ten, two, eight, sixteen); // 100 4 64 256
Symbol
// 工作中暂时没怎么用过,所以简单记录一下就行
// 我发现我进步了,以前遇到这种知识点我肯定又是记得详细的很
// 但是却没有自己的思考,以前不过就是把其他资料摘抄过来而已
// 现在我学会根据自己的工作使用情况,来判定这个知识点的重要性以及如何记录
// 以前那种把所有知识点全部摘抄过来,不过是自我感动罢了
// 现在要学会多独立思考,不要再做这样的自我感动行为了
const s1 = Symbol();
const s2 = Symbol();
console.log(s1, s2); // Symbol() Symbol()
let obj = {
[s1]: 's1',
[s2]: 's2',
};
console.log(obj[s1], obj[s2]); // s1 s2
Set
const set = new Set();
set.add(1);
set.add(2);
set.add(3);
console.log(set); // Set(3) { 1, 2, 3 }
// size
console.log(set.size); // 3
// delete
set.delete(1);
console.log(set); // Set(2) { 2, 3 }
// has
console.log(set.has(1)); // false
console.log(set.has(2)); // true
// forEach
set.forEach((item) => {
console.log(item); // 依次打印 2 3
});
// for of
for (const item of set) {
console.log(item); // 依次打印 2 3
}
// clear
set.clear();
console.log(set); // Set(0) {}
// 数组去重
let arr = [1, 2, 3, 1, 5, 2];
const newSet = new Set(arr);
console.log(newSet); // Set(4) { 1, 2, 3, 5 }
// Set 转数组
const setToArr = Array.from(newSet);
const setToArr1 = [...newSet];
console.log(setToArr); // [ 1, 2, 3, 5 ]
console.log(setToArr1); // [ 1, 2, 3, 5 ]
WeakSet
// WeakSet 和 Set 区别
// 1. WeakSet 只能传入对象格式
// 2. WeakSet 为弱引用,在垃圾回收检查引用情况时,WeakSet 虽然引用着,但是照样会进行垃圾回收
// 3. WeakSet 无法遍历
const weakSet = new WeakSet();
// TypeError: Invalid value used in weak set
// weakSet.add(1);
const obj = {
name: 'abc',
};
// add
weakSet.add(obj);
// has
console.log(weakSet.has(obj)); // true
// delete
weakSet.delete(obj);
console.log(weakSet.has(obj)); // false
Map
// Map 与对象区别:Map 可以使用对象作为 key
const obj1 = {
name: 'abc',
};
const obj2 = {
name: 'abc',
};
const obj3 = {
[obj1]: 'test',
[obj2]: 'test222',
};
// 对象作为其他对象的 key 时,只能直接解析为 '[object Object]',然后导致两个 key 一样,后面的数值会覆盖前面的
console.log(obj3); // { '[object Object]': 'test222' }
const map = new Map();
// Map 的 key 可以是对象和普通类型
map.set(obj1, 't1');
map.set(obj2, 't2');
map.set('test', 'ttt');
console.log(map);
/**
Map(3) {
{ name: 'abc' } => 't1',
{ name: 'abc' } => 't2',
'test' => 'ttt'
}
*/
// size
console.log(map.size); // 3
// get
console.log(map.get('test')); // ttt
// has
console.log(map.has('test')); // true
// delete
console.log(map.delete('test')); // true
// forEach
map.forEach((value, key) => {
console.log(value, key);
// t1 { name: 'abc' }
// t2 { name: 'abc' }
});
// for of
for (const [key, value] of map) {
console.log(key, value);
// { name: 'abc' } t1
// { name: 'abc' } t2
}
// clear
map.clear();
console.log(map); // Map(0) {}
WeakMap
// WeakMap 和 Map 区别
// 1. WeakMap 的 key 只能是对象数据格式
// 2. WeakMap 对 key 中的对象是弱引用,在垃圾回收中,如果该对象除了 WeakMap 之外没有其他引用指向,则会被垃圾回收
const weakMap = new WeakMap();
// TypeError: Invalid value used as weak map key
// weakMap.set(1, 'test');
const obj = {
name: 'abc',
};
// set
weakMap.set(obj, 'test');
// 因为 weakMap 无法遍历,因此打印出来是 <items unknown>
console.log(weakMap); // WeakMap { <items unknown> }
// get
console.log(weakMap.get(obj)); // test
// has
console.log(weakMap.has(obj)); // true
// delete
console.log(weakMap.delete(obj)); // true