變數
(一) const 與 let以及var
1. 三者之間區別
- 1.變數提升
- var宣告 無論宣告在何處,都會被視為宣告在函式的最頂部
- let和const宣告不會提升
- 2.作用域
- var是函式作用域, 在函式內部作用, 但是{}裡是一樣會提升的
- let和const是塊級作用域, 在{}裡就形成了一個作用域
- 3.重複宣告
- var 可以重複定義
- let和const不可以重複定義,否則報錯
- 4.const常量不可修改
- const 宣告的變數都會被認為是常量,意思就是它的值被設定完成後就不能再修改了;
- 5. 如果const的是一個物件,物件所包含的值是可以被修改的。抽象一點兒說,就是物件所指向的地址沒有變就行:
const student = { name: 'cc' }
student.name = 'yy';// 不報錯
student = { name: 'yy' };// 報錯
複製程式碼
2. 暫時性死區
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
複製程式碼
- 上面程式碼中,存在全域性變數
tmp
,但是塊級作用域內let
又宣告瞭一個區域性變數tmp
,導致後者繫結這個塊級作用域,所以在let
宣告變數前,對tmp
賦值會報錯。
有幾個點需要注意:
# let 關鍵詞宣告的變數不具備變數提升(hoisting)特性
# let 和 const 宣告只在最靠近的一個塊中(花括號內)有效
# 當使用常量 const 宣告時,請使用大寫變數,如:CAPITAL_CASING
# const 在宣告時必須被賦值
複製程式碼
3. 建議
在日常開發中,我的建議是全面擁抱let/const,一般的變數宣告使用let關鍵字,而當宣告一些配置項(類似介面地址,npm依賴包,分頁器預設頁數等一些一旦宣告後就不會改變的變數)的時候可以使用const,來顯式的告訴專案其他開發者,這個變數是不能改變的(const宣告的常量建議使用全大寫字母標識,單詞間用下劃線),同時也建議瞭解var關鍵字的缺陷(變數提升,汙染全域性變數等),這樣才能更好的使用新語法
函式
(一) 箭頭函式(Arrow Functions)
- ES6 中,箭頭函式就是函式的一種簡寫形式,使用括號包裹引數,跟隨一個 =>,緊接著是函式體;
1. 箭頭函式對於使用function關鍵字建立的函式有以下區別:
- 箭頭函式沒有arguments(建議使用更好的語法,剩餘運算子替代)
- 箭頭函式沒有prototype屬性,不能用作建構函式(不能用new關鍵字呼叫)
- 箭頭函式沒有自己this,它的this是詞法的,引用的是上下文的this,即在你寫這行程式碼的時候就箭頭函式的this就已經和外層執行上下文的this繫結了(這裡個人認為並不代表完全是靜態的,因為外層的上下文仍是動態的可以使用call,apply,bind修改,這裡只是說明了箭頭函式的this始終等於它上層上下文中的this)
2. 箭頭函式最直觀的三個特點
# 不需要 function 關鍵字來建立函式
# 省略 return 關鍵字
# 繼承當前上下文的 this 關鍵字
複製程式碼
- 細節:當你的函式有且僅有一個引數的時候,是可以省略掉括號的。當你函式返回有且僅有一個表示式的時候可以省略{} 和 return;
3. 規則
- 使用了塊語句的箭頭函式不會自動返回值,你需要使用return語句將所需值返回。
- 不可以當作建構函式,即,不可以使用new 關鍵字來例項化物件,否則會丟擲一個錯誤。
- 不可以使用arguments物件,更不能通過arguments物件訪問傳入引數,該物件在函式體內不存在。如果要用,可以用 rest 引數代替。
- 不可以使用yield命令,因此箭頭函式不能用作 Generator 函式.
- 返回的就是一個物件, 需要在外面加一個括號 var getTempItem = id = > ({id: id,});
(二) 函式的引數預設值
// ES6之前,當未傳入引數時,text = 'default';
function printText(text) {
text = text || 'default';
console.log(text);
}
// ES6;
function printText(text = 'default') {
console.log(text);
}
printText('hello'); // hello
printText();// default
複製程式碼
(三) Promise(常用)
Promise作為ES6中推出的新的概念,改變了JS的非同步程式設計,現代前端大部分的非同步請求都是使用Promise實現,fetch這個web api也是基於Promise的,這裡不得簡述一下之前統治JS非同步程式設計的回撥函式,回撥函式有什麼缺點,Promise又是怎麼改善這些缺點
回撥函式缺點
- 多重巢狀,導致回撥地獄
- 程式碼跳躍,並非人類習慣的思維模式
- 信任問題,你不能把你的回撥完全寄託與第三方庫,因為你不知道第三方庫到底會怎麼執行回撥(多次執行)
- 第三方庫可能沒有提供錯誤處理
- 不清楚回撥是否都是非同步呼叫的(可以同步呼叫ajax,在收到響應前會阻塞整個執行緒,會陷入假死狀態,非常不推薦)
Promise
針對回撥函式這麼多缺點,ES6中引入了一個新的概念Promise,Promise是一個建構函式,通過new關鍵字建立一個Promise的例項,來看看Promise是怎麼解決回撥函式的這些問題
- 不清楚回撥是否都是非同步呼叫的(可以同步呼叫ajax,在收到響應前會阻塞整個執行緒,會陷入假死狀態,非常不推薦)
- Promise在設計的時候保證所有響應的處理回撥都是非同步呼叫的,不會阻塞程式碼的執行,Promise將then方法的回撥放入一個叫微任務的佇列中(MicroTask),確保這些回撥任務在同步任務執行完以後再執行,這部分同樣也是事件迴圈的知識點,有興趣的朋友可以深入研究一下
建議
- 在日常開發中,建議全面擁抱新的Promise語法,其實現在的非同步程式設計基本也都使用的是Promise
- 建議使用ES7的async/await進一步的優化Promise的寫法,async函式始終返回一個Promise,await可以實現一個"等待"的功能,async/await被成為非同步程式設計的終極解決方案,即用同步的形式書寫非同步程式碼,並且能夠更優雅的實現非同步程式碼順序執行以及在發生非同步的錯誤時提供更精準的錯誤資訊
字串
(一) 字串模板
- 需要拼接字串的時候儘量改成使用模板字串:
// bad
const foo = 'this is a' + example;
// good
const foo = `this is a ${example}`;
複製程式碼
- 而對ES6來說
- 基本的字串格式化。將表示式嵌入字串中進行拼接。用${}來界定;
- ES6反引號(``)直接搞定;
陣列
for ... of迴圈
- for ... of是作為ES6新增的遍歷方式,允許遍歷一個含有iterator介面的資料結構並且返回各項的值,和ES3中的for ... in的區別如下
- for ... of只能用在可迭代物件上,獲取的是迭代器返回的value值, for ... in 可以獲取所有物件的鍵名
- for ... in會遍歷物件的整個原型鏈,效能非常差不推薦使用, 而for ... of只遍歷當前物件不會遍歷它的原型鏈
- 對於陣列的遍歷,for ... in會返回陣列中所有可列舉的屬性(包括原型鏈上可列舉的屬性), for ... of只返回陣列的下標對應的屬性值
- for ... of迴圈的原理其實也是利用了可迭代物件內部部署的iterator介面,如果將for ... of迴圈分解成最原始的for迴圈,內部實現的機制可以這麼理解
- 可以看到只要滿足第二個條件(iterator.next()存在且res.done為true)就可以一直迴圈下去,並且每次把迭代器的next方法生成的物件賦值給res,然後將res的value屬性賦值給for ... of第一個條件中宣告的變數即可,res的done屬性控制是否繼續遍歷下去
3. for... of迴圈同時支援break,continue,return(在函式中呼叫的話)並且可以和物件解構賦值一起使用
- arr陣列每次使用for ... of迴圈都返回一物件({a:1},{a:2},{a:3}),然後會經過物件解構,尋找屬性為a的值,賦值給obj.a,所以在每輪迴圈的時候obj.a會分別賦值為1,2,3
物件
(一) 物件屬性/方法簡寫(常用)
1. es6允許當物件的屬性和值相同時,省略屬性名
- 需要注意的是**
- 省略的是屬性名而不是值
- 值必須是一個變數
2. 物件屬性簡寫經常與解構賦值一起使用
- 結合上文的解構賦值,這裡的程式碼會其實是宣告瞭x,y,z變數,因為bar函式會返回一個物件,這個物件有x,y,z這3個屬性,解構賦值會尋找等號右邊表示式的x,y,z屬性,找到後賦值給宣告的x,y,z變數
3. 方法簡寫
- es6允許當一個物件的屬性的值是一個函式(即是一個方法),可以使用簡寫的形式
Module模組化(常用)
在ES6 Module出現之前,模組化一直是前端開發者討論的重點,面對日益增長的需求和程式碼,需要一種方案來將臃腫的程式碼拆分成一個個小模組,從而推出了AMD,CMD和CommonJs這3種模組化方案,前者用在瀏覽器端,後面2種用在服務端,直到ES6 Module出現
- ES6 Module預設目前還沒有被瀏覽器支援,需要使用babel,在日常寫demo的時候經常會顯示這個錯誤
Module特點
- ES6 Module是靜態的,也就是說它是在編譯階段執行,和var以及function一樣具有提升效果(這個特點使得它支援tree shaking)
- 自動採用嚴格模式(頂層的this返回undefined)
- ES6 Module支援使用export {<變數>}匯出具名的介面,或者export default匯出匿名的介面
心得:ES6不僅支援變數的匯出,也支援常量的匯出。 export const sqrt = Math.sqrt;//匯出常量
心得:一條import 語句可以同時匯入預設函式和其它變數。import default Method, { otherMethod } from 'xxx.js';
module.js匯出
import匯入
- 這兩者的區別是,export {<變數>}匯出的是一個變數的引用,export default匯出的是一個值
- 什麼意思呢,就是說在a.js中使用import匯入這2個變數的後,在module.js中因為某些原因x變數被改變了,那麼會立刻反映到a.js,而module.js中的y變數改變後,a.js中的y還是原來的值
ES6 Module和CommonJs的一些區別
- CommonJs輸出的是一個值的拷貝,ES6 Module通過export {<變數>}輸出的是一個變數的引用,export default輸出的是一個值
- CommonJs執行在伺服器上,被設計為執行時載入,即程式碼執行到那一行才回去載入模組,而ES6 Module是靜態的輸出一個介面,發生在編譯的階段
- CommonJs在第一次載入的時候執行一次並且會生成一個快取,之後載入返回的都是快取中的內容
import()動態載入
- 關於ES6 Module靜態編譯的特點,導致了無法動態載入,但是總是會有一些需要動態載入模組的需求,所以現在有一個提案,使用把import作為一個函式可以實現動態載入模組,它返回一個Promise,Promise被resolve時的值為輸出的模組
- Vue中路由的懶載入的ES6寫法就是使用了這個技術,使得在路由切換的時候能夠動態的載入元件渲染檢視
解構賦值
理解
解構賦值可以直接使用物件的某個屬性,而不需要通過屬性訪問的形式使用,物件解構原理個人認為是通過尋找相同的屬性名,然後原物件的這個屬性名的值賦值給新物件對應的屬性, 鍵找鍵,找到了就賦值了
- 解構陣列
var arr = [1, 2, 3, 4];
let [a, b, c, d] = arr;
console.log(a); // 1
console.log(b); // 2
複製程式碼
- 解構物件
var luke = { occupation: 'jedi', father: 'anakin' };
let {occupation, father} = luke;
console.log(occupation); // jedi
console.log(father); // anakin
複製程式碼
- vuex使用物件解構
建議
同樣建議使用,因為解構賦值語意化更強,對於作為物件的函式引數來說,可以減少形參的宣告,直接使用物件的屬性(如果巢狀層數過多我個人認為不適合用物件解構,不太優雅)
剩餘運算子rest/擴充套件運算子(常用)
剩餘/擴充套件運算子同樣也是ES6一個非常重要的語法,使用3個點(...),後面跟著一個含有iterator介面的資料結構
擴充套件運算子
- 以陣列為例,使用擴充套件運算子使得可以"展開"這個陣列,可以這麼理解,陣列是存放元素集合的一個容器,而使用擴充套件運算子可以將這個容器拆開,這樣就只剩下元素集合,你可以把這些元素集合放到另外一個陣列裡面, 代替ES3中陣列原型的concat方法
剩餘運算子
剩餘運算子最重要的一個特點就是替代了以前的arguments
rest只是形參, 可以隨意取名
- 訪問函式的arguments物件是一個很昂貴的操作,以前的arguments.callee,arguments.caller都被廢止了,建議在支援ES6語法的環境下不要在使用arguments物件,使用剩餘運算子替代(箭頭函式沒有arguments,必須使用剩餘運算子才能訪問引數集合)
- 剩餘運算子可以和陣列的解構賦值一起使用,但是必須放在最後一個,因為剩餘運算子的原理其實是利用了陣列的迭代器,它會消耗3個點後面的陣列的所有迭代器,讀取所有迭代器生成物件的value屬性,剩運算子後不能在有解構賦值,因為剩餘運算子已經消耗了所有迭代器,而陣列的解構賦值也是消耗迭代器,但是這個時候已經沒有迭代器了,所以會報錯
- 這裡first會消耗右邊陣列的一個迭代器,...arr會消耗剩餘所有的迭代器,而第二個例子...arr直接消耗了所有迭代器,導致last沒有迭代器可供消耗了,所以會報錯,因為這是毫無意義的操作
區別
剩餘運算子和擴充套件運算子的區別就是,剩餘運算子會收集這些集合,放到右邊的陣列中,擴充套件運算子是將右邊的陣列拆分成元素的集合,它們是相反的
在物件中使用擴充套件運算子
- 這個是ES9的語法,ES9中支援在物件中使用擴充套件運算子,之前說過陣列的擴充套件運算子原理是消耗所有迭代器,但物件中並沒有迭代器,我個人認為可能是實現原理不同,但是仍可以理解為將鍵值對從物件中拆開,它可以放到另外一個普通物件中
- 其實它和另外一個ES6新增的API相似,即Object.assign,它們都可以合併物件,但是還是有一些不同Object.assign會觸發目標物件的setter函式,而物件擴充套件運算子不會
建議
- 使用擴充套件運算子可以快速的將類陣列轉為一個真正的陣列
- 合併多個陣列
函式柯理化
類(class) (ES6)
對熟悉Java,object-c,c#等純面嚮物件語言的開發者來說,都會對class有一種特殊的情懷。ES6 引入了class(類),讓JavaScript的物件導向程式設計變得更加簡單和易於理解。
class Animal {
// 建構函式,例項化的時候將會被呼叫,如果不指定,那麼會有一個不帶引數的預設建構函式.
constructor(name,color) {
this.name = name;
this.color = color;
}
// toString 是原型物件上的屬性
toString() {
console.log('name:' + this.name + ',color:' + this.color);
}
}
var animal = new Animal('dog','white');//例項化Animal
animal.toString();
console.log(animal.hasOwnProperty('name')); //true
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__.hasOwnProperty('toString')); // true
class Cat extends Animal {
constructor(action) {
// 子類必須要在constructor中指定super 函式,否則在新建例項的時候會報錯.
// 如果沒有置頂consructor,預設帶super函式的constructor將會被新增、
super('cat','white');
this.action = action;
}
toString() {
console.log(super.toString());
}
}
var cat = new Cat('catch')
cat.toString();
// 例項cat 是 Cat 和 Animal 的例項,和Es5完全一致。
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true
複製程式碼
includes
includes() 函式用來判斷一個陣列是否包含一個指定的值,如果包含則返回 true,否則返回false。
includes 函式與 indexOf 函式很相似,下面兩個表示式是等價的:
arr.includes(x)
arr.indexOf(x) >= 0
複製程式碼
接下來我們來判斷數字中是否包含某個元素:
在ES7之前的做法
使用indexOf()驗證陣列中是否存在某個元素,這時需要根據返回值是否為-1來判斷:
let arr = ['react', 'angular', 'vue'];
if (arr.indexOf('react') !== -1)
{
console.log('react存在');
}
複製程式碼
使用ES7的includes()
使用includes()驗證陣列中是否存在某個元素,這樣更加直觀簡單:
let arr = ['react', 'angular', 'vue'];
if (arr.includes('react'))
{
console.log('react存在');
}
複製程式碼
指數操作符
在ES7中引入了指數運算子**
,**
具有與Math.pow(..)
等效的計算結果。
不使用指數操作符
使用自定義的遞迴函式calculateExponent或者Math.pow()進行指數運算:
function calculateExponent(base, exponent)
{
if (exponent === 1)
{
return base;
}
else
{
return base * calculateExponent(base, exponent - 1);
}
}
console.log(calculateExponent(2, 10)); // 輸出1024
console.log(Math.pow(2, 10)); // 輸出1024
複製程式碼
複製程式碼
使用指數操作符
使用指數運算子**,就像+、-等操作符一樣:
console.log(2**10);// 輸出1024
複製程式碼
1