JavaScript語(yǔ)法基礎(chǔ)—語(yǔ)句和表達(dá)式

關(guān)于

本文由 [WowBar][WowBar] 團(tuán)隊(duì)首發(fā)于 [GitHub][GitHub]
作者: yvongyang

  • 目錄

  1. 表達(dá)式
  2. 語(yǔ)句
  3. 表達(dá)式語(yǔ)句
  4. 比較
  5. 參考
  • 語(yǔ)句和表達(dá)式

    JavaScript 中表達(dá)式和語(yǔ)句的主要區(qū)別在于一條語(yǔ)句執(zhí)行一個(gè)動(dòng)作,一個(gè)表達(dá)式產(chǎn)生一個(gè)值。意思是一個(gè)表達(dá)式執(zhí)行后一定會(huì)生成一個(gè)值,而語(yǔ)句不一定會(huì)產(chǎn)生值。語(yǔ)句主要是用來執(zhí)行動(dòng)作,程序就是由一系列語(yǔ)句組成。
    例如:

    // 表達(dá)式
    name
    1 + x
    getNames()
    
    // 語(yǔ)句
    var name = 'yang';
    function getNames() {}
    var foo = getNames() {};
    

    接下來的內(nèi)容里不會(huì)介紹表達(dá)式,只是列出來表達(dá)式的分類,語(yǔ)句部分會(huì)分別介紹語(yǔ)句的用法和示例,如果對(duì)于表達(dá)式和語(yǔ)句的內(nèi)容比較清楚的可以直接跳到本章最后一部分——表達(dá)式和語(yǔ)句的比較。

  • 表達(dá)式

    表達(dá)式分為基本的表達(dá)式(包括基本關(guān)鍵字),還有左值表達(dá)式以及運(yùn)算符。

    1. 基本表達(dá)式

    • this關(guān)鍵字
    • 字面量(null,布爾值字面量,數(shù)字字面量,字符串字面量)
    • 初始化字面量(數(shù)組字面量[],對(duì)象字面量{},正則表達(dá)式字面量/ab+c/i
    • 函數(shù)表達(dá)式
    • 類表達(dá)式
    • 分組操作符()
    • 模板字面量 `..${...}..`

    2. 左值表達(dá)式

    • 屬性訪問符
    • new
    • 元屬性:new.target
    • super
    • 函數(shù)調(diào)用
    • 參數(shù)列表(arguments, ...arguments)
    • import

    3. 運(yùn)算符

    • 一元
      delete, void, typeof, +, -, ~, !
    • 算術(shù)
      +, -, /, *, %, A++, A--, ++A, --A
    • 比較
      <, >, <=, >=, in, instanceof, ==, ===, !==, !===
    • 條件
      condition ? ifTrue : ifFalse
    • 賦值
      =, -=, +=, *=, /=, &=, |=, 解構(gòu)賦值如[a, b] = [1, 2]、{a, b} = {a: 1, b: 2}等
    • 逗號(hào)
      ,
    • 位移,二進(jìn)制,二元邏輯
      <<, >>, >>>; &, ^, |; &&, ||等
  • 語(yǔ)句

    語(yǔ)句分為聲明語(yǔ)句、流程控制語(yǔ)句和其他語(yǔ)句。
    其中,流程控制語(yǔ)句分為基本流程控制語(yǔ)句、迭代語(yǔ)句、跳轉(zhuǎn)語(yǔ)句和條件語(yǔ)句。具體如下。

    1. 聲明語(yǔ)句

    • 1.1 變量聲明

      1.1.1 var 聲明

      聲明一個(gè)變量,并可以地將其初始化為一個(gè)值

      var a;
      var a = 2;
      var a = 2, b = 3; // 多個(gè)變量的初始化
      

      var 聲明的變量是可以提升的,提升意味著無論變量實(shí)際在哪里聲明的,都會(huì)被當(dāng)成在當(dāng)前作用域頂部聲明的變量??聪旅媸纠?4。根據(jù)示例 1,2,3 顯示的情況,建議始終聲明變量,無論它們?cè)诤瘮?shù)還是全局作用域內(nèi)。

      var 聲明的函數(shù)表達(dá)式不能提升。

      對(duì)于未聲明的變量,可以用 typeof 檢測(cè)其是否存在且不會(huì)報(bào)錯(cuò)。

      // 示例 1: 聲明的變量的作用域在其聲明位置的上下文中,而未聲明變量是全局的;
      // 建議始終聲明變量,無論它們是否在函數(shù)還是全局作用域內(nèi)
      function x() {
          y = 1;   // 在嚴(yán)格模式(strict mode)下會(huì)拋出 ReferenceError 異常
          var z = 2;
      }
      x();
      
      console.log(y); // 打印 "1"
      console.log(z); // 拋出 ReferenceError: z 未在 x 外部聲明
      
      // 示例 2:聲明的變量在任何代碼執(zhí)行前創(chuàng)建(會(huì)被提升),未聲明變量只有在執(zhí)行賦值操作時(shí)被創(chuàng)建;
      console.log(a);                // 拋出 ReferenceError。
      console.log('still going...'); // 永不執(zhí)行。
      
      console.log(a);                // 打印 "undefined" 或 ""(不同瀏覽器實(shí)現(xiàn)不同)。
      var a;
      console.log('still going...'); // 打印 "still going..."。
      
      
      // 示例 3:聲明的變量是它所在上下文環(huán)境的不可配置屬性,非聲明變量是可配置的(如可被刪除)
      var a = 1;
      b = 2;
      
      delete this.a; // 在嚴(yán)格模式(strict mode)下拋出TypeError,其他情況下執(zhí)行失敗并無任何提示。
      delete this.b;
      
      console.log(a, b); // 拋出ReferenceError。
      // 'b'屬性已經(jīng)被刪除。
      
      // 示例 4: 變量提升
      var x = y, y = 'A';
      console.log(x + y); // undefinedA
      
      // 實(shí)際會(huì)被轉(zhuǎn)換為:
      var x;
      var y;
      x = y;
      y = 'A';
      

      1.1.2 let 聲明

      聲明一個(gè)塊級(jí)作用域的變量,并可以將其初始化。

      let x;
      let x = 1;
      let x = 1, y = 2; 
      

      與 var 關(guān)鍵字聲明變量的不同點(diǎn)在于:

      1. var 聲明的變量只能是全局或者整個(gè)函數(shù)塊的,let/const 聲明的變量只在其聲明的塊或子塊中使用;(示例 1)
      2. let/const 不會(huì)在全局聲明時(shí)創(chuàng)建 window 對(duì)象的屬性,而 var 會(huì)。(示例 2)
      3. let/const 在同一個(gè)塊作用域或函數(shù)中不能重復(fù)聲明(會(huì)報(bào)錯(cuò)),var 可以;(示例 3,4)
      4. var 聲明的變量會(huì)被初始化為 undefined,let/const 聲明的變量直到它們的定義被執(zhí)行時(shí)才會(huì)初始化。量會(huì)被初始化為 undefined,let/const 聲明的變量直到它們的定義被執(zhí)行時(shí)才會(huì)初始化。
      // 示例 1
      function varTest() {
          var x = 1;
          {
              var x = 2;  // 同樣的變量!
              console.log(x);  // 2
          }
          console.log(x);  // 2
      }
      
      function letTest() {
          let x = 1;
          {
              let x = 2;  // 不同的變量
              console.log(x);  // 2
          }
          console.log(x);  // 1
      }
      
      // 示例 2
      var x = 'global';
      let y = 'global';
      console.log(this.x); // "global"
      console.log(this.y); // undefined
      
      // 示例 3
      if (x) {
          let foo;
          let foo; // SyntaxError thrown.
      }
      
      // 示例 4:case 沒用 `{}` 包裹起來沒形成塊作用域,所以兩個(gè) `foo` 會(huì)在同一個(gè)塊中被聲明,所以報(bào)錯(cuò)。
      let x = 1;
      switch(x) {
      case 0:
          let foo;
          break;
          
      case 1:
          let foo; // SyntaxError for redeclaration.
          break;
      }
      
      // 示例 5
      function do_something() {
          console.log(bar); // undefined
          console.log(typeof foo); // ReferenceError,typeof也不安全
          var bar = 1;
          let foo = 2;
      }
      
      // 示例 6
      function go(n) {
          // n here is defined!
          console.log(n); // Object {a: [1,2,3]}
      
          for (let n of n.a) { // ReferenceError
              console.log(n);
          }
      }
      
      go({a: [1, 2, 3]});
      

      1.1.3 const

      聲明一個(gè)塊作用域中的變量,并必須初始化一個(gè)值。與 let 用法基本相同,除了聲明的變量的值不能被改變。

      let a = 1;
      a = 2;
      console.log(a); // 2
      
      const c = 1;
      c = 2;  // Uncaught SyntaxError: Invalid or unexpected token     
      
    • 1.2 函數(shù)聲明

      每個(gè)函數(shù)都是一個(gè) Function 對(duì)象,與其他對(duì)象的區(qū)別在于可被調(diào)用;

      若函數(shù)沒有 return 語(yǔ)句,則返回 undefined;

      函數(shù)是值傳遞方式(對(duì)象是引用傳遞);

      Es6 開始,嚴(yán)格模式下,塊里的函數(shù)作用域?yàn)檫@個(gè)塊。非嚴(yán)格模式下的塊級(jí)函數(shù)不要用。

