深入淺出ES6(十七):展望未來

你可能正在使用的特性

ES6除了新增特性外還有兩類特殊的特性:其中一些特性早已被各大廠商廣泛實現並使用,但是它們並沒有被標準化;還有一些特性採自過去其它的標準。

  • 定型數組(Typed arrays)數組緩衝(ArrayBuffer)數據視圖(DataView)這些特性全都來自WebGL標準,自這些特性出現後也被陸續用於實現許多其它的API,例如Canvas、Web Audio還有WebRTC。這些特性可以協助你處理大量原始的二進制數據或數值型數據。

    舉個例子,如果Canvas的渲染上下文中缺少了你想要的特性,而你又認爲這個特性足夠重要,你可以藉助以上特性自己實現一個:

        var context = canvas.getContext("2d");
        var image = context.getImageData(0, 0, canvas.width, canvas.height);
        var pixels = image.data;  // 一個Uint8ClampedArray對象
        // ... 你的代碼
        // ... 直接修改原始的`像素`數據
        // ... 然後將修改後的數據寫回canvas:
        context.putImageData(image, 0, 0);
  • 在標準化的進程中,定型數組採用了例如.slice()、.map()還有.filter()這樣的方法

  • Promise。不要覺得promise很難,簡而言之,它只不過是一段異步JS編程的構建代碼塊,它可以爲你提供一些非立即可用的值。舉個例子,當你調用fetch()方法獲取數據時,它不會阻塞,而是會立即返回一個promise對象。此時fetch函數一直在後臺運行,直到響應到達時纔會回調。結合promise的許多特性要優於單獨使用回調實現各種功能,你可完美地將它們鏈接起來;它們是一等的值,相關操作非常有趣,例如promise.all()可以等到所有promise都完成之後才運行,promise.race()會在多個promise間製造一種競爭關係,當其中一個完成時,其它promise則被拒絕;用於錯誤處理的樣板代碼要簡單得多。目前有一些瀏覽器暫不支持promise功能,可以加入polyfill爲用戶提供相關功能,如果你還不瞭解什麼是promise,那就來看看Jake Archibald深入剖析promise的文章吧。

  • 塊級作用域內部函數。這是一個不推薦使用的特性,但是你可能無意中已經使用過了。

    在ES 1-5中,下面這段代碼從技術角度來看是非法的:

        if (temperature > 100) {
          function chill() {
            return fan.switchOn().then(obtainLemonade);
          }
          chill();
        }

    此處的函數聲明位於if代碼塊內部,標準中不支持這樣的聲明操作。函數聲明只有在最外層作用域或次外層函數作用域中才是合法的。

    但是這種方式卻被幾乎所有的主流瀏覽器所接受。

    雖然每一家廠商的實現相互之間不能完美兼容,但是基本都可以運行,而且目前有許多web頁面仍在使用這種函數聲明方式。

    謝天謝地,在ES6中將這種聲明方式也納入標準化範圍啦,函數聲明會被提升到封閉代碼塊的頂部。

    到目前爲止,Firefox和Safari中尚未實現這一新標準,如果需要請使用函數表達式來進行聲明:

        if (temperature > 100) {
          var chill = function () {
            return fan.switchOn().then(obtainLemonade);
          };
          chill();
        }

    許多年前,委員會認爲向後兼容的限制非常複雜,所以沒有將塊級作用域函數進行標準化。當時沒有人可以解決這些問題,而在ES6中,委員會在非嚴格模式下添加了一個非常奇怪的規則從而克服了這個困難,我不便在這裏作過多解釋,請相信我,一定要使用嚴格模式,切記,切記!

  • 函數名稱。長久以來,所有主流的JS引擎都會爲命名函數添加一個.name屬性,而這一做法並沒有得到標準的支持。ES6標準對這一行爲進行了支持,由於在這之前通過函數表達式聲明的函數被認爲是未命名函數,新標準通過命名感知的方式爲其也添加了.name屬性。

        > var lessThan = function (a, b) { return a < b; };
        > lessThan.name
            "lessThan"

    對於其它函數,例如爲.then方法添加一個回調函數作爲參數,新標準仍然不能斷定這個函數的名稱,因此fn.name就會被置爲一個空字符串。

