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

關(guān)于

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

  • 目錄

  1. 表達式
  2. 語句
  3. 表達式語句
  4. 比較
  5. 參考
  • 語句和表達式

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

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

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

  • 表達式

    表達式分為基本的表達式(包括基本關(guān)鍵字)蛹批,還有左值表達式以及運算符撰洗。

    1. 基本表達式

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

    2. 左值表達式

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

    3. 運算符

    • 一元
      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}等
    • 逗號
      ,
    • 位移,二進制泣刹,二元邏輯
      <<, >>, >>>; &, ^, |; &&, ||等
  • 語句

    語句分為聲明語句助析、流程控制語句和其他語句。
    其中椅您,流程控制語句分為基本流程控制語句外冀、迭代語句、跳轉(zhuǎn)語句和條件語句掀泳。具體如下雪隧。

    1. 聲明語句

    • 1.1 變量聲明

      1.1.1 var 聲明

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

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

      var 聲明的變量是可以提升的员舵,提升意味著無論變量實際在哪里聲明的脑沿,都會被當(dāng)成在當(dāng)前作用域頂部聲明的變量÷砥В看下面示例 4庄拇。根據(jù)示例 1,2韭邓,3 顯示的情況措近,建議始終聲明變量溶弟,無論它們在函數(shù)還是全局作用域內(nèi)。

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

      對于未聲明的變量可很,可以用 typeof 檢測其是否存在且不會報錯。

      // 示例 1: 聲明的變量的作用域在其聲明位置的上下文中凰浮,而未聲明變量是全局的我抠;
      // 建議始終聲明變量,無論它們是否在函數(shù)還是全局作用域內(nèi)
      function x() {
          y = 1;   // 在嚴(yán)格模式(strict mode)下會拋出 ReferenceError 異常
          var z = 2;
      }
      x();
      
      console.log(y); // 打印 "1"
      console.log(z); // 拋出 ReferenceError: z 未在 x 外部聲明
      
      // 示例 2:聲明的變量在任何代碼執(zhí)行前創(chuàng)建(會被提升)袜茧,未聲明變量只有在執(zhí)行賦值操作時被創(chuàng)建菜拓;
      console.log(a);                // 拋出 ReferenceError。
      console.log('still going...'); // 永不執(zhí)行笛厦。
      
      console.log(a);                // 打印 "undefined" 或 ""(不同瀏覽器實現(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
      
      // 實際會被轉(zhuǎn)換為:
      var x;
      var y;
      x = y;
      y = 'A';
      

      1.1.2 let 聲明

      聲明一個塊級作用域的變量,并可以將其初始化梦湘。

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

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

      1. var 聲明的變量只能是全局或者整個函數(shù)塊的瞎颗,let/const 聲明的變量只在其聲明的塊或子塊中使用;(示例 1)
      2. let/const 不會在全局聲明時創(chuàng)建 window 對象的屬性捌议,而 var 會哼拔。(示例 2)
      3. let/const 在同一個塊作用域或函數(shù)中不能重復(fù)聲明(會報錯),var 可以瓣颅;(示例 3倦逐,4)
      4. var 聲明的變量會被初始化為 undefinedlet/const 聲明的變量直到它們的定義被執(zhí)行時才會初始化宫补。量會被初始化為 undefined僻孝,let/const 聲明的變量直到它們的定義被執(zhí)行時才會初始化。
      // 示例 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 沒用 `{}` 包裹起來沒形成塊作用域守谓,所以兩個 `foo` 會在同一個塊中被聲明,所以報錯您单。
      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

      聲明一個塊作用域中的變量,并必須初始化一個值虐秦。與 let 用法基本相同平酿,除了聲明的變量的值不能被改變凤优。

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

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

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

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

      Es6 開始,嚴(yán)格模式下还绘,塊里的函數(shù)作用域為這個塊楚昭。非嚴(yán)格模式下的塊級函數(shù)不要用。

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

寫法示例:

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

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

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

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

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

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

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

函數(shù)表達式 name 屬性:
被函數(shù)表達式賦值的變量有 name 屬性铃辖,如果把這個變量賦值給另一個變量剩愧,name 屬性值也不會改變。

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

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

1.2.1 function

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

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

