近兩天更新完基本內容,后續長期更新,建議關注收藏點贊。
ES6(ECMAScript 2015)是現代 JavaScript 的基礎,在前端面試中非常常見。
- 本文已匯總的本站筆記
ES6最重要10特性
對象新增
數組新增
異步、生成器
Promise
模塊化
類、繼承
async / await
set / weakset
目錄
- 變量聲明(let、const、var)
- 解構賦值Destructuring Assignment
- 模板字符串
- 箭頭函數(Arrow Function)
- 默認參數 & 剩余參數 & 擴展運算符(...)
- 對象字面量增強
- Symbol
- Set 和 Map
- Promise(異步編程)
- 類(class)和繼承
- 模塊化(ES Modules)
- 迭代器與生成器(進階)
- 可選鏈 & 空值合并運算符(ES2020+)
- 其他
- ?WeakSet
- async/await
- 擴展
ES6+(7~12)
Math數字新增
變量聲明(let、const、var)
- var 全局;let和const是塊級作用域,僅在某一塊有效。
- const是常量不可改動其引用對象,既然是常量不能重新進行賦值,如果是基本數據類型,不能更改值,如果是復雜數據類型,不能更改地址值。const聲明時必須賦值。
- ???????let 和 const 不存在變量提升,在賦值前不可訪問->有“暫時性死區TDZ“
解構賦值Destructuring Assignment
- 如果解構不成功,變量跟數值個數不匹配的時候,變量的值為undefined。解構賦值解 undefined:數組解構允許、對象結構不允許、帶默認值的解構允許
- 數組解構用中括號包裹,多個變量用逗號隔開,對象解構用花括號包裹,多個變量用逗號隔開
const [a, b = 2] = [1]; // a = 1, b = 2
const { x, y } = { x: 10, y: 20 };let x = 1, y = 2;
[x, y] = [y, x]; // 交換變量let[a,b,c]=[1,2,3];
let [, a, , b] = arr;//JSON中的解構賦值
let json={name:'a',age:18};
let {n,a}=json;//都是undefined
let {name,age}=json;//必須名字對應上
//起別名let {name: myName, age: myAge} = json; // myName myAge 屬于別名console.log(myName); // 'a' console.log(myAge); // 18
//對象解構
function show({x, y}) {//從傳入的對象中提取 x 和 y 屬性并將它們作為函數的局部變量使用console.log(x, y);
}
模板字符串
- 模板字符串和傳統字符串拼接的區別?
項目 | 傳統字符串拼接 | 模板字符串 |
---|---|---|
引號 | 單引號 ' 或雙引號 " | 反引號 |
拼接方式 | 使用 + 拼接 | 使用 ${} 插值 |
多行字符串 | 需要手動加 \n 或 + | 支持直接換行 |
支持表達式 | 不直觀 | 直接寫表達式、函數、變量等${func(a)} |
箭頭函數(Arrow Function)
箭頭函數屬于表達式函數,因此不存在函數提升
箭頭函數中沒有 arguments,只能使用 … 動態獲取實參
簡化了函數表達式的寫法,并且不綁定 this,即 this 始終指向定義時的上下文。
箭頭函數不能當構造函數。
- 箭頭函數和普通函數有什么區別
箭頭函數沒有prototype,所以箭頭函數本身沒有this,使用new調用箭頭函數會報錯TypeError: fun is not a constructor
,不能當構造函數
箭頭函數 沒有自己的 this、arguments、super,箭頭函數的this指向繼承自外層,且不能通過 call/bind/apply 改變
不可以使用yield命令,因此箭頭函數不能用作 Generator 函數。 - call() 和 apply() 對箭頭函數的影響
對于普通函數,call() 和 apply() 可以顯式地改變 this 綁定。但對于箭頭函數來說,不能改變 - 何時使用箭頭函數
適用于回調函數,例如 setTimeout,避免 this 綁定錯誤
適用于類方法,防止 this 綁定丟失 - 優點
解決了this執行環境所造成的一些問題。比如:解決了匿名函數this指向的問題(匿名函數的執行環境具有全局性),包括setTimeout和setInterval中使用this所造成的問題
處理回調函數時特別有用,避免了this綁定問題。 - 箭頭函數有兩種格式。
- 只包含一個表達式,連{ … }和return都省略掉了。自動作為返回值被返回
- 可以包含多條語句,這時候就不能省略{ … }和return。如果參數不是一個,就需要用括號()括起來。
const add = (a, b) => a + b;
let show=(...args)=>{//操作return;
}// 兩個參數:
(x, y) => x * x + y * y// 無參數:
() => 3.14// 可變參數:
(x, y, ...rest) => {var i, sum = x + y;for (i=0; i<rest.length; i++) {sum += [i];}return sum;
}如果要返回一個對象,就要注意,如果是單表達式,這么寫的話會報錯:
x => { foo: x }
正確寫法:
x => ({ foo: x })
- 輸出練習
//例子1
const obj = {value: 42,getValue: () => {console.log(this.value);}
};
obj.getValue(); // undefined,this 不是 obj,而是外層作用域的 this
obj.getValue.call({ value: 100 }); // 仍然是 undefined,call() 無法改變 this//例子2
function outer() {const arrowFunc = () => {console.log(this); // 取決于 outer() 調用時的 this};arrowFunc();
}const obj1 = { method: outer };
obj1.method(); // this 是 obj1const obj2 = { method: outer.bind({ x: 1 }) };
obj2.method(); // this 是 { x: 1 },但箭頭函數內的 this 仍然是 obj2.method() 調用時的 this//例子3
var obj = {birth: 1990,getAge: function (year) {var b = this.birth; // 1990var fn = (y) => y - this.birth; // this.birth仍是1990return fn.call({birth:2000}, year);//2015-1990}
};
obj.getAge(2015); // 25//例子4
const obj = { name: '張三'} function fn () { console.log(this);//this 指向 是obj對象return () => { console.log(this);
//this 指向 的是箭頭函數定義的位置,
//那么這個箭頭函數定義在fn里面,而這個fn指向是的obj對象,所以這個this也指向是obj對象} } const resFn = fn.call(obj); resFn();
默認參數 & 剩余參數 & 擴展運算符(…)
ES6中,函數也可以給默認參數了
...args
是什么?與 arguments 區別?
...args
是 剩余參數(rest parameters) 的語法,是 ES6 引入的標準,用于收集函數的多余參數為一個數組。它與傳統的 arguments 類似,但更強大、更靈活。
對比點 | ...args | arguments |
---|---|---|
類型 | 真正的數組 Array | 類數組對象(非真正數組) |
是否可用于箭頭函數 | ? 可用 | ? 不可用 |
是否支持解構、數組方法 | ? 支持(如 map , reduce ) | ? 不支持直接用數組方法 |
是否明確命名 | ? 參數名明確(如 args) | ? 不明確,只有 arguments |
可讀性 | ? 好 | ? 較差 |
- 擴展運算符Spread & Rest
如果傳入的參數連正常定義的參數都沒填滿,最后的擴展參數會接收一個空數組(注意不是undefined),但固定的參數如果沒傳會是undefined- 作用
合并、復制數組、傳參數組
參數收集、解構
合并、復制對象
字符串展開為數組
- 作用
// 擴展數組
const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4];// 擴展對象
const obj1 = { name: 'Alice' };
const obj2 = { ...obj1, age: 25 };// Rest 參數(收集函數的剩余參數)
function sum(...numbers) {return numbers.reduce((acc, num) => acc + num, 0);
}window.onload=function (){let aLi = document.querySelectorAll('ul li');let arrLi = [...aLi];//可以把ul li轉變為數組arrLi.pop();arrLi.push('asfasdf');console.log(arrLi);
}function foo(a, b, ...rest) {console.log('a = ' + a);console.log('b = ' + b);console.log(rest);
}foo(1, 2, 3, 4, 5);
// 結果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]foo(1);
// 結果:
// a = 1
// b = undefined
// Array []let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']//擴展運算符可以將數組或者對象轉為用逗號分隔的參數序列let ary = [1, 2, 3];...ary // 1, 2, 3console.log(...ary); // 1 2 3,相當于下面的代碼console.log(1,2,3);//擴展運算符可以應用于合并數組
// 方法一 let ary1 = [1, 2, 3];let ary2 = [3, 4, 5];let ary3 = [...ary1, ...ary2];// 方法二 ary1.push(...ary2);//將類數組或可遍歷對象轉換為真正的數組
let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];
對象字面量增強
const name = 'Bob';
const person = {name, // 屬性簡寫greet() { ... } // 方法簡寫//只是省略了 function 關鍵字和冒號 :,更清爽。
};
Symbol
- 定義
原本數據類型有 number、string、boolean、null、undefined,增加symbol
Symbol 是一種新的原始數據類型,通常用于對象屬性的唯一標識,避免命名沖突。symbol不可以new,symbol()返回的是一個唯一值。 - 作用
用作對象屬性名,唯一性,防止屬性沖突 - Symbol 如何作為對象屬性?
const key = Symbol('id');
const obj = { [key]: 123 };
Set 和 Map
Set 是一個無重復元素的集合,Map 是一個鍵值對集合(類似于Object)。
- Set 與 Array 區別?去重?
特性 | Array | Set |
---|---|---|
是否允許重復值 | ? 允許 | ? 不允許 |
是否有順序 | ? 有順序(按插入順序) | ? 有順序(按插入順序) |
支持索引訪問 | ? arr[0] 可訪問 | ? 不支持 set[0] Set 沒有索引 |
數據結構 | 有序列表 | 唯一集合 |
是否可遍歷 | ? 支持 | ? 支持 |
去重能力 | ? 需要手動處理 | ? 自動去重 |
//set array互相轉換
const arr = [1, 2, 2, 3];
const unique = [...new Set(arr)]; // ? 數組去重
console.log(unique); // [1, 2, 3]const set = new Set([1, 2, 3]);
const backToArray = Array.from(set); // ? Set 轉數組
- Map 與 Object 有何不同?
特性 | Object | Map |
---|---|---|
鍵的類型支持 | 只能是字符串或 Symbol | 任意類型(對象、函數、數字等) |
是否有原型鏈 | 有,默認繼承自 Object | 沒有原型,純凈鍵值對 |
鍵的順序 | 無保障,按照插入順序可能亂 | ? 按插入順序保存 |
獲取長度 | 需 Object.keys().length | map.size |
是否可迭代 | ? 不能直接 for...of | ? 可直接 for...of |
適合頻繁增刪 | ? 效率低 | ? 性能更優 |
是否用于 JSON 數據 | ? 常用于結構化數據 | ? 不能直接序列化 |
- Map v.s. Set
它們好多方法都一樣。除了set get
特性 | Map | Set |
---|---|---|
結構 | 鍵值對(key => value) | 值的集合(value) |
是否唯一 | key 唯一 | 所有值唯一 |
是否可迭代 | ? 是(按插入順序) | ? 是(按插入順序) |
常見用途 | 存儲需要鍵值對的數據 | 存儲去重的值 |
鍵的類型 | 任意類型(對象、函數都行) | 值本身是唯一的,無鍵 |
方法 | set() , get() , has() , delete() , clear() | add() , has() , delete() , clear() |
const set = new Set([1, 2, 3, 3]); // { 1, 2, 3 } 重復的只顯示一個
//set中的元素可以是任何可迭代對象 如數組、字符串、Map、Set
let str = "hello";
let set = new Set(str);
console.log(set); // Set { 'h', 'e', 'l', 'o' }let map = new Map([["a", 1],["b", 2],["c", 3]
]);
let set = new Set(map);
console.log(set); // Set { ['a', 1], ['b', 2], ['c', 3] }set.add(5);
set.delete(1);
set.has(2) //是否有這個值在set中 返回布爾值
set.size
set.clear()//清除所有成員 無返回值//Set 結構的實例與數組一樣,也擁有forEach方法,用于對每個成員執行某種操作,沒有返回值。
s.forEach(value => console.log(value))//set也有這些方法
for(let item of setArr){//默認就是valueconsole.log(item);
}
for(let item of setArr.keys()){console.log(item);}
for(let item of setArr.values()){}
for(let [k,v] of setArr.entries()){}let new_Set=[...set]//讓set轉為數組//類數組都有的特性 filter 是true的才留下
set = new Set([...set].filter(val=>val%2==0));//--------------
const map = new Map([['a', 1], ['b', 2]]);
const userMap = new Map();
userMap.set('name', 'Alice');
userMap.set('age', 30);
console.log(userMap.get('name')); // 'Alice'
console.log(userMap.has('age')); // true
userMap.delete('age');
console.log(userMap.size); // 1
userMap.clear(); // 清空所有鍵值// 1. for...of
for (const [key, value] of map) {console.log(key, value);
}// 2. forEach
map.forEach((value, key) => {console.log(key, value);
});// 3. 獲取鍵、值、鍵值對
console.log([...map.keys()]); // ['a', 'b']
console.log([...map.values()]); // [1, 2]
console.log([...map.entries()]); // [['a', 1], ['b', 2]]
Promise(異步編程)
Promise 是 ES6 引入的一種解決異步問題的方式,能夠更優雅地處理異步任務和錯誤。
Promise是一個構造函數,代表一個異步操作。調用.then(成功的回調函數,失敗的回調函數)
方法時,成功的回調函數是必選的、失敗的回調函數是可選的。
如果上一個 .then() 方法中返回了一個新的 Promise 實例對象,則可以通過下一個 .then() 繼續進行處理。
- 優點:
解決了回調函數帶來的回調地獄問題。
鏈式調用使得異步操作更加簡潔、可讀。
提供了統一的錯誤處理機制(.catch())。
//回調地獄 嵌套多層
setTimeout(()=>{console.log('ok')setTimeout(()=>{console.log('ok')setTimeout(()=>{console.log('ok')//....continue...},3000)},2000)
},1000)
- 缺點:比回調函數稍微復雜一些,需要一定的學習成本。
- Promise 的三種狀態?
pending(待定)、fulfilled(已完成)和 rejected(已拒絕)。 - .then()、.catch()、.finally() 的執行順序?
成功時.then()->finally()
失敗時.catch()->finally()
finally()始終最后執行 - async/await 如何配合 Promise 使用?
await所在的函數加async,await 后面必須是一個 Promise(或類 Promise 對象)
try…catch 捕獲 Promise 的 reject
async 函數本身也返回 Promise
function getError() {return new Promise((_, reject) => {setTimeout(() => reject('出錯了'), 1000);});
}async function fetchWithError() {try {const res = await getError();console.log(res);} catch (err) {console.error('捕獲錯誤:', err); // 輸出:捕獲錯誤: 出錯了}
}let a = 1;
let promise = new Promise(function(resolve, reject){
//將現有的東西,轉成一個promise對象,有resolve成功狀態;reject失敗狀態if(a==10){resolve('成功');}else{reject('失敗鳥');}
});
//promise.then(success, fail);
promise.then(res=>{console.log(res);
},err=>{ //err可以省略不寫 也可以改成catchconsole.log(err);
})
promise.then(res=>{console.log(res);
}).catch(err=>{ //reject,發生錯誤,別名console.log(err);
})
- 批量處理promise
Promise.all([p1, p2, p3]): 并行執行異步任務。把promise打包,扔到一個數組里面,打包完還是一個promise對象。必須確保,所有的promise對象,都是resolve狀態,都是成功狀態
Promise.race([p1, p2, p3]): 只要有一個成功,就返回
let p1 = Promise.resolve('aaaa');
let p2 = Promise.resolve('bbbb');
let p3 = Promise.resolve('cccc');Promise.all([p1,p2,p3]).then(res=>{//console.log(res);let [res1, res2, res3] = res;console.log(res1, res2, res3);
})const promiseArr=[//數組中promise實例哪個先執行完 就返回哪個結果fs.readFile('./1.txt', 'utf8'),fs.readFile('./2.txt', 'utf8'),fs.readFile('./3.txt', 'utf8'),
]
Promise.race(promiseArr)
.then(res=>{console.log(res)
})
.catch(err=>{console.log(err.message)
})
類(class)和繼承
- class 與構造函數的區別?
相同點
都可以創建實例對象;
實例通過 new 創建;
實例上的屬性是構造函數內部 this 定義的;
共享方法都掛載在 prototype 上;
都可以繼承(但 class 更方便);
對比點 | 構造函數(Function) | Class(類) |
---|---|---|
語法可讀性 | 較低 | 更清晰、接近傳統面向對象 |
原型方法定義 | 手動掛載在 prototype 上 | 自動添加到 prototype |
嚴格模式 | 默認非嚴格模式 | 自動使用嚴格模式,不能被提升 |
構造函數調用 | 可以不加 new (但會出錯) | 必須使用 new 調用 |
類體方法枚舉性 | 自定義方法默認可枚舉 | 類中方法默認不可枚舉 |
靜態方法 | 需要手動掛載 | 使用 static 定義更方便 |
類本身 | 是語法糖,本質還是函數 | 本質是 function |
繼承 | 借用 call 或 Object.create | 使用 extends 更優雅 |
- 構造函數首字母應當大寫,而普通函數首字母應當小寫,一些語法檢查工具如jslint將可以幫你檢測到漏寫的new
- ES6 引入了面向對象編程的語法糖,使用 class 關鍵字來定義類,簡化了傳統的面向對象方法。class的定義包含了構造函數constructor,避免手動繼承(操作原型鏈)Student.prototype.hello = function () {…}這樣分散的代碼。
JavaScript中,可以用關鍵字new來調用構造函數,并返回一個對象。它綁定的this指向新創建的對象,并默認返回this,也就是說,不需要在最后寫return this;用new 創建的對象還從原型上獲得了一個constructor屬性,它指向函數Student本身
xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true
Object.getPrototypeOf(xiaoming) === Student.prototype; // true
xiaoming instanceof Student; // true
- ES6 中的 class 是沒有提升功能的,而在 ES5 中,使用函數模擬的類是有“提升”效果的。即class 并沒有函數提升的特性,這意味著類聲明必須在使用之前聲明。如果在類 person 定義之前訪問它,會拋出 ReferenceError 錯誤。這是因為 ES6 的 class 聲明是 塊級作用域,并且沒有提升。
在 ES5 中,我們通常使用構造函數來模擬類,而 JavaScript 中的函數聲明是有提升的(hoisting),也就是說函數聲明會被提升到作用域的頂部,所以我們可以在函數聲明之前調用它。 - 靜態方法 在方法前面加static,不需要創建實例,直接調用類.類方法,class_name.static_func()
- 繼承:新方法 子類extends父類
//父類class Person{constructor(name){this.name = name;}showName(){return `名字為: ${this.name}`;}}//子類class Student extends Person{constructor(name,skill){//別忘了加上參數super(name);
//擴展子類而不是覆蓋對應函數時,必須super()把父類對應函數拉過來 this.skill = skill;}showSkill(){super.showSkill(); //父級的同名方法執行return `哥們兒的技能為: ${this.skill}`;}}//調用let stu1 = new Student('Strive','逃學');console.log(stu1.showSkill());let a = 'strive';
let b = 'method';class Person {//ES6 class
//注意首字母大寫!!constructor(name, age) {this.name = name;this.age = age;}greet() {console.log(`Hello, I'm ${this.name}`);}
// 計算屬性名(computed property names)在 ES6 中動態創建對象的屬性或方法名。
//[a + b] 這種語法可以通過表達式來計算屬性名或方法名。[a + b]() {console.log("This is a dynamic method!");}//get 和 set 是用于定義**屬性訪問器(accessor properties)**的關鍵字。
//通過這兩個方法,可以自定義屬性的獲取(getter)和設置(setter)行為。
//Getter:用來獲取屬性的值。
//Setter:用來設置屬性的值。
/*
使用 getter 和 setter 的優點:
你可以在設置屬性時,加入額外的邏輯,例如數據驗證、格式化等。
獲取屬性時,你可以動態計算或處理值,而不需要暴露復雜的內部邏輯。
*/// Getter: 獲取_name的值,通過 person.name 的方式訪問,實際上調用的是 get name() 方法。get name() {return this._name;}// Setter: 設置_name的值,通過 person.name = 'newName' 的方式調用,實際上會觸發 set name(value) 方法set name(value) {if (value.length < 3) {console.log("Name must be at least 3 characters long.");} else {this._name = value;}}
}const p = new Person('Alice', 25);
p.greet();<script>function Person(name, age){//ES5 模擬類的函數this.name = name;this.age = age;}/* Person.prototype.showName = function(){return `名字為: ${this.name}`;};Person.prototype.showAge = function(){return `年齡為: ${this.age}`;}; */
//新舊版對比Object.assign(Person.prototype,{ //assign 合并showName(){return `名字為: ${this.name}`;},showAge(){return `年齡為: ${this.age}`;}});let p1 = new Person('Strive', 18);console.log(p1.showName());console.log(p1.showAge());//父類function Person(name){this.name = name;}Person.prototype.showName = function(){return `名字是: ${this.name}`;};//子類function Student(name,skill){Person.call(this,name); //繼承屬性 //使用矯正this函數this.skill = skill;}Student.prototype = new Person(); //繼承方法//調用let stu1 = new Student('Strive','逃學');console.log(stu1.showName());</script>
模塊化(ES Modules)
- CommonJS 適用于服務器端的 Javascript 模塊化。nodejs默認支持CommonJS模塊化規范,如果想在nodejs中體驗ES6模塊化規范,需要按照以下步驟配置:確保安裝v14.15.1及+版本的nodejs,在package.json根節點中添加"type":"module"節點
- ES Module v.s. CommonJS
相同點:都是模塊化規范,導入導出模塊
不同點:
CommonJS出現在ES6前,不支持動態導入,運行時加載,同步加載模塊。導出的是值的拷貝,導入模塊執行完后模塊內部的變化不會影響外部。導出用module.exports or exports
,導入用require
。適用于服務端nodeJS。共享模塊內的變量。
ES Module ES6引入,支持動態引入,編譯時加載,異步加載模塊。導出的是值的引用,外部和內部時刻保持一致變化。適用于瀏覽器。export and import
。頂層this是undefined,在嚴格模式下運行,每個模塊有各自的頂層作用域,內部變量不會全局共享。有利于前端代碼拆分、懶加載優化性能、靜態分析Tree Shaking。 - ES6模塊化規范
ES6 模塊化規范是瀏覽器端與服務器端通用的模塊化開發規范。它的出現極大的降低了前端開發者的模塊化學習成本,開發者不需再額外學習 AMD、CMD 或 CommonJS 等模塊化規范。
ES6 模塊化規范中定義:
? 每個 js 文件都是一個獨立的模塊
? 導入其它模塊成員使用 import 關鍵字
有提升效果,import會自動提升到頂部,首先執行,也就是說:不管放前面放后面都最先引入
無論導入多少次 都只導入一次
? 向外共享模塊成員使用 export 關鍵字
- 默認導出/導入
注意:每個模塊只允許一次export default否則報錯
export default {n1,show
}//a.js
export function get_data(url){ //默認導出fetch(url).then(response => response.json()).then(data => {console.log(data);return data;});
}
//b.js
import { get_data } from './utils.js'; //默認導入
//同時要在b.html中模塊化的引入b.js//html
<script type="module" src="./js/b.js"></script>
- 按需導出/導入
每個模塊可以多次。
按需導入時可用as重命名,可以和默認導入一起使用
// 按需導出
export const add = (a, b) => a + b;
export const a=12; //也可以導出變量
const b=1;
const c=2;
export {b as beta ,c as cici}
export function say(){}// 按需導入
import { add,a,cici } from './math.js';
- 如果是單純執行某個模塊代碼,不需要export,可以直接導入并執行即可。
// c.js
for(let i=0;i<3;i++)console.log(i)//index.js
import './c.js' //直接導入并執行
- html文件中如何引入?
<script type="module"></script>
+上述引入方式
<script type="module">import * as modTwo from './modules/2.js';console.log(modTwo.aaa);
</script>
- import/export 和 require/module.exports 區別?
特性 | import/export (ES6) | require/module.exports (CommonJS) |
---|---|---|
規范 | ES6(ECMAScript 2015)模塊化規范 | CommonJS 是 Node.js 模塊化規范 |
執行時機 | 靜態分析(編譯時) | 動態加載(運行時) |
是否支持異步加載 | 支持異步加載(import() ) | 不支持(同步加載) |
默認導入/導出 | 使用 export default | 通過 module.exports 或 exports 導出 |
導入方式 | import {a, b} from './file' | const {a, b} = require('./file') |
動態導入 | 支持動態導入(import() ) | 僅能使用 require 動態加載(但相對較麻煩) |
運行時加載方式 | 在文件編譯階段完成依賴關系 | 在運行時逐步解析依賴 |
是否支持循環依賴 | 支持(但會警告) | 支持(解決方案:require 返回緩存模塊) |
是否會被提升 | 不會,必須在頂部使用 | 會提升,可以在文件任何位置使用 |
瀏覽器支持 | 原生支持(需要模塊化支持的瀏覽器) | 需要通過構建工具(如 Webpack)支持 |
Node.js 版本支持 | Node.js 12+(需要開啟 "type": "module" ) | 默認支持 |
加載方式 | 優化,支持 Tree Shaking | 每次執行都會重新加載模塊 |
- export default 和 export 有什么不同?
特性 | export default | export (命名導出) |
---|---|---|
數量 | 每個模塊只能有一個 default 導出 | 每個模塊可以有多個命名導出 |
導入方式 | 導入時可以自定義名字 | 必須使用與導出時相同的名字 |
適用場景 | 用于導出模塊的主要功能/類/對象等 | 用于導出多個功能、常量、函數等 |
導入時的靈活性 | 導入時名稱不受限制 | 導入時名稱必須一致 |
動態導入 | 適用于動態導入,便于重命名導入 | 適用于動態導入,并保持一致性 |
迭代器與生成器(進階)
generator函數是‘生成器’,解決異步,深度嵌套的問題,現在有更好的解決方案: async/await
function * gen(){yield 'welcome';//第一步yield 'to';//第二步return '牧碼人';//第三步}let g1 = gen();console.log(g1.next()); //{value: "welcome", done: false沒完成}console.log(g1.next()); //{value: "to", done: false}console.log(g1.next()); //{value: "牧碼人", done: true}console.log(g1.next());//{ value: undefined, done: true }for(let val of g1){console.log(val);//不會遍歷到return!
}//生成器可以解構賦值
let [a, ...b] = gen();//let[a.b.c]
//擴展運算符console.log(...gen());//轉換為數組
console.log(Array.from(gen()));//應用:請求
<script src="https://unpkg.com/axios/dist/axios.min.js"></script><script>//https://api.github.com/users/itstrivefunction * gen(){let val = yield 'aaa';yield axios.get(`https://api.github.com/users/${val}`);}let g1 = gen();let username = g1.next().value;//console.log(g1.next(username).value);g1.next(username).value.then(res=>{console.log(res.data);});</script>/*
整個流程沒有問題,
實際上你只是通過生成器來控制異步操作的執行順序(類似協程的模式)。
但是,生成器的部分在這里并沒有發揮出它的全部優勢。
通常生成器和異步操作結合的方式是通過 yield 來暫停執行,等待異步操作完成,
然后再繼續執行后續的代碼。你可以通過使用 async/await 來簡化代碼結構,
或者將生成器的控制交給一個專門的調度器。
*///改進
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>//https://api.github.com/users/itstrivefunction* gen() {let val = yield 'aaa'; // This is where we get the usernamelet response = yield axios.get(`https://api.github.com/users/${val}`);return response.data;//返回一個 Promise 對象}function handleGenerator(gen) {const iterator = gen();function iterate(result) {if (result.done) return; // If done, stop//配合Promise.then()result.value.then(val => {iterate(iterator.next(val));
// Pass the result to the next yield
//遞歸調用});}iterate(iterator.next());
// Start the iteration}handleGenerator(gen);
</script>
可選鏈 & 空值合并運算符(ES2020+)
更方便地處理嵌套對象和可能為 null 或 undefined 的值。
- 可選鏈(Optional Chaining)
可選鏈(?.
)是用來安全地訪問嵌套對象的屬性,它會自動判斷中間的任何一部分是否為 null 或 undefined,如果是,則會短路并返回 undefined,而不會拋出錯誤。
user?.info?.name
user->user.info->user.info.name - 空值合并運算符(Nullish Coalescing Operator)
與 邏輯 OR 運算符 (||
) 類似,但??
僅在左側值為 null 或 undefined 時 才會返回右側值,而||
會在左側值是任何假值(如 0, false, “”, NaN 等)時返回右側值。 - 練習
const name = user?.info?.name ?? 'default';
其他
?WeakSet
- 定義
WeakSet 是 ES6 引入的一種新的集合類型。
弱引用:WeakSet 對象的元素是 弱引用,這意味著如果一個對象沒有其他引用指向它,垃圾回收機制會回收它,而不會因為它在 WeakSet 中的存在而阻止回收。
WeakSet 支持以下基本操作:
add(value):將一個對象添加到 WeakSet 中。如果該對象已經存在,什么都不做。初始往里面添加東西,是不行的。最好用add添加
has(value):檢查某個對象是否在 WeakSet 中。如果存在返回 true,否則返回 false。
delete(value):從 WeakSet 中刪除一個對象。如果該對象存在,
// 創建一些對象
let obj1 = {name: 'Alice'};
let obj2 = {name: 'Bob'};
let obj3 = {name: 'Charlie'};// 創建 WeakSet
let weakSet = new WeakSet();// 添加對象到 WeakSet
weakSet.add(obj1);
weakSet.add(obj2);// 檢查對象是否在 WeakSet 中
console.log(weakSet.has(obj1)); // true
console.log(weakSet.has(obj3)); // false// 刪除對象
weakSet.delete(obj1);
console.log(weakSet.has(obj1)); // false
- 為什么會有weakset?
1)跟蹤對象的存在性:WeakSet 可用于跟蹤對象是否曾經存在,而不阻止這些對象的垃圾回收。例如,某些需要動態管理的對象,且在不再需要時能被自動清除。
2)避免內存泄漏:由于 WeakSet 使用弱引用,集合中的對象可以被垃圾回收,避免了因長期引用對象而導致的內存泄漏。???????在一些緩存系統中,可以使用 WeakSet 來存儲對象,確保不再被引用時自動被清除。 - WeakSet 與 Set 的區別:
async/await
在此之前 promise只能通過鏈式.then()方式處理,可讀性不好,比較復雜。
- async/await 是 ES8(ECMAScript 2017)引入的語法糖,旨在讓異步代碼更簡潔、更易讀。它是基于 Promise的,提供了更直觀的方式來處理異步操作,避免了傳統回調函數帶來的“回調地獄”(callback hell)。async/await 本質上 是寫法上看起來像同步,但底層仍是異步執行。async-await是配套使用的。在 async 方法中,第一個 await 之前的代碼會同步執行,await 之后的代碼會異步執行。
- 雖然await寫法是一步一步的,但 await 后面的操作其實是非阻塞的,它只是讓當前 async 函數“暫停”,而不會阻塞整個程序的運行。你可以把 await 理解成一個“暫停點”,程序會在那兒等著 Promise 完成,然后繼續往下走。
注意:await 是暫停,但不是“阻塞線程” - JavaScript 是 單線程的
await 并不是開了一個新線程,而是把當前 async 函數掛起,等 Promise 完成后再“恢復”繼續執行;同時,主線程可以繼續處理其他任務(比如事件監聽、渲染頁面、執行其他函數等);類似于單核 CPU 的分時復用,這邊暫停下來,先去忙別的事,這邊ok了再繼續回來執行。 - async 關鍵字
async 用來標記一個函數為異步函數。當一個函數被標記為 async 時,它會返回一個 Promise對象。即使函數內部沒有顯式返回 Promise,async 函數會自動將返回值封裝在一個 Promise 對象中。
當一個 async 函數中的 await 語句后面的 Promise 變成 reject(即發生錯誤)時,整個 async 函數會中斷執行,并且后續的代碼不會再被執行,除非你在 async 函數內使用了 try…catch 來捕獲錯誤。 - await 關鍵字
await 只能在 async 函數內使用,它會暫停函數的執行,等待一個 Promise 完成,并返回 Promise 的結果。如果 Promise 被拒絕,它會拋出異常,必須使用 try/catch 來捕獲錯誤。
一旦 Promise 被解析,await 會返回解析值。如果 Promise 被拒絕,await 會拋出拒絕的錯誤。
await后面可以是promise對象,也可以數字、字符串、布爾值等。
async function fetchData() {try {let response = await fetch("https://api.github.com/users/octocat");let data = await response.json();return data;} catch (error) {console.log("錯誤:", error);}
}
fetchData(); // 如果網絡請求失敗,輸出錯誤async function getUserRepos(username) {let userResponse = await fetch(`https://api.github.com/users/${username}`);let user = await userResponse.json();let reposResponse = await fetch(user.repos_url);let repos = await reposResponse.json();return repos;
}
getUserRepos("octocat").then((repos) => {console.log(repos); // 輸出該用戶的所有倉庫
});