實用特性

  • Object.assign(target, ...sources)一個新的標準庫函數,與Underscore中的_.extend()類似。

  • 應用於函數調用的展開(Spread)運算符。這玩意兒跟Nutella沒半毛錢關係,即使Nutella打開之後口感非常棒(譯者注:Nutella是一個巧克力醬品牌,其廣告語中使用的spread一詞深入人心)。但它的確是一個美味的特性,我想你會喜歡它的。

    在文章《深入淺出ES6(五):不定參數和默認參數》中,我們介紹了不定參數(Rest),通過這種方式聲明的函數可以接受任意數量的參數,相對於之前傳遞隨機而又略顯笨拙的arguments對象而言,這顯然是一種更爲優雅的選擇。

        function log(...stuff) {  // stuff是不定參數。
          var rendered = stuff.map(renderStuff); // 它實際上是一個數組
          $("#log").add($(rendered));
        }

    其實還有另外一種匹配語法,可以用這種語法給函數傳遞任意數量的參數,可以讓fn.apply()函數看起來更優雅:

        // 記錄數組中的所有值
        log(...myArray);

    展開運算符適用於所有可迭代對象,所以你可以用log(...mySet)將所有的內容記錄在一個Set中。

    與不定參數不同的是,你可以在同一個參數列表中多次使用展開操作符:

        // kicks在trids之前
        log("Kicks:", ...kicks, "Trids:", ...trids);

    展開運算符也可以讓多維數組展開變得不再複雜:

        > var smallArrays = [[], ["one"], ["two", "twos"]];
        > var oneBigArray = [].concat(...smallArrays);
        > oneBigArray
            ["one", "two", "twos"]

    ...但也可能只有我有這個迫切的需求。如果真的是這樣,那還是去責備Haskell好啦。

  • 應用於構建數組的展開運算符。在另外一篇文章《深入淺出ES6(六):解構 Destructuring》中,我們還討論瞭如何將解構改造爲“不定(rest)”模式。你可以通過這種方法從數組中取出任意數量的元素。

        > var [head, ...tail] = [1, 2, 3, 4];
        > head
            1
        > tail
            [2, 3, 4]

    你猜怎麼着!這兒還有一種匹配語法可以將任意數量的元素整合到數組中:

        > var reunited = [head, ...tail];
        > reunited
            [1, 2, 3, 4]

    此處所有的規則與應用於函數調用的展開運算符完全相同,也就是說,在相同的數組中你也可以使用多次展開運算符,等等。

  • 完全尾調用。於我而言,很難用如此短的篇幅介紹這個優化後的新特性。

    Structure and Interpretation of Computer Programs》一書可以幫助你更好地理解這個特性,如果你喜歡這本書,就繼續閱讀下去吧,或者直接跳到章節1.2.1——線性遞歸和迭代,正如文中所述,在ES6標準中所有的實現也都必須是“尾遞歸(Tail-Recursive)”。

    目前尚無主流的JS引擎實現這一功能。雖然新功能實現起來很困難,但是都會盡快實現。

文本

  • 升級Unicode版本。 ES5中要求至少支持Unicode 3.0中的所有的字符;而ES6要求至少支持Unicode 5.1.0中的所有字符。現在你可以用古希臘B型線性文字命名你的函數了!

    由於直到Unicode 7.0纔會支持A型線性文字,而且維護用一門尚未被譯解的語言編寫的代碼可能很困難,所以目前不建議冒險使用。

    (有的JavaScript引擎支持了添加於Unicode 6.1的emoji表情,即使在這些引擎中也不能將Unicode特殊字符作爲一個變量名稱。出於某些原因,Unicode財團決定不把它歸類爲標識符字符。)

  • 長Unicode轉義序列。 ES6新標準與之前的版本相似,也支持類似\u212A的 四位數Unicode轉義序列,這個特性非常好,你可以在字符串中使用這種序列,如果純粹出於好奇,或者你的項目沒有任何代碼審查的策略,也可以用它來類 命名變量哦。但是,對於像U+13021這樣的字符就不能正常解析,數字13021有五位數字,比四位還多一位,你就無法看到埃及象形文字中一個倒立的人了。

