WFX-Maniac

0%

ES6解構賦值重點整理

  1. 模式匹配:

    只要等号两边的模式相同,左边的变量就会被赋予对应的值。

  • 這個
    let [a, b, c] = [1, 2, 3];
  • 等同於這個
    let a = 1;
    let b = 2;
    let c = 3;
  • 這個
    let [foo, [[bar], baz]] = [1, [[2], 3]];
  • 等同於這個
    let foo = 1;
    let bar = 2;
    let baz = 3;
  • 如果解構不成功,變量的值就等於undefined。
    let [foo] = [];
    let [bar,foo] = [1];
    以上兩種情況都屬於解構失敗,foo的值都會等於undefined。
  • 解構賦值的等號右邊必須要是一個可以迭代的東西,如果不是的話會報錯
    let [qoo]=null //報錯
  • 支援預設值,但也只有遇上對應的子項為 undefined才會採用預設值
    let [foo = true] = [];
    foo // true
    let [x, y = ‘b’] = [‘a’]; // x=’a’, y=’b’
    let [x, y = ‘b’] = [‘a’, undefined]; // x=’a’, y=’b’
    let [x = 1] = [null];x // null 因為子項不等於undefined
  • 字串也可以做模式匹配
    const [a, b, c, d, e] = ‘hello’;
    會變成
    const a= ‘h’,
    const b= ‘e’
  1. 對象匹配

    • Basic

      1
      2
      3
      4
      5
      6
      let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
      foo // "aaa"
      bar // "bbb"

      let { baz } = { foo: 'aaa', bar: 'bbb' };
      baz // undefined
    • 如果變數的名稱與複製目標的屬性名稱不一樣的話可以這樣做

      1
      2
      3
      4
      5
      6
      7
      let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
      baz // "aaa"

      let obj = { first: 'hello', last: 'world' };
      let { first: f, last: l } = obj;
      f // 'hello'
      l // 'world'
    • 對象匹配也可以有預設值,而採用預設值的標準跟模式匹配一樣:undefined

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      var {x = 3} = {};
      x // 3

      var {x, y = 5} = {x: 1};
      x // 1
      y // 5

      //這個比較需要記憶
      var {x: y = 3} = {};
      y // 3

      //這個比較需要記憶
      var {x: y = 3} = {x: 5};
      y // 5

      var { message: msg = 'Something went wrong' } = {};
      msg // "Something went wrong"
    • 注意不要把大括號寫在行首

      1
      2
      3
      4
      5
      6
      {x} = {x: 1};//報錯
      //應該改寫成
      let {x} = {x: 1};
      //或
      let x;//這行不寫的話x 會變全域
      ({x} = {x: 1};)//這種方式盡量不要用
    • 函數參數的解構

      1
      2
      3
      4
      5
      function add([x, y]){
      return x + y;
      }

      add([1, 2]); // 3
    • 函数参数的解构也可以使用默认值。

      1
      2
      3
      4
      5
      6
      7
      8
      function move({x = 0, y = 0} = {}) {
      return [x, y];
      }

      move({x: 3, y: 8}); // [3, 8]
      move({x: 3}); // [3, 0]
      move({}); // [0, 0]
      move(); // [0, 0]
    • 注意下列兩者的不同

      1
      2
      3
      4
      5
      6
      7
      8
      9
      //函數參數本身附帶預設值
      function move({x = 0, y = 0} = {}) {
      return [x, y];
      }
      // 傳進來的物件會取代掉{}
      move({x: 3, y: 8}); // [3, 8]
      move({x: 3}); // [3, 0]
      move({}); // [0, 0]
      move(); // [0, 0]
      1
      2
      3
      4
      5
      6
      7
      8
      function move({x, y} = { x: 0, y: 0 }) {
      return [x, y];
      }
      // 傳進來的物件會取代掉{ x: 0, y: 0 }
      move({x: 3, y: 8}); // [3, 8]
      move({x: 3}); // [3, undefined]
      move({}); // [undefined, undefined]
      move(); // [0, 0]
  2. 解構賦值的用途

    • 交換變數值

      1
      2
      3
      4
      let x = 1;
      let y = 2;

      [x, y] = [y, x];
    • 從物件/陣列 快速宣告多個值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      function example() {
      return [1, 2, 3];
      }
      let [a, b, c] = example();

      // 返回一个对象

      function example() {
      return {
      foo: 1,
      bar: 2
      };
      }
      let { foo, bar } = example();
    • 快速指定函數預設參數值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      $.ajax = function (url, {
      async = true,
      beforeSend = function () {},
      cache = true,
      complete = function () {},
      crossDomain = false,
      global = true,
      // ... more config
      } = {}) {
      // ... do stuff
      };
  3. generator function(生成器函數)是什麼?

    • 在ES6中定義一個生成器函數很簡單,在函數後跟上「*」即可:

      1
      2
      3
      4
      5
      6
      7
      8
      function* foo1() { };
      function *foo2() { };
      function * foo3() { };

      foo1.toString(); // "function* foo1() { }"
      foo2.toString(); // "function* foo2() { }"
      foo3.toString(); // "function* foo3() { }"

    • 调用生成器函数会产生一个生成器(generator)。生成器拥有的最重要的方法是 next(),用来迭代:

      1
      2
      3
      4
      5
      6
      7
      8
      function* fibs() {
      let a = 0;
      let b = 1;
      while (true) {
      yield a;//碰到yield就會return 一個值
      [a, b] = [b, a + b];
      }
      }

