摘要:一共講解了個常用的新特性,講解過程也是由淺入深。最后一個新增的方法主要是為了彌補當做構造函數使用時產生的怪異結果。特性共享父級對象共享父級不能當做構造函數語法最簡表達式前后對比很容易理解,可以明顯看出箭頭函數極大地減少了代碼量。
上周在公司組織了 ES6 新特性的分享會,主要講了工程化簡介、ES6 的新特性與前端常用的幾種構建工具的配合使用。ES6 這塊主要講了一些我們平時開發中經常會用到的新特性。在這里整理一下關于 ES6 的部分。
一共講解了 8 個常用的 ES6 新特性,講解過程也是由淺入深。廢話不多說,下面進入正文。
// Before function decimal(num, fix) { fix = fix === void(0) ? 2 : fix; return +num.toFixed(fix); }
// After function decimal(num, fix = 2) { return +num.toFixed(fix); }
首先,我們看一下之前我們是怎么寫函數默認值的:我們通常會使用三元運算符來判斷入參是否有值,然后決定是否使用默認值運行函數(如示例中 fix = fix === void(0) ? 2 : fix)
而在 ES6 中,我們可以直接在函數的顯示入參中指定函數默認值(function decimal(num, fix = 2){}),很明顯,這種寫法更自然易懂,也更加方便,不過有一點需要注意:
設定了默認值的入參,應該放在沒有設置默認值的參數之后,也就是我們不應該這樣寫:function decimal(fix = 2, num){},雖然通過變通手段也可以正常運行,但不符合規范。
模板字符串 特性 & 語法// Before // Before.1 var type = "simple"; "This is a " + type + " string join." // Before.2 var type = "multiline"; "This is a " + type + " string." // Before.3 var type = "pretty singleline"; "This is a " + type + " string." // OR // Before.4 "This " + "is" + "a" + type + "string."
// After var type = "singleline"; `This is a ${type} string.` var type = "multiline"; `This is a ${type} string.` var type = "pretty singleline"; `This is a ${type} string.`
我們之前在對字符串和變量進行拼接的時候,通常都是反復一段一段使用引號包裹的字符串,再反復使用加號進行拼接(Before.1)。多行字符串的時候我們還要寫上蹩腳的 來換行以得到一個多行的字符串(Before.2)。
在字符串過長的時候可能會使用 在編輯器中書寫多行字符串來表示單行字符串,用來方便較長的字符串在編輯器中的閱讀(Before.3),或者簡單粗暴的反復引號加號這樣多行拼接(Before.4)。
ES6 中我們可以使用反引號(`,位于 TAB 上方)來輸入一段簡單明了的多行字符串,還可以在字符串中通過 ${變量名} 的形式方便地插入一個變量,是不是方便多了!
解構賦值 數組解構var [a, ,b] = [1, 2, 3, 4, 5]; console.log(a); // => 1 console.log(b); // => 3
數組解構,使用變量聲明關鍵字聲明一個形參數組([a, , b]),等號后跟一個待解構目標數組([1, 2, 3]),解構時可以通過留空的方式跳過數組中間的個別元素,但是在形參數組中必須留有相應空位才可以繼續解構之后的元素,如果要跳過的元素處于數組末端,則在形參數組中可以不予留空。
對象解構var {b, c} = {a: 1, b: 2, c: 3}; console.log(b); // => 2 console.log(c); // => 3
對象解構與數組解構大體相同,不過需要注意一點
形參對象({b, c})的屬性或方法名必須與待解構的目標對象中的屬性或方法名完全相同才能解構到對應的屬性或方法
對象匹配解構var example = function() { return {a: 1, b: 2, c: 3}; } var {a: d, b: e, c: f} = example(); console.log(d, e, f); // => 1, 2, 3
對象匹配解構是對象解構的一種延伸用法,我們可以在形參對象中使用:來更改解構后的變量名。
函數入參解構function example({param: value}) { return value; } console.log(example({param: 5})); // => 5
函數的入參解構也是對象解構的一種延伸用法,我們可以通過改寫入參對象目標值為變量名的方式,在函數內部直接獲取到入參對象中某個屬性或方法的值。
函數入參默認值解構function example({x, y, z = 0}) { return x + y + z; } console.log(example({x: 1, y: 2})); // => 3 console.log(example({x: 1, y: 2, z: 3})); // => 6
這是入參解構的另一種用法,我們可以在入參對象的形參屬性或方法中使用等號的方式給入參對象的某些屬性或方法設定默認值。
Let & Const Let無變量提升
// Before console.log(num); // => undefined var num = 1;
// After console.log(num); // => ReferenceError let num = 1;
使用 var 聲明的變量會自動提升到當前作用域的頂部,如果聲明位置與作用域頂部之間有另一個同名變量,很容易引起難以預知的錯誤。使用 let 聲明的變量則不會進行變成提升,規避了這個隱患。
注意:var 聲明的變量提升后雖然在聲明語句之前輸出為 undefined,但這并不代表 num 變量還沒有被聲明,此時 num 變量已經完成聲明并分配了相應內存,只不過該變量目前的值為 undefined,并不是我們聲明語句中賦的初始值 1。
有塊級作用域
// Before { var num = 1; console.log(num); // => 1 } console.log(num); // => 1
// After { let num = 1; console.log(num); // => 1 } console.log(num); // => ReferenceError
let 聲明的變量只能在當前塊級作用域中使用,最常見的應用大概就是 for(let i = 0, i < 10; i++) {},相信許多小伙伴在面試題中見過,哈哈。
禁止重復聲明
// Before var dev = true; var dev = false; console.log(dev); // => false
// After let dev = true; let dev = false; // => SyntaxError
var 聲明的變量可以重復聲明,而且不會有任何警告或者提示,就這樣悄悄的覆蓋了一個值,隱患如變量提升一樣讓人擔憂。( ̄┰ ̄*)
而 let 聲明的變量如果進行重復聲明,則會直接拋出一個語法錯誤(是的,就是直接明確地告訴你:你犯了一個相當低級的語法錯誤哦)
Const無變量提升
有塊級作用域
禁止重復聲明
前 3 點跟 let 一個套路,就不多說了
禁止重復賦值
const DEV = true; DEV = false; // => TypeError
基于靜態常量的定義我們可以很明顯知道,const 聲明的常量一經聲明便不能再更改其值,無需多說。
必須附初始值
const DEV; // => SyntaxError
也是基于定義,const 聲明的常量既然一經聲明便不能再更改其值,那聲明的時候沒有附初始值顯然是不合理的,一個沒有任何值的常量是沒有意義的,浪費內存。
新增庫函數ES6 新增了許多(相當多)的庫函數,這里只介紹一些比較常用的。
Number題外話:多了解一下內建函數與方法有時候可以很方便高效地解決問題。有時候絞盡腦汁寫好的一個算法,沒準已經有內建函數實現了!而且內建函數經過四海八荒眾神的考驗,性能一定不錯,哈哈。
Number.EPSILON Number.isInteger(Infinity); // => false Number.isNaN("NaN"); // => false
首先是 ? 這個常量屬性,表示小數的極小值,主要用來判斷浮點數計算是否精確,如果計算誤差小于該閾值,則可以認為計算結果是正確的。
然后是 isInteger() 這個方法用來判斷一個數是否為整數,返回布爾值。
最后是 isNaN() 用來判斷入參是否為 NaN。是的,我們再也不用通過 NaN 不等于 NaN 才能確定一個 NaN 就是 NaN 這種反人類的邏輯來判斷一個 NaN 值了!
if(NaN !== NaN) { console.log("Yes! This is actually the NaN!"); }
另外還有兩個小改動:兩個全局函數 parseInt() 與 parseFloat() 被移植到 Number 中,入參反參保持不變。這樣所有數字處理相關的都在 Number 對象上嘞!規范多了。
String"abcde".includes("cd"); // => true "abc".repeat(3); // => "abcabcabc" "abc".startsWith("a"); // => true "abc".endsWith("c"); // => true
inclueds() 方法用來判斷一個字符串中是否存在指定字符串
repeat() 方法用來重復一個字符串生成一個新的字符串
startsWith() 方法用來判斷一個字符串是否以指定字符串開頭,可以傳入一個整數作為第二個參數,用來設置查找的起點,默認為 0,即從字符串第一位開始查找
endsWith() 與 startsWith() 方法相反
ArrayArray.from(document.querySelectorAll("*")); // => returns a real array. [0, 0, 0].fill(7, 1); // => [0, 7, 7] [1, 2, 3].findIndex(function(x) { return x === 2; }); // => 1 ["a", "b", "c"].entries(); // => Iterator [0: "a"], [1: "b"], [2: "c"] ["a", "b", "c"].keys(); // => Iterator 0, 1, 2 ["a", "b", "c"].values(); // => Iterator "a", "b", "c" // Before new Array(); // => [] new Array(4); // => [,,,] new Array(4, 5, 6); // => [4, 5, 6] // After Array.of(); // => [] Array.of(4); // => [4] Array.of(4, 5, 6); // => [4, 5, 6]
首先是 from() 方法,該方法可以將一個類數組對象轉換成一個真正的數組。還記得我們之前常寫的 Array.prototype.slice.call(arguments) 嗎?現在可以跟他說拜拜了~
之后的 fill() 方法,用來填充一個數組,第一個參數為將要被填充到數組中的值,可選第二個參數為填充起始索引(默認為 0),可選第三參數為填充終止索引(默認填充到數組末端)。
findIndex() 用來查找指定元素的索引值,入參為函數,函數形參跟 map() 方法一致,不多說。最終輸出符合該條件的元素的索引值。
entries()、keys()、values() 三個方法各自返回對應鍵值對、鍵、值的遍歷器,可供循環結構使用。
最后一個新增的 of() 方法主要是為了彌補 Array 當做構造函數使用時產生的怪異結果。
Objectlet target = { a: 1, b: 3 }; let source = { b: 2, c: 3 }; Object.assign(target, source); // => { a: 1, b: 2, c: 3}
assign() 方法用于合并兩個對象,不過需要注意的是這種合并是淺拷貝。可能看到這個方法我們還比較陌生,不過了解過 jQuery 源碼的應該知道 $.extend() 這個方法,例如在下面這個粗糙的 $.ajax() 模型中的應用:
$.ajax = function(opts) { var defaultOpts = { method: "GET", async: true, //... }; opts = $.extend(defaultOpts, opts); }
從這我們可以看到 TC39 也是在慢慢吸收百家所長,努力讓 JavaScript 變得更好,更方便開發者的使用。
MathObject 新增的特性當然不止這一個 assign() 方法,一共增加了十多個新特性,特別是對屬性或方法名字面量定義的增強方面,很值得一看,感興趣的自行查找資料進行了解哈,印象會更深刻!
Math 對象上同樣增加了許多新特性,大部分都是數學計算方法,這里只介紹兩個常用的
Math.sign(5); // => +1 Math.sign(0); // => 0 Math.sign(-5); // => -1 Math.trunc(4.1); // => 4 Math.trunc(-4.1); // => -4
sign() 方法用來判斷一個函數的正負,使用與對應返回值如上。
trunc() 用來取數值的整數部分,我們之前可能經常使用 floor() 方法進行取整操作,不過這個方法有一個問題就是:它本身是向下取整,當被取整值為正數的時候計算結果完全 OK,但是當被取整值為負數的時候:
Math.floor(-4.1); // => -5
箭頭函數插播一個小 Tip:使用位操作符也可以很方便的進行取整操作,例如:~~3.14 or 3.14 | 0,也許這更加方便 : )
箭頭函數無疑是 ES6 中一個相當重要的新特性。
特性共享父級 this 對象
共享父級 arguments
不能當做構造函數
語法var arr = [1, 2, 3, 4, 5, 6]; // Before arr.filter(function(v) { return v > 3; }); // After arr.filter(v => v > 3); // => [4, 5, 6]
前后對比很容易理解,可以明顯看出箭頭函數極大地減少了代碼量。
var arr = [1, 2, 3, 4, 5, 6]; arr.map((v, k, thisArr) => { return thisArr.reverse()[k] * v; }) // => [6, 10, 12, 12, 10, 6]
一個簡單的首尾相乘的算法,對比最簡表達式我們可以發現,函數的前邊都省略了 function 關鍵字,但是多個入參時需用括號包裹入參,單個入參是時可省略括號,入參寫法保持一致。后面使用胖箭頭 => 連接函數名與函數體,函數體的寫法保持不變。
// Before var obj = { arr: [1, 2, 3, 4, 5, 6], getMaxPow2: function() { var that = this, getMax = function() { return Math.max.apply({}, that.arr); }; return Math.pow(getMax(), 2); } } // After var obj = { arr: [1, 2, 3, 4, 5, 6], getMaxPow2: function() { var getMax = () => { return Math.max.apply({}, this.arr); } return Math.pow(getMax(), 2); } }
注意看中第 5 行 var that = this 這里聲明的一個臨時變量 that。在對象或者原型鏈中,我們以前經常會寫這樣一個臨時變量,或 that 或 _this,諸如此類,以達到在一個函數內部訪問到父級或者祖先級 this 對象的目的。
如今在箭頭函數中,函數體內部沒有自己的 this,默認在其內部調用 this 的時候,會自動查找其父級上下文的 this 對象(如果父級同樣是箭頭函數,則會按照作用域鏈繼續向上查找),這無疑方便了許多,我們無需在多余地聲明一個臨時變量來做這件事了。
注意:
某些情況下我們可能需要函數有自己的 this,例如 DOM 事件綁定時事件回調函數中,我們往往需要使用 this 來操作當前的 DOM,這時候就需要使用傳統匿名函數而非箭頭函數。
在嚴格模式下,如果箭頭函數的上層函數均為箭頭函數,那么 this 對象將不可用。
另,由于箭頭函數沒有自己的 this 對象,所以箭頭函數不能當做構造函數。
我們知道在函數體中有 arguments 這樣一個偽數組對象,該對象中包含該函數所有的入參(顯示入參 + 隱式入參),當函數體中有另外一個函數,并且該函數為箭頭函數時,該箭頭函數的函數體中可以直接訪問父級函數的 arguments 對象。
function getSum() { var example = () => { return Array .prototype .reduce .call(arguments, (pre, cur) => pre + cur); } return example(); } getSum(1, 2, 3); // => 6
由于箭頭函數本身沒有 arguments 對象,所以如果他的上層函數都是箭頭函數的話,那么 arguments 對象將不可用。
最后再鞏固一下箭頭函數的語法:
當箭頭函數入參只有一個時可以省略入參括號;
當入參多余一個或沒有入參時必須寫括號;
當函數體只有一個 return 語句時可以省略函數體的花括號與 return 關鍵字。
類 & 繼承類也是 ES6 一個不可忽視的新特性,雖然只是句法上的語法糖,但是相對于 ES5,學習 ES6 的類之后對原型鏈會有更加清晰的認識。
特性本質為對原型鏈的二次包裝
類沒有提升
不能使用字面量定義屬性
動態繼承類的構造方法中 super 優先 this
類的定義/* 類不會被提升 */ let puppy = new Animal("puppy"); // => ReferenceError class Animal { constructor(name) { this.name = name; } sleep() { console.log(`The ${this.name} is sleeping...`); } static type() { console.log("This is an Animal class."); } } let puppy = new Animal("puppy"); puppy.sleep(); // => The puppy is sleeping... /* 實例化后無法訪問靜態方法 */ puppy.type(); // => TypeError Animal.type(); // => This is an Animal class. /* 實例化前無法訪問動態方法 */ Animal.sleep(); // => TypeError /* 類不能重復定義 */ class Animal() {} // => SyntaxError
以上我們使用 class 關鍵字聲明了一個名為 Animal 的類。
雖然類的定義中并未要求類名的大小寫,但鑒于代碼規范,推薦類名的首字母大寫。
兩點注意事項:
在類的定義中有一個特殊方法 constructor(),該方法名固定,表示該類的構造函數(方法),在類的實例化過程中會被調用(new Animal("puppy"));
類中無法像對象一樣使用 prop: value 或者 prop = value 的形式定義一個類的屬性,我們只能在類的構造方法或其他方法中使用 this.prop = value 的形式為類添加屬性。
最后對比一下我們之前是怎樣寫類的:
function Animal(name) { this.name = name; } Animal.prototype = { sleep: function(){ console.log("The " + this.name + "is sleeping..."); } }; Animal.type = function() { console.log("This is an Animal class."); }
類的繼承class 關鍵字真真讓這一切變得清晰易懂了~
class Programmer extends Animal { constructor(name) { /* 在 super 方法之前 this 不可用 */ console.log(this); // => ReferenceError super(name); console.log(this); // Right! } program() { console.log("I"m coding..."); } sleep() { console.log("Save all files."); console.log("Get into bed."); super.sleep(); } } let coder = new Programmer("coder"); coder.program(); // => I"m coding... coder.sleep(); // => Save all files. => Get into bed. => The coder is sleeping.
這里我們使用 class 定義了一個類 Programmer,使用 extends 關鍵字讓該類繼承于另一個類 Animal。
如果子類有構造方法,那么在子類構造方法中使用 this 對象之前必須使用 super() 方法運行父類的構造方法以對父類進行初始化。
在子類方法中我們也可以使用 super 對象來調用父類上的方法。如示例代碼中子類的 sleep() 方法:在這里我們重寫了父類中的 sleep() 方法,添加了兩條語句,并在方法末尾使用 super 對象調用了父類上的 sleep() 方法。
俗話講:沒有對比就沒有傷害 (*゜ー゜*),我們最后來看一下以前我們是怎么來寫繼承的:
function Programmer(name) { Animal.call(this, name); } Programmer.prototype = Object.create(Animal.prototype, { program: { value: function() { console.log("I"m coding..."); } }, sleep: { value: function() { console.log("Save all files."); console.log("Get into bed."); Animal.prototype.sleep.apply(this, arguments); } } }); Programmer.prototype.constructor = Programmer;
如果前文類的定義中的前后對比不足為奇,那么這個。。。
給你一個眼神,自己去體會 (⊙?⊙),一臉懵逼.jpg
模塊啊哈,終于寫到最后一部分了。
模塊系統是一切模塊化的前提,在未推出 ES6 Module 標準之前,相信大伙兒已經被滿世界飛的 AMD、CMD、UMD、CommonJS 等等百花齊放的模塊化標準搞的暈頭轉向了吧。但是,現在 TC39 在 ECMAScript2015(ES6) 版本里終于推出了正式的模塊化規范,前端模塊系統的大一統時代已經到來了!
OMG,這段話寫的好燃 orz
廢話有點多。。。
下面咱們來了解一個這個模塊系統的基本規范。
特性為方便描述,下文中導出對象指一切可導出的內容(變量、函數、對象、類等),勿與對象(Object)混淆。
導入對象同理。
封閉的代碼塊
每個模塊都有自己完全獨立的代碼塊,跟作用域類似,但是更加封閉。
無限制導出導出
一個模塊理論上可以導出無數個變量、函數、對象屬性、對象方法,甚至一個完整的類。但是我們應該時刻牢記單一職責這一程序設計的基本原則,不要試圖去開發一個臃腫的巨大的面面俱到的模塊,合理控制代碼的顆粒度也是開發可維護系統必不可少的一部分。
嚴格模式下運行
模塊默認情況下在嚴格模式下運行("use strict;"),這時候要注意一些取巧甚至有風險的寫法應該避免,這也是保證代碼健壯性的前提。
export const DEV = true; export function example() { //... } export class expClass { //... } export let obj = { DEV, example, expClass, //... }
使用 export 關鍵字,后面緊跟聲明關鍵字(let、function 等)聲明一個導出對象,這種聲明并同時導出的導出方式稱作內聯導出。
未被導出的內容(變量、函數、類等)由于獨立代碼塊的原因,將僅供模塊內部使用(可類比成一種閉包)。
// module example.js const DEV = true; function example() { //... } class expClass { //... } let obj = { DEV, example, expClass, //... } // module example.js export {DEV, example, expClass, obj}; export {DEV, example as exp, expClass, obj};
相對于內聯導出,上邊的這種方式為對象導出。我們可以像寫普通 JS 文件一樣寫主要的功能邏輯,最后通過 export 集中導出。
在導出時我們可以使用 as 關鍵字改變導出對象的名稱。
export default {DEV, example as exp, expClass, obj}; // OR export default obj; // OR export default const DEV = true;
我們可以在 export 關鍵字后接 default 來設置模塊的默認導出對象,需要注意的是:一個模塊只能有一個默認導出。
先不多說,后面講導入的時候再細講相互之間的關聯。
模塊的導入與使用前文我們定義了一個名為 example 的模塊,寫在文件 example.js中,下面我們來導入并使用這個模塊。
import example from "./example.js"; // OR import default as example from "./example.js";
使用 import 關鍵字導入一個模塊,上邊這兩種寫法是等效的。默認導入對象既是模塊默認導出對象,即對應模塊定義中的 export default 所導出的內容。
此外我們還可以這樣導入一個模塊:
import {DEV, example} from "./example.js"; import * as exp from "./example.js"; import {default as expMod, * as expAll, DEV, example as exp} from "./example.js";
這種導入方式對應模塊定義中的 export {DEV, example, expClass, obj} 或 export const DEV = true。下面我們逐行分析:
第一行,我們使用對象導入的方式導入一個模塊內容,可能有些人已經發現,這跟解構賦值很相似,但也有不同,下面會講到。需要注意的是形參對象({DEV, example})與模塊定義中導出的名稱必須保持一致。
第二行,導入時可以使用通配符 * 配合 as 關鍵字一次性導出模塊中所有內容,最終導入的內容放在 exp 對象中。
第三行,在使用對象導入來導入一個模塊的指定內容時,也可以使用 as 關鍵字更改最終導入對象的名稱,這里表現出與解構賦值的一個不同之處,忘記解構賦值的小伙伴可以翻翻前文對比一下哈~
最后,在導入一個模塊后我們就可以直接使用模塊的函數、變量、類等了,完整的代碼示例:
import {DEV, example, expClass as EC} from "./example.js"; if(DEV) { let exp = new EC(); // anything you want... example(); }
好嘞!到這里,ES6 常用的 8 個新特性就講完了,恭喜你耐心地看完了。當然,還有許多地方沒有講到,有時間的話會考慮繼續寫一些。
好嘞,就這樣吧,希望對你有所幫助,拜拜~<(* ̄▽ ̄*)/。
文中部分專業名詞由于未找到合適譯文,最后自行翻譯,如有不妥,歡迎指正。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84084.html
摘要:以下簡稱是語言的下一代標準。因為當前版本的是在年發布的,所以又稱。用它所聲明的變量,只在命令所在的代碼塊內有效。的繼承機制,實質是先創造父類的實例對象所以必須先調用方法,然后再用子類的構造函數修改。 ECMAScript 6(以下簡稱ES6)是JavaScript語言的下一代標準。因為當前版本的ES6是在2015年發布的,所以又稱ECMAScript 2015。 也就是說,ES6就是E...
摘要:以下簡稱是語言的下一代標準。的繼承機制,實質是先創造父類的實例對象所以必須先調用方法,然后再用子類的構造函數修改。總結以上就是最常用的一些語法,可以說這的語法,在的日常使用中占了追加十分鐘好的嗎分鐘掌握核心內容下 ECMAScript 6(以下簡稱ES6)是JavaScript語言的下一代標準。因為當前版本的ES6是在2015年發布的,所以又稱ECMAScript 2015。 也就是說...
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
閱讀 1317·2021-10-27 14:14
閱讀 3574·2021-09-29 09:34
閱讀 2477·2019-08-30 15:44
閱讀 1716·2019-08-29 17:13
閱讀 2569·2019-08-29 13:07
閱讀 867·2019-08-26 18:26
閱讀 3342·2019-08-26 13:44
閱讀 3210·2019-08-26 13:37