在ES5中,你需要用兩個轉義(亦即一個[UTF-16代理對][26])來表示,這種感覺完全像是生活在黑暗時代:冰冷、悲慘、野蠻。而ES6,則像意大利文藝復興時期的黎明一樣,爲我們帶來了極大的改變:你現在可以直接寫`\u{13021}`啦。

  • 更好地支持基本多文種平面(BMP)以外的字符。所以現在用德賽萊特字母表裏的字符組成的字符串也可以調用.toUpperCase().toLowerCase()方法了!

    String.fromCodePoint(...codePoints)String.fromCharCode(...codeUnits)本質上相同,只不過額外增加了BMP外的碼位支持。

  • Unicode正則表達式。 ES6中的正則表達式支持一個新的修飾符:u。在加入修飾符u的正則表達式中,BMP以外的字符都被視爲單個字符,而不是兩個相互分離的碼元。舉個例子,如果不加u/./只能匹配字符“&#x1f62d;”的前半部分,但是/./u就可以匹配整個字符。

    想要支持更多能夠處理Unicode的大小寫不敏感匹配還有長Unicode轉義序列,記得要給正則表達式添加修飾符u。欲瞭解詳情,請閱讀Mathias Bynens撰寫的更詳細的文章

  • 粘性正則表達式。該特性爲正則表達式添加了一個粘性修飾符y,與Unicode無關。它只會查找始於特定偏移量的匹配,偏移量給自它的.lastIndex屬性;如果沒有相應匹配,它會立即返回null,不會在字符串中繼續向前掃描來查找下一處匹配。

  • 官方版國際化標準。標準規定,任何提供國際化相關特性的ES6實現必須支持ECMAScript 2015國際化API規範——ECMA-402。這個單立標準對Intl對象進行了詳細說明。Firefox、Chrome、IE 11+和Node 0.12已經全部支持了這一特性。

數字

  • 二進制和八進制數字字面量。想要用不一樣的方式來表示數字8675309麼?0x845fed不是正確的答案。新標準中使用0o表示八進制,使用0b表示二進制,所以你可以寫成0o41057755(八進制)或者0b100001000101111111101101(二進制)。

    Number(str)識別字符串的規則與之相同:Number("0b101010")會返回42。

    (快速提示:number.toString(base)parseInt(string, base)按照原始方式來對數字進行任意基底轉換。)

  • 新的Number函數和常量。這些新特性是專門爲Number打造的,如果你感興趣可以親自瀏覽標準,從Number.EPSILON開始瀏覽。

    這其中最有趣的新思想當屬“安全整數”範圍,從-(2^53-1)到+(2^53-1)都在這個範圍之中。從JS誕生開始這組特殊的數字範圍就已經存在,這個範圍中的每一個整數都可以表示一個JS數字,這也是++--操作符合法的運行範圍。超出這個範圍之外的奇數整型數字不能被表示爲64位浮點數,所以對能被表示的數字(都是偶數)進行遞增或遞減操作得不到正確的結果。爲了讓你的代碼不受影響,代碼中新增加了兩個常量Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER,還增加了一個斷言函數Number.isSafeInteger(n)

  • 新的Math函數。 ES6標準中新增了一些雙曲三角函數和它們的逆函數,例如Math.sinh()Math.cosh()Math.tanh()Math.asinh()Math.acosh()Math.atanh(),用於計算立方根的Math.cbrt(x)函數、用於計算直角三角形斜邊的Math.hypot(x, y)函數,用於計算同底對數的Math.log2(x)Math.log10(x)函數,幫助計算整數對數的Math.clz32(x)函數以及其它的一些函數。

    Math.sign(x)可以用來獲取數字的符號。

    ES6也添加了Math.imul(x, y)函數,用以計算x的y次冪。JS中沒有64位整數或大整數是一件很奇怪的事情,所以在某些情況下這個函數非常有用,比如編譯器可以用這個函數來優化代碼操作,Emscripten就用它來實現JS中的64位整數乘法。

與之類似的是,Math.fround(x)用於支持32位浮點數操作。

最後的最後

以上就你想要講的所有內容咯?

