都 2021 年了還不會連 ES6/ES2015 更新了什么都不知道吧
- es6 / es2015
- let & const
- 塊級作用域
- 解構
- 陣列解構
- 物件解構
- 模板字串
- Math + Number + String + Object 擴展方法
- 默認引數
- 剩余 與 展開 運算子
- 箭頭函式
- 物件字面量
- 代理 Proxy
- 反射 Reflect
- 期約 Promises
- 類
- 類的繼承
- 內置物件可被繼承
- 類的靜態方法
- Map + Set + WeakMap + WeakSet
- 迭代器 & 生成器 & for of
- 迭代器
- 生成器
- for of
- Symbols
- Modules
- Unicode
- 二進制 與 八進制字面量
JavaScript 的歷史也算是非常久遠了,最早能追溯回 90 年代,網景公司和微軟為了能夠搶占市場大打出手,分別推出了只有自家產品才能夠使用的 JavaScript 版本,這就讓開發們非常頭疼了——寫一份代碼已經很難了,為了適配多端兼容還得再整一個一樣,又不完全一樣的版本出來,
為了解決這個問題,最終由 ECMA International(前身為歐洲計算機制造商協會)統一規范,并且將該規范命名為 ECMAScript,作為 JavaScript 的統一標準,
自此,神仙打架的日子結束了,開發們的日子也稍微好過一些了,
事件繼續推進到了 2008 年后,移動端的興起對 JavaScript 的影響是巨大的,也因此,社區中的大神對于那些功能應該被放入當時的 ES5 而產生了極大的分歧,最終導致了 ES6 的標準的限定也陷入了滯后,
從現在的角度看來,ES6 的變化真的可以說是翻天覆地的變化,也許,這樣的變化對于當時的開發來說還是太早了,
時間又向后推進幾年,到了 2015 年,ECMAScript6 終于作為正式版本發布,官方名稱為 ECMAScript 2015,旨在更頻繁的進行少量的迭代,并且,從 ECMAScript6(ES6/ES2015)之后,所有的版本迭代將會以 ECMAScript + 年份 作為正式的官方名稱,
歷史講完了,下面就肩帶概述一下從 ES2015-ES2021 分別都有什么新特性,
es6 / es2015
最主要的版本迭代,推出了很多對于 JavaScript 來說革命性的新特性,
let & const
在 ES6 之前,JavaScript 想要申明變數只能使用關鍵字 var,JavaScript 特有的作用域提升讓 var 具有一些特殊的行為,如:
for (var i = 0; i < 5; i++) {}
console.log(i); // 5
使用 let 和 const 可以很好地規避這個問題,上面同樣的代碼,以 let 為例:
for (let i = 0; i < 5; i++) {}
console.log(i); // Uncaught ReferenceError: i is not defined
const 與 let 最大的區別有兩點:
- let 的值是可變的,而 const 是不可變的,如:
let b = 10; // 10
b = 20; // 20
const c = 10; // 10
c = 20; // Uncaught TypeError: Assignment to constant variable.
- let 初始化可以不用給值,const 不給值會報錯
let b; // undefined
const c; // Uncaught SyntaxError: Missing initializer in const declaration
所以,目前的推薦用法是:
- 規避使用 var 去宣告變數
更多關于作用域提升的內容,可以看之前記得筆記:var, let, const 的區別與相同
塊級作用域
即 花括號{},這也是 ES6 新增特性之一,在花括號之中的內容獨立作為一個作用域,外部無法訪問,
在塊級作用域搭配 let 和 const 可以有效地避免變數名污染的問題,如:
for (let i = 0; i < 3; i++) {
for (let i = 0; i < 3; i++) {
console.log(i);
}
}
// 0
// 1
// 2
// 0
// 1
// 2
// 0
// 1
// 2
因為外部作用域無法訪問到內部的變數,而內部的 i 會就近尋找作用域上的變數,為了方便理解,還是建議將內外回圈體重的變數取不同的名字,
解構
快速提取變數的方法,可以用于陣列和物件,
陣列解構
陣列解構與根據 index 取值有些像:
const testArr = [1, 2, 3];
const [val1, val2, val3] = testArr;
console.log(val1, val2, val3); // 1, 2, 3
// 等同于
const val1 = testArr[0],
val2 = testArr[1],
val3 = testArr[2];
console.log(val1, val2, val3); // 1, 2, 3
在學習演算法的程序中就會聽經常的用到陣列解構:
// 用陣列解構
function swap(arr, i, j) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
// 不用陣列解構
function swap2(arr, i, j) {
const temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
在作業中,在拆解固定值也挺方便的,例如說經常會出現有些字串使用 - 進行分割:
const [base, str2] = someStr.split('-');
// 不用陣列解構
const splittedStr = someStr.split('-');
const base = splittedStr[0],
str2 = splittedStr[1];
物件解構
根據屬性名進行解構,解構中的變數名一定是匹配類中的屬性名的,使用方法如下:
const getUserAddress = (metaData) => {
return {
zipcode: 123456,
addr1: 'some addr',
addr2: 'some addr2',
state: 'some state',
// 其他屬性...
};
};
const { zipcode, addr1, addr2, state } = getUserAddress('dummy data');
// 或提取常用的函式出來,假設做數學運算,經常會用到一些數學常量和函式,就可以先提取出來
const { PI, abs, sin } = Math;
const { log } = console;
log(sin(PI));
對于解決變數名重復,ES6 也已經有了自己的解決方法,依舊以上面的函式為例:
const state = 'placeholder';
const getUserAddress = (metaData) => {
return {
zipcode: 123456,
addr1: 'some addr',
addr2: 'some addr2',
state: 'some state',
// 其他屬性...
};
};
const {
zipcode,
addr1,
addr2,
state: currentState,
} = getUserAddress('dummy data');
模板字串
非常好用的特性之一,它最大的特點就在于能夠拼接變數以及保證原有的格式,如果說有的時候需要寫一點 HTML 的東西,就非常的有用了:
// 拼接字串
const fullName = `${firstName} ${lastName}`;
// 保證原有格式增強可讀性
const tempHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
`;
console.log(tempHtml);
/*
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
*/
效果圖如下:
二者混用其實是最能夠體現它效果強大的地方:
// 準備要發送出去的html
const tempHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
${bodyEl}
</body>
</html>
`;
Math + Number + String + Object 擴展方法
-
Math
增加了一些常見的變數,如 Number.EPSILON,最后用的就是增加了兩個非常有用的靜態方法:
和一些數學中常用的計算方法,如 acosh,sin 等,除非特定專案,否則用的比較少,
-
Number
新增了對 NaN 和無窮大的處理的靜態函式,
Number.isInteger(Infinity); // false Number.isNaN('NaN'); // false -
String
-
includes,是否包含指定字串
-
startsWith,是否以該字串開頭
-
endsWith,是否以該字串結尾
-
repeat,重復字串
使用方法:
const msg = 'Error: some error message'; msg.includes('Error'); // true msg.startsWith('Error'); // true msg.endsWith('Error'); // false '6'.repeat(3); // '666s' -
-
Object
Object.assign(target, ...sources),將所有可列舉屬性的值從衣蛾或多個源物件分配到目標物件,是一個淺復制的好方法,
默認引數
現在 JavaScript 可以為形參提供默認值,這樣的好處就在于可以省略掉一些空值的判斷,如:
function func(id, name, age, interests = []) {
// 進行操作
}
// 以前可能會寫
function func(id, name, age, interests) {
if (interest && interest.length > 0) {
// 進行操作
}
}
現在就算再呼叫函式時沒有提供引數,也可以直接進行回圈操作了,
注:需要提供默認值的引數一定是放在最后的引數,所以才能做到在沒有傳值的時候設定默認值,
剩余 與 展開 運算子
即 ... 運算子,可以用來傳遞剩余的引數,也可以用來展開陣列或是物件,
-
當用來傳遞剩余引數時
可以用來展開所有的引數,并且以陣列的形式在函式內被呼叫,
這要哪個的優點在于可以避免使用
arguments來獲取所有的引數,同時,因為...會獲取剩余所有的引數,因此只能放置在引數的最后,并且只能用 1 次,function func(...args) { console.log(args); // [1,2,3,4,] } func(1, 2, 3, 4); -
當用來展開時
可以用來展開資料,同樣也可以用來做淺拷貝:
const test = [1, 2, 3, 4, 5]; const copy = [...test]; console.log(copy); // [1, 2, 3, 4, 5] const test2 = { name: 'test', age: 18 }; const copy2 = { ...test2 }; console.log(copy2); // { name: 'test', age: 18 } // 當需要輸出一些值,就不需要用 test[0] test[1] 這樣扣值 console.log(...test); // 1 2 3 4 5
箭頭函式
現在比較推薦的寫法,可以簡化定義函式的代碼,語法為:
const/let/var 函式名 = (引數) => {}
// 使用傳統函式
function foo(val) {
return val + 1;
}
// 使用箭頭函式
// 當只有一行時,可以忽略花括號直接在 => 后寫要回傳的值
const foo = (val) => val + 1;
// 回傳多行時,可以用圓括號包起來
const foo = (val) => (
<div>
<div>paragraph1</div>
<div>paragraph2</div>
</div>
);
// 當操作有多行時,需要擁花括號包起來,并在 return 后申明回傳值
const foo = (val) => {
console.log(val);
return val + 1;
};
與普通的函式相比,并且沒有自己的 this,arguments,super 或 new.target,
箭頭函式可以用來取代以前使用匿名函式的地方,
物件字面量
有了以下幾個有用的語法糖
-
當變數名一致時,可以省略變數名
const data = { name: 'name', age: 18 }; const testData = { 'Content-Type': 'text/plain', data, }; console.log(testData); // { 'Content-Type': 'text/plain', data: { name: 'name', age: 18 } } -
計算屬性名,也就是用
[]動態傳遞屬性名這個操作在回圈呼叫時,或者需要動態傳遞引數時非常有用
const data = { name: 'name', age: 18 }; for (const key in data) { console.log(data[key]); } // name // 18
代理 Proxy
ES6 新推出的代理功能,Proxy 是對于目標物件的抽象,在實際操作目標物件時,可以通過 Proxy 實作對基本操作的攔截和自定義,
語法為:
const p = new Proxy(target, handler)
我覺得 MDN 中,通過 Proxy 系結驗證的案例挺好的:
let validator = {
set: function (obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// The default behavior to store the value
obj[prop] = value;
// 表示成功
return true;
},
};
let person = new Proxy({}, validator);
person.age = 100;
console.log(person.age);
// 100
person.age = 'young';
// 拋出例外: Uncaught TypeError: The age is not an integer
person.age = 300;
// 拋出例外: Uncaught RangeError: The age seems invalid
通過將驗證抽離出來,能夠對驗證方法進行有效復用,在做資料驗證時,也能夠省去很多功夫,
反射 Reflect
Reflect 是一個內置的靜態物件,它提供攔截 JavaScript 操作的方法,它的方法與 proxy handlers 的方法相同,
mdn 上的用法也很全:
const duck = {
name: 'Maurice',
color: 'white',
greeting: function () {
console.log(`Quaaaack! My name is ${this.name}`);
},
};
Reflect.has(duck, 'color');
// true
Reflect.has(duck, 'haircut');
// false
可能是說 Reflect 主要的作用還是為了能夠提供一個標準的操作方法,如:
// 判斷是否有某個屬性
if (property in obj) {
}
// 洗掉某個屬性
delete obj.proprety;
// 獲取所有的屬性名
Object.keys(obj);
// 使用 Reflect 進行同樣的操作
Reflect.has(obj, property);
Reflect.deleteProperty(obj, property);
Reflect.ownKesy(obj);
相比較而言,Reflect 能夠提供一個更加統一的介面呼叫標準,對于新手而言,查檔案也會方便一些,
期約 Promises
提供了一種更好地解決異步編程的方案,通過鏈式呼叫扁平化函式呼叫,解決回呼地獄的問題,
傳統的寫法可能會這么寫:
function fetchData(callback) {
fetchData2((err, data) => {
if (err) return handleRejected2;
fetchData3((err, data) => {
if (err) return handleRejected3;
fetchData4((err, data) => {
// ...
});
});
});
}
但是使用了 Promiise 之后,就可以使用 then 去鏈式呼叫:
const promise = new Promise(resolve, reject);
promise
.then(fetchData, handleRejected1)
.then(fetchData2, handleRejected2)
.then(fetchData3, handleRejected3)
.then(fetchData4, handleRejected4);
從結構上來說也會更加的清晰一些,
類
以前是采用 function 的原型鏈繼承法去實作的繼承,如:
function Person(name) {
this.name = name;
}
// 添加新的原型鏈繼承方法
Person.prototype.work = function () {
console.log('996是福報……?');
};
而使用 class 的結構會更加的清晰:
class Person {
constructor(name) {
this.name = name;
}
work() {
console.log('996是福報……?');
}
}
其實之前有其他的語言基礎的話,使用 class 理解起來應該非常的輕松——和其他的語言非常的相似,
類的繼承
而類的繼承的方法,也與其他的語言看起來非常相似,下面是來自 MDN 的例子:
class Polygon {
constructor(height, width) {
this.name = 'Polygon';
this.height = height;
this.width = width;
}
sayName() {
ChromeSamples.log('Hi, I am a ', this.name + '.');
}
sayHistory() {
ChromeSamples.log(
'"Polygon" is derived from the Greek polus (many) ' + 'and gonia (angle).'
);
}
}
class Square extends Polygon {
constructor(length) {
super(length, length);
this.name = 'Square';
}
getArea() {
return this.height * this.width;
}
}
內置物件可被繼承
內置物件,如 Array, Date 等可被用來繼承:
// User code of Array subclass
class MyArray extends Array {
constructor(...args) {
super(...args);
}
}
var arr = new MyArray();
arr[1] = 12;
arr.length == 2;
類的靜態方法
注意,靜態方法是直接掛載在類上,而不是類的實體上,
目前感覺主要的呼叫方法就是回傳一個新的物件:
class Square extends Polygon {
constructor(length) {
super(length, length);
this.name = 'Square';
}
static create(name) {
return new Square(name);
}
}
const square = Square.create('square');
Map + Set + WeakMap + WeakSet
JavaScript 中的 Map, Set, WeakMap, WeakSet 之前的筆記寫的還挺詳細的,
這里放幾個關鍵點:
-
Map 存盤的是鍵值對
-
對于多數 Web 開發來說,使用 Object 還是 Map 只是個人偏好問題,影響不大,不過對于在乎性能和記憶體的用戶來說,二者還是有區別的
Object Map 記憶體占用 瀏覽器實作不同 瀏覽器實作不同 \newline 但是給定固定記憶體大小,Map 能比 Object 多存盤 50%的鍵值對 插入性能 大致相當 大致相當 \newline 一般來說性能會稍微好一些 插入順序 不會維護 會維護 查找速度 大型資料源中,Object 和 Map 的性能差異極小 \newline 但是資料量比較小的情況下,使用 Object 更快 \newline 所以涉及大量查找的情況使用 Object 會比較好 大型資料源中,Object 和 Map 的性能差異極小 洗掉性能 使用 delete 洗掉物件的屬性一直為人所詬病 \newline 在 JavaScript 高級程式設計第四章學習筆記 中也提到過: \newline 另外,使用 delete 操作也會動態更新屬性,從而使得兩個類無法共享同樣的隱藏類 \newline 很多時候都會使用將屬性值設定為 null 或 undefined 作為折中 相比較而言,Map 的 delete 方法都比插入和查找更快 \newline 如果代碼涉及到大量洗掉的操作,那么毋庸置疑的應該使用 Map -
WeakMap 可以擬態私有變數
-
WeakMap 很適合關聯 DOM 節點元資料
-
Set 的很多 API 與行為與 Map 是共有的,因此操作上會有一些相似性
-
WeakSet 基本 API 與 Set 相似,基本特性與 WeakMap 相似
迭代器 & 生成器 & for of
又是一篇寫過筆記的內容:JavaScript 高級程式設計第 7 章 迭代器和生成器 學習筆記
迭代器
迭代器主要是抽象了內部的迭代方法,傳統用法中,如果要迭代某個物件,例如說陣列,那么就一定需要對它的結構有所了解,
但是在實際開發中,下游的開發人員并不可能了解所有封裝后物件的結構,因此,對封裝后物件中的內容進行迭代就成了一個比較苦惱的事情,
這里舉一個簡單的例子:
// 商品串列
class ProductList {
constructor() {
// 初始化所有的產品
this._productList = [];
}
addItem(name, price) {
// 這里偷懶就新建一個類,而是直接用物件存盤了
const product = {
id: this._productList.length + 1,
name,
price,
date: new Date(),
};
this._productList.push(product);
}
// 實作獲得所有商品的迭代
[Symbol.iterator]() {
// 注意 this 的指向問題,return中的next是一個閉包,指向會變
let count = 0;
const length = this._productList.length,
productList = this._productList;
return {
next() {
if (count < length) {
return { done: false, value: productList[count++] };
} else {
return { done: true, value: undefined };
}
},
};
}
}
const productList = new ProductList();
productList.addItem('apple', 10);
productList.addItem('pear', 5);
for (product of productList) {
console.log(product);
// { id: 1, name: 'apple', price: 10, date: 2021-06-07T14:15:42.994Z }
// { id: 2, name: 'pear', price: 5, date: 2021-06-07T14:15:42.994Z }
}
在這個案例之中就能夠看出來,當我們需要迭代整個商品串列去獲得單獨的一個商品時,并不需要關注商品串列的結構是什么,只需要呼叫 for of 即可,
如果明天需求變了,產品的儲存方式從陣列變成了 Map,或是其他的自定義結構了,那么只要實作了迭代器的介面,就一直可以使用 for of 去獲得商品,
也因此,只要修改 迭代器 中的代碼就可以了,而不需要牽一發動全身,上下尋找參考到處修改,
生成器
生成器 是實作了 迭代器介面的 自定義迭代器,
比起迭代器來說它更加的靈活,具有通過 yield,在函式塊內實作暫停和恢復執行代碼的能力,
語法為:
function* func(){}
通常來說生成器的用法更多,因為它的實作方法簡單,并且實作了迭代器介面,但是生成器是一個函式,因此,當處理的情況比較復雜(很少見)時,就不得不用迭代器去實作了,
for of
for of 是 ES6 新增的迭代方法,它可以用在任何實作了迭代器介面的集合上,
比起 forEach 而言,for of 具有可以使用 break 去中斷操作的屬性,而非強制性的使用 return 去回傳整個函式,
Symbols
Symbol 是 ES6 新增的一個基本型別,可以接受字串作為自身的描述,
它具有不可復制性,因此可以被用來實作其他類的擴展功能——使用 Symbol 作為物件屬性,就不用擔心會重寫已有的函式了,
另外,因為 Symbol 具有不可復制性,所以就算在作用域之外重新定義一個具有相同字串描述的 Symbol 也不代表它們的參考地址相同,因此,也可以用來模擬私有變數,
Modules
ES6 自帶的模塊屬性,算是對 import/export 實作的一個標準吧,不需要再借用社區的規范了,自帶嚴格模式,
使用方式如下:
// lib/math.js
export function sum(x, y) {
return x + y;
}
export var pi = 3.141593;
// app.js
import * as math from 'lib/math';
console.log('2π = ' + math.sum(math.pi, math.pi));
Unicode
JavaScript 完全支持 unicode:
// same as ES5.1
'𠮷'.length == 2;
// new RegExp behaviour, opt-in ‘u’
'𠮷'.match(/./u)[0].length == 2;
// new form
'\u{20BB7}' == '𠮷';
'𠮷' == '\uD842\uDFB7';
// new String ops
'𠮷'.codePointAt(0) == 0x20bb7;
// for-of iterates code points
for (var c of '𠮷') {
console.log(c);
}
二進制 與 八進制字面量
即宣告二進制使用 b,八進制使用 o:
0b111110111 === 503; // true
0o767 === 503; // true
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/286329.html
標籤:其他