定義函數(shù)的方式有3種:
函數(shù)聲明: 普通函數(shù)聲明,生成器函數(shù)聲明
構(gòu)造函數(shù): 普通的構(gòu)造函數(shù) Function, 生成器構(gòu)造函數(shù) GeneratorFunction。(不推薦構(gòu)造函數(shù)的方式定義函數(shù),函數(shù)體為字符串,會(huì)引起其他問題)
函數(shù)表達(dá)式: 函數(shù)表達(dá)式,函數(shù)生成器表達(dá)式,箭頭函數(shù)表達(dá)式

寫法示例:

 // 函數(shù)聲明定義函數(shù)
 function getName(name1, name2, ...) {
     // 語(yǔ)句
 }

 // 構(gòu)造函數(shù)定義函數(shù)
 var getName = new Function('name1', 'name2', 'return "myName:" + name1');
 getName('yang'); // "myName:yang"

 // 函數(shù)表達(dá)式定義函數(shù)
 var getName = function(name1, name2) {
     return 'myName:' + name1;
 }

 // 函數(shù)表達(dá)式
 (function bar() {})

函數(shù)聲明和表達(dá)式區(qū)別:
1. 最主要的區(qū)別在于函數(shù)表達(dá)式可以省略函數(shù)名稱,就是創(chuàng)建匿名函數(shù);
2. 函數(shù)表達(dá)式未省略函數(shù)名稱,函數(shù)名只能在函數(shù)體內(nèi)用,函數(shù)聲明的函數(shù)名可以在其作用域內(nèi)被使用;
2. 函數(shù)聲明可以提升,函數(shù)表達(dá)式不可以提升,所以表達(dá)式不能在調(diào)用之前使用;
3. 函數(shù)表達(dá)式可被用作 IIFE(即時(shí)調(diào)用的函數(shù)表達(dá)式)。

   var y = function x() {};
   alert(x); // throws an error

   // IIFE: 函數(shù)只使用一次時(shí)調(diào)用
   (function() {
       // 語(yǔ)句
   })();

函數(shù)表達(dá)式 name 屬性:
被函數(shù)表達(dá)式賦值的變量有 name 屬性,如果把這個(gè)變量賦值給另一個(gè)變量,name 屬性值也不會(huì)改變。

 // 匿名函數(shù):name屬性的值就是被賦值的變量的名稱(隱藏值)
 var func = () => {}
 // func.name
 // "func"

 // 非匿名函數(shù):那name屬性的值就是這個(gè)函數(shù)的名稱(顯性值)
 var funb = function haha() {}
 // funb.name
 // "haha"
 
 var fund = func;
 // fund.name
 // "func"

1.2.1 function

// 不同引擎中最大的傳參數(shù)量不同
function name(param1, param2, ...) {
  // 語(yǔ)句
}

1.2.2 函數(shù)生成器聲明 function*