搞清楚並且用最簡單的方法解釋js的apply/call/bind

  • 先解釋apply/call

    apply/call 最簡單的解釋其實就是“把一個參數丟進去指定的函數裡面當this”

    1
    2
    3
    4
    5
    6
    7
    //假設有一個變數叫做qq, 初始值為'wow'
    var qq = 'wow';
    function poop(a,b){
    console.log(this,a,b);
    }
    poop.apply(qq);//wow undefine undefine
    // 這邊其實就是把qq丟進去poop()裡面當作this, 用call 的話亦然
  • apply和call的差別

    雖然差別都是丟變數進去函數當this用,但是這兩個的差異在於支援參數不同

    1
    2
    3
    4
    //apply的第一個參數傳入之後會變成函數中的this,但是第二個參數(必須要是array)傳入之後會變成函數原始支援的參數群
    theFunction.apply(valueForThis, arrayOfArgs)
    //apply的第一個參數傳入之後會變成函數中的this,但是第二個以後的參數就是函數原始的所有參數群
    theFunction.call(valueForThis, arg1, arg2, ...)
    1
    2
    3
    //以上面的poop()來看
    poop().apply(qq,[1,2])//'wow' 1 2
    poop().call(qq,1,2)//'wow' 1 2
  • 解釋bind

bind的用途跟apply/call 有點不同,他是會return 一個把指定變數綁進去當this的函數

1
2
3
4
5
6
7
8
function aa(a,b){
console.log(this,a,b)
}
aa(11,22);//window 11 22 aa函數在沒被綁之前的this 都是window
var ccc=[1,2,3];
var gg = aa.bind(ccc);// 把ccc綁給aa之後重新包裝成一個gg, aa其實沒有被變動
gg(44,44)//[1,2,3] 44 44 從這邊開始this被綁死了

  • bind 的進階用法

    固定上層scope 的this,這個其實就是ES6 arrow function內部的做法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var a = {
    gg:'wow',
    ff:function(){
    setTimeout(function(){
    console.log(this.gg)
    }.bind(this))
    }
    }

    a.ff(); //wow

變數提升與let:

  • 變數提升最基本的案例:

    1
    2
    3
    4
    5
    console.log(a)
    //在"var a = 10" 之前去呼叫a, 其實會輸出undefine 而不是 ReferenceError: a is not defined.
    //那是因為var 其實是先把a 在最開頭做一個宣告a為變數,然後最後才賦予值。
    var a = 10

  • 函數中變數提升的狀況:

    • 這個狀況:
      1
      2
      3
      4
      5
      function test(v){
      console.log(v)
      var v = 3
      }
      test(10)
    • 其實等於這個狀況
      1
      2
      3
      4
      5
      6
      7
      8
      function test(v=10){
      var v;// 引入的參數其實也會在開頭被先宣告,而且宣告的優先權會比函數內部的變數宣告來得高
      var v;
      v = 10
      console.log(v)
      v = 3
      }
      test(10)
  • let/const 與暫時性死區(Temporal Dead Zone)

    • 雖然let 可以用來避免用var產生的hoisting狀況,但是這並不代表let沒有hoisting行為
    • 在「提升之後」以及「賦值之前」這段「期間」,如果你存取它就會拋出ReferenceError錯誤,而這段期間就稱做是 TDZ,它是一個為了解釋 let 與 const 的 hoisting 行為所提出的一個名詞。
      1
      2
      3
      4
      5
      6
      7
      let x = 'outer value';

      (function() {
      // 在函數的起始處會產生x的 TDZ起始點 x
      console.log(x) // TDZ期間存取,產生ReferenceError錯誤
      let x = 'inner value' // 對x的宣告語句,這裡結束 TDZ for x
      })()

let與if的搭配

  • 單行的if宣告不能在判斷boolean 之後let宣告
1
2
3
4
5
6
7
// 第一种写法,报错
if (true) let x = 1;

// 第二种写法,不报错
if (true) {
let x = 1;
}
  • 應該避免在塊級作用域內聲明函數。如果確實需要,也應該寫成函數表達式,而不是函數聲明語句。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 塊級作用域內部的函數聲明語句,建議不要使用
{
let a = 'secret';
function f() {
return a;
}
}
// 塊級作用域內部,優先使用函數表達式
{
let a = 'secret';
let f = function () {
return a;
};
}

const的重要意義 – 固定傳址

大家都知道const 宣告的值就是不變的常數,但是實際上const最重要的應用點其實是在於固定變數儲存位置的地址

1
2
3
4
5
const a = [];
a.push('Hello'); // 可執行
a.length = 0; // 可執行
a = ['Dave']; // 錯誤,可以藉由這樣的方式避免a被換址

如果真的想將對象凍結,應該使用Object.freeze方法。

1
2
3
4
5
const foo = Object.freeze({});

// 常規模式時,下面一行不起作用;
// 嚴格模式時,該行會報錯
foo.prop = 123;