定義一個生成器函數(shù)娇斩,返回一個 Generator 對象仁卷。
Generator 對象:由 generator function 返回的對象,符合可迭代協(xié)議和迭代器協(xié)議犬第。

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

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

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

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

 function* yieldAndReturn() {
     yield "Y";
     return "R";//顯式返回處还棱,可以觀察到 done 也立即變?yōu)榱?true
     yield "unreachable";// 不會被執(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)移交給另一個生成器函數(shù)(當(dāng)前生成器暫停執(zhí)行),調(diào)用 next() 方法時惭等,如果傳入了參數(shù)珍手,那么這個參數(shù)會傳給上一條執(zhí)行的 yield 語句左邊的變量:

 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)造器使用,否則會報錯辞做。

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

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

1.2.3 async function

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

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

可以包含 await 指令,await 會暫停異步函數(shù)的執(zhí)行五垮,并等待 Promise 執(zhí)行乍惊,然后繼續(xù)執(zhí)行異步函數(shù),并返回結(jié)果放仗。
await 只能在異步函數(shù)中使用润绎,否則會報錯。
async/await 是為了簡化使用多個 Promise 時的行為诞挨,就像是結(jié)合了 generators 和 promises莉撇。

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

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

 // Async:return 時,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 中的類跟其他語言中的類類似棍郎,是基于原型繼承的。不過 ES6 中的類是基于已有自定義類型的語法糖银室,typeof 檢測類可以發(fā)現(xiàn)為 function涂佃。

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

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

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

    后續(xù)會單獨講講類。

