7 個使人驚訝的 JavaScript 「特性」

在過去的幾個月裏,我對 JSHint 作了一些改進,主要是,學習 ES6(我最自豪的是從新實現了變量做用域)的過程當中我碰到了幾個特性,它們讓我驚訝,其中大部分是關於 ES6 的特性但也有一部分是 ES3 特性,這些特性我之前從未用過,而如今我將開始使用它們。git

從任何一個代碼塊中 break

你應該已經知道你能夠從任意循環中 break 和 continue —— 這是一個至關標準的程序設計語言結構。但你可能沒有意識到,你能夠給循環添加一個 label ,而後跳出任意層循環:es6

outer: for(var i = 0; i < 4; i++) {
    while(true) {
        continue outer;
    }
}複製代碼

label 特性一樣適用於 break 和 continue。你在 switch 語句中確定見過 break:github

switch(i) {
   case 1:
       break;
}複製代碼

順便說一句,這是爲何 Crockford 建議你的 case 不該該縮進 —— 由於 break 跳出的是 switch 而不是 case,可是我認爲縮進 case 的可讀性更好。你也能夠給 switch 語句添加 label:chrome

myswitch: switch(i) {
   case 1:
       break myswitch;
}複製代碼

你能夠作的另外一件事是建立任意塊(我知道你能夠在 C# 裏面這麼寫,我指望其餘語言也能夠)。數組

{
  {
      console.log(""I'm in an abritrary block"");
  }
}複製代碼

所以,咱們能夠把 label 和 break 放在一塊兒,用來從任意代碼塊中跳出。安全

outer: {
  inner: {
      if (true) {
        break outer;
      }
  }
  console.log(""I will never be executed"");
}複製代碼

注意到,這隻適用於 break —— 由於你只能在一個循環中 continue。我從未見過 label 被使用在 JavaScript 中,我想知道爲何 —— 我想可能由於若是我須要 break 兩層,說明把這個代碼塊放在一個函數裏可能更好,這樣我可使用一個單層的 break 或者一個提早的 return 來達到一樣的目的。ide

儘管如此,若是我想要保證每一個函數只有一個 return 語句(這不是個人菜),那麼我可使用帶 label 的 brock。例如,看下面這個多個 return 語句的函數:函數

function(a, b, c) {
  if (a) {
     if (b) {
       return true;
     }
     doSomething();
     if (c) {
       return c;
     }
  }
  return b;
}複製代碼

而若是使用 label:學習

function(a, b, c) {
  var returnValue = b;
  myBlock: if (a) {
     if (b) {
       returnValue = true;
       break myBlock;
     }
     doSomething();
     if (c) {
       returnValue = c;
     }
  }
  return returnValue;
}複製代碼

還有另外一種選擇,用更多代碼塊……設計

function(a, b, c) {
  var returnValue = b;
  if (a) {
     if (b) {
       returnValue = true;
     } else {
       doSomething();
       if (c) {
         returnValue = c;
       }
    }
  }
  return returnValue;
}複製代碼

我最喜歡原版,而後是使用 else 的版本,最後纔是使用 label 的版本 —— 可是,這多是由於個人寫碼習慣?

解構一個已存在的變量

首先,有個怪異的寫法我沒法解釋。貌似 ES3 中你能夠添加一個小括號到一個簡單的賦值語句左邊的變量上,而這樣寫不會有問題:

var a;
(a) = 1;
assertTrue(a === 1);複製代碼

若是你能想到爲何這樣寫能夠,請在底下評論!

解構的過程是一個將變量從一個數組或者一個對象中拉取出來的過程。最多見的是如下例子:

function pullOutInParams({a}, [b]) {
  console.log(a, b);
}
function pullOutInLet(obj, arr) {
  let {a} = obj;
  let [b] = arr;
  console.log(a, b);
}
pullOutInParams({a: ""Hello"" }, [""World""]);
pullOutInLet({a: ""Hello"" }, [""World""]);複製代碼

而你能夠不使用 var 或 let 或 const。對數組你可讓下面的代碼如你的指望運行:

var a;
[a] = array;複製代碼

可是,對於對象,你必須將整個賦值語句用小括號括起來:

var a;
({a} = obj);複製代碼

必須這樣寫的理由是,不加括號沒法區分代碼是解構賦值仍是塊級做用域,由於你可使用匿名代碼塊而 ASI(automatic semi-colon insertion,自動插入括號)會將變量轉成能夠執行的表達式(以下面的例子所示,可以產生反作用……),這樣就產生了歧義。

var a = {
   get b() {
     console.log(""Hello!"");
   }
};
with(a) {
  {
    b
  }
}複製代碼

回到原始的例子,咱們給咱們的賦值語句裏的變量加了圓括號 —— 你可能認爲它也適用於解構,但它不是。

var a, b, c;
(a) = 1; //這句不是變量解構
[b] = [2];
({c} = { c : 3 });複製代碼

對數值進行解構

解構的另外一個方面你可能也沒有意識到,屬性名不是必需要是不帶引號的字符串,它們也能夠是數值:

`var {1 : a} = { 1: true };`複製代碼

或者帶引號的字符串:

`var {""1"" : a} = { ""1"": true };`複製代碼

或者你可能想要用一個計算的表達式做爲名字:

var myProp = ""1"";
var {[myProp] : a} = { [myProp]: true };複製代碼

這會很容易寫出形成困惑的代碼:

var a = ""a"";
var {[a] : [a]} = { a: [a] };複製代碼

類聲明是塊級做用域的

函數聲明會被提高,意味着你能夠將函數聲明寫在函數調用以後:

func();
function func() {
  console.log(""Fine"");
}複製代碼

函數表達式與此相反,由於賦值一個變量的時候,變量聲明被提高,可是具體賦值沒有被提高。

func(); // func 被聲明, 可是值爲 undefined, 因此這裏拋出異常: ""func is not a function""
var func = function func() {
  console.log(""Fine"");
};複製代碼

類(Classes)成爲 ES6 流行的部分,而且已被普遍吹捧爲函數的語法糖。因此你可能會認爲如下代碼是能夠工做的:

new func();

class func {
  constructor() {
    console.log(""Fine"");
  }
}複製代碼

然而,儘管它基本上是語法糖,但前面的代碼是不能工做的。這實際上等價於:

new func();

let func = function func() {
  console.log(""Fine"");
}複製代碼

這意味着咱們的 func 調用在暫時性死區(TDZ),這會致使引用錯誤。

同名參數

我認爲不可能指定同名的參數,然而,卻能夠!

function func(a, a) {
  console.log(a);
}

func(""Hello"", ""World"");
// 輸出 ""World""複製代碼

在嚴格模式下不行:

function func(a, a) {
  ""use strict"";
  console.log(a);
}

func(""Hello"", ""World"");
// 在 chrome 下報錯 - SyntaxError: Strict mode function may not have duplicate parameter names複製代碼

typeof 不安全

好吧,我偷了這篇文章的結論,可是這值得強調。

在 ES6 以前,衆所周知使用 typeof 老是能安全地找出某個變量的定義,無論它是否被聲明:

if (typeof Symbol !== ""undefined"") {
  // Symbol 可用
}
// 下面的代碼拋異常,若是 Symbol 沒有被聲明 
if (Symbol !== ""undefined"") {
}複製代碼

可是,如今這個在不使用 let 或者 const 聲明變量的時候纔好使。由於有了 TDZ,會致使變量未聲明時產生引用錯誤。從本質上講,變量被提高到塊級做用域的開始,可是在聲明前的任何訪問都會產生引用錯誤。在 JSHint 的做用域管理中,我必須記錄一個變量的用法,若是它使用 let 或者 const 聲明於當前塊級做用域或者它的父級做用域,提早訪問就會有引用錯誤。而若是是使用 var 語句聲明的,那麼它就是可用的,可是 JSHint 會給出一個警告,而若是它沒有被聲明,那麼它使用全局做用域,JSHint 可能會有另一種警告。

if (typeof Symbol !== ""undefined"") {
  // Symbol 不可用,產生 reference error
}
let Symbol = true;複製代碼

新數組

我老是避免使用 new Array 構造函數,一部分緣由是由於它的參數既能夠是一個長度又能夠是一個元素列表:

new Array(1); // [undefined]
new Array(1, 2); // [1, 2]複製代碼

可是,一個同事最近使用它遇到了一些我之前沒有見過的東西:

var arr = new Array(10);
for(var i = 0; i < arr.length; i++) {
  arr[i] = i;
}
console.dir(arr);複製代碼

上面的代碼產生一個 0 到 9 的數組。然而,若是將它重構爲使用 map:

var arr = new Array(10);
arr = arr.map(function(item, index) { return index; });
console.dir(arr);複製代碼

上面的代碼運行後 arr 沒有變化。彷佛 new Array(length) 用指定長度建立了一個數組,可是沒有設置任何值,因此引用它的長度能夠工做,可是枚舉元素不能夠。若是我設置一個數值會怎麼樣?

var arr = new Array(10);
arr[8] = undefined;
arr = arr.map(function(item, index) { return index; });
console.dir(arr);複製代碼

如今我獲得了一個數組,第 8 個元素等於 8,可是其餘全部的值依然是 undefined。看一下 map 的 polyfill 實現,它循環每個元素(這是爲何 index 是正確的),可是它使用的是 in 來檢查一個屬性是否被設置。你若是使用數組直接量,也會獲得一樣的結果。

var arr = [];
arr[9] = undefined;
// or
var arr = [];
arr.length = 10;複製代碼

其餘

Mozilla 的開發者博客有一篇很棒的文章關於箭頭函數,其中包含使用 <!-- 做爲一種官方的 ES6 註釋的細節。一整個系列的博客文章也都值得仔細閱讀。

相關文章
相關標籤/搜索