[TOC] # 三明治筆記 有問題可以直接在筆記上留言^ D ^ ## Chapter02 ### 變數 變數命名規範:只能用$、_及英文字母開頭,大小寫有區分 不能用保留字 #### 字串模板 ```javascript! var name = "Martha"; var introduction = `Hello ${name}! Nice to meet u.`; // 注意這裡用的是撇`,不是單引或雙引號 console.log(introduction); // Hello Martha! Nice to meet u. ``` #### 轉型 ```javascript! var age = 28; var age_str = String(age); console.log(typeof age_str); // typeof不用加括號 // string var age_num = Number(age_str); console.log(typeof age_num); // number // 快速轉型小技巧 28+"" // 轉成字串 +"28" // 轉成數值 !!28 // 轉成布林值 // 但是如果+前面有數字則全部轉為字串後相加 console.log(5 + "28"); // 528 ``` #### 數字 數字只有一種型別,沒有區分整數和浮點數 ```javascript! parseInt(); // 轉成整數 parseFloat(); // 轉成有小數點的數字 ``` NaN也屬於數字 null屬於null,但檢查型別會出現object,是一個JS無法修復的bug ```javascript! var sth_null = null; console.log(typeof sth_null); // object ``` #### 布林值與反向運算子 ```javascript! console.log(Boolean(age)); // true console.log(Boolean(sth_null)); // false console.log(!Boolean(age)); // false console.log(!Boolean(sth_null)); // true ``` #### 物件object ```javascript! var sth_object = { age: 28, // age稱為鍵,又稱為屬性property,28為值 toy: "reindeer", // 分隔鍵值使用逗號, }; // 兩種取值方法 console.log(sth_object["age"]); // 屬性名稱皆為字串 console.log(sth_object.age); // 取不存在的屬性不會報錯,只會出現undefined console.log(sth_object.whatever); // undefined // 轉字串 console.log(String(sth_object)); // [object Object] ``` #### 陣列array,像是沒有屬性的物件 ```javascript! var sth_array = ["baby", 18]; console.log(sth_array[0]); // baby console.log(sth_array.length); // 2 // 此時sth_object和sth_array所存放的其實是記憶體位置 []===[]; // false 記憶體位置不同 {}==={}; // false 記憶體位置不同 var a = []; var b = a; a === b; // true a和b指到相同的記憶體位置 ``` ### 函式 用function宣告,裡面由return結束,return後面的程式碼會被忽略 ### 運算子 #### 比較 兩側都是字串,則比較unicode編碼值 undefined會被轉為NaN,而NaN和任何數值比較都會變成false #### 相依性與優先序 相依性:從什麼方向開始計算 優先序:誰先開始計算 ### 強制轉型coercion 只會轉成三種類型 1. 字串 2. 數字 3. 布林 ```javascript! // 注意object的布林值是true Boolean([]); // true Boolean({}); // true Boolean(function() {}); // true ``` 物件轉字串、數字,由javascript本身執行一套名叫ToPrimitive的演算流程 ### 判斷式 ```javascript! // switch var a = 3; switch (a) { case 1: console.log("a is 1"); break; // 記得加break; 如果和函式一起使用也可以寫return case 2: console.log("a is 2"); break; default: // 像是else的功能 console.log("Idk what a is"); } // Idk what a is switch (a) { case 1: case 2: console.log("a is 1 or 2"); // 符合case1或2都會執行此行 break; default: console.log("Idk what a is"); } ``` ### 三元運算子 條件判斷 ? 為true時執行 : 為false時執行; 物件內賦值時好用,其他時候就不要用 ```javascript! var whattype = "28" ? true : false; console.log(whattype); // true // 物件內賦值 { name: condition ? "Jeremy":"Watson", sexual: condition ? "male":"female" } ``` ## Chapter03 ### 編譯、直譯 編譯語言:寫完程式碼後就預先編譯 直譯語言:執行程式碼時才透過直譯器編譯,無法獨立執行需要有可編譯且執行結果的環境,通常由稱為引擎的工具提供,例如Chrome的引擎稱為V8,Firefox的引擎則叫SpiderMonkey ![image](https://hackmd.io/_uploads/SyoklnK7C.png) ### 執行環境 execution context **引擎** 提供 **執行環境** 執行環境:任何程式碼可以被執行、讀取的地方,例如函式 * 全域執行環境 - 創造this變數,this指向全域物件window ![image](https://hackmd.io/_uploads/rJYge2YQA.png) - 記憶體指派 * 函式執行環境 - 函式被呼叫時才會產生 - 在函式內產生arguments物件 - 可以同時存在好幾個 * eval函式內的執行環境 - 已不被使用,eval is evil #### 執行環境堆疊 函式內有其他函式,先進後出 ### 作用域 #### 語彙環境 lexical environment > 「這段程式碼寫在哪?」 #### 範疇 scope > 「變數可以被使用的範圍」 函式範疇 ```javascript function whereiam() { var varinfunction = 5; } console.log(varinfunction); // 在函式範疇內就算是var也取不到 // ReferenceError: varinfunction is not defined ``` 區塊範疇 區塊:{}裡面的範圍 ```javascript if (1) { var varinfunction = "var is here!"; let letinfunction = "let is here!"; } console.log(varinfunction); // here! console.log(letinfunction); // ReferenceError: letinfunction is not defined ``` #### 作用域鍊 scope chain 找不到變數時向外層尋找變數的依據和順序 要知道外層是哪裡 和執行環境產生的先後順序不一定相同 #### 提升現象 hoisting var變數只要在同一環境下有宣告,不需要在使用前先宣告才能使用,因為會先指派undefined給這個var變數,但let, const不會,所以不能使用 可以理解成:javascript幫你做了C語言中的宣告動作 ```javascript! console.log(a); var a = 29; // undefined // 而不是29 ``` 和[全域執行環境](#執行環境-execution-context)一開始做的事情(創造階段)有關:在執行程式碼前先指派了記憶體位置,保留到全域記憶體global memory/heap ```javascript! // 函式內容在創造階段除了指派記憶體外也會存入內容,所以可以印出hello hello(); function hello() { console.log("hello"); } // hello // 暫時無法使用的區域,稱為暫時性死區temporal dead zone (TDZ) function hello() { console.log(hello); // 這裡是TDZ const hello = "hello!!"; } hello(); console.log("secondary call"); hello(); // ReferenceError: Cannot access 'hello' before initialization ``` ### var, let, const var: 全域,可以重複宣告 let: 區域,不能重複宣告! const: 區域,不能修改,只能被讀取,且宣告時一定要指派內容 ```javascript // 無法在全域讀取區域常數const function hello() { // console.log(hello); if (true) { const hello = "hello!!"; } console.log(hello); } hello(); console.log("secondary call"); hello(); // [Function: hello] // secondary call // [Function: hello] ``` ```javascript // 如果const的內容是物件,那所謂不能修改是指記憶體位置不能被修改 const sth_object = { age: 28, toy: "reindeer", }; sth_object.height = 160; // 因此會看起來內容是可以被修改的 console.log(sth_object.height); // 160 ``` ## Chapter04 判斷簡單型別使用 typeof 判斷複雜型別使用 [instanceof](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/instanceof) 或是取出 [constructor.name](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor) ### 物件型別 陣列、函式也是物件(函式物件) 可以用`Array.isArray`來判斷是不是陣列 ### 原始型別 又稱純值(primitive type)或基本型別 ES6才出現的symbol,透過`Symbol()`產生 需要保證東西不一樣的時候才會使用,實作上幾乎不會使用到 ```javascript! let sth_symbol = Symbol("hey!"); console.log(sth_symbol); // Symbol(hey!) console.log(typeof first_symbol); // symbol ``` 兩個symbol是不同的值 ```javascript! let a = "hey!"; let b = "hey!"; console.log(a === b); // true let first_symbol = Symbol("hey!"); let second_symbol = Symbol("hey!"); console.log(first_symbol === second_symbol); // false ``` ### 變數指派 #### 原始型別 存放的是內容,複製的也是內容 變數a指派給變數b後修改變數b的內容不影響變數a的內容,稱為傳值呼叫call by value #### 物件型別 存放的是記憶體位置,複製的也是記憶體位置 傳參考呼叫call by reference 為什麼叫參考而不是指標?參考是指永遠不會改變的關係(這個變數永遠指向這個記憶體位置,且這個記憶體位置永遠存放這個內容);指標可以更改指向的記憶體位置 ```javascript const mother; // 報錯,因為參考一定要綁定內容 const mother = "Linda"; let myName; // myName是一種指標,可以更改 ``` #### Call by sharing 有時候看起來像傳值、有時候看起來像傳參考,所以額外取名為call by sharing 問題:要怎麼查詢記憶體位置? javascript無法做到 ```javascript! // 傳入函式的是物件時,會變更到函式外的物件內容 let partylist = ["Jerry", "Andy", "Gary"]; function addName(list) { list.push("Yoda"); } addName(partylist); console.log(partylist); // [ 'Jerry', 'Andy', 'Gary', 'Yoda' ] // 傳入的是純值時不會變更 let partynum = 4; function addNum(a) { a += 1; return a; } console.log(addNum(partynum)); // 5 console.log(partynum); // 4 // 沒有變更到原本的值 // 函式內重新指派變數,卻又不會更改到函式外的物件 let partylist = ["Jerry", "Andy", "Gary"]; function addName(list) { list = ["Yoda"]; } addName(partylist); console.log(addName(partylist)); // undefined console.log(partylist); // [ 'Jerry', 'Andy', 'Gary' ] // 沒有被函式修改 ``` ## Chapter05 ### 陳述式 不會產生值的地方,例如宣告、if/else、迴圈 ### 表達式 執行完會產生值,例如函式呼叫、變數指派、運算式 #### 函式陳述式、函式表達式 ```javascript // 函式陳述式 // 因為提升的關係,可以在宣告前呼叫 a(); function a() { ... } // 函式表達式,直接把函式指派給一個變數 // 被提升的只有變數,不包含函式內容 a(); // 報錯 let a = function() { ... } ``` 函式是一種特殊的物件 ```javascript function a() { ... } // 等同於 { name: a, code: "..."; // 示意,非實際程式碼 } console.log(a.name); // a ``` ### 各種函式 #### 立即執行函式 IIFE, immediately invoked function expression 被創造後馬上執行的函式 有點像是強制把函式陳述式改成函式表達式 ```javascript function() { console.log("Hi"); }; // 報錯,JavaScript認為這個是陳述式,而陳述式必須要有名字 // 和運算子結合改成表達式的寫法 (function sayHi() { console.log("Hi"); }) // 當成一個物件使用 // 取用name (function sayHi() { console.log("Hi"); }).name; // 呼叫這個函式 (function sayHi() { console.log("Hi"); })(); // Hi // 可以傳入參數 (function sayHi(name) { console.log(`Hi, ${name}!`); })("Jeremy"); // Hi, Jeremy! // 改成箭頭函式的寫法 ((name) => { console.log(`Hi, ${name}!`); })("Jeremy"); // Hi, Jeremy! ``` #### 一級函式 一個語言有一級函式的特性,代表函式被視為變數,可以作為參數傳入另一個函式,也可以作為另一個函式的回傳值 ```javascript // 用函式作為參數及回傳值 function eatFunction(fn, data) { return fn(data); }; ``` #### 高階函式 HOF, high order function 可以接收函式作為參數,或是回傳函式作為輸出 透過傳入函式達到2*3的效果,如果只是2+3的效果,傳入的函式就只是一個單純的callback function ```javascript // 利用自行實作Array.map來了解什麼叫做透過函式傳入另一個函式 function arrayMap(fn, array){ ... } ``` **forEach**代替for迴圈對陣列元素做事情 ```javascript let array = [1, 2, 3]; // for for(let i = 0; i < array.length; i++){ // ... console.log(element); } // forEach array.forEach(function (element) { // ... console.log(element); }) ``` #### 箭頭函式 ```javascript const add = (a, b) => a + b; // 這裡的a+b就是回傳值 const add = (a, b) => (a + b); // 當有多行表達式時使用 const add = (a, b) => { a + b } // 無法作用 const add = (a, b) => { return a + b } // 無法作用 const add = (a) => (b) => a + b; // 等同於 const add = function(a) { return function(b) { return a + b; } } ``` **arguments** 函式裡的arguments物件是用來接收傳入函式的引數 長得很像陣列,稱為array-liked 但是箭頭函式沒有arguments!!!也沒有this!!! ```javascript function add(a, b){ // argument[0] = a, argument[1] = b //... } ``` #### 回呼函式 callback function 在函式裡面執行的另一個函式 確保某段邏輯在另一段邏輯之後被執行 ### 閉包 closure 起因來自於函式引用到外部函式的變數 嘗試透過範疇鍊向外尋找變數,發現外部的函式有這個變數,所以JavaScript會暫時保留這個外部的函式的記憶體,這樣才能存取這個變數 這個被暫時保留的記憶體空間就是閉包 ![image](https://hackmd.io/_uploads/Sk0ljt3Q0.png) ![image](https://hackmd.io/_uploads/SkHboY3m0.png) 當內部函式被回傳後,除了自己本身的程式碼外,也可以取得內部函式「當時環境」的變數值,記住了執行當時的環境 > https://ithelp.ithome.com.tw/articles/10193009 避免修改到全域變數,但又要修改變數 ```javascript // 沒有使用閉包的樣子 var count = 0; // 宣告全域變數,重新呼叫函式時才能保留結果 function counter(){ return ++count; } console.log( counter() ); // 1 console.log( counter() ); // 2 console.log( counter() ); // 3 // 使用閉包的樣子 /// 寫法1 function counter(){ var count = 0; function innerCounter(){ return ++count; } return innerCounter; } /// 寫法2 function counter(){ var count = 0; return function(){ return ++count; } } ///寫法3 var counter = () => { var count = 0; return () => ++count; } ///執行 var countFunc = innerCounter; // ReferenceError: innerCounter is not defined var countFunc = counter(); // countFunc = innerCounter var countFunc2 = counter(); console.log( countFunc() ); // 執行innerCounter(),修改innerCounter外部的變數count // 1 console.log( countFunc() ); // 2 console.log( countFunc() ); // 3 // countFunc和countFunc2不會互相影響 console.log( countFunc2() ); // 1 console.log( countFunc2() ); // 2 ``` ### 其餘參數 需要一次處理多個參數且不確定參數數量時使用 ![image](https://hackmd.io/_uploads/S1MGjFnQR.png) ```javascript // 使用rest來取用其餘參數形成的陣列 ...rest rest.forEach ``` ## Chapter06 * **JavaScript Runtime Environment(JRE)** 包含: JavaScript引擎 Web API Event Queue Event Table Event Loop * **Event Queue** 非同步的function被儲列的位置 先進先出的運行流程(和堆疊的先進後出不同) - MacroTask 在Event Queue裡面等待被以非同步方式執行的事情 對瀏覽器來說,JavaScript引擎也是一個MacroTask - MicroTask Promise產生的then或catch裡面要執行的事情 在MicroTask Queue裡面等待 - MicroTask會在每個MacroTask結束後被執行 ```javascript console.log("Start"); setTimeout(() => { console.log("Macro"); }); Promise.resolve().then(() => { console.log("Micro1"); }); Promise.resolve().then(() => { console.log("Micro2"); }); console.log("End"); // Start // End // Micro1 // Micro2 // Macro ``` * **Event Table** 和event queue互相搭配,紀錄在非同步的目的達成後,有哪些函式或事件要執行 例如setTimeout裡給定的函式和秒數 setTimeout在event table計時,計時完成後送到event queue裡 * **Event Loop** 負責無時無刻不檢查主執行環境是否是空的、event queue是不是空的 - 優先執行MicroTask Queue的任務 ```javascript // 印出333 for (var i = 0; i < 3; i++) { setTimeout(function(){ console.log(i) }, 1000) } // 印出012 for (let i = 0; i < 3; i++) { // 不懂 setTimeout(function(){ console.log(i) }, 1000) } for (var i = 0; i < 3; i++) { (function(x) { setTimeout(function(){console.log(i)}, 1000) })(i) } ``` ### Promise ```javascript // new關鍵字用來創造物件 // 和new搭配的函式(這裡是Promise)稱為 // 建構函式(Constructor function) let promise = new Promise(function(resolve, reject){ // ... }) // resolve和reject也是函式 // 當行為成功時呼叫resolve // 當行為失敗時呼叫reject let promise = new Promise(function(resolve, reject){ // ... if (user.name === "Watson") { resolve("Hi"); }else{ reject("Who r u"); } }) ``` #### .then, .catch ```javascript // 接續上一塊程式碼 // 分別寫了兩個函式要處理promise成功與失敗時的處理 function invitePeople(people){ // 成功時邀請 console.log(people); return people + "OK"; } function rejectPeople(reason){ // 失敗時拒絕 console.log(reason); return reason + "No"; } // 用then讓這兩個函式去接promise的結果 promise.then(invitePeople, rejectPeople); // 或是用catch promise.then(invitePeople) .catch(rejectPeople); // 等同於將promise的結果傳入這兩個函式 // invitePeople("Hi") // rejectPeople("Who r u") promise.then(invitePeople) .then(invitePeople) // 把上一個invitePeople的return值傳入第二個then的函式 ``` #### Promise.resolve("就是要邀請"), Promise.reject("就是要拒絕") 用Promise.resolve或Promise.reject來直接取得成功或失敗的結果 ```javascript // "就是要邀請"直接傳入invitePeople let everybodyInParty = Promise.resolve("就是要邀請"); everybodyInParty.then(invitePeople); // "就是要拒絕"直接傳入rejectPeople let nobodyInParty = Promise.reject("就是要拒絕"); nobodyInParty.catch(rejectPeople); ``` #### Promise.all, Promise.race ```javascript let promise1 = Promise.resolve("1"); let promise2 = Promise.resolve("2"); let promise3 = Promise.reject("QQ"); // Promise.all // 陣列中所有Promise都成功才會回傳這些Promise的成功結果 let successGroup = Promise.all([promise1, promise2]) successGroup.then(invitePeople).catch(rejectPeople); // [ '1', '2' ] // 否則回傳失敗的結果 let failGroup = Promise.all([promise1, promise2, promise3]) failGroup.then(invitePeople).catch(rejectPeople); // QQ // Promise.race // 陣列中只要有Promise成功或失敗,回傳第一個成功或失敗的結果 let raceGroup = Promise.race([promise1, promise2, promise3]); raceGroup.then(invitePeople).catch(rejectPeople); // 1 ``` ### async, await #### async ==async宣告的函式一定會回傳Promise== ```javascript async function addPerson() { return "FangFang"; // return的是一個Promise } console.log(addPerson()); // Promise { 'FangFang' } addPerson().then((name) => { // 所以要用then來取用結果 console.log(name); }); // FangFang ``` #### await await只能用在async關鍵字宣告的function內 await等待的必須是另一個Promise ```javascript let vip = Promise.resolve("Jami"); async function addPerson() { let name = await vip; // await等待另一個Promise (這裡是vip) return name; } addPerson().then((name) => { console.log(name); }); // Jami ``` #### try...catch try區塊也可以透過throw直接中止程式碼並跳到catch區塊 ```javascript try { throw "Hahahaha"; } catch(error) { console.log(error); // Hahahaha } ``` 搭配async/await ```javascript let fakeguy = new Promise(function (resolve) { throw "I am fake guy!!"; // 讓fakeguy函式丟一個錯誤 resolve("I am good guy!!"); // 這段就不會被讀到了 }); async function addPerson() { try { let name = await fakeguy; // 這邊就會丟一個錯誤到catch console.log(name); } catch (error) { console.log(error); // catch接到錯誤後把error印出 } } addPerson(); // I am fake guy!! ``` ![image](https://hackmd.io/_uploads/BkeD33U40.png) ## Chapter07 利用函式建構一個物件,名稱以大寫開頭以區分一般的函式 使用this ```javascript function Bag(){ // 大寫開頭 this.owner = "CReticulata", this.item = "Surface pro", } const blackBag = new Bag(); // 不加new的話就會直接執行這個函式 console.log(blackBag); // { owner: "CReticulata", item: "Surface pro" } ``` 物件可以使用物件函式是來自於**繼承** 可以透過in來查詢繼承而來的屬性,hasOwnProperty不行 ### 解構賦值 ES6才有的 物件的解構賦值 ```javascript const userInfo = { name: "Cre", height: 157, }; // 變數名稱一定要是物件內原本有的key const { name, height } = userInfo; console.log(height); // 157 // 設定別名 const { name: nameOfUserA, height } = userInfo; console.log(nameOfUserA); // Cre ``` ### 複製物件 淺拷貝是為了避免無限複製迴圈的問題 ![image](https://hackmd.io/_uploads/BJ3OtHxHA.png) #### 展開運算子`...` ```javascript const userInfoA = { name: "Cre", height: 157, }; const userInfoB = { ...userInfoA }; console.log(userInfoB); // { name: 'Cre', height: 157 } // 一樣的屬性會被後者蓋掉 const userInfoA = { name: "Cre", height: 157, age:18, }; const userInfoB = { name: "Jeremy", height: 180, }; const userInfoC = { ...userInfoA, ...userInfoB }; console.log(userInfoC); // { name: 'Jeremy', height: 180, age: 18 } ``` ![image](https://hackmd.io/_uploads/Sy_KtrlB0.png) #### Object.assign ```javascript const userInfoA = { name: "Cre", height: 157, }; const userInfoB = { age:18, }; // Object.assign(目標物件, 來源物件) const userInfoC = Object.assign(userInfoA, userInfoB) console.log(userInfoC); // { name: 'Cre', height: 157, age: 18 } ``` #### 深拷貝 使用JSON或是Object.create ### 屬性描述器 (實作上比較不會用到) #### Object.getOwnPropertyDescriptor Object.getOwnPropertyDescriptor(物件, "屬性") ```javascript const userInfoA = { name: "Cre", height: 157, }; const description = Object.getOwnPropertyDescriptor(userInfoA, "name"); console.log(description); // { // value: 'Cre', // writable: true, // enumerable: true, // configurable: true // } ``` * value: 值 * writable: 可否被變更 * enumerable: 巡訪時能否被列出 * configurable: 這些屬性描述器能否被修改(能不能用Object.defineProperty修改) ==false後要怎麼改回來?== * get: 物件屬性上的getter函式,定義**取用**屬性時的行為 * set: 物件屬性上的setter函式,定義**指派**屬性時的行為 描述器有設定get或set的話會變成存取描述器(accessor descriptor),否則為資料描述器(data descriptor) ![image](https://hackmd.io/_uploads/HJf5FSgrA.png) ```javascript // 如果address前不加底線區隔 class Person { constructor(name) { this.name = name; this.address = 'Taipei'; } get address(){ console.log('get'); return this.address; } set address(value){ console.log('set'); this.address = value; } } let kuro = new Person('Kuro') kuro.address // 瀏覽器會跑get // get // 'Taipei' // 但node會噴錯 console.log(kuro.address); // 瀏覽器跟node都會噴錯 // 所以內容封裝用底線區隔 class Person { constructor(name) { this.name = name; this._address = "Taipei"; } get address() { console.log("get"); return this._address; } set address(value) { console.log("set"); this._address = value; } } let kuro = new Person("Kuro"); console.log(kuro.address); // get // Taipei console.log(kuro._address); // Taipei ``` #### Object.defineProperty 修改屬性描述器的內容 #### Object.getOwnPropertyNames 得到所有屬性的名字,就算enumerable為false也可以 ### This binding綁定:指向哪一個物件 #### 預設的綁定 函式被直接呼叫,this指到全域 ```javascript function whereAmI() { console.log(this.a); } var a = "global"; whereAmI(); // 在NodeJS會出錯 // TypeError: Cannot read properties of undefined (reading 'a') // 瀏覽器 // global ``` #### 隱含的綁定 this指向用來呼叫這個函式的物件 ```javascript function whereAmI() { console.log(this.message); } const playground = { message: "hey!", whereAmI: whereAmI, // 這裡要設置 }; playground.whereAmI(); // 這裡的whereAmI()是執行playground裡面的屬性 // hey! whereAmI(); // undefined ``` 隱含綁定的消失 ```javascript const playground = { message: "hey!", whereAmI: function () { console.log(this.message); }, }; const whereAmI = playground.whereAmI; // this指向全域了 whereAmI(); // TypeError: Cannot read properties of undefined (reading 'message') ``` #### 明確的綁定 .call(指定this指向的物件, 傳入該函式的參數...) .apply(指定this指向的物件, 以陣列方式傳入該函式的參數...) ```javascript function whereAmI() { console.log(this.message); } const playground = { message: "hey!", }; whereAmI.call(playground); // hey! whereAmI.apply(playground); // hey! // 硬綁定 // 使用.bind const bindLocation = whereAmI.bind(playground); bindLocation(); ``` ![image](https://hackmd.io/_uploads/ryysYBeB0.png) #### new運算子的綁定 參考第七章最前面的[說明](https://hackmd.io/PlVO4B_BRl-lsHURNJcxLg#Chapter07) #### 箭頭函式的this 箭頭函式的this會依照這個箭頭函式的程式碼實際位置而定 看不懂setTimeout的例子XD ```javascript const userInfo = { name: "Helen", changeName: function () { this.name = "Jeremy"; function changeNameAgain() { this.name = "Watson"; console.log(this); console.log(userInfo.name); } setTimeout(changeNameAgain, 1000); }, }; userInfo.changeName(); // Timeout { // ... // } // Jeremy // 使用箭頭函式 const userInfo = { name: "Helen", changeName: function () { this.name = "Jeremy"; const changeNameAgain = () => { console.log(this); this.name = "Watson"; console.log(userInfo.name); } setTimeout(changeNameAgain, 1000); }, }; userInfo.changeName(); // { name: 'Jeremy', changeName: [Function: changeName] } // Watson ``` ## Chapter08 ### 原型 原型:物件之間特殊的連結 `[[prototype]]` ```javascript const objectA = { name: "object A", }; // Object.create執行完後收到一個空物件,並以傳入的物件為原型 const objectB = Object.create(objectA); console.log(Object.getPrototypeOf(objectB)); // { name: 'object A' } console.log(objectB.__proto__); // { name: 'object A' } console.log(objectB); // {} console.log(objectB.name); // object A ``` 問題 ```javascript function userInfo(name) { this.name = name; } userInfo.prototype.setAge = function (age) { this.age = age; }; // 用Object.create創造objectB const objectB = Object.create(userInfo); console.log(Object.getPrototypeOf(objectB)); // [Function: userInfo] objectB.setAge(18); // TypeError: objectB.setAge is not a function // 用new const objectB = new userInfo("hey"); console.log(Object.getPrototypeOf(objectB)); // { setAge: [Function (anonymous)] } objectB.setAge(18); console.log(objectB.age); // 18 // 用Object.create的話要這樣做 const objectB = Object.create(userInfo.prototype); ``` ### 原型鍊 ```javascript const array = [] array.__proto__.__proto__ === Object.prototype // true ``` ![image](https://hackmd.io/_uploads/BJh0dNtBC.png) ```javascript function Human(weight, height) { this.weight = weight; this.height = height; } function User(name) { this.name = name; } Human.prototype.getHumanHeight = function () { return this.height; }; User.prototype.getName = function () { return this.name; }; console.log(User.prototype); // { getName: [Function (anonymous)] } // 利用Object.create將Human設為User的前代類別 // 意即User.prototype被修改為一個新的空物件,而這個物件的原型指向Human User.prototype = Object.create(Human.prototype); console.log(User.prototype); // Human {} ``` #### constructor 指回這個物件本身 ```javascript console.log(User); // [Function: User] console.log(User.prototype.constructor); // [Function: User] // 用Object.create會把constructor指向的東西改掉 User.prototype = Object.create(Human.prototype); console.log(User.prototype.constructor); // [Function: Human] // 所以有些人會把他設定回來 User.prototype.constructor = User; console.log(User.prototype.constructor); // [Function: User] ``` 讓前代類別的內容出現在透過後代類別的建構函式所產生的物件上 ```javascript function User(name, weight, height) { this.name = name; Human.call(this, weight, height) } ``` ### class ```javascript function Human(weight, height) { this.weight = weight; this.height = height; } Human.prototype.getHumanHeight = function () { return this.height; }; // 換成用class class Human { constructor(weight, height) { this.weight = weight; this.height = height; } getHumanHeight() { return this.height; } } ``` #### extends 用extends來繼承 ```javascript class User extends Human { constructor(name, weight) { super(weight); // super一定要出現不然會報錯如下 // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor this.name = name; } } const personA = new User("Amy"); console.log(personA); // User { weight: undefined, height: undefined, name: 'Amy' } ``` ### static 透過static定義靜態方法 靜態方法?只能在該class取用 ```javascript // 不用static,可以在後代取用 class Human { constructor(weight, height) { this.weight = weight; this.height = height; } getHumanHeight() { return this.height; } } class User extends Human { constructor(name, weight, height) { super(weight, height); this.name = name; } } const personA = new User("OK", 160, 170); console.log(personA.getHumanHeight()); // 170 // 用static,後代不能取用 class Human { constructor(weight, height) { this.weight = weight; this.height = height; } static getHumanHeight() { return this.height; } } class User extends Human { constructor(name, weight, height) { super(weight, height); this.name = name; } } const personA = new User("OK", 160, 170); console.log(personA.getHumanHeight()); // TypeError: personA.getHumanHeight is not a function // 但繼承的class可以使用 User.getHumanHeight() // undefined ``` ### 原始型別包裹物件 Wrapper Object ![image](https://hackmd.io/_uploads/S1T1FNYrA.png) 不加new也可以用,是因為JavaScript會在這一瞬間幫你把這個字串轉成對應型別的物件 取用完屬性之後,這個包裹物件就消失了 ```javascript const string = String("something"); console.log(string.length); // 9 console.log(string.indexOf("e")); // 3 ```