2. 流程語句

  • 2.1 基本語句

    2.1.1 塊語句

    組合0或多個語句, 可以與label一起用叠纹。
    {語句組合} 或 標(biāo)簽標(biāo)識符: {語句組合}

    塊語句示例:
    示例 2 不會報錯季研,因為塊級作用域的存在,并且輸出的是 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;
    }
    

    塊語句返回值示例:
    塊返回的值為塊中最后一條語句的返回值与涡,不過因為語句的值獲取不到,所以了解即可持偏。

    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 空語句

    不會執(zhí)行任何語句
    ;

    空語句示例:

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

    2.2.1 while/do...while

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

    while 可在某個 condition(條件表達式)值為真的前提下驼卖,執(zhí)行循環(huán)直到表達式值為false;do...while 執(zhí)行指定語句的循環(huán)直到 condition(條件表達式)值為 false鸿秆,與 while 語句區(qū)別在于在執(zhí)行 statement 后檢測 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)卿叽,含三個可選的表達式桥胞,表達式包圍在圓括號中并由分號分割,后跟一個在循環(huán)中執(zhí)行的語句(通常是一個塊語句考婴,即用 {} 包裹起來的語句)贩虾。

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

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

    for (variable of iterable) {
       //statements
    }
    

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

    for (variable in object)
      statement
    

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

    for await (variable of iterable) 
       statement
    
    

    for :
    如果省略了中間可選的條件表達式(condition 塊),則必須確保在循環(huán)體內(nèi)跳出(break 語句)奔则,不然會陷入死循環(huán)蛮寂。
    如果省略所有表達式,則確保跳出循環(huán)并且修改增量易茬,使break語句 在某條件下為 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)只遍歷可枚舉屬性酬蹋,不可枚舉屬性不會遍歷及老,例如 String 的 indexOf() 方法,或者 Object.toString() 方法范抓。
    通常骄恶,在迭代過程中最好不要在對象上進行添加、修改或者刪除屬性的操作匕垫,因為不能保證這些被修改的屬性能被訪問到僧鲁。

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

    for await...of:
    異步生成器(或迭代異步可迭代對象)已經(jīng)實現(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 條件語句

    2.3.1 if

    條件判斷

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

    2.3.2 switch

    評估一個表達式绢淀,若表達式的值與 case 子句匹配則執(zhí)行 case 子句相關(guān)聯(lián)的語句。

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

    2.4.1 break 語句

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

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

    2.4.2 continue 語句

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

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

    // 示例 1: 循環(huán)中的 continue 語句
    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 語句

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

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

    2.4.4 try...catch 語句

    標(biāo)記要嘗試的語句塊败潦,并指定一個出現(xiàn)異常時拋出的響應(yīng)本冲。

    try {
        try_statements
    }
     [catch (exception_var_1) {}]
     [catch (exception_var_2) {}]
    // exception_var_1, exception_var_2 保存 throw 語句指定的值(如 catch(e) 中的 e ), 可以用這個標(biāo)識符獲取拋出的異常信息,只在 catch 子句內(nèi)部使用劫扒。
     [finally {}] 
    // 在 try 塊和 catch 塊之后執(zhí)行檬洞,在下一個 try 聲明之前執(zhí)行,無論是否有異常拋出總是執(zhí)行
    
    • 可以嵌套一個或更多的 try 語句沟饥,如果內(nèi)部的 try 語句沒有 catch 子句疮胖,就會進入包裹它的 try 語句的 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 塊返回一個值闷板,無論 try 和 catch 塊中是否有任何 return 語句,此值都將成為整個 try-catch-finally 的返回值院塞。
      // try-catch 中的 return 必須是作為函數(shù)的返回值才行遮晚,不然會報錯(見下面 return 語句)。此中情況下 try-catch 要放在函數(shù)中運行拦止。
      (function() {
          try {
              try {
                  throw new Error('oops');
              }
              catch (ex) {
                  console.error('inner', ex.message);
                  throw ex;
              }
              finally {
                  console.log('finally');
                  return;
              }
          }
          // 因為在 finally 中 return县遣,所以 `oops` 不會拋到外層
          catch (ex) {
              console.error('outer', ex.message);
          }
      })();
      
      // Output:
      // inner oops
      // finally
      // undefined // 整個函數(shù)的返回值
      

2.4.5 return 語句

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

在 return 關(guān)鍵字和被返回的表達式之間若使用行終止符(回車換行符顶瞒,行分隔符和段分隔符)則會自動分號插入夸政,如:

return
a + b;
// 會被自動轉(zhuǎn)換為
return;
a + b;

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

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

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

3. 其他語句

3.1 debugger

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

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

export: 從模塊中導(dǎo)出函數(shù),對象或原始值袱贮,以便其他程序可以通過import語句使用仿便。
導(dǎo)出模式分為兩種:命名導(dǎo)出默認導(dǎo)出≡芪。可以在每一個模塊中定義多個命名導(dǎo)出嗽仪,但是只允許一個默認導(dǎo)出。
導(dǎo)入/導(dǎo)出的模塊都是運行在嚴(yán)格模式下窑业。

導(dǎo)出示例:

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

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

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

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

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

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

導(dǎo)入示例:

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

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

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

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

3.3 label

在語句前加個可以引用的標(biāo)識符镀岛,可以和 break 或 continue 語句一起用弦牡。
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)格模式下漂羊,可以對函數(shù)聲明進行標(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 語句(不推薦椭豫,了解即可),用于擴展語句的作用域鏈旨指。
在 ECMAScript 5 嚴(yán)格模式中該標(biāo)簽已被禁止捻悯。推薦的替代方案是聲明一個臨時變量來承載你所需要的屬性。

 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
// 因為作用域中存在 Math 變量淤毛,所以先查找該變量中的 Math 對象是否有 PI 屬性今缚,發(fā)現(xiàn)沒有所以報錯。
// 'with' 語句將變量 Math 對象添加到作用域鏈的頂端低淡,在查找變量值 PI 時姓言,會在指定的對象 Math 中查找,發(fā)現(xiàn)沒有所以報錯蔗蹋。with 查找對象時何荚,會先從當(dāng)前作用域中查找,所以查找起來將會很慢猪杭。而且調(diào)試起來也會麻煩餐塘。
  • 表達式語句

    任何表達式都可以成為語句,就是說在任何需要寫語句的地方皂吮,都可以寫表達式戒傻,這樣的語句叫做表達式語句税手,表達式語句是一種特殊的語句。反過來需纳,我們不能在寫表達式的地方寫語句芦倒。

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


    ECMAScript 2020 If 語句語法

    示例:

    // callback 為表達式語句器钟,是一種特殊的語句
    if (true) callback()
    
  • 比較

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

    1.看是否產(chǎn)生值判斷妙蔗,對表達式求值一定會返回值俱箱,對語句求值未可能有返回值也可能沒有返回值;
    2.看后面是否有分號灭必,有分號的一定是語句,沒有分號的可能是表達式也可能是語句乃摹。

    下面兩個例子禁漓,第一個能成功 log 的原因在于 if 語句括號里應(yīng)該為表達式,而 true 是表達式中的布爾值字面量孵睬。第二個 var a = 0 是聲明語句而不是表達式播歼,沒有返回值,所以會報錯掰读。

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

    2. 相似的表達式和語句

    2.1 if 語句和條件表達式

    if 語句和條件表達式表示的含義一樣,只是一個是語句,一個是表達式會返回值而已摩桶。

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

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

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

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

    示例:

    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 對象字面量表達式和塊語句

    我們知道對象字面量是表達式颖医,它的寫法為 {key: value} 形式位衩,塊語句是用 {} 包裹的語句。當(dāng)塊里包含的是 label 語句且 label 語句后面是表達式語句的時候熔萧,對象字面量表達式和塊語句的寫法可能會完全一致糖驴。
    示例:

    {
        foo: bar(3, 5)
    }
    

    上面的例子中既是對象字面量,又可以說是塊語句佛致。作為塊語句而言贮缕,塊里則是標(biāo)簽為 foo 的 label 語句,標(biāo)簽后的語句為一個函數(shù)調(diào)用表達式俺榆,根據(jù)前面對表達式語句的定義感昼, 可以知道bar(3, 5)是一個表達式語句。

    所以在程序中我們看到的 {} 有可能是字面量罐脊,有可能是塊語句定嗓,根據(jù)上下文情況區(qū)分∑甲溃看下面的例子:

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

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

    2.4 表達式中的逗號和語句中的分號

    在 JavaScript 中寇损,語句是用分號隔離,例如 foo(); bar()裳食;表達式可用逗號隔離矛市,例如 foo(), bar(),兩個表達式都會執(zhí)行诲祸,只是會返回后面的表達式的值尘盼。

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

    3. 使用對象字面量和函數(shù)表達式作為語句

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

    因為表達式語句是一種特殊的語句,所以其上下文為語句上下文籽懦,這種情況中的 {} 會被當(dāng)作塊處理于个,function 開頭的語法會被當(dāng)作函數(shù)聲明。為了避免歧義暮顺,JavaScript語法禁止表達式語句使用 {function 開頭厅篓。如果一定要用這兩個開頭的表達式,并且讓它們僅作為表達式處理捶码,則可以將表達式放到 () 分組操作符中羽氮,使它們出現(xiàn)在表達式的上下文中,并且不會改變表達式結(jié)果惫恼。

    (Expression)——分組操作符档押,由圓括號包裹表達式和子表達式,返回執(zhí)行表達式的結(jié)果祈纯。

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

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

    • eval

    eval 是在語句上下文中解析其參數(shù),所以例子中上面的示例將 {} 解析為塊倾贰,所以里面為 label 語句冕碟,因而輸出為 123。 加上括號后匆浙,{} 及其里面的內(nèi)容的上下文為表達式安寺,所以 {foo: 123} 被視為字面量表達式,因而輸出為 {foo: 123}首尼。

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

    有一點要注意的是软能,連續(xù)使用 IIFEs 時迎捺,要記得加分號,否則會報錯查排。因為后面的 IIFE 會把前面的 IIFE 的結(jié)果當(dāng)作函數(shù)調(diào)用凳枝。
    若想省略分號,可以將一元運算符放在立即調(diào)用函數(shù)表達式前面,因為會有自動分號插入岖瑰。自動分號插入機制后續(xù)我也會講到叛买,此處埋上第二個伏筆。

    (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)容合作請聯(lián)系作者
  • 序言:七十年代末蹋订,一起剝皮案震驚了整個濱河市率挣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌露戒,老刑警劉巖椒功,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異玫锋,居然都是意外死亡蛾茉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門撩鹿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谦炬,“玉大人,你說我怎么就攤上這事节沦〖迹” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵甫贯,是天一觀的道長吼鳞。 經(jīng)常有香客問我,道長叫搁,這世上最難降的妖魔是什么赔桌? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮渴逻,結(jié)果婚禮上疾党,老公的妹妹穿的比我還像新娘。我一直安慰自己惨奕,他們只是感情好雪位,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梨撞,像睡著了一般雹洗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卧波,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天时肿,我揣著相機與錄音,去河邊找鬼港粱。 笑死嗜侮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锈颗,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼顷霹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了击吱?” 一聲冷哼從身側(cè)響起淋淀,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎覆醇,沒想到半個月后朵纷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡永脓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年袍辞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片常摧。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡搅吁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出落午,到底是詐尸還是另有隱情谎懦,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布溃斋,位于F島的核電站界拦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏梗劫。R本人自食惡果不足惜享甸,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梳侨。 院中可真熱鬧蛉威,春花似錦、人聲如沸猫妙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽割坠。三九已至,卻和暖如春妒牙,著一層夾襖步出監(jiān)牢的瞬間彼哼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工湘今, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留敢朱,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像拴签,于是被迫代替她去往敵國和親孝常。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容