定義一個(gè)生成器函數(shù),返回一個(gè) Generator 對(duì)象。
Generator 對(duì)象:由 generator function 返回的對(duì)象,符合可迭代協(xié)議和迭代器協(xié)議。

 function *gen() {
     // 語(yǔ)句
     yield 10;
     x = yield 'foo';
     yield x;
 }

生成器函數(shù)在執(zhí)行時(shí)能暫停,后面又能從暫停處繼續(xù)執(zhí)行;
調(diào)用一個(gè)生成器函數(shù)并不能馬上執(zhí)行它里面的語(yǔ)句,而是返回一個(gè)這個(gè)生成器的迭代器對(duì)象;
當(dāng)?shù)鞯?next() 方法被調(diào)用時(shí),其內(nèi)的語(yǔ)句會(huì)執(zhí)行到第一個(gè)后續(xù)出現(xiàn) yield 的位置為止,yield 后面緊跟迭代器要返回的值。

next() 返回一個(gè)對(duì)象,包含兩個(gè)屬性:value 和 done,value 表示本次 yield 表達(dá)式的返回值,done 為布爾類型,表示生成器是否已經(jīng)執(zhí)行完畢并返回。

若在生成器函數(shù)中調(diào)用 return 語(yǔ)句時(shí),會(huì)導(dǎo)致生成器立即變?yōu)橥瓿蔂顟B(tài),即調(diào)用 next() 方法返回的對(duì)象的 done 為 true,return 后面的值會(huì)作為當(dāng)前調(diào)用 next() 返回的 value 值。

 function* yieldAndReturn() {
     yield "Y";
     return "R";//顯式返回處,可以觀察到 done 也立即變?yōu)榱?true
     yield "unreachable";// 不會(huì)被執(zhí)行了
 }

 var gen = yieldAndReturn()
 console.log(gen.next()); // { value: "Y", done: false }
 console.log(gen.next()); // { value: "R", done: true }
 console.log(gen.next()); // { value: undefined, done: true }

yield* 表示將執(zhí)行權(quán)移交給另一個(gè)生成器函數(shù)(當(dāng)前生成器暫停執(zhí)行),調(diào)用 next() 方法時(shí),如果傳入了參數(shù),那么這個(gè)參數(shù)會(huì)傳給上一條執(zhí)行的 yield 語(yǔ)句左邊的變量:

 function* anotherGenerator(i) {
     yield i + 1;
     yield i + 2;
     yield i + 3;
 }

 function* generator(i){
     yield i;
     yield* anotherGenerator(i);// 移交執(zhí)行權(quán)
     yield i + 10;
 }

 var gen = generator(10);

 console.log(gen.next().value); // 10
 console.log(gen.next().value); // 11
 console.log(gen.next().value); // 12
 console.log(gen.next().value); // 13
 console.log(gen.next().value); // 20

生成器函數(shù)不能當(dāng)作構(gòu)造器使用,否則會(huì)報(bào)錯(cuò)。

function*表達(dá)式function*聲明 有相似的語(yǔ)法,唯一區(qū)別在于 function*表達(dá)式 可以省略函數(shù)名。

 var x = function*(y) {
     yield y * y;
 };

1.2.3 async function

定義一個(gè)返回 AsyncFunction 對(duì)象的異步函數(shù)。
異步函數(shù)指通過事件循環(huán)異步執(zhí)行的函數(shù),會(huì)通過一個(gè)隱式的 Promise 返回結(jié)果。
Js 中每個(gè)異步函數(shù)都是 AsyncFunction 對(duì)象,該對(duì)象不是全局對(duì)象,需要用 Object.getPrototypeOf(async function(){}).constructor 獲取

async function name(param1, param2, ...) {
  // 語(yǔ)句
 }

可以包含 await 指令,await 會(huì)暫停異步函數(shù)的執(zhí)行,并等待 Promise 執(zhí)行,然后繼續(xù)執(zhí)行異步函數(shù),并返回結(jié)果。
await 只能在異步函數(shù)中使用,否則會(huì)報(bào)錯(cuò)。
async/await 是為了簡(jiǎn)化使用多個(gè) Promise 時(shí)的行為,就像是結(jié)合了 generators 和 promises。

使用 async 函數(shù)重寫 promise 鏈:

 // Promise
 function getProcessedData(url) {
     return downloadData(url) // 返回一個(gè) promise 對(duì)象
         .catch(e => {
             return downloadFallbackData(url)  // 返回一個(gè) promise 對(duì)象
         })
         .then(v => {
             return processDataInWorker(v); // 返回一個(gè) promise 對(duì)象
         });
 }

 // Async:return 時(shí),async function 的返回值將被隱式地傳遞給 Promise.resolve。
 async function getProcessedData(url) {
     let v;
     try {
         v = await downloadData(url);
     } catch (e) {
         v = await downloadFallbackData(url);
     }
     return processDataInWorker(v);
 }
  • 1.3 類聲明

    ES6 中的類跟其他語(yǔ)言中的類類似,是基于原型繼承的。不過 ES6 中的類是基于已有自定義類型的語(yǔ)法糖,typeof 檢測(cè)類可以發(fā)現(xiàn)為 function。

    // 簡(jiǎn)單的類聲明
    class PersonClass {
        constructor(name) {
            this.name = name;
        }
    
        sayName() {
            console.log(this.name);
        }
    }
    
    //  自定義類型實(shí)現(xiàn)上述代碼
    function PersonType(name) {
        this.name = name;
    }
    
    PersonType.prototype.sayName = function() {
        console.log(this.name);
    }
    

    上述例子可以看出,類中的構(gòu)造函數(shù)實(shí)際相當(dāng)于自定義類型的 PersonType 函數(shù),類中的 sayName 方法是構(gòu)造函數(shù)原型上的方法。

    定義類的兩種形式:類聲明 和 類表達(dá)式。
    類聲明和類表達(dá)式的代碼都是強(qiáng)制嚴(yán)格模式的。
    和函數(shù)表達(dá)式一樣,類表達(dá)式也可以省略類名。如果不省略類名,則類表達(dá)式中的類名只能在類體內(nèi)部使用。

    后續(xù)會(huì)單獨(dú)講講類。

2. 流程語(yǔ)句

  • 2.1 基本語(yǔ)句

    2.1.1 塊語(yǔ)句

    組合0或多個(gè)語(yǔ)句, 可以與label一起用。
    {語(yǔ)句組合} 或 標(biāo)簽標(biāo)識(shí)符: {語(yǔ)句組合}

    塊語(yǔ)句示例:
    示例 2 不會(huì)報(bào)錯(cuò),因?yàn)閴K級(jí)作用域的存在,并且輸出的是 1。

    // 示例 1
    var a = 1;
    {
        var a = 2;
    }
    console.log(a);  // Output:2
    
    // 示例 2
    const a = 1;
    {
        const a = 2;
    }
    console,log(a); // Output:1
    
    // 示例 3   
    label: {
        const a = 1;
    }
    

    塊語(yǔ)句返回值示例:
    塊返回的值為塊中最后一條語(yǔ)句的返回值,不過因?yàn)檎Z(yǔ)句的值獲取不到,所以了解即可。

    var a;
    function b() {return 'yang';}
    try {
        throw 'haha';
    } catch(e) {
    }
    // Output: undefined
    
    var a;
    function b() {return 'yang';}
    // Output: ? b() {return 'yang';}
    

    2.1.2 空語(yǔ)句

    不會(huì)執(zhí)行任何語(yǔ)句
    ;

    空語(yǔ)句示例:

    // 跟 for 循環(huán)一起的空語(yǔ)句(空語(yǔ)句最好寫注釋以防混淆)
    for (let i = 0; i < 5; i++) /* Empty statement */;  
    
    // if語(yǔ)句
    if (one); // do nothing
    else if (two); // do nothing
    else
        all();
    
  • 2.2 迭代語(yǔ)句

    2.2.1 while/do...while

    while (condition)
      statement // 想執(zhí)行多行語(yǔ)句可用塊語(yǔ)句
    
    do
      statement // 想執(zhí)行多行語(yǔ)句可用塊語(yǔ)句
    while (condition);
    

    while 可在某個(gè) condition(條件表達(dá)式)值為真的前提下,執(zhí)行循環(huán)直到表達(dá)式值為false;do...while 執(zhí)行指定語(yǔ)句的循環(huán)直到 condition(條件表達(dá)式)值為 false,與 while 語(yǔ)句區(qū)別在于在執(zhí)行 statement 后檢測(cè) condition,所以 statement 至少執(zhí)行一次。

    兩者差別示例:

    var i = 1;
    do {
        console.log('do..while', i);
        i++;
    } while (i < 1);
    // 輸出:
    // "do...while"
    // 1
    
    var j = 1;
    while (j < 1) {
        console.log('while', j);
        j++;
    }
    // 沒有輸出
    

    2.2.2 for/for...of/for...in/for await...of

    1.for: 創(chuàng)建循環(huán),含三個(gè)可選的表達(dá)式,表達(dá)式包圍在圓括號(hào)中并由分號(hào)分割,后跟一個(gè)在循環(huán)中執(zhí)行的語(yǔ)句(通常是一個(gè)塊語(yǔ)句,即用 {} 包裹起來的語(yǔ)句)。

    // initialization 為一個(gè)表達(dá)式(包含賦值表達(dá)式)或者變量聲明,若沒有任何語(yǔ)句要執(zhí)行,則使用空語(yǔ)句 `(;)`
     for ([initialization]; [condition]; [final-expression])
        statement
    

    2.for...of: 循環(huán)遍歷可迭代對(duì)象(Array, Map, Set, String, TypedArray, arguments 對(duì)象等)要迭代的值。

    for (variable of iterable) {
       //statements
    }
    

    3.for...in: 以任意順序迭代對(duì)象的可枚舉屬性。(除 Symbol 以外)

    for (variable in object)
      statement
    

    4.for await...of: 在異步或同步可迭代對(duì)象上創(chuàng)建一個(gè)迭代循環(huán),為每個(gè)不同屬性的值執(zhí)行語(yǔ)句。

    for await (variable of iterable) 
       statement
    
    

    for :
    如果省略了中間可選的條件表達(dá)式(condition 塊),則必須確保在循環(huán)體內(nèi)跳出(break 語(yǔ)句),不然會(huì)陷入死循環(huán)。
    如果省略所有表達(dá)式,則確保跳出循環(huán)并且修改增量,使break語(yǔ)句 在某條件下為 true.(見示例 2)

    // 示例 1
    var arr = [];
    for (var i = 0; i < 9; i++) {
        arr.push(function() {
            console.log(i);
        });
    }
    console.log(arr.forEach(item => console.log(item())));
    // Output:
    // [9, 9, 9, 9, 9, 9, 9, 9, 9]
    
    // 示例 2
    var i = 0;
    
    for (;;) {
        if (i > 3) break;
            console.log(i);
        i++;
    }
    
    // 示例 3
    for (var i = 0; i < 9; i++);
    console.log(i);  // 9
    

    for...of:

    // 迭代 Array
    let iterable = [10, 20, 30];
    
    for (let value of iterable) {
        value += 1;
        console.log(value);
    }
    // Output:
    // 11
    // 21
    // 31
    
    // 迭代 String
    let iterable = 'boo';
    
    for (let value of iterable) {
        console.log(value);
    }
    // b
    // o
    // o
    
    // 迭代 Map
    let iterable = new Map([['a', 1], ['b', 2]]);
    
    for (let [key, value] of iterable) {
        console.log(value);
    }
    // 1
    // 2
    

    for...in:
    for...in 循環(huán)只遍歷可枚舉屬性,不可枚舉屬性不會(huì)遍歷,例如 String 的 indexOf() 方法,或者 Object.toString() 方法。
    通常,在迭代過程中最好不要在對(duì)象上進(jìn)行添加、修改或者刪除屬性的操作,因?yàn)椴荒鼙WC這些被修改的屬性能被訪問到。

    Object.prototype.objCustom = function() {};
    Array.prototype.arrCustom = function() {};
    
    let iterable = [3, 5, 7];
    iterable.foo = 'hello';
    
    for (let i in iterable) {
        console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
    }
    
    for (let i in iterable) {
        if (iterable.hasOwnProperty(i)) {
            console.log(i); // logs 0, 1, 2, "foo"
        }
    }
    
    for (let i of iterable) {
        console.log(i); // logs 3, 5, 7
    }
    

    注意:

    • for...in 訪問 Array 時(shí)不一定會(huì)按次序訪問元素,這是依賴執(zhí)行環(huán)境的,而且訪問的是數(shù)組的索引,除了索引外還包括其他的屬性以及繼承的屬性;
    • for...of 語(yǔ)句遍歷可迭代對(duì)象要迭代的數(shù)據(jù),所以用它來遍歷 Array 里的值更好;
    • for...in 遍歷可枚舉屬性時(shí),若只考慮對(duì)象自身屬性,不包含原型,可以用 getOwnPropertyNames() 或者 hasOwnProperty() 來確定是否是對(duì)象自身屬性。

    for await...of:
    異步生成器(或迭代異步可迭代對(duì)象)已經(jīng)實(shí)現(xiàn)了異步迭代器協(xié)議,用 for await...of 循環(huán):

    async function* asyncGenerator() {
        var i = 0;
        while (i < 3) {
            yield i++;
        }
    }
    
    (async function() {
        for await (num of asyncGenerator()) {
            console.log(num);
        }
    })();
    // 0
    // 1
    // 2
    
  • 2.3 條件語(yǔ)句

    2.3.1 if

    條件判斷

    if (condition)
      statement1
    [else if (condition)
     statement2]
    [else
     statement3] 
    //中括號(hào)表示可選
    

    2.3.2 switch

    評(píng)估一個(gè)表達(dá)式,若表達(dá)式的值與 case 子句匹配則執(zhí)行 case 子句相關(guān)聯(lián)的語(yǔ)句。

    switch (expression) {
      case value1:
          // 當(dāng) expression 的結(jié)果與 value1 匹配時(shí),執(zhí)行此處語(yǔ)句
      [break;]
          ...
      [default:
          // 如果 expression 與上面的 value 值都不匹配,執(zhí)行此處語(yǔ)句
          [break;]]
      // 中括號(hào)表示可選
    
  • 2.4 跳轉(zhuǎn)語(yǔ)句

    2.4.1 break 語(yǔ)句

    中止當(dāng)前循環(huán)(或 switch 語(yǔ)句 或 label 語(yǔ)句),直接執(zhí)行被中止語(yǔ)句后面的語(yǔ)句。
    break [label];
    label (可選)—標(biāo)簽相關(guān)標(biāo)識(shí)符,如果 break 語(yǔ)句不在一個(gè)循環(huán)或 switch 語(yǔ)句中,則該項(xiàng)是必須的。

    // 示例 1: 循環(huán)中的 break 語(yǔ)句
    var i = 0;
    while (i < 6) {
        i += 1;
        if (i == 3) break;
        console.log(i);
    }
    // Output:
    // 1
    // 2
    
    // 示例 2: break 語(yǔ)句和被標(biāo)記的塊語(yǔ)句
    outer_block: {
        inner_block: {
            console.log('1');
            break outer_block;
        }
        console.log ('haha') //被跳過
    }
    

    2.4.2 continue 語(yǔ)句

    終止執(zhí)行當(dāng)前(或標(biāo)簽)循環(huán)的語(yǔ)句,直接執(zhí)行下一個(gè)迭代循環(huán)。
    continue [label];

    break 語(yǔ)句的區(qū)別是,continue 并不會(huì)終止循環(huán)的迭代:
    在 while 循環(huán)中,控制流跳轉(zhuǎn)回條件判斷;
    在 for 循環(huán)中,控制流跳轉(zhuǎn)到更新語(yǔ)句。

    // 示例 1: 循環(huán)中的 continue 語(yǔ)句
    var i = 0;
    while (i < 6) {
            i += 1;
        if (i == 3) continue;
        console.log(i);
    }
    // Output:
    // 1
    // 2
    // 4
    // 5
    // 6
    
    //  示例 2
    var a = 0;
    var b = 8;
    
    checkAB: while(...) {
        checkB: while(...) {
            continue checkB; //每次都跳到 checkB 開始執(zhí)行
        }
    }
    

    2.4.3 throw 語(yǔ)句

    拋出一個(gè)用戶自定義的異常。當(dāng)前函數(shù)的執(zhí)行將被停止(throw之后的語(yǔ)句將不會(huì)執(zhí)行),并且控制將被傳遞到調(diào)用堆棧中的第一個(gè)catch塊。如果調(diào)用函數(shù)中沒有catch塊,程序?qū)?huì)終止。
    throw expression;

    throw "Error"; // 拋出了一個(gè)值為字符串的異常
    throw 42;       // 拋出了一個(gè)值為整數(shù)42的異常
    throw true;     // 拋出了一個(gè)值為true的異常
    

    2.4.4 try...catch 語(yǔ)句

    標(biāo)記要嘗試的語(yǔ)句塊,并指定一個(gè)出現(xiàn)異常時(shí)拋出的響應(yīng)。

    try {
        try_statements
    }
     [catch (exception_var_1) {}]
     [catch (exception_var_2) {}]
    // exception_var_1, exception_var_2 保存 throw 語(yǔ)句指定的值(如 catch(e) 中的 e ), 可以用這個(gè)標(biāo)識(shí)符獲取拋出的異常信息,只在 catch 子句內(nèi)部使用。
     [finally {}] 
    // 在 try 塊和 catch 塊之后執(zhí)行,在下一個(gè) try 聲明之前執(zhí)行,無論是否有異常拋出總是執(zhí)行
    
    • 可以嵌套一個(gè)或更多的 try 語(yǔ)句,如果內(nèi)部的 try 語(yǔ)句沒有 catch 子句,就會(huì)進(jìn)入包裹它的 try 語(yǔ)句的 catch 子句。

      try {
          try {
              throw new Error("oops");
          }
          catch (ex) {
              console.error("inner", ex.message);
          }
          finally {
              console.log("finally");
          }
      }
      catch (ex) {
          console.error("outer", ex.message);
      }
      // Output:
      // "inner" "oops"
      // "finally"
      // "outer" "oops"
      
      • finally 塊返回一個(gè)值,無論 try 和 catch 塊中是否有任何 return 語(yǔ)句,此值都將成為整個(gè) try-catch-finally 的返回值。
      // try-catch 中的 return 必須是作為函數(shù)的返回值才行,不然會(huì)報(bào)錯(cuò)(見下面 return 語(yǔ)句)。此中情況下 try-catch 要放在函數(shù)中運(yùn)行。
      (function() {
          try {
              try {
                  throw new Error('oops');
              }
              catch (ex) {
                  console.error('inner', ex.message);
                  throw ex;
              }
              finally {
                  console.log('finally');
                  return;
              }
          }
          // 因?yàn)樵?finally 中 return,所以 `oops` 不會(huì)拋到外層
          catch (ex) {
              console.error('outer', ex.message);
          }
      })();
      
      // Output:
      // inner oops
      // finally
      // undefined // 整個(gè)函數(shù)的返回值
      

