.st0{fill:#FFFFFF;}

JavaScript 筆記 – 2 

 2024-03-01

By  Mason - 預計閱讀時間約  分鐘

Spread Syntax and Rest Parameters

Spread Syntax

- spread syntax 是擴展 array 中的元素 (使用在 array 和 function invocation)

- 使用在 array

範例 1:

    let num1 = [1, 2, 3];

    let num2 = [4, 5, 6];

    console.log([...num1, ...num2]); 

    結果為: [1, 2, 3, 4, 5, 6]

範例 2:

    const parts = ["肩膀", "膝蓋"];

    const otherParts = ["頭", ...parts, "身體", "腳"];

    console.log(otherParts);

    結果為: [ '頭', '肩膀', '膝蓋', '身體', '腳' ]

- 使用在 function invocation

範例 :

    function sum(a, b, c, d, e) {

        return a + b + c + d + e;

    }

    let myArr = [1, 2, 3];

        console.log(sum(10, ...myArr, 5)); 

    結果為: 21

- 利用 spread syntax 來複製 array (非 copy by reference)

範例 :

    const arr = [1, 2, 3];

    const arr2 = [...arr];

    arr.push(4);

    console.log(arr);

    console.log(arr2);

    結果為:  [ 1, 2, 3, 4 ]

                     [ 1, 2, 3 ]

Rest Parameters

- Rest Prarmeters 和 Spread Syntax 的語法幾乎一模一樣。

- Rest Prarmeters 是收集多個元素並將他們壓縮為單個 JS array

- 使用在 function definition

範例 1:

    function sum2(...theArgs) {

        console.log(theArgs);

    }

    sum2(1, 2, 3, 4, 5, 6, 7);  // 壓縮為單個 JS array

    結果為: [ 1, 2, 3, 4, 5, 6, 7 ]

範例 2:

    function sum3(...theArgs) {

      let total = 0;

      for (let i = 0; i < theArgs.length; i++) {

        total += theArgs[i];

      }

    return total;

    }

    console.log(sum3(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

    console.log(sum3(1, 2, 3, 4, 5));

    結果為: 55 和 15

Primitive, Reference Data Types

- primitive data type 不是 Object,都沒有自己的 attributes 和 methods。 此外,裝有 primitive data type 的 variable 確實擁有數值,而不僅僅是對其數值的記憶體位置的 reference。

- Object 和 array 都是 Reference Data Type。Reference Data type 變數中,儲存的值是 Reference ,也就是記憶體的位址,指向儲存真實內容的記憶體區塊的位置。

Primitive Coercion(強迫)

- JavaScript 會自動將 primitive data 放入一個 wrapper object 中, 讓它們可以繼承 wrapper object 的屬性和方法, 例如: string.length 屬性 or number.toFix() 方法。

- 這種自動裝箱行為在 JavaScript 代碼中是不可以觀察到的,這就是 primitive coercion

- 我們也可以自行製作 wrapper object ,但不建議如此做,因為會佔大量 RAM。<8-3範例-時間宣告比較>

    let name = new String("Wilson");

    console.log(typeof name);

    結果為:object

Javascript String Comparison

- 英文字母越後面越大,第一個字母相同則依序比較第二、第三...。

    console.log("abandon" < "apple");

    console.log("12" < "2");

    結果為:都是 true

進階 Array Methods

   arr.map(callbackFn)

    - 創建一個新 array ,在 arr 中每個元素經過 callbackFn 執行後返回的值都會添加到此新的 array 裡面。

   - arr.map(callbackFn) >>> A new array with each element being the result of the callback function.

範例:

    let arr = [1, 2, 3, 4, 5, 6, 7];

    let newArr = arr.map((i) => i ** 2);

    console.log(newArr);

    結果為:(return a new array)

    [1,  4,  9, 16, 25, 36, 49]

   - arr.forEach() >>> return undefine

範例:

    let arr = [1, 2, 3, 4, 5, 6, 7];

    let newArr = arr.forEach((i) => {

        console.log(i ** 2);

    });

    console.log(newArr);

    結果為:(return undefined)

    1

    4

    9

    16

    25

    36

    49

    undefined

範例 1:

    let languages = ["Java", "C++", "Python", "Javascript"];

    let lanResult = languages.map((lang) => lang.toUpperCase());

    console.log(lanResult);

    結果為:

    [ 'JAVA', 'C++', 'PYTHON', 'JAVASCRIPT' ]

範例 2:

    const codeLanguages = [

    { name: "Python", rating: 9.5, popularity: 9.7, trending: "super hot" },

    { name: "Java", rating: 9.4, popularity: 8.5, trending: "hot" },

    { name: "C++", rating: 9.2, popularity: 7.7, trending: "hot" },

    { name: "PHP", rating: 9.0, popularity: 5.7, trending: "decreasing" },

    { name: "JS", rating: 8.5, popularity: 8.7, trending: "hot" },

    ];

    let codeResult = codeLanguages.map((lang) => {

      return lang.name.toUpperCase();

    });

    console.log(codeResult);

    結果為:

    [ 'PYTHON', 'JAVA', 'C++', 'PHP', 'JS' ]

   arr.find(callbackFn)

    - 找到 arr 中第一個滿足 callbackFn 條件值 (return true) 的元素,並返回該元素,如果沒有值滿足 callbackFn 條件,則返回 undefined。

範例 :

    const codeLanguages = [

    { name: "Python", rating: 9.5, popularity: 9.7, trending: "super hot" },

    { name: "Java", rating: 9.4, popularity: 8.5, trending: "hot" },

    { name: "C++", rating: 9.2, popularity: 7.7, trending: "hot" },

    { name: "PHP", rating: 9.0, popularity: 5.7, trending: "decreasing" },

    { name: "JS", rating: 8.5, popularity: 8.7, trending: "hot" },

    ];

    let highPop = codeLanguages.find((lang) => {

      return lang.popularity > 9.5;

    });

    console.log(highPop);

    結果為:

    { name: 'Python', rating: 9.5, popularity: 9.7, trending: 'super hot' }

   arr.filter(callbackFn)

    - 過濾出所有滿足 callbacFn 中 return true 的元素。

範例 :

    const codeLanguages = [

    { name: "Python", rating: 9.5, popularity: 9.7, trending: "super hot" },

    { name: "Java", rating: 9.4, popularity: 8.5, trending: "hot" },

    { name: "C++", rating: 9.2, popularity: 7.7, trending: "hot" },

    { name: "PHP", rating: 9.0, popularity: 5.7, trending: "decreasing" },

    { name: "JS", rating: 8.5, popularity: 8.7, trending: "hot" },

    ];

    let rating = codeLanguages.filter((lang) => lang.rating >= 9.2);

    console.log(rating);

    結果為:

    [

      {

         name: 'Python',

         rating: 9.5,

         popularity: 9.7,

         trending: 'super hot'

       },

      { name: 'Java', rating: 9.4, popularity: 8.5, trending: 'hot' },

      { name: 'C++', rating: 9.2, popularity: 7.7, trending: 'hot' }

    ]

   arr.some(callbackFn)

    - 測試 arr 中,是否至少有一個元素通過 callbackFn 測試並 return true (return type 為 boolean)。

範例 :

    const codeLanguages = [

    { name: "Python", rating: 9.5, popularity: 9.7, trending: "super hot" },

    { name: "Java", rating: 9.4, popularity: 8.5, trending: "hot" },

    { name: "C++", rating: 9.2, popularity: 7.7, trending: "hot" },

    { name: "PHP", rating: 9.0, popularity: 5.7, trending: "decreasing" },

    { name: "JS", rating: 8.5, popularity: 8.7, trending: "hot" },

    ];

    let lessPop = codeLanguages.some((lang) => lang.popularity <= 6);

    console.log(lessPop );

    結果為:true

   arr.every(callbackFn)

    - 測試 arr 中, 是否所有元素都通過 callbackFn 測試並return true (return type 為 boolean)。

範例 :

    const codeLanguages = [

    { name: "Python", rating: 9.5, popularity: 9.7, trending: "super hot" },

    { name: "Java", rating: 9.4, popularity: 8.5, trending: "hot" },

    { name: "C++", rating: 9.2, popularity: 7.7, trending: "hot" },

    { name: "PHP", rating: 9.0, popularity: 5.7, trending: "decreasing" },

    { name: "JS", rating: 8.5, popularity: 8.7, trending: "hot" },

    ];

    let allPop = codeLanguages.every((lang) => lang.popularity > 6);

    console.log(allPop );

    結果為:false

JS 內建排序函式

- 若想要把 array 內部的元素由小到大排序,可以用 JavaScript 內建的 sort() 方法。

- sort() 方法對 array 的元素進行就地排序,也就是說 array 會被永久改變。(絕大多數 JavaScript 內建的 method 並不會改變調用此 method 的變數的值,例如,String 的 toUpperCase() 就是一種)。

範例 1:

    let Arrmy = [1, 5, 3, 2, 4, 7, 8, 0];

    myarr.sort(); // array 會被永久改變

    console.log(myArr);

    結果為:[0, 1, 2, 3, 4, 5, 7, 8]  

範例 2:

    let myName = "Grace";

    myName.toUpperCase(); // 不會改變調用此 method 的變數的值

    console.log(myName);

    結果為:Grace  

    myName = myName .toUpperCase(); // assign to 變數

    結果為:GRACE

- 若希望保留未經過排序的 array,則需要先製作一個完整的複製品。

範例 :

    let Arrmy = [1, 5, 3, 2, 4, 7, 8, 0];

    let mySortedArr = [...Arrmy]; // 複製一個 array

    lmySortedArr.sort(); // default sort (遞增)

    console.log(Arrmy);

    結果為:[1, 5, 3, 2, 4, 7, 8, 0]

    console.log(mySortedArr);

    結果為:[0, 1, 2, 3, 4, 5, 7, 8]

    console.log(mySortedArr.reverse()); // 反轉

    結果為:[8, 7, 5, 4, 3, 2, 1, 0]  

- sort() 語法:

    sort()

    sort(compareFn)

- compareFn 是定義排序順序的函數。如果省略,則將 array 元素按照 JavaScript 預設方式排序(由小到大)。(數字: 1 < 2 < 3...;字母:a < b < c...)

範例 :

    let fruits = ["Watermelon", "Apple", "Banana"];

    fruits.sort();

    console.log(fruits);

    結果為:["Apple", "Banana", "Watermelon"]  

- 若我們要根據自己提供的 compareFn 來排序,則此 compareFn 需要有兩個 parameter:a, b,sort() 會根據 compareFn 的 return value 來決定排序順序。若 return a - b,則採升序排序,若 return b - a,則採降序排序。其它 return 值為:

sort - compareFn

範例 1 :

    let num = [9, 4, 3, 5, 6, 1, 0];

    num.sort((a, b) => {

      return b - a; // 降序排序

    });

    console.log(num);

    結果為:[9, 6, 5, 4, 3, 1, 0]  

範例 2 :

    let fruits = ["Watermelon", "Apple", "Banana"];

    fruits.sort((a, b) => {

      if (a.length > b.length) {

        return 1;  // > 0, a after b; 所以字母較長的排在後面

      } else {

        return -1;

      }

    });

    console.log(fruits);

    結果為:['Apple', 'Banana', 'Watermelon']  

- sort() 在使用時,如果沒有放入參數 compareFn,則 array 當中的每個元素將先被轉換為 String,再以 Unicode 編碼位置進行比較來排序。在 Unicode 編碼位置較後面者,會被排序在較後方。此 Unicode 編碼順序其實就是前面的 String comparison 影片中談過的字典順序。舉例來說,"banana" 會被排在 "cherry" 之前。同樣道理,在 Unicode 順序中 "1500" 會在 "2" 的前面,所以 1500 會被排在 2 前面。

範例  :

    let myArr = [830, 1500, 1026, 48, 311, 122, 216, 93, 2];

    myArr.sort();

    console.log(myArr);

    結果為:[1026, 122, 1500, 2, 216, 311, 48, 830, 93]  

for of loop and for in loop

for of loop

- 創建一個迴圈,去循環可迭代對象 (iterable) 內的每個元素,可迭代對象:string, array, array-like object (例如:NodeList, HTMLCollection), TypedArray, Map, Set, user-defined。 

- Object 並不是 iterable >> 不能用 for of loop

範例 1: for of loop

    let numbers = [10, 20, 30];

    for (let n of numbers) {

        console.log(n);

    }

    結果為:10

                    20

                    30

比較:for loop

    let numbers = [10, 20, 30];

    for (let i = 0; i < numbers.length; i++) {

        console.log(numbers[i]);

    }

    結果為:10

                    20

                    30

比較:forEach

    let numbers = [10, 20, 30];

    numbers.forEach((n) => console.log(n));

    結果為:10

                    20

                    30

範例 2: for of loop - string

    let myString = "Grace";

    for (let i of myString) {

      console.log(i);

    }

    結果為:G

                    r

                    a

                    c

                    e


for in loop

- 創建一個迴圈去循環一個 JS 物件中所有可枚舉屬性 (enumerable properties)

- 對 object 來說, enumerable properties 就是 keys

範例: 

    let Wilson = {

        name: "Wilson Ren",

        age: 26,

    };

    for (let property in Wilson) {

      console.log(property);

    // console.log(Wilson[property]);

    }

    結果為:name

                    age

                    // Wilson Ren

                    // 26

- 對 array 來說, enumerable properties 就是 indices

- 對 String 來說, enumerable properties 就是 indices

範例: 

    let inNumbers = [100, 44, 22];

    for (let i in inNumbers) {

      console.log(i); // index 的值

      // console.log(inNumbers[i]);

    }

    結果為:0

                    1

                    2

                    // 100

                    // 44

                    // 22

Execution Context (執行環境)

- 當 JS 引擎執行程式碼 (script) 時,便會創建 execution contexts (執行環境)。JS 會建立兩種執行環境:

    1. 全域執行環境 (Global Execution Context)

    2. 函式執行環境 (Function Execution Context)

每種 execution context 都包含兩種階段:創造階段 (creation phase)執行階段 (execution phase)

全域執行環境 (Global Execution Context)

- 當初次執行一份 JavaScript 程式碼時,JS 引擎會創造第一種 execution context,稱為全域執行環境 (Global Execution Context)

- 在此 Global Execution Context 內部,會先進入創造階段 (creation phase)

    a. 創建 global object (例如:瀏覽器中的 window object,或 Node.js 中的 global object)

    b. 建立 scope

    c. 創建 this 關鍵字,並綁訂至 global object

    d. 將 variable、class 和 function 分配至記憶體 (RAM)。(hoisting 步驟)

- creation phase 結束後,會進入執行階段 (execution phase)

    a. 逐行 (line by line) 執行程式碼

    b. 遇到遞迴時,則使用 call stack 來排定工作順序

函式執行環境 (Function Execution Context)

- 每次的 function call,JS 引擎也會創造一個 function execution context,函式執行環境 (Function Execution Context) 和全域執行環境 (Global Execution Context) 非常類似,一樣也有創造階段 (creation phase) 和 執行階段 (execution phase)

- 函式執行環境 (Function Execution Context) 不創建 global object,而是創建 argument object。此 argument object 包含了被放入此函式的 parameters 的數值參照值 (a reference to all the parameters passed into the function)。

- 函式執行環境 (Function Execution Context) 的創造階段 (creation phase)

    a. 創建 argument object

    b. 建立 scope (依照 closure 準則)

    c. 創建 this 關鍵字

    d. 將 variable、class 和 function 分配至記憶體 (RAM)。(hoisting 步驟)

- creation phase 結束後,會進入執行階段 (execution phase)

    a. 逐行 (line by line) 執行程式碼

    b. 遇到遞迴時,則使用 call stack 來排定工作順序

execution context

Hoisting (提升)

- 是指 JS 引擎在執行代碼之前,將 function、variables 或 class 的 declaration 移動到其範圍頂部的過程

- 其優點之一是它允許我們在 code 中,declare function 之前就可以使用這個 function。(只對 function declaration 有用)

- Hoisting 也適用於 variables,因此可以在 declaration 和/或 initialization 之前在 code 中使用 variables。然而 JavaScript 只 hoist declaration,而不是 initialization!也就是說,let x = 10; 這段程式碼只有 let x 會被放到程式碼頂部。

- Hoisting 發生時,對於使用 var 做 declaration 的 variable 會給定初始值 undefined 並完成 initialization。然而,對於使用 let、const 做 declaration 的 variable 並不會給定任何初始值。

    console.log(x);

    var x;

    結果:undefined

- let 可以 declare without initialization,且我們可以使用 console.log() 檢查 let 的變數值是 undefined,但這個 undefined 的 initialization 並不像 var 是發生在 creation phase 的 hoisting 階段發生的,而是在 execution phase 的階段。

    let x; // declare without initialization

    console.log(x);

    結果:undefined


    console.log(x);

    let x;

    結果:Cannot access 'x' before initialization


Scope and Closure

- Scope 是指在當前的 execution context 之中,變數的可訪問性 (accessibility) 為何?

    let x = 10; // 全域變數 global variable

    function hello() {

        function hello2() {

            console.log(x + 10);

        }

        hello2();

    }

    hello();


    結果:20


    function hello3() {

        let a = 10; // local variable

    }

JavaScript 的變數有以下幾種 Scope:

    1. Global scope

    - The default scope for all code running in the script.

    let myName = "Wilson";

   

    function sayHi() {

        console.log(myName + "說你好");

        function sayHi2() {

            console.log(myName + "說你好");

        }

        sayHi2();

    }


    sayHi()


    結果:Wilson說你好

                Wilson說你好


    2. Module scope

    - The scope for code running in module mode.

    3. Function scope

    - The scope is created with a function.

    function hello() {

        let myName = "Wilson";  // Function Scope

    }


    console.log(myName);


    結果:ReferenceError: myName is not defined

    4. Block scope (用 let 或是 const 去宣告的變數)

    - The scope created with a pair of curly braces (a block). 

    - 常見於 loop 和 if statement

    if(true) {

        let x = 10;

    }


    console.log(x);


    結果:ReferenceError: x is not defined

Closure

- 在 function execution context 中,如果發現不在 function score 內部的函數,JavaScript 將轉到其它地方查找。Closure (閉包) 就是指這種將函數與其周圍的狀態或語詞環境結合在一起的組合。

- 在 JavaScript 中,每次 function execution context 都會在 creation phase 創建 closure。

- Closure 的規則是:

    1. 從 Argument Object 以及 local variable 去尋找。

    let c = 100;

    function add(a, b) {

        

        return a + b + c;

    }


    console.log(add(3, 4));


    結果:107

    let c = 100;

    function add(a, b) {

        let c = 5;

        return a + b + c;

    }


    console.log(add(3, 4));


    結果:12

    2. 若從 1 處找不到,則從分配給函數的記憶體位置開始尋找。

    let myName = "小華";

   

    function sayHi() {

        let myName = "小明";

        console.log(myName + "說你好"); // 小明說你好

        sayHi2(); // 小華說你好

    }


    function sayHi2() {

         console.log(myName + "說你好");

    }

    sayHi();


    結果:小明說你好

                小華說你好


    let myName = "小華";

   

    function sayHi() {

        let myName = "小明";

        console.log(myName + "說你好"); // 小明說你好

        sayHi2(); // 小明說你好


        function sayHi2() {

          console.log(myName + "說你好");

        }

    }


    sayHi();


    結果:小明說你好

                小明說你好


    3. 若在目前的 execution context 找不到,就繼續往外層、往全域一層一層的去找。 

    let myName = "小華";

   

    function sayHi() {

        console.log(myName + "說你好");


        function sayHi2() {

          console.log(myName + "說你好");

        }


        function sayHi3() {

          console.log(myName + "說你好");

        }

        sayHi3(); // 小華說你好

    }


    sayHi(); // 小華說你好


    結果:小華說你好

                小華說你好

Call Stack and Recursion

Call Stack(堆疊)

- Call stack 是 JS 引擎追蹤本身在調用多個函數的程式碼中位置的機制 (資料結構的一種)。

- Call stack 可以包住我們知道 JS 引擎當前正在運行什麼函式以及該函式中調用那些函式等。

- Last-in-first-out (後進先出)

call stack

- 其機制為:

    1. 當執行函式 f1 時,JS引擎將其添加到 Call stack 中,然後開始執行該函式。

    2. 若該函式內部又調用其它函式 f2 時,則函式 f2 添加到 Call stack中,然後開始執行該函式。

    3. 當 f2 執行完畢後,JS引擎將其從 Call stack 中取出,並且從 f1 停止的位置繼續執行。

範例:

    function f1() {

        console.log("開始執行f1。。。"); 

        f2(); 

        console.log("結束執行f1。。。"); 

    }


    function f2() {

        console.log("開始執行f2。。。"); 

        console.log("結束執行f2。。。"); 

    }



    f1();


    結果:開始執行f1。。。

                開始執行f2。。。

                結束執行f2。。。

                結束執行f1。。。

    4. 如果 Call stack 堆疊過高,高出記憶體分配給 call stack 的最大空間,則會導致 stack overflow 的問題。 例如:Recursion 遞迴。

stack overflow

Recursion 遞迴

- 在數學上,遞迴關係 (recurrence relation) 是一種定義數列的方式:數列的每一項目定義為前面項的函數。例如:我們可以定義數列 S

    1. A base case S(1) = 2

    2. S(n) = 2 x S (n - 1) for n >= 2

    所以,S 會是等比數列 2, 4, 8, 16, 32, ...

範例:

    function s(n) {

        if (n ==1) { 

            return 2;

        } 

        return 2 * s(n - 1); 

    }


    console.log(s(10)); 


    結果:1024

範例:recursive

    function addUpto(n) {

    // 方法一:for loop

    // 方法二:等差數列公式解

    // 方法三:recursive

        if (n ==1) { 

            return 1;

        } 

        return n + addUpto(n - 1); 

    }


    console.log(addUpto(10)); 

    console.log(addUpto(100)); 

    console.log(addUpto(1000)); 


    結果:55

                5050

                500500

- 程式語言中,當一個函式內部,執行自己這個函式,這種情況就是遞迴演算法 (recursive algorithm),因此遞迴演算法 (recursive algorithm) 絕對會產生 call stack。

Constructor Function

 - 在 JavaScript 當中,若 function 被調用時使用了 new 關鍵字,則會被當成 constructor function 來使用。

- 在 JavaScript 當中,constructor function 以大寫開頭

- constructor function 中的 this 關鍵字指的是這一個新製作的物件

- 此新的物件會占用額外的記憶體 (reference data type),並自動 return

範例:

    function Person(name, age) {

        this.name = name;

        this.age = age;

        this.sayHi = function () {

            console.log(this.name + "說你好");

        };

    }

    let wilson = new Person("Wilson Ren", 26);

    let mike = new Person("Mike Huang", 26);

    let grace = new Person("Grace Xie", 26);

    grace.sayHi();


    結果:Grace Xie說你好

- 透過使用 Constructor Function,我們可以大量製造 attributes 與 methods 相似的物件。

- Constructor Function 製作出的每個物件是獨立的,所以會單獨占用記憶體位置。

constructor function

// reference data type

console.log(wilson.sayHi == mike.sayHi);

結果: false (比較記憶體的位置)

Inheritance and the Prototype Chain

 - 在 JavaScript 中,每個物件都有一個 private attribute 叫做 __proto__

 - __proto__屬性存放的值是另一個物件。若物件 A 的 __proto__屬性的值是設定成另一個物件 B,則物件 A 就會繼承物件 B 的所有 attribute 和 methods。

範例:

let wilson = {

  name: "Wison",

  sayHi() {

    console.log("你好");

  },

};

let mike = {

  __proto__: wilson,

};

console.log(mike.name); // 繼承 wilson 物件的屬性

mike.sayHi(); // 繼承 wilson 物件的 method

結果: Wilson

             你好

 - 每個 constructor function 都可以設定 prototype 屬性 (prototype 屬性本質上來說,就是一個 empty object)。

範例:

function Person(name, age) {

  this.name = name;

  this.age = age;

  this.sayHi = function () {

    console.log(this.name + "說你好");

  };

}

console.log(Person.prototype);

結果: {} (空的物件)

 - 所有從 constructor function 製作出來的物件,其__proto__屬性內定義的 attributes and methods 都是自動指向 (繼承) constructor function 的 prototype 屬性內的 attributes and methods。 => Prototype Inheritance

範例:

let wilson = new Person("Wilson Ren", 26); // wilson.__proto__ => Person.prototype

let mike = new Person("Mike Huang", 26);  // mike.__proto__ => Person.prototype

 - constructor function A 製作的物件 obj,如果檢查 obj.__proto__ == A.prototype,結果為 true。因為 obj.__proto__ 以及 A.prototype 都是' reference data type,所以 true 代表兩者指向同個記憶體位置。

範例:

console.log(wilson.__proto__ == Person.prototype); // true

console.log(mike.__proto__ == Person.prototype); // true

 - 若從 constructor function 製作出的每個物件都有相似的 methods,我們可以將 methods 全部移動到 constructor function 的 prototype 屬性內部,來節省記憶體(RAM)空間。 (Object- Oriented Programming)

範例:

function Person(name, age) {

  this.name = name;

  this.age = age;

  this.sayHi = function () {

    console.log(this.name + "說你好");

  };

}

Person.prototype.hello = function () {

  console.log(this.name + "說哈囉!");

};

let wilson = new Person("Wilson Ren", 26); 

let mike = new Person("Mike Huang", 26);

Person.prototype.type = "人類";

wilson.hello(); // Wilson Ren說哈囉!

mike.hello(); // Mike Huang說哈囉!

console.log(wilson.hello == mike.hello); // true => 指向同個記憶體位置

console.log(wilson.type); // 人類

console.log(mike.type); // 人類

 - JS 內建的資料類型都有繼承其他的 prototype。 例如: [1, 2, 3] 這個 array 繼承了 Array Prototype,而 Array Prototype 又繼承自 Object Prototype。這種 Prototype 不斷往上連結的結果就叫做 Prototype Chain

 - JavaScript 中的所有物件最後的 Prototype Chain 都會連到一個叫做 "Object Prototype" 的地方。Object Prototype 是 Prototype Chain 的終點

範例:array

let arr = [1, 2, 3];

arr.push(4); // 自動繼承了 array.prototype.push()

console.log(arr); // [ 1, 2, 3, 4 ]

範例:string

let myString = "this is my string..."; // primitive coercion

newString = myString.toUpperCase(); // 自動繼承了 string.prototype.toUpperCase()

console.log(newString); // THIS IS MY STRING...

Function Methods

 - function 是一種特別的物件,所有 function 都有繼承 Object prototype

 - Function.prototype 內有以下三個常用的 methods:

function.bind(obj) 

 - 將 function 的 this 關鍵字綁定給 obj

範例:

let Grace = {

  name: "Grace",

  age: 26,

};

function getAge() {

  return this.age;

}

let newFunction = getAge.bind(Grace);

console.log(newFunction()); // 26

functtion.call(obj, arg1, /* ..., */ argN)

 - 使用給定的 obj 當作 this 值來調用函示。 arg1, /* ..., */ argN 為 optional

functtion.apply(obj, argsArray) 

 - 和 call 相同,但 arguments 是使用 arguments array。

範例:

let Mike = {

  name: "Mike",

  age: 27,

};

function getInfo(country, height) {

  console.log(this.name + "來自" + country + ", 身高為" + height + "cm");

  return this.age;

}

getInfo.call(Mike, "台灣", 170); // Mike來自台灣, 身高為170cm

getInfo.apply(Mike, ["台灣", 170]); // Mike來自台灣, 身高為170cm

Prototype Inheritance in Constructors

 - constructor function A 可以透過兩個設定來繼承另一個 constructor function B 的 prototype 物件屬性:

    1. 在 constructor function A 的內部執行 B.call(this, arg1, ..., argsN),我們可以透過這段程式碼將 B 所設定的物件屬性套給 A 做使用。

範例:

function Person(name, age) {

  this.name = name;

  this.age = age;

}

Person.prototype.sayHi = function () {

  console.log(this.name + "說你好");

};

function Student(name, age, major, grade) {

  Person.call(this, name, age);

  this.major = major;

  this.grade = grade;

}

let mike = new Student("Mike Hung", 26, "Chemistry", 3.5);

console.log(mike.name); // Mike Hung

console.log(mike.major); // Chemistry

    2. 設定 A.prototype = Object.create(B.prototype)。

 - Object.create() 可以創建一個全新的物件,這樣一來,所有 B.prototype 內部的所有 attributes 和 methods 都可以套用給 A.prototype。

 - 所有 A.prototype 新增或設定的額外 attributes 和 methods 都需寫在 "A.prototype = Object.create(B.prototype)" 這行程式碼的下方。

範例:

function Person(name, age) {

  this.name = name;

  this.age = age;

}

Person.prototype.sayHi = function () {

  console.log(this.name + "說你好");

};

function Student(name, age, major, grade) {

  Person.call(this, name, age);

  this.major = major;

  this.grade = grade;

}

Student.prototype = Object.create(Person.prototype); // 創建一個 Person() 全新的物件,並繼承了 Person 物件裡面的 sayHi()

Student.prototype.study = function () {

  console.log(this.name + "正在努力讀" + this.major);

}; // 新增一個 function。所有 A.prototype 新增或設定的額外 attributes 和 methods 都需寫在 "A.prototype = Object.create(B.prototype)" 這行程式碼的下方。

let steve = new Student("Steve Wong", 23, "Food Science", 3);

steve.sayHi(); // Steve Wong說你好

steve.study(); // Steve Wong正在努力讀Food Science

Class

 - Class 語法可以用來取代 constructor function。

 - Class 語法是 JavaScript 基於現有的 prototype inheritance 的語法糖。(Class 語法與 constructor function 語法可以完全互換)

範例:

// constructior function

function Student(name, age, major) {

  this.name = name;

  this.age = age;

  this.major = major;

}

Student.prototype.sayHi = function () {

  console.log(this.name + "說你好");

};

// Class 語法

class Student {

  constructor(name, age, major) {

    this.name = name;

    this.age = age;

    this.major = major;

  }

  sayHi() {

    console.log(this.name + "說你好");

  }

}

 - 若一個 constructor function 要繼承另一個 constructor function 的 prototype 物件,則可以使用 extends 關鍵字。

範例:

// constructior function

function Person(name, age) {

  this.name = name;

  this.age = age;

}

Person.prototype.sayHi = function () {

  console.log(this.name + "說你好");

};

function Student(name, age, major, grade) {

  Person.call(this, name, age);

  this.major = major;

  this.grade = grade;

}

Student.prototype = Object.create(Person.prototype); // 創建一個 Person() 全新的物件,並繼承了 Person 物件裡面的 sayHi()

Student.prototype.study = function () {

  console.log(this.name + "正在努力讀" + this.major);

}; // 新增一個 function。所有 A.prototype 新增或設定的額外 attributes 和 methods 都需寫在 "A.prototype = Object.create(B.prototype)" 這行程式碼的下方。

let steve = new Student("Steve Wong", 23, "Food Science", 3);

steve.sayHi(); // Steve Wong說你好

steve.study(); // Steve Wong正在努力讀Food Science

// Class 語法

class Person {

  constructor(name, age) {

    this.name = name;

    this.age = age;

  }

  sayHi() {

    console.log(this.name + "說你好");

  }

}

// super

class Student extends Person {

  constructor(name, age, major, grade) {

    super(name, age); // 代表 extends Person 的 constructor function

    this.major = major;

    this.grade = grade;

  }

  study() {

    console.log(this.name + "正在努力讀" + this.major);

  }

}

let mike = new Student("Mike Huang", 26, "Chemistry", 3.5);

mike.sayHi(); // Mike Huang說你好

mike.study(); // Mike Huang正在努力讀Chemistry

 - Static 關鍵字在 Class (constructor function)上定義 attrubutes and 可以透過 Class 本身來訪問 static variable (attrubutes) 或是執行 static method。

 - Static 關鍵字所設定出來的 attrubutes and methods 屬於 Class 本身,不屬於由 Class 製作出的物件。

 - 本質上來說,Static 關鍵字就是將 attrubutes and methods 設定在 constructor function 這個物件上而不是在 constructor.prototype 這個 constructor function 的屬性上

 - instance property, instance method => 由 Class 製作出的物件可以使用。

 - JavaScript 當中的內建 Class (or constructor function) 有許多 static properties, static methods, instance properties and instance methods。

    例如:Array.isArray() 就是 Array Class 的 static method,可以用來確認某個資料是否為 Array(用 typeof 確認 Array 時,結果只能看到 Object)

    例如:Math Class 內部所有的 properties and methods 都是 Static,所以可以使用 Math.E、Math.PI、Math.floor() 等功能

範例:

// constructior function

function Student(name, age, major) {

  this.name = name;

  this.age = age;

  this.major = major;

}

Student.exampleProperty = 10;

Student.exampleFunction = function () {

  console.log("這是一個沒有特別功能的function");

}; // 此 method 直接設定在 function 裡面

Student.exampleFunction(); // 這是一個沒有特別功能的function

Student.prototype.sayHi = function () {

  console.log(this.name + "說你好");  // 此 method 設定在 function 的 prototype 裡面

};

let mike = new Student("Mike Huang", 26, "Chemistry", 3.5);

mike.exampleFunction(); // 不可以使用此 method

mike.sayHi(); // Mike Huang說你好


// Class 語法

class Student {

  static exampleProperty = 10; // static property

  constructor(name, age, major) {

    this.name = name; // instance property

    this.age = age; // instance property

    this.major = major; // instance property

  }

  // instance method (由 Class 製作出的物件)

  sayHi() {

    console.log(this.name + "說你好");

  }

  // static method

  static exampleFunction() {

    console.log("這是一個沒有特別功能的function");

  }

}

let mike = new Student("Mike", 26, "chemistry");

mike.exampleFunction(); // 無法使用

Student.exampleFunction(); // 這是一個沒有特別功能的function

範例:用 Class 來做一個 Circle 物件 (計算圓的面積)

class Circle {

  static allCircles = [];

  constructor(radius) {

    this.radius = radius;

    Circle.allCircles.push(this);

  }

  getArea() {

    return Math.PI * this.radius * this.radius;

  }

  getPerimeter() {

    return 2 * Math.PI * this.radius;

  }

  // static

  static getAreaFormula() {

    return "圓面積公式為pi * r * r";

  }

  // static method

  static getAllCirclesAreaTotal() {

    let total = 0;

    Circle.allCircles.forEach((circle) => {

      total += circle.getArea();

    });

    return total;

  }

}

let c1 = new Circle(5);

let c2 = new Circle(10);

let c3 = new Circle(15);

console.log(Circle.getAllCirclesAreaTotal()); // 1099.5574287564277

Ternary Operator, default parameters, backtick

Ternary Operator

 - Ternary Operator 是 JavaScript 唯一用到三個運算元的運算子。

 - 在一個條件之後會跟著一個問號 (?),如果條件是 truthy,則在冒號 (:) 前的表達式會被執行,如果條件是 falsy,則在冒號 (:) 後的表達式會被執行。

 - 常被用來當作 if 表達式的簡潔寫法。

 - 語法為:

        condition ? expressionIfTRue : expressionIfFalse

範例 1:if statement

let age = 20;

let price;

if (age < 18) {

    price = 50;

} else {

    price = 150;

}

console.log(price); // 150

範例 1:Ternary Operator

let age = 20;

let price = age < 18 ? 50 : 150;

console.log(price); // 150


範例 2:if statement

let age = 20;

let price;

if (age < 18) {

    price = 50;

} else if (age <60) {

    price = 150;

} else {

    price = 75;

}

console.log(price); // 150

範例 2:Ternary Operator

let age = 20;

let price = age < 18 ? 50 : age < 60 ? 150 : 75;

console.log(price); // 150

Default Parameters

 - 當調用了 function 但沒有給定足夠數量的 arguments 時,parameter 會被設定成為 undefined。

 - 在 function 設定 Default Parameters 可以讓 functions 有預設的初始化值。

範例 :

function multiply(a = 1, b = 1) {

    return a * b;

}

console.log(multiply(10)); // 只給一個 parameter 時,會帶入 b 的預設值 1

console.log(multiply()); // 都不給 parameters 時,會帶入 a 和 b 的預設值 1


結果為:10 * 1 = 10

                1 * 1 = 1

backtick (``)

let age = 26;

let address = "台灣";

// 用 "" 表示

let myName = "Wilson 的年齡是" + age + ".且來自於" + address;

// 用 ‵` 表示

let myName = `Wilson 的年齡是${age}且來自於${address}。`;

console.log(myName); // Wilson 的年齡是26且來自於台灣。

Destructuring Assignment

 - 可以將 array 中的值或 object 中的屬性 unpack 到不同的變量中。常見的語法:

const [a, b] = array;

範例:

let arr = [1, 2, 3, 4, 5, 6, 7];

let [a1, a2, a3, a4, a5, a6, a7] = arr; // destructuring assignment

console.log("a1 is" + a1); 

結果為: a1 is 1 

const [a, b, ...rest] = array;

範例:

let arr = [100, 200, 300, 400, 500];

let [a1, a2, ...everything] = arr; // destructuring assignment

console.log(a1, a2, everything); 

結果為: 100 200 [ 300, 400, 500 ] 

const {a, b} = obj;  (重要!!!)

範例:

let Wilson = {

    name: "Wilson Ren",

    age: 26,

    address: "Hawaii",

    height: 179,

    weight: 75,

}

let { address } = Wilson; // destructuring assignment

console.log(address); 

結果為: Hawaii 

let { address, height, weight } = Wilson; // destructuring assignment

console.log(address, height, weight); 

結果為: Hawaii 179 75

Switch Statement

 - Switch Statement 是 if statement 的另一種選項,兩者的功能性完全相同。

 - Switch Statement 會先獲得一個 expression,再將 expression 拿去跟一系列的 case 做比較。

 - 在 Switch Statement 中,如果省略 break,則程式將繼續執行下一個 case,甚至執行到 default 子句,而不管該 case 的值是否匹配,此情形稱為 fall-through。

範例:if statement

let day = prompt("請輸入今天星期幾?");

if (day == "星期一") {

  alert("Today is Monday!!");

} else if (day == "星期二") {

  alert("Today is Tuesday!!");

} else if (day == "星期三") {

  alert("Today is Wednesday!!");

} else if (day == "星期四") {

  alert("Today is Thursday!!");

} else if (day == "星期五") {

  alert("Today is Friday!!");

} else if (day == "星期六") {

  alert("Today is Saturday!!");

} else if (day == "星期日") {

  alert("Today is Sunday!!");

} else {

  console.log("Cannot determine your day.");

}

範例:Switch statement

let day = prompt("請輸入今天星期幾?");

switch (day) {

  case "星期一":

    alert("Today is Monday!!");

    break;

  case "星期二":

    alert("Today is Tuesday!!");

    break;

  case "星期三":

    alert("Today is Wednesday!!");

    break;

  case "星期四":

    alert("Today is Thursday!!");

    break;

  case "星期五":

    alert("Today is Friday!!");

    break;

  case "星期六":

    alert("Today is Saturday!!");

    break;

  case "星期日":

    alert("Today is Sunday!!");

    break;

  default:

    alert("Cannot determine your day.");

}

錯誤處理

-  JavaScript 發生的錯誤會被自動做成一個 Error Object。

- JavaScript 中,如果要執行一段可能會出錯的程式碼,可以將該程式碼放入 try...catch... 語句當中。(常在後端中使用)

 - 語法為:

    try {

        tryStatements

    } catch (exceptionVar) {

        catchStatements

    } finally {

        finallyStatements

    }

tryStatements - 要執行的語句。

catchStatements - 如果在 try 中引發異常,則執行的語句。

exceptionVar (optional) - 一個變數,用於保存 catch 當中已捕獲的錯誤。

finallyStatements - 在完成 try...catch...語句時,一定會執行的語句。無論是否有發生異常,finallyStatements 都會執行。

 - 可使用的語法:

    try...catch...

    try...finally...

    try...catch...finally...

範例 1:

try {

    whatever(); // 未定義完成的 function

} catch {

    console.log("有錯誤");

}

結果為:有錯誤

範例 2:用變數 e 保存 catch 已捕獲的錯誤

try {

    whatever; // 未定義完成的 function

} catch(e) {

    console.log(e);

}

結果為:

ReferenceError: whatever is not defined...

instanceof operator (binary operator)

- 查看某個物件是否為某個 class 的 instance。

- object 和 instance 都是物件的意思。

範例 3:

class Person {

    constructor(name) {

        this.name = name;

    }

}

let mike = new Person("Mike Hung");

console.log(mike instanceof Person);

結果為:true (mike 物件是 class Person 的 instance)

Error 種類查詢:Error - JavaScript - MDN

範例 4:區分 Error 種類

try {

  whatever();

} catch (e) {

  if (e instanceof TypeError) {

    console.log("發生TypeError");

  } else if (e instanceof ReferenceError) {

    console.log("發生ReferenceError");

  } else {

    console.log("發生其它種類的Error");

  }

}

結果為:

發生ReferenceError

範例 5:try...catch...finally...

try {

  whatever();

} catch (e) {

  if (e instanceof TypeError) {

    console.log("發生TypeError");

  } else if (e instanceof ReferenceError) {

    console.log("發生ReferenceError");

  } else {

    console.log("發生其它種類的Error");

  }

} finally {

    console.log("不管有無錯誤,都會執行的程式碼");

}


結果為:

發生ReferenceError

不管有無錯誤,都會執行的程式碼

客製化錯誤訊息

- throw (丟), catch

範例 1:

// function 提供者在 function 內部寫入一個如果發生錯誤的錯誤訊息

function sumArray(arr) {

  // Array Class static method

  if (!Array.isArray(arr)) {

    throw new TypeError("參數並非array!!");

  }

  let result = 0;

  arr.forEach((element) => {

    result += element;

  });

  return result;

}


// 使用者利用 try... catch... 查詢、接收錯誤信息

try {

  console.log(sumArray([1, 2, 3, 4, 5]));

  sumArray("hello");

} catch (e) {

  console.log(e);

}


結果為:

15 // 正常執行,沒報錯

TypeError: 參數並非array!!...

範例 2:客製化錯誤訊息

// function 提供者在 function 內部寫入一個如果發生錯誤的客製化錯誤訊息

class NotArrayError extends TypeError {

    constructor(message) {

        super(message);  //  extends TypeError 的 constructor function

    }

    printSolution() {

    return "請確定參數為 Array,再執行程式碼";

    }

}


function sumArray(arr) {

  // Array Class static method

  if (!Array.isArray(arr)) {

    throw new NotArrayError("參數並非array!!");

  }

  let result = 0;

  arr.forEach((element) => {

    result += element;

  });

  return result;

}


// 使用者利用 try... catch... 查詢、接收錯誤信息

try {

  console.log(sumArray([1, 2, 3, 4, 5])); // 1

  sumArray("hello"); 

} catch (e) {

  console.log(e); // 2

  console.log(e.printSolution); // 3

}


結果為:

1. 15 // 正常執行,沒報錯

2. NotArrayError: 參數並非array!!...

3. 請確定參數為 Array,再執行程式碼

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >