寫本篇文章目的是爲了夯實基礎,基於阮一峯老師的著做 ECMAScript 6 入門 以及 tc39-finished-proposals 這兩個知識線路總結提煉出來的重點和要點,涉及到從 ES2015
到 ES2021
的幾乎全部知識,基本上都是按照一個知識點配上一段代碼的形式來展現,因此篇幅較長,也正是由於篇幅過長,因此就沒把 Stage 2
和 Stage 3
階段的提案寫到這裏,後續 ES2021
更新了我再同步更新。html
有 5 個提案已經列入 Expected Publication Year in 2021 因此本篇中暫且把他們歸爲 ES2021。前端
因爲篇幅限制,因此將文章分紅了 2 篇,想要看完整版的能夠直接看個人:github bloggit
能寫好 JS
當然是重要的,可是做爲一個前端,咱們也要了解本身所使用語言的發展歷程,這裏強烈推薦看 《JavaScript 20年》,本書詳細記載和解讀了自 1995 年語言誕生到 2015 年 ES6
規範制定爲止,共計 20 年的 JavaScript
語言演化歷程。es6
2011 年,發佈了 ECMAScript 5.1
版,而 2015 年 6 月發佈了 ES6
的第一個版本又叫 ES2015
。ES6
實際上是一個泛指,指代 5.1 版本之後的下一代標準。TC39
規定將於每一年的 6 月發佈一次正式版本,版本號以當年的年份爲準,好比當前已經發布了 ES2015
、ES2016
、ES2017
、ES2018
、ES2019
、ES2020
等版本。github
任何人均可以向 TC39
提案,要求修改語言標準。一種新的語法從提案到變成正式標準,須要經歷五個階段。每一個階段的變更都須要由 TC39
委員會批准。正則表達式
Stage 0
- Strawperson
(展現階段)Stage 1
- Proposal
(徵求意見階段)Stage 2
- Draft
(草案階段)Stage 3
- Candidate
(候選人階段)Stage 4
- Finished
(定案階段)一個提案只要能進入 Stage 2
,就差很少確定會包括在之後的正式標準裏面。ECMAScript
當前的全部提案,能夠在這裏查看 ecma262。關於提案流程能夠在這裏 TC39_Process 看到更加詳細的信息。算法
const
:聲明一個常量,let
:聲明一個變量;const/let
聲明的常量/變量都只能做用於代碼塊(塊級做用域或函數做用域)裏;數組
if (true) {
let name = '布蘭'
}
console.log(name) // undefined
複製代碼
const/let
不存在變量提高,因此在代碼塊裏必須先聲明而後纔可使用,這叫暫時性死區;瀏覽器
let name = 'bubuzou'
if (true) {
name = '布蘭'
let name
}
console.log(name)
複製代碼
const/let
不容許在同一個做用域內,重複聲明;緩存
function setName(name) {
let name = '' // SyntaxError
}
複製代碼
const
聲明時必須初始化,且後期不能被修改,但若是初始化的是一個對象,那麼不能修改的是該對象的內存地址;
const person = {
name: '布蘭'
}
person.name = 'bubuzou'
console.log(person.name) // 'bubuzou'
person = '' // TypeError
複製代碼
const/let
在全局做用域中聲明的常量/變量不會掛到頂層對象(瀏覽器中是 window )的屬性中;
var name = '布蘭'
let age = 12
console.log(window.name) // '布蘭'
console.log(window.age) // undefined
複製代碼
解構類型:
字符串解構
let [a, b, c = 'c'] = '12'
console.log(a, b, c) // '1' '2' 'c'
複製代碼
數值解構
let {toFixed: tf} = 10
console.log( tf.call(Math.PI, 2) ) // 3.14
複製代碼
布爾值解構
let {toString: ts} = true
console.log( ts.call(false) ) // 'false'
複製代碼
數組解構:等號右側的數據具備 Iterator
接口能夠進行數組形式的解構賦值;
// 解構不成功的變量值爲 undefined
let [a, b, c] = [1, 2]
console.log(a, b, c) // 1, 2, undefined
// 能夠設置默認值
let [x, y, z = 3] = [1, 2, null]
console.log(x, y, z) // 1, 2, null
複製代碼
什麼樣的數據具備
Iterator
接口呢?若是一個對象可以經過 [Symbol.iterator] 訪問,且可以返回一個符合迭代器協議的對象,那麼該對象就是可迭代的。目前內置的可迭代對象有:String、Array、TypeArray、Map、Set、arguments 和 NodeList 等。
對象解構:與數組按照索引位置進行解構不一樣,對象解構是按照屬性名進行解構賦值,若是在當前對象屬性匹配不成功則會去對象的原型屬性上查找:
// 默認寫法
let { name: name, age: age } = { name: '布蘭', age: 12 }
複製代碼
// 簡寫
let { name, age } = { name: '布蘭', age: 12 }
複製代碼
// 更名且設置默認值
let { name: name1, age: age1 = 12 } = { name: '布蘭' }
console.log(name1, age1) // '布蘭' 12
複製代碼
函數參數解構:其實就是運用上面的對象解構和數組解構規則;
function move({x = 0, y = 0} = {}) {
console.log([x, y])
return [x, y];
}
move({x: 3, y: 8}) // [3, 8]
move({x: 3}) // [3, 0]
move({}) // [0, 0]
move() // [0, 0]
複製代碼
解構要點:
undefined
;null
和 undefined
都沒法轉成對象,因此沒法解構。解構應用:
交換變量的值;
let x = 1, y = 2;
[x, y] = [y, x]
console.log(x, y) // 2 1
複製代碼
經過函數返回對象屬性
function getParams() {
return {
name: '布蘭',
age: 12,
}
}
let {name, age} = getParams()
複製代碼
經過定義函數參數來聲明變量
let person = {
name: '布蘭',
age: 12
}
init(person)
// 普通用法
function init(person) {
let {name, age} = person
}
// 更簡潔用法
function init({name, age}) {}
複製代碼
指定函數參數默認值
function initPerson({name = '布蘭', age = 12} = {}) {
console.log(name, age)
}
initPerson() // '布蘭' 12
initPerson({age: 20}) // '布蘭' 20
複製代碼
提取 JSON
數據
let responseData = {
code: 1000,
data: {},
message: 'success'
}
let { code, data = {} } = responseData
複製代碼
遍歷 Map 結構
let map = new Map()
map.set('beijing', '北京')
map.set('xiamen', '廈門')
for (let [key, value] of map) {
console.log(key, value)
}
複製代碼
輸入模塊的指定方法和屬性
const { readFile, writeFile } = require("fs")
複製代碼
可使用 Unicode
編碼來表示一個字符:
// 如下寫法均可以用來表示字符 z
'\z' // 轉義
'\172' // 十進制表示法
'\x7A' // 十六進制表示法
'\u007A' // Unicode 普通表示法
'\u{7A}' // Unicode 大括號表示法
複製代碼
www.52unicode.com 這個網站能夠查詢到常見符號的 Unicode 編碼。
可使用 for...of
正確遍歷字符串:
let str = '😀🤣😜😍🤗🤔'
for (const emoji of str) {
console.log(emoji) // 😀🤣😜😍🤗🤔
}
for(let i = 0, l = str.length; i < l; i++) {
console.log(str[i]) // 不能正確輸出表情
}
複製代碼
模板字符串使用兩個反引號標識(``),能夠用來定義多行字符串,或者使用它在字符串中插入變量:
let name = 'hero'
let tips = `Hello ${name}, welcome to my world.`
alert( tips )
複製代碼
標籤模板:在函數名後面接一個模板字符串至關於給函數傳入了參數進行調用:
let name = '布蘭', age = 12;
let tips = parse`Hello ${name}, are you ${age} years old this year?`
function parse(stringArr, ...variable) {
}
// 至關於傳遞以下參數進行調用 parse 函數
parse(["Hello ", ", are you ", " years old this year?"], name, age)
複製代碼
String.fromCodePoint()
用於從 Unicode
碼點返回對應字符,能夠支持 0xFFFF
的碼點:
String.fromCharCode(0x1f600) // ""
String.fromCodePoint(0x1f600) // "😀"
複製代碼
String.raw()
返回把字符串全部變量替換且對斜槓進行轉義的結果:
String.raw`Hi\n${2+3}!` // "Hi\n5!"
複製代碼
codePointAt()
返回字符的十進制碼點,對於 Unicode
大於 0xFFFF
的字符,會被認爲是2個字符,十進制碼點轉成十六進制可使用 toString(16)
:
let emoji = '🤣'
emoji.length // 2
emoji.charCodeAt(0).toString(16) // 'd83d'
emoji.charCodeAt(1).toString(16) // 'de00'
String.fromCodePoint(0xd83d, 0xde00) === '🤣' // true
複製代碼
normalize()
方法會按照指定的一種 Unicode
正規形式將當前字符串正規化。(若是該值不是字符串,則首先將其轉換爲一個字符串):
let str1 = '\u00F1'
let str2 = '\u006E\u0303'
str1 // ñ
str2 // ñ
str1 === str2 // false
str1.length === str2.length // false
str1.normalize() === str2.normalize() // true
複製代碼
字符串是否包含子串:
let s = 'Hello world!'
s.includes('o') // true
s.startsWith('Hello') // true
s.endsWith('!') // true
複製代碼
這三個方法都支持第二個參數,表示開始搜索的位置:
let s = 'Hello world!'
s.includes('Hello', 6) // false
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
複製代碼
上面代碼表示,使用第二個參數 n
時,endsWith
的行爲與其餘兩個方法有所不一樣。它針對前 n
個字符,而其餘兩個方法針對從第 n
個位置直到字符串結束。
repeat(n)
將當前字符串重複 n
次後,返回一個新字符串:
'x'.repeat(2) // 'xx'
'x'.repeat(1.9) // 'x'
'x'.repeat(NaN) // ''
'x'.repeat(undefined) // ''
'x'.repeat('2a') // ''
'x'.repeat(-0.6) // '',解釋:0 ~ 1 之間的小數至關於 0
'x'.repeat(-2) // RangeError
'x'.repeat(Infinity) // RangeError
複製代碼
二進制(0b)和八進制(0o)表示法:
let num = 100
let b = num.toString(2) // 二進制的100:1100100
let o = num.toString(8) // 八進制的100:144
0b1100100 === 100 // true
0o144 === 100 // true
複製代碼
Number.isFinite()
判斷一個數是不是有限的數,入參若是不是數值一概返回 false
:
Number.isFinite(-2.9) // true
Number.isFinite(NaN) // false
Number.isFinite('') // false
Number.isFinite(false) // true
Number.isFinite(Infinity) // false
複製代碼
Number.isNaN()
判斷一個數值是否爲 NaN
,若是入參不是 NaN
那結果都是 false
:
Number.isNaN(NaN) // true
Number.isFinite('a'/0) // true
Number.isFinite('NaN') // false
複製代碼
數值轉化:Number.parseInt()
和 Number.parseFloat()
,非嚴格轉化,從左到右解析字符串,遇到非數字就中止解析,而且把解析的數字返回:
parseInt('12a') // 12
parseInt('a12') // NaN
parseInt('') // NaN
parseInt('0xA') // 10,0x開頭的將會被當成十六進制數
複製代碼
parseInt()
默認是用十進制去解析字符串的,其實他是支持傳入第二個參數的,表示要以多少進制的 基數去解析第一個參數:
parseInt('1010', 2) // 10
parseInt('ff', 16) // 255
複製代碼
參考:parseInt
Number.isInteger()
判斷一個數值是否爲整數,入參爲非數值則必定返回 false
:
Number.isInteger(25) // true
Number.isInteger(25.0) // true
Number.isInteger() // false
Number.isInteger(null) // false
Number.isInteger('15') // false
Number.isInteger(true) // false
Number.isInteger(3.0000000000000002) // true
複製代碼
若是對數據精度的要求較高,不建議使用 Number.isInteger() 判斷一個數值是否爲整數。
Number.EPSILON
表示一個可接受的最小偏差範圍,一般用於浮點數運算:
0.1 + 0.2 === 0.3 // false
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON // true
複製代碼
Number.isSafeInteger()
用來判斷一個數是否在最大安全整數(Number.MAX_SAFE_INTEGER
)和最小安全整數(Number.MIN_SAFE_INTEGER
)之間:
Number.MAX_SAFE_INTEGER === 2 ** 53 -1 // true
Number.MAX_SAFE_INTEGER === 9007199254740991 // true
Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER // true
Number.isSafeInteger(2) // true
Number.isSafeInteger('2') // false
Number.isSafeInteger(Infinity) // false
複製代碼
Math.trunc()
:返回數值整數部分
Math.sign()
:返回數值類型(正數 一、負數 -一、零 0)
Math.cbrt()
:返回數值立方根
Math.clz32()
:返回數值的 32 位無符號整數形式
Math.imul()
:返回兩個數值相乘
Math.fround()
:返回數值的 32 位單精度浮點數形式
Math.hypot()
:返回全部數值平方和的平方根
Math.expm1()
:返回 e^n - 1
Math.log1p()
:返回 1 + n 的天然對數(Math.log(1 + n)
)
Math.log10()
:返回以 10 爲底的 n 的對數
Math.log2()
:返回以 2 爲底的n的對數
Math.sinh()
:返回 n 的雙曲正弦
Math.cosh()
:返回 n 的雙曲餘弦
Math.tanh()
:返回 n 的雙曲正切
Math.asinh()
:返回 n 的反雙曲正弦
Math.acosh()
:返回 n 的反雙曲餘弦
Math.atanh()
:返回 n 的反雙曲正切
數組擴展運算符(...)將數組展開成用逗號分隔的參數序列,只能展開一層數組:
// 應用一:函數傳參
Math.max(...[1, 2, 3]) // 3
// 應用二:數組合並
let merge = [...[1, 2], ...[3, 4], 5, 6] // 1, 2, 3, 4, 5, 6
// 應用三:淺克隆
let a = [1, 2, 3]
let clone = [...a]
a === clone // false
// 應用四:數組解構
const [x, ...y] = [1, 2, 3]
x // 1
y // [2, 3]
複製代碼
Array.from()
能夠將類數組對象( NodeList
,arguments
)和可迭代對象轉成數組:
// 應用一:字符串轉數組
Array.from('foo') // ['f', 'o', 'o']
// 應用二:數組合並去重
let merge = [...[1, 2], ...[2, 3]]
Array.from(new Set(merge)) // ['1', '2', '3']
// 應用三:arguments 轉數組
function f() {
return Array.from(arguments)
}
f(1, 2, 3) // [1, 2, 3]
複製代碼
若是 Array.from()
帶第二個參數 mapFn
,將對生成的新數組執行一次 map
操做:
Array.from([1, 2, 3], (x) => x * x ) // [1, 4, 9]
Array.from({length: 3}, (v, i) => ++i) // [1, 2, 3]
複製代碼
Array.of()
將一組參數轉成數組:
Array.of(1, 2, 3) // [1, 2, 3]
// 相似於
function arrayOf(...params){
return [].slice.call(params)
}
arrayOf(1, 2, 3) // [1, 2, 3]
複製代碼
Array.copyWithin()
在當前數組內部,將制定位置的成員複製到其餘位置(會覆蓋原來位置的成員),最後返回一個新數組。接收 3 個參數,參數爲負數表示右邊開始計算:
target
(必選):替換位置的索引;start
(可選):從該位置開始讀取數據,默認爲 0;end
(可選):從該位置結束讀取數據(不包括該位置的數據),默認爲原數組長度;[1, 2, 3, 4, 5].copyWithin(-1) // [1, 2, 3, 4, 1]
[1, 2, 3, 4, 5].copyWithin(1) // [1, 1, 2, 3, 4]
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) // [4, 2, 3, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, -3, -1) // [3, 4, 3, 4, 5]
複製代碼
查找第一個出現的子成員:find()
和 findIndex()
:
// 找出第一個偶數
[1, 6, 9].find((val, index, arr) => val % 2 === 0) // 6
// 找出第一個偶數的索引位置
[1, 6, 9].findIndex((val, index, arr) => val % 2 === 0) // 1
複製代碼
fill()
使用給定的值來填充數組,有 3 個參數:
value
:填充值;start
(可選),開始索引,默認爲 0;end
(可選):結束索引,默認爲數組長度,不包括該索引位置的值;// 初始化空數組
Array(3).fill(1) // [1, 1, 1]
[1, 2, 3, 4].fill('a', 2, 4) // [1, 2, 'a', 'a']
複製代碼
經過 keys()
(鍵名)、entries()
(鍵值)和 values()
(鍵值對) 獲取數組迭代器對象,能夠被 for...of
迭代,
let arr = ['a', 'b', 'c']
for (let x of arr.keys()) {
console.log(x) // 1, 2, 3
}
for (let v of arr.values()) {
console.log(v) // 'a' 'b' 'c'
}
for (let e of arr.entries()) {
console.log(e) // [0, 'a'] [0, 'b'] [0, 'c']
}
複製代碼
數組空位,是指數組沒有值,好比:[,,]
,而像這種 [undefined]
是不包含空位的。因爲 ES6
以前的一些 API
對空位的處理規則很不一致,因此實際操做的時候應該儘可能避免空位的出現,而爲了改變這個現狀,ES6
的 API
會默認將空位處理成 undefined
:
[...[1, , 3].values()] // [1, undefined, 3]
[1, , 3].findIndex(x => x === undefined) // 1
複製代碼
對象屬性簡寫:
let name = '布蘭'
let person = {
name,
getName() {
return this.name
}
}
// 等同於
let person1 = {
name: '布蘭',
getName: function() {
return this.name
}
}
複製代碼
屬性名錶達式:在用對象字面量定義對象的時候,容許經過屬性名錶達式來定義對象屬性:
let name = 'name',
let person = {
[name]: '布蘭',
['get'+ name](){
return this.name
}
}
複製代碼
方法的 name
屬性,存在好幾種狀況,這裏僅列出常見的幾種:
狀況一:普通對象方法的 name
屬性直接返回方法名,函數聲明亦是如此,函數表達式返回變量名:
let person = {
hi(){}
}
person.hi.name // 'hi'
複製代碼
狀況二:構造函數的 name
爲 anonymous
:
(new Function).name // 'anonymous'
複製代碼
狀況三:綁定函數的 name
將會在函數名前加上 bound
:
function foo() {}
foo.bind({}).name // 'bound foo'
複製代碼
狀況四:若是對象的方法使用了取值函數(getter
)和存值函數(setter
),則 name
屬性不是在該方法上面,而是該方法的屬性的描述對象的 get
和 set
屬性上面:
let o = {
get foo(){},
set foo(x){}
}
o.foo.name // TypeError: Cannot read property 'name' of undefined
let descriptor = Object.getOwnPropertyDescriptor(o, "foo")
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
複製代碼
屬性的可枚舉性
對象的每一個屬性都有一個描述對象(Descriptor
),用來控制該屬性的行爲。能夠經過 Object.getOwnPropertyDescriptor()
來獲取對象某個屬性的描述:
let person = { name: '布蘭', age: 12 }
Object.getOwnPropertyDescriptor(person, 'name')
// {
// configurable: true,
// enumerable: true,
// value: "布蘭",
// writable: true,
// }
複製代碼
這裏的 enumerable
就是對象某個屬性的可枚舉屬性,若是某個屬性的 enumerable
值爲 false
則表示該屬性不能被枚舉,因此該屬性會被以下 4 種操做忽略:
for...in
:只遍歷對象自身的和繼承的可枚舉的屬性;Object.keys()
:返回對象自身的全部可枚舉的屬性的鍵名;JSON.stringify()
:只串行化對象自身的可枚舉的屬性;Object.assign()
: 只拷貝對象自身的可枚舉的屬性。let person = { name: '布蘭' }
Object.defineProperty(person, 'age', {
configurable: true,
enumerable: false,
value: 12,
writable: true
})
person // { name: '布蘭', age: 12 }
// 如下操做都將忽略 person 對象的 age 屬性
for (let x in person) {
console.log(x) // 'name'
}
Object.keys(person) // ['name']
JSON.stringify(person) // '{"name": "布蘭"}'
Object.assign({}, person) // { name: '布蘭' }
複製代碼
Reflect.ownKeys(obj)
: 返回一個數組,包含對象自身的(不含繼承的)全部鍵名,無論鍵名是 Symbol
或字符串,也無論是否可枚舉:
// 基於上面的代碼
Reflect.ownKeys(person) // ['name', 'age']
複製代碼
super
關鍵字,指向對象的原型對象,只能用於對象的方法中,其餘地方將報錯:
let person = {
name: '布蘭',
getName() {
return super.name
}
}
Object.setPrototypeOf(person, {name: 'hello'})
person.getName() // 'hello'
// 如下幾種 super 的使用將報錯
const obj1 = {
foo: super.foo
}
const obj2 = {
foo: () => super.foo
}
const obj3 = {
foo: function () {
return super.foo
}
}
複製代碼
Object.is()
用來判斷兩個值是否相等,表現基本和 ===
同樣,除了如下兩種狀況:
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
複製代碼
Object.assign()
用於對象的合併,將源對象(source
)的全部可枚舉屬性,複製到目標對象(target
),若是有同名屬性,則後面的會直接替換前面的:
let target = { a: 1 }
let source1 = { a: 2, b: 3, d: {e: 1, f: 2} }
let source2 = { a: 3, c: 4, d: {g: 3} }
Object.assign(target, source1, source2)
target // { a: 3, b: 3, c: 4, d: {g: 3} }
複製代碼
Object.assign()
實行的是淺拷貝,若是源對象某個屬性是對象,那麼拷貝的是這個對象的引用:
let target = {a: {b: 1}}
let source = {a: {b: 2}}
Object.assign(target, source)
target.a.b = 3
source.a.b // 3
複製代碼
__proto__
屬性是用來讀取和設置當前對象的原型,而因爲其下劃線更多的是表面其是一個內部屬性,因此建議不在正式場合使用它,而是用下面的 Object.setPrototypeOf()
(寫操做)、Object.getPrototypeOf()
(讀操做)、Object.create()
(生成操做)代替。
Object.setPrototypeOf()
用於設置對象原型,Object.getPrototypeOf()
用於讀取對象原型:
let person = {name: '布蘭'}
Object.setPrototypeOf(person, {name: '動物'})
Object.getPrototypeOf(person) // {name: '動物'}
複製代碼
RegExp
構造函數,容許首參爲正則表達式,第二個參數爲修飾符,若是有第二個參數,則修飾符以第二個爲準:
let reg = new RegExp(/xYz\d+/gi, i)
reg.flags // 'i'
複製代碼
正則方法調用變動:字符串對象的 match()
、replace()
、search()
、split()
內部調用轉爲調用 RegExp
實例對應的 RegExp.prototype[Symbol.方法]
;
u
修飾符:含義爲 Unicode
模式,用來正確處理大於 \uFFFF
的 Unicode
字符。也就是說,若是待匹配的字符串中可能包含有大於 \uFFFF
的字符,就必須加上 u
修飾符,才能正確處理。
// 加上 u 修飾符才能讓 . 字符正確識別大於 \uFFFF 的字符
/^.$/.test('🤣') // false
/^.$/u.test('🤣') // true
// 大括號 Unicode 字符表示法必須加上 u 修飾符
/\u{61}/.test('a') // false
/\u{61}/u.test('a') // true
// 有 u 修飾符,量詞才能正確匹配大於 \uFFFF 的字符
/🤣{2}/.test('🤣🤣') // false
/🤣{2}/u.test('🤣🤣') // true
複製代碼
RegExp.prototype.unicode
屬性表示正則是否設置了 u
修飾符:
/🤣/.unicode // false
/🤣/u.unicode // true
複製代碼
y
修飾符,與 g
修飾符相似也是全局匹配;不一樣的是 g
是剩餘字符中匹配便可,而 y
則是必須在剩餘的第一個字符開始匹配才行,因此 y
修飾符也叫黏連修飾符:
let s = 'aaa_aa_a'
let r1 = /a+/g
let r2 = /a+/y
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
r1.exec(s) // ["aa"]
r2.exec(s) // null
複製代碼
RegExp.prototype.sticky
屬性表示是否設置了 y
修飾符:
/abc/y.sticky // true
複製代碼
RegExp.prototype.flags
屬性會返回當前正則的全部修飾符:
/abc🤣/iuy.flags // 'iuy'
複製代碼
函數參數默認值。參數不能有同名的,函數體內不能用 let
和 const
聲明同參數名的變量:
function printInfo(name = '布蘭', age = 12) {}
複製代碼
使用參數默認值的時候,參數不能有同名的:
function f(x, x, y) {} // 不報錯
function f(x, x, y = 1) {} // 報錯
複製代碼
函數體內不能用 let
和 const
聲明同參數名的變量:
// 報錯
function f(x, y) {
let x = 0
}
複製代碼
函數的 length
屬性會返回沒有指定默認值的參數個數,且若是設置默認值的參數不是尾參數,則 length
再也不計入後面的參數:
(function f(x, y){}).length // 2
(function f(x, y = 1){}).length // 1
(function f(x = 1, y){}).length // 0
複製代碼
剩餘(rest
) 參數(...變量名)的形式,用於獲取函數的剩餘參數,注意 rest
參數必須放在最後一個位置,能夠很好的代替 arguments
對象:
function f(x, ...y) {
console.log(x) // 1
for (let val of y) {
coonsole.log(val) // 2 3
}
}
f(1, 2, 3)
複製代碼
嚴格模式:只要函數參數使用了默認值、解構賦值或者擴展運算符,那麼函數體內就不能顯示的設定爲嚴格模式,由於嚴格模式的做用範圍包含了函數參數,而函數執行的順序是先執行參數,而後再執行函數體,執行到函數體裏的 use strict
的時候,那麼此時由於函數參數已經執行完成了,那函數參數還要不要受到嚴格模式的限制呢?這就出現矛盾了。規避限制的辦法有兩個:設置全局的嚴格模式或者在函數體外在包一個當即執行函數而且聲明嚴格模式:
// 解法一
'use strict'
function f(x, y = 2) {
}
// 解法二
let f = (function(){
'use strict'
return function(x, y = 2) {}
})()
複製代碼
箭頭函數語法比函數表達式更簡潔,而且沒有本身的 this
、arguments
,不能用做構造函數和用做生成器。
幾種箭頭函數寫法:
let f1 = () => {} // 沒有參數
let f2 = (x) => {} // 1個參數
let f3 = x => {} // 1個參數能夠省略圓括號
let f4 = (x, y) => {} // 2個參數以上必須加上圓括號
let f5 = (x = 1, y = 2) => {} // 支持參數默認值
let f6 = (x, ...y) => {} // 支持 rest 參數
let f7 = ({x = 1, y = 2} = {}) // 支持參數解構
複製代碼
箭頭函數沒有本身的 this
:
function Person(){
this.age = 0
setInterval(() => {
this.age++
}, 1000)
}
var p = new Person() // 1 秒後 Person {age: 1}
複製代碼
經過 call/apply
調用箭頭函數的時候將不會綁定第一個參數的做用域:
let adder = {
base: 1,
add: function(a) {
let f = v => v + this.base
return f(a)
},
addThruCall: function(a) {
let f = v => v + this.base
let b = {
base: 2
}
return f.call(b, a)
}
}
adder.add(1) // 輸出 2
adder.addThruCall(1) // 仍然輸出 2
複製代碼
箭頭函數沒有本身的 arguments
對象,不過可使用 rest
參數代替:
let log = () => {
console.log(arguments) // ReferenceError
}
log(2, 3)
// 剩餘參數代替寫法
let restLog = (...arr) => {
console.log(arr) // [2, 3]
}
restLog(2, 3)
複製代碼
箭頭函數不能用做構造器,和 new
一塊兒用會拋出錯誤:
let Foo = () => {}
let foo = new Foo()
// TypeError: Foo is not a constructor
複製代碼
箭頭函數返回對象字面量,須要用圓括號包起來:
let func2 = () => ({foo: 1})
複製代碼
尾調用和尾遞歸
首先得知道什麼是尾調用:函數的最後一步調用另一個函數:
// 是尾調用
function f(x) {
return g(x)
}
// 如下都不是尾調用
function f(x) {
let y = g(x)
return y
}
function f(x) {
let y = g(x)
return g(x) + 1
}
function f(x) {
g(x)
// 由於最後一步是 return: undefined
}
複製代碼
尾調用有啥用?咱們知道函數的相互調用是會生成「調用幀」的,而「調用幀」裏存了各類信息好比函數的內部變量和調用函數的位置等,全部的「調用幀」組成了一個「調用棧」。若是在函數的最後一步操做調用了另一個函數,由於外層函數裏調用位置、內部變量等信息都不會再用到了,全部就無需保留外層函數的「調用幀」了,只要直接用內層函數的「調用幀」取代外層函數的「調用幀」便可:
function f() {
let m = 1
let n = 2
return g(m + n)
}
f()
// 等同於
function f() {
return g(3)
}
f()
// 等同於
g(3)
複製代碼
這樣一來就很明顯的減小了調用棧中的幀數,內存佔用就少了,因此這就是尾調用的優化做用。尾遞歸也是如此,遞歸若是次數多那就須要保留很是多的「調用幀」,因此常常會出現棧溢出錯誤,而使用了尾遞歸優化後就不會發生棧溢出的錯誤了:
// 常規遞歸的斐波那契函數
function Fibonacci(n) {
if ( n <= 1 ) {return 1}
return Fibonacci(n - 1) + Fibonacci(n - 2)
}
Fibonacci(100) // 超時
// 尾遞歸優化後的斐波那契函數
function Fibonacci2(n, ac1 = 1, ac2 = 1) {
if( n <= 1 ) {return ac2}
return Fibonacci2(n - 1, ac2, ac1 + ac2)
}s
Fibonacci2(100) // 573147844013817200000
複製代碼
Symbol
是一個新的原始類型,用來表示一個獨一無二的值,能夠經過 Symbol()
函數來建立一個 Symbol
類型的值,爲了加以區分,能夠傳入一個字符串做爲其描述:
let s1 = Symbol('foo')
let s2 = Symbol('foo')
s1 === s2 // false
複製代碼
Symbol
類型沒法經過數學運算符進行隱式類型轉換,可是能夠經過 String()
顯示轉成字符串或者經過 Boolean()
顯示轉成布爾值:
let s = Symbol('foo')
String(s) // "Symbol('foo')"
s.toString() // "Symbol('foo')"
Boolean(s) // true
複製代碼
引入 Symbol
最大的初衷其實就是爲了讓它做爲對象的屬性名而使用,這樣就能夠有效避免屬性名的衝突了:
let foo = Symbol('foo')
let obj = {
[foo]: 'foo1',
foo: 'foo2'
}
obj[foo] // 'foo1'
obj.foo // 'foo2'
複製代碼
Symbol
屬性的不可枚舉性,不會被 for...in
、for...of
、Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
等枚舉:
let person = {
name: '布蘭',
[Symbol('age')]: 12,
}
for (let x in person) {
console.log(x) // 'name'
}
Object.keys(person) // ['name']
Object.getOwnPropertyNames(person) // ['name']
JSON.stringify(person) // '{"name":"布蘭"}'
複製代碼
可是能夠經過 Object.getOwnPropertySymbols()
獲取到對象的全部 Symbol
屬性名,返回一個數組:
// 基於上面的代碼
Object.getOwnPropertySymbols(person) // [Symbol(age)]
複製代碼
靜態方法:
Symbol.for()
按照描述去全局查找 Symbol
,找不到則在全局登記一個:
let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')
s1 === s2 // true
複製代碼
Symbol.for()
的這個全局登記特性,能夠用在不一樣的 iframe
或 service worker
中取到同一個值。
Symbol.keyFor()
根據已經在全局登記的 Symbol
查找其描述:
let s = Symbol.for('foo')
Symbol.keyFor(s) // 'foo'
複製代碼
Symbol
的內置值:
Symbol.hasInstance
:指向一個內部方法,當其餘對象使用 instanceof
運算符判斷是否爲此對象的實例時會調用此方法;Symbol.isConcatSpreadable
:指向一個布爾,定義對象用於 Array.prototype.concat()
時是否可展開;Symbol.species
:指向一個構造函數,當實例對象使用自身構造函數時會調用指定的構造函數;Symbol.match
:指向一個函數,當實例對象被 String.prototype.match()
調用時會從新定義match()的行爲;Symbol.replace
:指向一個函數,當實例對象被 String.prototype.replace()
調用時會從新定義 replace()
的行爲;Symbol.search
:指向一個函數,當實例對象被 String.prototype.search()
調用時會從新定義 search()
的行爲;sSymbol.split
:指向一個函數,當實例對象被 String.prototype.split()
調用時會從新定義 split()
的行爲;Symbol.iterator
:指向一個默認遍歷器方法,當實例對象執行 for...of
時會調用指定的默認遍歷器;Symbol.toPrimitive
:指向一個函數,當實例對象被轉爲原始類型的值時會返回此對象對應的原始類型值;Symbol.toStringTag
:指向一個函數,當實例對象被 Object.prototype.toString()
調用時其返回值會出如今 toString()
返回的字符串之中表示對象的類型;Symbol.unscopables
:指向一個對象,指定使用 with
時哪些屬性會被 with
環境排除;Set
是一種新的數據結構,相似數組,可是它沒有鍵只有值,且值都是惟一的。能夠經過構造函數生成一個新實例,接收一個數組或者可迭代數據結構做爲參數:
new Set([1, 2, 3]) // Set {1, 2, 3}
new Set('abc') // Set {'a', 'b', 'c'}
複製代碼
Set
判斷兩個值是否是相等用的是 sameValueZero 算法,相似於 ===
,惟一的區別是,在 Set
裏 NaN
之間被認爲是相等的:
let set = new Set()
let a = NaN
let b = NaN
set.add(a)
set.add(b)
set.size // 1
複製代碼
相同對象的不一樣實例也被 Set
認爲是不相等的:
let set = new Set()
let a = {a: 1}
let b = {a: 1}
set.add(a)
set.add(b)
set.size // 2
複製代碼
Set
是有順序的,將按照插入的順序進行迭代,可使用 for...of
迭代:
let set = new Set([1, 3])
set.add(5)
set.add(7)
for(let x of set) {
console.log(x)
}
複製代碼
Set
實例屬性和方法:
Set.prototype.constructor
:構造函數,默認就是 Set
函數;Set.prototype.size
:返回 Set
實例的成員總數;Set.prototype.add(value)
:添加某個值,返回 Set
結構自己;Set.prototype.delete(value)
:刪除某個值,返回一個布爾值,表示刪除是否成功;Set.prototype.has(value)
:返回一個布爾值,表示該值是否爲Set的成員;Set.prototype.clear()
:清除全部成員,沒有返回值;Set.prototype.keys()
:返回鍵名的遍歷器;Set.prototype.values()
:返回鍵值的遍歷器;Set.prototype.entries()
:返回鍵值對的遍歷器;Set.prototype.forEach()
:使用回調函數遍歷每一個成員;let set = new Set([1, 3])
set.add(5) // Set {1, 3, 5}
set.size // 3
set.delete(1) // true,1 已被刪除
set.has(1) // false
set.keys() // SetIterator {3, 5}
set.clear()
set.size // 0
複製代碼
Set
應用場景:
數組去重:
[...new Set([1, 3, 6, 3, 1])] // [1, 3, 6]
Array.from(new Set([1, 3, 6, 3, 1])) // [1, 3, 6]
複製代碼
字符串去重:
[...new Set('abcbacd')].join('') // 'abcd'
複製代碼
求兩個集合的交集/並集/差集:
let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])
// 並集
let union = new Set([...a, ...b]) // Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x))) // set {2, 3}
// (a 相對於 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x))) // Set {1}
複製代碼
遍歷修改集合成員的值:
let set = new Set([1, 2, 3])
// 方法一
let set1 = new Set([...set].map(val => val * 2)) // Set {2, 3, 6}
// 方法二
let set2 = new Set(Array.from(set, val => val * 2)) // Set {2, 4, 6}
複製代碼
WeakSet
對象容許將弱保持對象存儲在一個集合中:
let ws = new WeakSet()
let foo = {}
ws.add(foo) // WeakSet {{}}
ws.has(foo) // true
ws.delete(foo) // WeakSet {}
複製代碼
和 Set
的區別:
WeakSet
只能是對象的集合,而不能是任何類型的任意值;WeakSet
持弱引用:集合中對象的引用爲弱引用。若是沒有其餘的對 WeakSet
中對象的引用,那麼這些對象會被當成垃圾回收掉。這也意味着 WeakSet
中沒有存儲當前對象的列表。正由於這樣,WeakSet
是不可枚舉的,也就沒有 size
屬性,沒有 clear
和遍歷的方法。實例方法:
WeakSet.prototype.add(value)
:添加一個新元素 value
;WeakSet.prototype.delete(value)
:從該 WeakSet
對象中刪除 value
這個元素;WeakSet.prototype.has(value)
:返回一個布爾值, 表示給定的值 value
是否存在於這個 WeakSet
中;Map
是一種相似於 Object
的這種鍵值對的數據結構,區別是對象的鍵只能是字符串或者 Symbol
,而 Map
的鍵能夠是任何類型(原始類型、對象或者函數),能夠經過 Map
構造函數建立一個實例,入參是具備 Iterator
接口且每一個成員都是一個雙元素數組 [key, value]
的數據結構:
let map1 = new Map()
map1.set({}, 'foo')
let arr = [['name', '布蘭'], ['age', 12]]
let map2 = new Map(arr)
複製代碼
Map
中的鍵和 Set
裏的值同樣也必須是惟一的,遵循 sameValueZero 算法,對於同一個鍵後面插入的會覆蓋前面的,
let map = new Map()
let foo = {foo: 'foo'}
map.set(foo, 'foo1')
map.set(foo, 'foo2')
map.get(foo) // 'foo2'
複製代碼
對於鍵名同爲 NaN
以及相同對象而不一樣實例的處理同 Set
的值同樣:
let a = NaN
let b = NaN
let map = new Map()
map.set(a, 'a')
map.set(b, 'b')
map.size // 1
map.get(a) // 'b'
let c = {c: 'c'}
let d = {c: 'c'}
map.set(c, 'c')
map.set(d, 'd')
map.size // 3
map.get(c) // 'c'
複製代碼
實例屬性和方法:
Map.prototype.size
:返回 Map
對象的鍵值對數量;Map.prototype.set(key, value)
:設置 Map
對象中鍵的值。返回該 Map
對象;Map.prototype.get(key)
: 返回鍵對應的值,若是不存在,則返回 undefined
;Map.prototype.has(key)
:返回一個布爾值,表示 Map
實例是否包含鍵對應的值;Map.prototype.delete(key)
: 若是 Map
對象中存在該元素,則移除它並返回 true
;Map.prototype.clear()
: 移除 Map
對象的全部鍵/值對;Map.prototype.keys()
:返回一個新的 Iterator
對象, 它按插入順序包含了 Map
對象中每一個元素的鍵;Map.prototype.values()
:返回一個新的 Iterator
對象,它按插入順序包含了 Map
對象中每一個元素的值;Map.prototype.entries()
:返回一個新的 Iterator
對象,它按插入順序包含了 Map
對象中每一個元素的 [key, value]
數組;Map.prototype.forEach(callbackFn[, thisArg])
:按插入順序遍歷 Map
;let map = new Map()
map.set({a: 1}, 'a')
map.set({a: 2}, 'b')
for (let [key, value] of map) {
console.log(key, value)
}
// {a: 1} 'a'
// {a: 2} 'b'
for (let key of map.keys()) {
console.log(key)
}
// {a: 1}
// {a: 2}
複製代碼
相似於 Map
的結構,可是鍵必須是對象的弱引用,注意弱引用的是鍵名而不是鍵值,於是 WeakMap
是不能被迭代的;
let wm = new WeakMap()
let foo = {name: 'foo'}
wm.set(foo, 'a') // Weak
wm.get(foo) // 'a'
wm.has(foo) // true
複製代碼
雖然 wm
的鍵對 foo
對象有引用,可是絲絕不會阻止 foo
對象被 GC
回收。當引用對象 foo
被垃圾回收以後,wm
的 foo
鍵值對也會自動移除,因此不用手動刪除引用。
實例方法:
WeakMap.prototype.delete(key)
:移除 key
的關聯對象;WeakMap.prototype.get(key)
:返回key關聯對象, 或者 undefined(沒有key關聯對象時);WeakMap.prototype.has(key)
:根據是否有 key
關聯對象返回一個 Boolean
值;WeakMap.prototype.set(key, value)
:在 WeakMap
中設置一組 key
關聯對象,返回這個 WeakMap
對象;Proxy
用來定義基本操做的的自定義行爲,能夠理解爲當對目標對象 target
進行某個操做以前會先進行攔截(執行 handler
裏定義的方法),必需要對 Proxy
實例進行操做才能觸發攔截,對目標對象操做是不會攔截的,能夠經過以下方式定義一個代理實例
let proxy = new Proxy(target, handler)
let instance = new Proxy({name: '布蘭'}, {
get(target, propKey, receiver) {
return `hello, ${target.name}`
},
})
instance.name // 'hello, 布蘭'
複製代碼
若是 handle
沒有設置任何攔截,那麼對實例的操做就會轉發到目標對象身上:
let target = {}
let proxy = new Proxy(target, {})
proxy.name = '布蘭'
target.name // '布蘭'
複製代碼
目標對象被 Proxy
代理的時候,內部的 this
會指向代理的實例:
const target = {
m: function () {
console.log(this === proxy)
}
}
const handler = {}
const proxy = new Proxy(target, handler)
target.m() // false
proxy.m() // true
複製代碼
靜態方法:
Proxy.revocable()
用以定義一個可撤銷的 Proxy
:
let target = {}
let handler = {}
let {proxy, revoke} = Proxy.revocable(target, handler)
proxy.foo = 123
proxy.foo // 123
revoke()
proxy.foo // TypeError
複製代碼
handle
對象的方法:
get(target, propKey, receiver)
:攔截對象屬性的讀取,好比proxy.foo和proxy['foo']。set(target, propKey, value, receiver)
:攔截對象屬性的設置,好比 proxy.foo = v或proxy['foo'] = v
,返回一個布爾值。has(target, propKey)
:攔截 propKey in proxy
的操做,返回一個布爾值。deleteProperty(target, propKey)
:攔截 delete proxy[propKey]
的操做,返回一個布爾值。
ownKeys(target)
:攔截 Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循環,返回一個數組。該方法返回目標對象全部自身的屬性的屬性名,而 Object.keys()
的返回結果僅包括目標對象自身的可遍歷屬性。getOwnPropertyDescriptor(target, propKey)
:攔截Object.getOwnPropertyDescriptor(proxy, propKey)
,返回屬性的描述對象。defineProperty(target, propKey, propDesc)
:攔截 Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一個布爾值。preventExtensions(target)
:攔截 Object.preventExtensions(proxy)
,返回一個布爾值。getPrototypeOf(target)
:攔截 Object.getPrototypeOf(proxy)
,返回一個對象。isExtensible(target)
:攔截 Object.isExtensible(proxy)
,返回一個布爾值。setPrototypeOf(target, proto)
:攔截 Object.setPrototypeOf(proxy, proto)
,返回一個布爾值。若是目標對象是函數,那麼還有兩種額外操做能夠攔截。apply(target, object, args)
:攔截 Proxy
實例做爲函數調用的操做,好比proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。construct(target, args)
:攔截 Proxy
實例做爲構造函數調用的操做,好比 new proxy(...args)
。Reflect
是一個內置的對象,它提供攔截 JavaScript
操做的方法。這些方法與 proxy handlers
的方法相同。Reflect
不是一個函數對象,所以它是不可構造的。
設計的目的:
Object
屬於語言內部的方法放到 Reflect
上;Object
方法的返回結果,讓其變得更合理;Object
操做變成函數行爲;Proxy handles
與 Reflect
方法一一對應,前者用於定義自定義行爲,然後者用於恢復默認行爲;靜態方法:
Reflect.apply(target, thisArgument, argumentsList)
對一個函數進行調用操做,同時能夠傳入一個數組做爲調用參數。和 Function.prototype.apply()
功能相似;Reflect.construct(target, argumentsList[, newTarget])
對構造函數進行 new
操做,至關於執行 new target(...args)
;Reflect.defineProperty(target, propertyKey, attributes)
和 Object.defineProperty()
相似。若是設置成功就會返回 true
;Reflect.deleteProperty(target, propertyKey)
做爲函數的 delete
操做符,至關於執行 delete target[name]
;Reflect.get(target, propertyKey[, receiver])
獲取對象身上某個屬性的值,相似於 target[name]
;Reflect.getOwnPropertyDescriptor(target, propertyKey)
相似於 Object.getOwnPropertyDescriptor()
。若是對象中存在該屬性,則返回對應的屬性描述符, 不然返回 undefined
;Reflect.getPrototypeOf(target)
相似於 Object.getPrototypeOf()
;Reflect.has(target, propertyKey)
判斷一個對象是否存在某個屬性,和 in
運算符 的功能徹底相同;Reflect.isExtensible(target)
相似於 Object.isExtensible()
;Reflect.ownKeys(target)
返回一個包含全部自身屬性(不包含繼承屬性)的數組。(相似於 Object.keys()
, 但不會受 enumerable
影響);Reflect.preventExtensions(target)
相似於 Object.preventExtensions()
。返回一個 Boolean
;Reflect.set(target, propertyKey, value[, receiver])
將值分配給屬性的函數。返回一個 Boolean
,若是更新成功,則返回 true
;Reflect.setPrototypeOf(target, prototype)
設置對象原型的函數. 返回一個 Boolean
, 若是更新成功,則返回 true
;能夠用 class
關鍵字來定義一個類,類是對一類具備共同特徵的事物的抽象,就好比能夠把小狗定義爲一個類,小狗有名字會叫也會跳;類是特殊的函數,就像函數定義的時候有函數聲明和函數表達式同樣,類的定義也有類聲明和類表達式,不過類聲明不一樣於函數聲明,它是沒法提高的;類也有 name
屬性
// 類聲明
class Dog {
constructor(name) {
this.name = name
}
bark() {}
jump() {}
}
Dog.name // 'Dog'
// 類表達式:能夠命名(類的 name 屬性取類名),也能夠不命名(類的 name 屬性取變量名)
let Animal2 = class {
// xxx
}
Animal2.name // 'Animal2'
複製代碼
JS
中的類創建在原型的基礎上(經過函數來模擬類,其實類就是構造函數的語法糖),和 ES5
中構造函數相似,可是也有區別,好比類的內部方法是不可被迭代的:
class Dog {
constructor() {}
bark() {}
jump() {}
}
Object.keys(Dog.prototype) // []
// 相似於
function Dog2(){
}
Dog2.prototype = {
constructor() {},
bark() {},
jump() {},
}
Object.keys(Dog2.prototype) // ['constructor', 'bark', 'jump']
複製代碼
基於原型給類添加新方法:
Object.assign(Dog.prototype, {
wag() {} // 搖尾巴
})
複製代碼
類聲明和類表達式的主體都執行在嚴格模式下。好比,構造函數,靜態方法,原型方法,getter
和 setter
都在嚴格模式下執行。
類內部的 this
默認指向類實例,因此若是直接調用原型方法或者靜態方法會致使 this
指向運行時的環境,而類內部是嚴格模式,因此此時的 this
會是 undefined
:
class Dog {
constructor(name) {
this.name = name
}
bark() {
console.log( `${this.name} is bark.` )
}
static jump() {
console.log( `${this.name} is jump.` )
}
}
let dog = new Dog('大黃')
let { bark } = dog
let { jump } = Dog
bark() // TypeError: Cannot read property 'name' of undefined
jump() // TypeError: Cannot read property 'name' of undefined
複製代碼
方法和關鍵字:
constructor
方法是類的默認方法,經過 new
關鍵字生成實例的時候,會自動調用;一個類必須有constructor
方法,若是沒有顯示定義,則會自動添加一個空的;constructor
默認會返回實例對象:
class Point {}
// 等同於
class Point {
constructor() {}
}
複製代碼
經過 get
和 set
關鍵字攔截某個屬性的讀寫操做:
class Dog {
get age(){
return 1
}
set age(val){
this.age = val
}
}
複製代碼
用 static
關鍵字給類定義靜態方法,靜態方法不會存在類的原型上,因此不能經過類實例調用,只能經過類名來調用,靜態方法和原型方法能夠同名:
class Dog {
bark() {}
jump() {
console.log('原型方法')
}
static jump() {
console.log('靜態方法')
}
}
Object.getOwnPropertyNames(Dog.prototype) // ['constructor', 'bark', 'jump']
Dog.jump() // '靜態方法'
let dog = new Dog()
dog.jump() // '原型方法'
複製代碼
公有字段和私有字段:
靜態公有字段和靜態方法同樣只能經過類名調用;私有屬性和私有方法只能在類的內部調用,外部調用將報錯:
class Dog {
age = 12 // 公有字段
static sex = 'male' // 靜態公有字段
#secret = '我是人類的好朋友' // 私有字段
#getSecret() { // 私有方法
return this.#secret
}
}
Dog.sex // 'male'
let dog = new Dog()
dog.#getSecret() // SyntaxError
複製代碼
公共和私有字段聲明是 JavaScript 標準委員會 TC39 提出的實驗性功能(第 3 階段)。瀏覽器中的支持是有限的,可是能夠經過 Babel 等系統構建後使用此功能。
new.target
屬性容許你檢測函數、構造方法或者類是不是經過 new
運算符被調用的。在經過 new
運算符被初始化的函數或構造方法中,new.target
返回一個指向構造方法或函數的引用。在普通的函數調用中,new.target
的值是 undefined
,子類繼承父類的時候會返回子類:
class Dog {
constructor() {
console.log(new.target.name)
}
}
function fn(){
if (!new.target) return 'new target is undefined'
console.log('fn is called by new')
}
let dog = new Dog() // 'Dog'
fn() // 'new target is undefined'
new fn() // 'fn is called by new'
複製代碼
類的繼承:
類能夠經過 extends
關鍵字實現繼承,若是子類顯示的定義了 constructor
則必須在內部調用 super()
方法,內部的 this
指向當前子類:
class Animal {
constructor(name) {
this.name = name
}
run() {
console.log(`${this.name} is running.`)
}
}
class Dog extends Animal{
constructor(name){
super(name) // 必須調用
this.name = name
}
bark() {
console.log(`${this.name} is barking.`)
}
}
let dog = new Dog('大黃')
dog.run() // '大黃 is running.'
複製代碼
經過 super()
調用父類的構造函數或者經過 super
調用父類的原型方法;另外也能夠在子類的靜態方法裏經過 super
調用父類的靜態方法:
// 基於上面的代碼改造
class Dog extends Animal{
constructor(name){
super(name) // 調用父類構造函數
this.name = name
}
bark() {
super.run() // 調用父類原型方法
console.log(`${this.name} is barking.`)
}
}
let dog = new Dog()
dog.bark()s
// '大黃 is running.'
// '大黃 is barking.'
複製代碼
子類的 __proto__
屬性,表示構造函數的繼承,老是指向父類;子類 prototype
屬性的 __proto__
屬性,表示方法的繼承,老是指向父類的prototype屬性:
class Animal {}
class Dog extends Animal {}
Dog.__proto__ === Animal // true
Dog.prototype.__proto__ === Animal.prototype // true
複製代碼
子類原型的原型指向父類的原型:
// 基於上面的代碼
let animal = new Animal()
let dog = new Dog()
dog.__proto__.__proto__ === animal.__proto__ // true
複製代碼
使用 extends
還能夠實現繼承原生的構造函數,以下這些構造函數均可以被繼承:
String()
Number()
Boolean()
Array()
Object()
Function()
Date()
RegExp()
Error()
class MyString extends String {
constructor(name){
super(name)
this.name = name
}
welcome() {
return `hello ${this.name}`
}
}
let ms = new MyString('布蘭')
ms.welcome() // 'hello 布蘭'
ms.length // 2
ms.indexOf('蘭') // 1
複製代碼
瀏覽器傳統加載模塊方式:
// 同步加載
<script src="test.js"></script>
// defer異步加載:順序執行,文檔解析完成後執行;
<script src="test.js" defer></script>
// async異步加載:亂序加載,下載完就執行。
<script src="test.js" async></script>
複製代碼
瀏覽器如今能夠按照模塊(加上 type="module"
)來加載腳本,默認將按照 defer
的方式異步加載;ES6
的模塊加載依賴於 import
和 export
這 2 個命令;模塊內部自動採用嚴格模式:
// 模塊加載
<script type="module" src="test.js"></script>
複製代碼
export
用於輸出模塊的對外接口,一個模塊內只能容許一個 export default
存在,如下是幾種輸出模塊接口的寫法:
// person.js
// 寫法一:單獨導出
export const name = '布蘭'
export const age = 12
// 寫法二:按需導出
const name = '布蘭', age = 12
export { name, age }
// 寫法三:重命名後導出
const name = '布蘭', age = 12
export { name as name1, age as age1 }
// 寫法四:默認導出
const name = '布蘭'
export default name
複製代碼
import
用於輸入其餘模塊的接口:
// 按需導入
import { name, age } './person.js'
// 導入後重命名
import { name1 as name, age1 as age } from './person.js'
// 默認導入
import person from './person.js'
// 總體導入
import * as person from './person.js'
// 混合導入
import _, { each } from 'lodash'
複製代碼
import
導入的細節:
as
進行重命名;import
命令具備提高效果,會提高到整個模塊的頭部,首先執行;import
是編譯時導入,因此不能將其寫到代碼塊(好比 if
判斷塊裏)或者函數內部;import
會執行所加載的模塊的代碼,若是重複導入同一個模塊則只會執行一次模塊;import
和 export
的複合寫法:export
和 import
語句能夠結合在一塊兒寫成一行,至關因而在當前模塊直接轉發外部模塊的接口,複合寫法也支持用 as
重命名。如下例子中須要在 hub.js
模塊中轉發 person.js
的接口:
// person.js
const name = '布蘭', age = 12
export { name, age }
// 按需轉發接口(中轉模塊:hub.js)
export { name, age } from './person.js'
// 至關於
import { name, age } from './person.js'
export { name, age }
// person.js
const name = '布蘭', age = 12
export default { name, age }
// 轉發默認接口(中轉模塊:hub.js)
export { default } from './person.js'
// 至關於
import person from './person.js'
export default person
複製代碼
ES6
模塊和 CommonJS
模塊的差別:
CommonJS
模塊輸出的是一個值的拷貝(一旦輸出一個值,模塊內部的變化就影響不到這個值),ES6
模塊輸出的是值的引用(是動態引用且不會緩存值,模塊裏的變量綁定其所在的模塊,等到腳本真正執行時,再根據這個只讀引用到被加載的那個模塊裏去取值);CommonJS
模塊是運行時加載,ES6
模塊是編譯時輸出接口;CommonJS
模塊的 require()
是同步加載模塊,ES6
模塊的 import
命令是異步加載,有一個獨立的模塊依賴的解析階段;Iterator
迭代器協議,爲各類數據結構提供了一種統一按照某種順序進行訪問的機制。一般部署在一個可迭代數據結構內部或其原型上。一個對象要可以成爲迭代器,它必須有一個 next()
方法,每次執行 next()
方法會返回一個對象,這個對象包含了一個 done
屬性(是個布爾值,true
表示能夠繼續下次迭代)和一個 value
屬性(每次迭代的值):
// 生成一個迭代器
let makeIterator = (arr) => {
let index = 0
return {
next(){
return index < arr.length ? {
value: arr[index++],
done: false
} : { done: true }
}
}
}
複製代碼
iterable
可迭代數據結構:內部或者原型上必須有一個 Symbol.iterator
屬性(若是是異步的則是 Symbol.asyncIterator
),這個屬性是一個函數,執行後會生成一個迭代器:
let obj = {
[Symbol.iterator]() {
return {
index: 0,
next() {
if (this.index < 3) {
return {value: this.index++, done: false}
} else {
return {done: true}
}
}
}
}
}
for (let x of obj) {
console.log(x) // 0 1 2
}
複製代碼
內置的一些可迭代數據結構有:String
、Array
、TypedArray
、Map
和 Set
、arguments
、NodeList
:
let si = 'hi'[Symbol.iterator]()
si // StringIterator
si.next() // {value: 'h', done: false}
si.next() // {value: 'i', done: false}
si.next() // {value: undefined, done: true}
複製代碼
for...of
:用於遍歷可迭代數據結構:
for...in
獲取索引,for...of
獲取值;for...in
獲取索引,for...of
獲取值;for...in
獲取鍵,for...of
需自行部署 [Symbol.iterator]
接口;Set
:for...of
獲取值, for (const v of set)
;Map
:for...of
獲取鍵值對,for (const [k, v] of map)
;length
的對象、arguments
對象、NodeList
對象(無 Iterator
接口的類數組可用 Array.from()
轉換);// 迭代字符串
for (let x of 'abc') {
console.log(x) // 'a' 'b' 'c'
}
// 迭代數組
for (let x of ['a', 'b', 'c']) {
console.log(x) // 'a' 'b' 'c'
}
// 遍歷 Set
let set = new Set(['a', 'b', 'c'])
for (let x of set) {
console.log(x) // 'a' 'b' 'c'
}
// 遍歷 Map
let map = new Map([['name', '布蘭'], ['age', 12]])
for (let [key, value] of map) {
console.log(key + ': ' + value) // 'name: 布蘭' 'age: 12'
}
複製代碼
for...of
和 for...in
對比
共同點:可以經過 break
、continue
和 return
跳出循環; 不一樣點: - for...in
的特色:只能遍歷鍵,會遍歷原型上屬性,遍歷無順序,適合於對象的遍歷; - for...of
的特色:可以遍歷值(某些數據結構能遍歷鍵和值,好比 Map
),不會遍歷原型上的鍵值,遍歷順序爲數據的添加順序,適用於遍歷可迭代數據結構;
Promise
這塊知識能夠直接看我以前寫的一篇文章:深刻理解Promise 很是完整。
function*
會定義一個生成器函數,調用生成器函數不會當即執行,而是會返回一個 Generator
對象,這個對象是符合可迭代協議和迭代器協議的,換句話說這個 Generator
是能夠被迭代的。
生成器函數內部經過 yield
來控制暫停,而 next()
將把它恢復執行,它的運行邏輯是以下這樣的:
yield
表達式,就暫停執行後面的操做,並將緊跟在 yield
後面的那個表達式的值做爲返回的對象的 value
屬性值;next
方法時,再繼續往下執行,直到遇到下一個 yield
表達式;yield
表達式,就一直運行到函數結束,直到 return
語句爲止,並將 return
語句後面的表達式的值,做爲返回的對象的 value
屬性值;return
語句,則返回的對象的 value
屬性值爲 undefined
;function* gen() {
yield 'hello'
yield 'world'
return 'end'
}
let g = gen()
g.next() // {value: 'hello', done: false}
g.next() // {value: 'world', done: false}
g.next() // {value: 'end', done: true}
複製代碼
在生成器函數內部可使用 yield*
表達式委託給另外一個 Generator
或可迭代對象,好比數組、字符串等;yield*
表達式自己的值是當迭代器關閉時返回的值(即 done
爲 true
時):
function* inner() {
yield* [1, 2]
return 'foo'
}
function* gen() {
let result = yield* inner()
console.log(result)
}
let g = gen()
g.next()
g.next()
g.next()
複製代碼
實例方法:
Generator.prototype.next()
:返回一個包含屬性 done
和 value
的對象。該方法也能夠經過接受一個參數用以向生成器傳值。若是傳入了參數,那麼這個參數會傳給上一條執行的 yield
語句左邊的變量:
function* f() {
let a = yield 12
console.log(a)
let b = yield a
console.log(b)
}
let g = f()
console.log(g.next('a'))
console.log(g.next('b'))
console.log(g.next('c'))
// {value: 12, done: false}
// 'b'
// {value: 'b', done: false}
// 'c'
// {value: undefined, done: true}
複製代碼
Generator.prototype.throw()
:用來向生成器拋出異常,若是內部捕獲了則會恢復生成器的執行(即執行下一條 yield
表達式),而且返回帶有 done
及 value
兩個屬性的對象:
function* gen() {
try {
yield 'a'
} catch(e) {
console.log(e)
}
yiele 'b'
yield 'c'
}
let g = gen()
g.next()
g.throw('error a')
g.next()
// {value: "a", done: false}
// 'error a'
// {value: "b", done: false}
// {value: "c", done: false}
複製代碼
若是內部沒有捕獲異常,將中斷內部代碼的繼續執行(相似 throw
拋出的異常,若是沒有捕獲,則後面的代碼將不會執行),此時異常會拋到外部,能夠被外部的 try...catch
塊捕獲,此時若是再執行一次 next()
,會返回一個值爲 done
屬性爲 true
的對象:
function* gen() {
yield 'a'
yield 'b'
yield 'c'
}
let g = gen()
g.next()
try {
g.throw('error a')
} catch(e) {
console.log(e)
}
g.next()
// {value: "a", done: false}
// 'error a'
// {value: undefined, done: true}
複製代碼
Generator.prototype.return()
:返回給定的值並結束生成器:
function* gen() {
yield 1
yield 2
yield 3
}
let g = gen()
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
複製代碼
應用:
將異步操做同步化,好比同時有多個請求,多個請求之間是有順序的,只能等前面的請求完成了才請求後面的:
function* main() {
let res1 = yield request('a')
console.log(res1)
let res2 = yield request('b')
console.log(res2)
let res3 = yield request('c')
console.log(res3)
}
function request(url) {
setTimeout(function(){ // 模擬異步請求
it.next(url)
}, 300)
}
let it = main()
it.next()
// 'a' 'b' 'c'
複製代碼
給對象部署 Iterator
接口:
function* iterEntries(obj) {
let keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
yield [key, obj[key]]
}
}
let obj = { foo: 3, bar: 7 }
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value)
}
// 'foo' 3
// 'bar' 7
複製代碼