2.4.5 return 語(yǔ)句

終止函數(shù)的執(zhí)行,并返回一個(gè)指定的值給函數(shù)的調(diào)用者。
return [[expression]]
返回表達(dá)式的值,如果忽略表達(dá)式的值,則會(huì)返回undefined.

在 return 關(guān)鍵字和被返回的表達(dá)式之間若使用行終止符(回車換行符,行分隔符和段分隔符)則會(huì)自動(dòng)分號(hào)插入,如:

return
a + b;
// 會(huì)被自動(dòng)轉(zhuǎn)換為
return;
a + b;

var a = 1;
var b = 2;
(function() {
    return
    a + b;
})()  // undefined

// 會(huì)被自動(dòng)轉(zhuǎn)換為
(function() {
    return a + b;
})()  // 3

也可以返回函數(shù)表達(dá)式,就是高階函數(shù)的定義,高階函數(shù)是一個(gè)接收函數(shù)作為參數(shù)或?qū)⒑瘮?shù)作為輸出返回的函數(shù)。

3. 其他語(yǔ)句

3.1 debugger

在程序中調(diào)用可用的調(diào)試功能,如設(shè)置斷點(diǎn)。
debugger

3.2 導(dǎo)入/導(dǎo)出:export、import

export: 從模塊中導(dǎo)出函數(shù),對(duì)象或原始值,以便其他程序可以通過import語(yǔ)句使用。
導(dǎo)出模式分為兩種:命名導(dǎo)出默認(rèn)導(dǎo)出??梢栽诿恳粋€(gè)模塊中定義多個(gè)命名導(dǎo)出,但是只允許一個(gè)默認(rèn)導(dǎo)出。
導(dǎo)入/導(dǎo)出的模塊都是運(yùn)行在嚴(yán)格模式下。