好吧,其實還有許多沒有講。我甚至沒有提到所有內建迭代器通用的原型對象IteratorPrototype,絕密的GeneratorFunction構造函數,Object.is(v1, v2),用於支持像ArrayPromise這些的子類化內建方法的Symbol.species函數,甚或是在ES6標準中指定多全局變量的具體細節,這一部分之前從未得到正式的標準化。

我確信我也會遺漏許多有用的內容。

但是如果你一路跟着我們走下來,你自然會知道未來將發展向何處。你已經知道現在就可以開始使用ES6特性,而且一旦你這樣做,無疑是選擇了一門比之前更好的語言。

前些日子,Josh Mock對我說他現在會下意識地優先使用ES6的特性,在大約50行代碼中已經用了八種不同的新特性,包括:模塊(Module)、類(Class)、默認參數(Argument Default)、SetMap、模板字符串、箭頭函數,還有let,是的,他忘記說for-of循環了。

這也是我目前所體會到的,互相結合的新特性非常實用,它們最終會滲透到你寫的每一行JS代碼中。

與此同時,每一個JS引擎都在加急實現並優化到目前爲止我們討論的所有特性。

我們的系列馬上就完結了,此時各大引擎也將多多少少實現了大部分語言特性,這就是最後一篇文章啦,我得去找點兒其它的事情來做。

開玩笑啦。ES7標準的提案已經在加速篩選中,我們就選取幾個來看一下吧:

  • 乘方操作符 2 ** 8將返回256.在Firefox Nightly中已經實現。

  • Array.prototype.includes(value)如果數組中包含給定值將返回true,這個特性在Firefox Nightly中也已實現,可以編寫相應的polyfill在不支持該特性的瀏覽器中使用。

  • 單指令多數據(SIMD)暴露現代CPU提供的128位SIMD指令, 這些指令可以對2、4或8組相鄰的數組元素同時進行算數運算。它們能夠加速各種各樣的算法加速,爲流媒體、密碼學、遊戲、圖像處理以及很多其它領域提供客 觀的性能提升。這些指令非常低階但是非常實用,在Firefox Nightly中也已實現,同樣可以編寫相應的polyfill在不支持該特性的瀏覽器中使用。

  • Async異步函數我們在《深入淺出ES6(三):生成器 Generators》一文中捎帶着提到過這個特性。Async函數與生成器類似,但是專門用於異步編程操作。當你調用生成器時,它會返回一個迭代器。當你調用async函數時,它會返回一個promise。生成器使用yield關鍵詞來暫停併產生值;async函數使用await關鍵詞來暫停並等待promise。

    我很難用簡短的幾句話把這些特性描述清楚,但是async函數將成爲ES7標準中具有里程碑意義的一個特性。

  • 定型對象定型數組中包含着定型元素,定型對象與定型數組的概念類似,簡單來說定型對象中的屬性都擁有確定的類型。

        // 創建一個新的結構類型,每一個Point都有兩個字段
        // 分別命名爲x和y。
        var Point = new TypedObject.StructType({
          x: TypedObject.int32,
          y: TypedObject.int32
        });
    
        // 現在創建一個這個類型的實例。
        var p = new Point({x: 800, y: 600});
        console.log(p.x); // 800

    你可能只會出於性能的原因才指定具體的類型。定型對象與定型數組通過爲變量、數組和對象指定具體的類型,壓縮內存的使用量並提高應用的性能,但是在逐對象、選擇性加入的基礎上,卻與處處都是靜態類型的語言相反。

    將JS作爲一個編譯目標也很有趣。

    定型對象在Firefox Nightly已得到實現。

  • 類和屬性修飾符修飾符是你爲屬性、類或方法添加的標籤。你可以通過這個示例初步瞭解一下:

        import debug from "jsdebug";
    
        class Person {
          @debug.logWhenCalled
          hasRoundHead(assert) {
            return this.head instanceof Spheroid;
          }
          ...
        }

    這裏的@debug.logWhenCalled就是修飾符,你可以想象一下它都對方法做了些什麼。

    提案中對此做出了詳細的解釋,同時也舉了許多示例。

我還想討論一下更令人激動的標準發展進程,對,我們不介紹語言特性了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章