重温 JavaScript ES6:内化思考&领会精髓

59 阅读7分钟

前言

JavaScript ES6 虽是我熟知的知识,但在实战中常有疏漏。部分 API 的运用细节总是记不牢,导致写出不够优雅的代码。因此,我决心再次系统温故 ES6,夯实基础知识。

这一次,我着重独立思考,思索 API 设计初衷及实战场景下的运用。毕竟,语法知识终须内化,才能够更灵活的运用。我将全面盘点 ES6 主要的语法特性,剖析示例。

ES6 知识点很多,本文着重核心部分。其他如 PromiseClass 等知识点另启专篇。希望通过这次温故知新,提高我 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