導(dǎo)出示例:

export let name; // 導(dǎo)出單個(gè)屬性
export const myName = 'yang'; // 導(dǎo)出常量
export class ClassName {} // 導(dǎo)出類
export default defaultName; // 導(dǎo)出默認(rèn)屬性

export {name1, name2...} // 導(dǎo)出列表
export {defaultName as default, name1 as Wang...} // 重命名導(dǎo)出,將 name1 作為默認(rèn)屬性導(dǎo)出, name2 重命名為 Wang

// 模塊重定向,導(dǎo)入指定路徑的模塊并導(dǎo)出
export * from ...; // 導(dǎo)出指定模塊所有導(dǎo)出的屬性,除了默認(rèn)導(dǎo)出值
export {default} from ...; // 導(dǎo)出指定模塊中的默認(rèn)導(dǎo)出值
export {name1, name2...} from ...; // 導(dǎo)出指定模塊中某些屬性
export {import1 as name1, import2 as name2...} from ...;  // 重命名導(dǎo)出指定模塊中某些屬性

import: 導(dǎo)入由另一個(gè)模塊導(dǎo)出的綁定。
瀏覽器中,import 語(yǔ)句只能在聲明了 type="module" 的 script 標(biāo)簽中使用。

還有一個(gè)類似函數(shù)動(dòng)態(tài)的 import(),不需要依賴 type="module" 的 script 標(biāo)簽。

靜態(tài) import 更容易從代碼靜態(tài)分析工具和 tree shaking 中受益,動(dòng)態(tài) import() 則在按需加載模塊時(shí)有用。

導(dǎo)入示例:

import * as names from 'export.js'; // 導(dǎo)入整個(gè)模塊內(nèi)容,使用 names 模塊名稱作為命名空間
import {myName, ClassName} from 'export.js'; // 導(dǎo)入多個(gè)接口
import name from 'export.js'; // 導(dǎo)入默認(rèn)接口(即用 export default 導(dǎo)出的接口)

import defaultName, {name1, newName as name2} from 'export.js'; // 同時(shí)導(dǎo)入默認(rèn)接口和多個(gè)其他接口,并重命名其中某些接口
import defaultName, * as names from 'export.js'; // 同時(shí)導(dǎo)入默認(rèn)接口和多個(gè)其他接口,其他接口全部導(dǎo)入并重命名為 names

import 'export.js';  // 導(dǎo)入的模塊作為副作用導(dǎo)入(只運(yùn)行模塊中的全局代碼),不導(dǎo)入模塊中的任何接口。

var promises = import('export.js'); // 可以像調(diào)用函數(shù)一樣來動(dòng)態(tài)的導(dǎo)入模塊。以這種方式調(diào)用,將返回一個(gè) promise。
promises.then((module) => {})

3.3 label

在語(yǔ)句前加個(gè)可以引用的標(biāo)識(shí)符,可以和 break 或 continue 語(yǔ)句一起用。
label: statement

// 標(biāo)記塊,并使用 break
foo: {
    console.log('face');
    break foo;
    console.log('this will not be executed');
}
console.log('swap');

// for 循環(huán)中使用標(biāo)記
var str = "";

loop1:
for (var i = 0; i < 5; i++) {
    if (i === 1) {
        continue loop1;
    }
    str = str + i;
}

console.log(str); // '0234'

目前在非嚴(yán)格模式下,可以對(duì)函數(shù)聲明進(jìn)行標(biāo)記,但是嚴(yán)格模式下不可以。生成器函數(shù)不論在什么模式下都不能被標(biāo)記。

L: function F() {}

'use strict';
L: function F() {}
// VM170:2 Uncaught SyntaxError: In strict mode code, functions can only be declared at top level or inside a block.

L: function* F() {}
// VM175:1 Uncaught SyntaxError: Generators can only be declared at the top level or inside a block.

3.4 with

with 語(yǔ)句(不推薦,了解即可),用于擴(kuò)展語(yǔ)句的作用域鏈。
在 ECMAScript 5 嚴(yán)格模式中該標(biāo)簽已被禁止。推薦的替代方案是聲明一個(gè)臨時(shí)變量來承載你所需要的屬性。

 with (expression) {
   statement
 }

示例:

var a, x, y;
var r = 10;
var Math = {};

with (Math) {
 a = PI * r * r;
 x = r * cos(PI);
 y = r * sin(PI / 2);
}
// Uncaught ReferenceError: PI is not defined
// 因?yàn)樽饔糜蛑写嬖?Math 變量,所以先查找該變量中的 Math 對(duì)象是否有 PI 屬性,發(fā)現(xiàn)沒有所以報(bào)錯(cuò)。
// 'with' 語(yǔ)句將變量 Math 對(duì)象添加到作用域鏈的頂端,在查找變量值 PI 時(shí),會(huì)在指定的對(duì)象 Math 中查找,發(fā)現(xiàn)沒有所以報(bào)錯(cuò)。with 查找對(duì)象時(shí),會(huì)先從當(dāng)前作用域中查找,所以查找起來將會(huì)很慢。而且調(diào)試起來也會(huì)麻煩。
  • 表達(dá)式語(yǔ)句

    任何表達(dá)式都可以成為語(yǔ)句,就是說在任何需要寫語(yǔ)句的地方,都可以寫表達(dá)式,這樣的語(yǔ)句叫做表達(dá)式語(yǔ)句,表達(dá)式語(yǔ)句是一種特殊的語(yǔ)句。反過來,我們不能在寫表達(dá)式的地方寫語(yǔ)句。

    下圖是 ecma262 規(guī)范中 If 語(yǔ)句的語(yǔ)法。其中有 Statement 的地方都可以使用 Expression 即表達(dá)式,例如下方示例中的 callback 函數(shù)調(diào)用表達(dá)式,就是替代了原來的語(yǔ)句,也是表達(dá)式語(yǔ)句。


    ECMAScript 2020 If 語(yǔ)句語(yǔ)法

    示例:

    // callback 為表達(dá)式語(yǔ)句,是一種特殊的語(yǔ)句
    if (true) callback()
    
  • 比較

    1. 如何區(qū)分表達(dá)式和語(yǔ)句呢?

    1.看是否產(chǎn)生值判斷,對(duì)表達(dá)式求值一定會(huì)返回值,對(duì)語(yǔ)句求值未可能有返回值也可能沒有返回值;
    2.看后面是否有分號(hào),有分號(hào)的一定是語(yǔ)句,沒有分號(hào)的可能是表達(dá)式也可能是語(yǔ)句。

    下面兩個(gè)例子,第一個(gè)能成功 log 的原因在于 if 語(yǔ)句括號(hào)里應(yīng)該為表達(dá)式,而 true 是表達(dá)式中的布爾值字面量。第二個(gè) var a = 0 是聲明語(yǔ)句而不是表達(dá)式,沒有返回值,所以會(huì)報(bào)錯(cuò)。

    if (true) {
        console.log('Hi');
    }
    // 輸出:
    // Hi
    
    if (var a = 0) {
        console.log('Hi');
    }
    // 輸出:
    // Uncaught SyntaxError: Unexpected token 'var'
    

    2. 相似的表達(dá)式和語(yǔ)句

    2.1 if 語(yǔ)句和條件表達(dá)式

    if 語(yǔ)句和條件表達(dá)式表示的含義一樣,只是一個(gè)是語(yǔ)句,一個(gè)是表達(dá)式會(huì)返回值而已。

    var x;
    var y = -1;
    
    // if 語(yǔ)句
    if (y >= 0) {
      x = y;
    } else {
      x = -y;
    }
    
    // 條件表達(dá)式
    x = (y >= 0 ? y : -y); // 括號(hào)不是必須的,加上括號(hào)更容易閱讀
    
    2.2 函數(shù)聲明和函數(shù)表達(dá)式

    函數(shù)表達(dá)式與函數(shù)聲明擁有幾乎相同的語(yǔ)法,但是有以下區(qū)別:

    • 在函數(shù)表達(dá)式中可以省略函數(shù)名稱,省略函數(shù)名稱即為匿名函數(shù);函數(shù)聲明中則不能省略函數(shù)名;
    • 函數(shù)表達(dá)式可以用作 IIFE (即時(shí)調(diào)用函數(shù)表達(dá)式),函數(shù)聲明不能用作 IIFE。
    // 函數(shù)表達(dá)式:省略函數(shù)名
    function () {}
    
    // 函數(shù)表達(dá)式:未省略函數(shù)名
    // 寫法與函數(shù)聲明完全一致
    function foo() {}
    

    未省略函數(shù)名的函數(shù)表達(dá)式與函數(shù)聲明沒有區(qū)別,但是作用不同:函數(shù)表達(dá)式產(chǎn)生值,即函數(shù);函數(shù)聲明導(dǎo)致動(dòng)作,創(chuàng)建一個(gè)變量,其值為函數(shù)。
    未省略函數(shù)名的函數(shù)表達(dá)式中的函數(shù)名只能在函數(shù)內(nèi)部自調(diào)用,在函數(shù)外部調(diào)用會(huì)報(bào)錯(cuò)。

    示例:

    var outSideFuncName = function inSideFuncName(x) {
      return x <= 1 ? 1 : x * inSideFuncName(x - 1);
    }
    outSideFuncName(5); // Output: 120
    
    > outSideFuncName
    // Output:
    // ? inSideFuncName(x) {
    //   return x <= 1 ? 1 : x * inSideFuncName(x - 1);
    // }
    
    > insideFuncName
    // Output:
    // Uncaught ReferenceError: inSideFuncName is not defined
    
    2.3 對(duì)象字面量表達(dá)式和塊語(yǔ)句

    我們知道對(duì)象字面量是表達(dá)式,它的寫法為 {key: value} 形式,塊語(yǔ)句是用 {} 包裹的語(yǔ)句。當(dāng)塊里包含的是 label 語(yǔ)句且 label 語(yǔ)句后面是表達(dá)式語(yǔ)句的時(shí)候,對(duì)象字面量表達(dá)式和塊語(yǔ)句的寫法可能會(huì)完全一致。
    示例:

    {
        foo: bar(3, 5)
    }
    

    上面的例子中既是對(duì)象字面量,又可以說是塊語(yǔ)句。作為塊語(yǔ)句而言,塊里則是標(biāo)簽為 foo 的 label 語(yǔ)句,標(biāo)簽后的語(yǔ)句為一個(gè)函數(shù)調(diào)用表達(dá)式,根據(jù)前面對(duì)表達(dá)式語(yǔ)句的定義, 可以知道bar(3, 5)是一個(gè)表達(dá)式語(yǔ)句。

    所以在程序中我們看到的 {} 有可能是字面量,有可能是塊語(yǔ)句,根據(jù)上下文情況區(qū)分??聪旅娴睦樱?/p>

    > [] + {}
    "[object Object]"
    
    > {} + []
    0
    

    為什么兩個(gè)結(jié)果不一致呢?原因就在于前面的 {} 被作為字面量計(jì)算的,后面的是作為塊語(yǔ)句計(jì)算。例中還涉及到隱式轉(zhuǎn)換的問題,此處埋個(gè)伏筆,后續(xù)我會(huì)單獨(dú)出一篇文章講解。

    2.4 表達(dá)式中的逗號(hào)和語(yǔ)句中的分號(hào)

    在 JavaScript 中,語(yǔ)句是用分號(hào)隔離,例如 foo(); bar();表達(dá)式可用逗號(hào)隔離,例如 foo(), bar(),兩個(gè)表達(dá)式都會(huì)執(zhí)行,只是會(huì)返回后面的表達(dá)式的值。

    > "a", "b"
    'b'
    
    > var x = ("a", "b");
    > x
    'b'
    

    3. 使用對(duì)象字面量和函數(shù)表達(dá)式作為語(yǔ)句

    我們已經(jīng)知道了表達(dá)式可以放在任何需要語(yǔ)句的地方,這種即表達(dá)式語(yǔ)句。對(duì)于某些表達(dá)式與語(yǔ)句沒有區(qū)別的情況,如 2.3 中的對(duì)象字面量和塊,2.4 中的函數(shù)表達(dá)式和函數(shù)聲明,如何區(qū)分是表達(dá)式還是語(yǔ)句呢?一般情況下,是根據(jù)出現(xiàn)在表達(dá)式上下文還是語(yǔ)句上下文中區(qū)分,但是有一種情況例外,就是表達(dá)式語(yǔ)句。

    因?yàn)楸磉_(dá)式語(yǔ)句是一種特殊的語(yǔ)句,所以其上下文為語(yǔ)句上下文,這種情況中的 {} 會(huì)被當(dāng)作塊處理,function 開頭的語(yǔ)法會(huì)被當(dāng)作函數(shù)聲明。為了避免歧義,JavaScript語(yǔ)法禁止表達(dá)式語(yǔ)句使用 {function 開頭。如果一定要用這兩個(gè)開頭的表達(dá)式,并且讓它們僅作為表達(dá)式處理,則可以將表達(dá)式放到 () 分組操作符中,使它們出現(xiàn)在表達(dá)式的上下文中,并且不會(huì)改變表達(dá)式結(jié)果。

    (Expression)——分組操作符,由圓括號(hào)包裹表達(dá)式和子表達(dá)式,返回執(zhí)行表達(dá)式的結(jié)果。

    還有另一種方式確保在表達(dá)式上下文中解析表達(dá)式,就是使用一元操作符,如 !+ 等。但是與 () 的不同點(diǎn)在于一元操作符會(huì)改變表達(dá)式的結(jié)果。見下方立即調(diào)用函數(shù)表達(dá)式中的示例。

    下面看看 eval 和立即調(diào)用函數(shù)表達(dá)式中的應(yīng)用。

    • eval

    eval 是在語(yǔ)句上下文中解析其參數(shù),所以例子中上面的示例將 {} 解析為塊,所以里面為 label 語(yǔ)句,因而輸出為 123。 加上括號(hào)后,{} 及其里面的內(nèi)容的上下文為表達(dá)式,所以 {foo: 123} 被視為字面量表達(dá)式,因而輸出為 {foo: 123}。

    > eval("{foo: 123}");
    123
    
    > eval("({ foo: 123 })")
    {foo: 123}
    
    • 立即調(diào)用函數(shù)表達(dá)式(IIFEs)
    // 立即調(diào)用函數(shù)表達(dá)式
    > (function () { return "hello" }())
    'hello'
    
    > (function () { return "hello" })()
    'hello'
    
    // 省略括號(hào)之后的立即調(diào)用函數(shù)表達(dá)式
    > function () { return "hello" }()
    Uncaught SyntaxError: Function statements require a function name
    
    > +function () {console.log('hello')}()
    hello
    NaN  // 返回 NaN 是因?yàn)楸磉_(dá)式返回的為 undefined,所以 +undefined 為NaN
    
    > void function () {console.log('hello')}()
    hello
    undefined // 同理,void undefined 返回 undefined
    

    有一點(diǎn)要注意的是,連續(xù)使用 IIFEs 時(shí),要記得加分號(hào),否則會(huì)報(bào)錯(cuò)。因?yàn)楹竺娴?IIFE 會(huì)把前面的 IIFE 的結(jié)果當(dāng)作函數(shù)調(diào)用。
    若想省略分號(hào),可以將一元運(yùn)算符放在立即調(diào)用函數(shù)表達(dá)式前面,因?yàn)闀?huì)有自動(dòng)分號(hào)插入。自動(dòng)分號(hào)插入機(jī)制后續(xù)我也會(huì)講到,此處埋上第二個(gè)伏筆。

    (function () {}())
    (function () {}())
    // VM613:1 Uncaught TypeError: (intermediate value)(...) is not a function
    
    (function () {}());
    (function () {}())
    // undefined
    
    void function () {}()
    void function () {}()
    // undefined
    
  • 參考

    https://2ality.com/2012/09/expressions-vs-statements.html
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容