[TOC] ## Chapter 1 > Until recently, **JavaScript didn’t have block-level variables** (as in other C-like languages); instead, we had to rely only on global variables and function-level variables. 這個是不是沒有更新到ES6? > 可能是撰寫當下還沒到ES6 > [name=Chris] ### 1.3.2 Testing ```javascript assert(condition, message); // condition 判斷式 // message 字串 // 如果condition是false,就會吐出message assert(a === 1, "Disaster! a is not 1!"); ``` `assert`是作者自己寫的,現在可以直接使用`console.assert()` ### 1.3.3 Performance analysis `console.time("")`及`console.timeEnd("")`搭配使用,可以計算他們兩個中間的運算經過了多少時間 ```javascript console.time("My operation"); for(var n = 0; n < maxCount; n++){ // ... } console.timeEnd("My operation"); // My operation: 0.034912109375 ms ``` ### 讀書會補充 - FP與OOP - FP: 處理複雜的動詞關係 - OOP: 處理複雜的名詞關係 - Design pattern, 觀察者模式 - 寫程式不用硬套FP或OOP > [name=Chris] - proxy - 先擋下外部的要求,做第一層處理後,再傳給真正的人 - 像是鋼鐵人的鋼鐵衣 > [name=Chris] ## Chapter 2 ### 2.2 The page-building phase 1. 解析HTML,建立DOM (Document Object Model) - DOM是由HTML建立而來,兩者不一樣 - DOM把HTML每一個tag視為一個node,然後把每個tag串起來 - 如果把`<p>`放到`<head>`裡面,瀏覽器會自動修復,把`<p>`放到`<body>`裡面 2. 執行JavaScript程式碼 - 遇到`<script>`,瀏覽器就會暫停建立DOM,先執行程式碼 - 全域物件 = `window` - `window`的其中一個屬性 = `document` = 當前頁面的DOM > The primary global object that the browser exposes to the JavaScript engine is the `window` object. > One of the most important properties of the global `window` object is the `document`, which represents the DOM of the current page. - JavaScript沒辦法操作還沒建立的element,所以才會習慣把`<script>`放在最後面 ### 2.3 Event handling - JavaScript程式碼執行時除了影響DOM,也會註冊event handler - event handler = function - 決定event是否發生以及把這個event丟到queue裡 和 event handling 不在同一個執行緒上 > The browser mechanism that puts events onto the queue is **external** to the page-building and event-handling phases. - 兩種方式可以註冊event handler 1. `document.body.onclick = function(){};` - 對於這個event就只能註冊這個function - 不常用了 > 不能做兩件事(兩個function) > [name=Chris] > 例如發API+loading+跳轉頁面 > [name=Mango] 2. `document.body.addEventListener("click", function(){});` - 可以對於這個event註冊各式各樣不同的function - 比較常用這個 > addEventListener - OOP的觀察者模式 > [name=Chris] ### 讀書會補充 - 什麼事情是single-thread, 什麼時候是非同步? > (events) are ==processed== only one at a time, as their turn comes. > Monitors event queue, processing any events one at a time. > > The ==handling== of events, and therefore the ==invocation== of their handling functions, is asynchronous. - 不用等事件處理完還是能觸發事件(event handling)、把function放到callstack等待執行(invocation) - 但事件執行(processing)是單執行緒 - 不是呼叫function就是事件處理 - 例如在頁面建立時的全域函數 - 事件處理要先有註冊事件 - JS17題只要沒有等待使用者輸入,就沒有事件處理 - 圖形化界面,就會有這兩種時期 > [name=Chris] ## Chapter 3 我們能對object做的,就也能對function做 function不同的點在於他可以被呼叫(invokable)並執行一個動作 函式作為一級物件是FP的特性 ~~這個作者很堅持要用日文羅馬拼音,但又拼錯~~ ### callback event handler就是一種callback function ```javascript // 這裡的function document.body.addEventListener("mousemove", function() { var second = document.getElementById("second"); addMessage(second, "Event: mousemove"); }); ``` 有些人認為呼叫callback一定要是非同步的才是callback,不過這不是作者定義的callback ### 好好利用函式的物件特性 > 可以不用深究 [name=Chris] 獨特函式儲存器 ```javascript var store = { nextId: 1, cache: {}, add: function(fn) { if (!fn.id) { // 如果function自己沒有一個屬性叫id // 則為function建立一個屬性叫做id,並且值設定為nextId // 設定完後nextId加1 fn.id = this.nextId++; // 把這個function儲存到cache裡面,並以fn.id作為屬性名稱 // 但這裡fn.id應該是數字不是字串,看起來會自動轉型? this.cache[fn.id] = fn; return true; } } }; function ninja(){} assert(store.add(ninja), "Function was safely added."); assert(!store.add(ninja), "But it was only added once."); ``` 屬性名稱確實會自動轉型 ![image](https://hackmd.io/_uploads/Byb9-bRdC.png) > [補充](https://stackoverflow.com/a/9571485) Memoization:會記得自己計算過的值的函式 判斷質數 判斷過的質數就存在這個函式身上,下次就不用再算了 ```javascript function isPrime(value) { // 初始化,讓這個函式建立存放資料的屬性 if (!isPrime.answers) { isPrime.answers = {}; } // 如果已經計算過,不是undefined,就回傳之前計算的結果 if (isPrime.answers[value] !== undefined) { return isPrime.answers[value]; } // 先判斷(value !== 1),如果value是1,(1 !== 1)就是false var prime = value !== 1; // 1不是質數 for (var i = 2; i < value; i++) { if (value % i === 0) { prime = false; break; } } return isPrime.answers[value] = prime; // 或寫成 // 以value本身作為屬性名稱,儲存質數判斷結果 isPrime.answers[value] = prime; return isPrime.answers[value]; } assert(isPrime(5), "5 is prime!" ); assert(isPrime.answers[5], "The answer was cached!" ); ``` 還是有缺點:需要更多記憶體、難做負載量測試或測量函式表現 負載量測試是啥? > load-test 負載測試 系統所能執行最大工作量運作測試的流程 > > 還是看不懂哈哈 ### 定義函式的四種方式 1. 宣告式與表達式 > :question:A function declaration must be placed on its own, as a separate JavaScript statement > 目前理解是分成獨立區塊 > function functionName > () > {} 宣告式一定要有名字,因為函式必須要能被呼叫,沒有名字就不能呼叫 ~~不能一直說欸欸欸的吧~~ 函式表達式:永遠會作為其他陳述式的一部分 #### IIFE IIFE也可以這樣寫 ```javascript !function () { console.log("from !") }(); ~function () { console.log("from ~") }(); -function () { console.log("from -") }(); +function () { console.log("from +") }(); ``` 基本上都是直接執行這個函式,不過瀏覽器會吐一個當下運算的值給你看,這個值會不一樣而已 ![image](https://hackmd.io/_uploads/HkVjfRovC.png) 為什麼IIFE一定要用一對小括號包起來?因為不包起來就很像宣告式!!! ```javascript // 包 (function () {}) (3); // 不包 function () {} (3); ``` 2. 箭頭函式(或稱lambda) 3.3.2 的範例應該是升冪 `=>`這個被稱為胖箭頭運算子 可愛 3. 函式建構式(function constructors) 從一段字串去建構一個函式 `new Function('a', 'b', 'return a + b')` 4. generator functions `function* myGen(){ yield 1; }` 目前看不懂,也還沒打算懂 哈哈 ### 預設參數 預設參數可以使用這個參數之前的參數 ```javascript // 例如message這個參數的預設值就使用到了ninja和action // 這兩個在他之前就定義好的參數 function performAction(ninja, action = "skulking", message = ninja + " " + action) { return message; } ``` 但不推薦這樣做!!因為這樣不好閱讀 但適當使用預設參數是好的,可以避免空值,或是做初始化設定 ### 練習題 第五題的katana應該是weapon才對 ```javascript function getNinjaWieldingWeapon(ninja, weapon = "katana"){ return ninja + " " + katana; // katana應該是weapon } ``` ### 讀書會補充 - sort的幾個要注意的case: 文字、數字、文字的數字 > "".localeCompare [MDN](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare) - 只有js可以這樣做的時候,寫js的時候就不要這樣做 ```javascript (function namedFunctionExpression () { })(); // 不用名稱也可以 (function () { })(); ``` - IIFE 用來做模組隔離,初始化的時候就執行,執行完這些變數就消失 ![image](https://hackmd.io/_uploads/rJa_Xq_YR.png) 這張圖可以理解成下面 ```javascript const myFnName = function(){}; // 把左右邊拆開,執行 // 左邊 myFnName() // 右邊 (function(){})() ``` - 為什麼lambda要叫做箭頭函式?只有js叫他箭頭函式 可以去稍微理解lambda代表的意義 - Rest parameters 的...可以理解為把這個array展開後等於傳入的引數 - flag - 舉旗那個人,要舉還不舉,所以是布林值,true or false - argument中直接傳true, false進去,把T/F當作資料在傳來傳去 - 用來控制訊號,控制function的行為,不是資料,當作設定config - 目前不會這樣設計了,好的程式碼不會用旗標,會用柯理化或是直接寫成兩個function - 忍者1的四種呼叫函式的方式 1. 直接呼叫 - bind(), ()=>{} - apply() - call() 3. 物件.method 5. 立即執行 6. 遞迴 - 看書的目的是你知道他在哪、查得到就好了,不用一次就要全部吃進去 ## Chapter 4 ### Listing 4.2的練習 把arguments改寫成rest parameters ```javascript function sum(...params) { return params.reduce((sum, currentParam)=> sum + currentParam, 0); } ``` 改變arguments也會改到有名字的parameter 除非使用嚴格模式 `"use strict";` ### 4.2 Invoking functions #### As a function `this`等於global(非嚴格模式) `this`等於undefined(嚴格模式) <- 比較符合直覺,因為沒有指定函式呼叫的物件 #### As a method (of an object) 這個object就成為function context,`this`指向這個object 會說ninja1有一個method叫做getMyThis,不會說whatsMyContext是ninja1的method ```javascript var ninja1 = { getMyThis: whatsMyContext }; ``` #### As a constructor constructor functions > 可以不要使用functions這個詞,直接叫constructor建構器就好 [name=Chris] 使用`new`關鍵字 1. 創造一個新的空物件 2. 這個物件作為this傳給constructor 3. 這個物件(也就是this)作為new的回傳值 :heavy_check_mark:作者說最後兩點點出了下面這個function作為constructor function很奇怪 因為照理說要定義好this,然後return這個物件(不用特地寫),但他只有return this ```javascript function whatsMyContext(){ return this; } new whatsMyContext(); ``` 就算constructor function return一個非物件的值,以new來建構還是會回傳object ```javascript function Ninja() { this.skulk = function () { return true; }; return 1; // return一個非物件的值 } ``` 如果回傳了一個其他的物件,則new就不會新建造一個物件了 順序: 1. 先創造一個新object 2. this設定為這個新object 3. 最後回傳另一個object (puppet) 指派給變數emperor --> this就不見了 ```javascript var puppet = { rules: false }; function Emperor() { this.rules = true; return puppet; } ``` #### Via the function’s apply or call methods 瀏覽器的event-handling system把invocation context設定為這個事件的target element,也就是`<button>`,而不是button物件 ```javascript <button id="test">Click Me!</button> // ... function Button(){ this.clicked = false; this.click = function() { this.clicked = true; assert(button.clicked,"The button has been clicked"); } } var button = new Button(); var elem = document.getElementById("test"); elem.addEventListener("click", button.click); ``` .apply() 傳入array,要輸入的已經是array就用apply .call() 傳入arguments,要輸入的是相互無關連的東西就用call ### 4.3.1 Using arrow functions to get around function contexts 箭頭函式沒有this,this是在這個箭頭函式被定義的時候決定的 用箭頭函式改寫 當`var button = new Button();`(12) this.click(6)的`this`就會被綁定成button 進而造成箭頭函式內this.clicked(7)的`this`也是button ```javascript= <button id="test">Click Me!</button> // ... function Button(){ this.clicked = false; this.click = () => { // 這裡改寫為箭頭函式 this.clicked = true; assert(button.clicked,"The button has been clicked"); } } var button = new Button(); var elem = document.getElementById("test"); elem.addEventListener("click", button.click); ``` 箭頭函式的`this`跟window 因為button是在全域宣告,所以箭頭函式內的`this`就變成window了 ```javascript var button = { clicked: false, click: () => { this.clicked = true; assert(button.clicked,"The button has been clicked"); assert(this == window, "In arrow function this == window"); assert(window.clicked, "clicked is stored in window"); } } ``` ### 4.3.2 Using the bind method 用bind method來呼叫function會創造一個新的function,且讓function context保持為當初傳入的物件 但除此之外的行為都跟之前的function一樣,畢竟他們有一樣的code ### 讀書會補充 this - 物件.method: this的正常情境 - ()=>{}: this的特例 如果方法呼叫時使用call,就會依照call傳入的物件來設定this ```javascript var ninja1 = { getMyThis: whatsMyContext }; ninja1.getMyThis.call(null) ``` ## Chapter 5 ### 5.1 Understanding closures 閉包可以讓function取得/操作外部的變數,而這些變數和定義這個function的同時處在同一個scope內。 > A closure allows a function to access and manipulate variables that are external to that function. Closures allow a function to access all the variables, as well as other func- tions, that are in scope when the function itself is defined. 舉例:在全域宣告一個變數`outerValue`和一個function `outerFunction()`,這個function可以取得這個變數。全域其實也是一個閉包,只是他是個不會消失的閉包。 當function被定義的那一刻,閉包就產生了。 ### 5.2.1 Mimicking private variables 當不需要讓使用者知道這些資訊時 JS本身並沒有支援私有變數 ### Listing 5.3 ```javascript function Ninja() { var feints = 0; this.getFeints = function(){ return feints; }; this.feint = function(){ feints++; }; } var ninja1 = new Ninja(); ninja1.feint(); // ninja1.feints === undefined 無法直接取feints這個變數 // ninja1.getFeints() === 1 // 透過.getFeints()這個accessor來取得變數,這種accessor通常會叫做getter var ninja2 = new Ninja(); // ninja2.getFeints() === 0 和ninja1毫無相關 ``` 看完5.6.1再回來看 :::spoiler 在創造.getFeints()和.feint()時會紀錄這兩個的[[Environment]]是Ninja environment 所以才可以找到Ninja environment裡的feints變數 ```javascript var imposter = {}; imposter.getFeints = ninja1.getFeints; // imposter.getFeints() === 1 成功取得,所以其實不是私有變數 ``` ::: ### Listing 5.4 讓callback function可以取得變數 並且這些變數不會污染全域 為什麼不要污染?因為做3個動畫就要創9個變數,做更多動畫就要更多變數 ```javascript function animateIt(elementId) { var elem = document.getElementById(elementId); var tick = 0; var timer = setInterval(function(){ // 這個callback可以取得elem, tick, timer這三個變數 if (tick < 100) { elem.style.left = elem.style.top = tick + "px"; tick++; } else { clearInterval(timer); } }, 10); } ``` ![image](https://hackmd.io/_uploads/Hyt05KI41l.png) ### 5.3 Tracking code execution with execution contexts 分成global execution context和function execution context,前者只有一個,後者只要有function就會產生 ==注意:== function context (也就是this) 跟 function execution context是不一樣的東西 JS是單執行緒,並且用call stack (也就是execution context stack)來追蹤正在執行的execution context和正在等待的execution context stack的定義: 一種資料結構 放東西只能從最上方,拿東西也只能從最上方 像是自助餐的托盤 ### 5.4 Keeping track of identifiers with lexical environments lexical environment 即 scopes (口語上) 內部的程式碼結構可以造訪到外部的程式碼結構 所以每一個lexical environment都會紀錄到其外部的lexical environment (outer/parent lexical environment),防止如果內部找不到某個identifier,就要去外部的lexical environment找 當function被創造時,這個function所處的lexical environment (也就是 outer lexical environment)會被紀錄在internal property [[Environment]]裡面 ```javascript function skulk() { var action = "Skulking"; function report() { var intro = "Aha!"; assert(intro === "Aha!", "Local"); assert(action === "Skulking", "Outer"); assert(ninja === "Muneyoshi", "Global"); } report(); } ``` report()的[[Environment]]為skulk environment skulk()的[[Environment]]為global environment ### 5.5.1 Variable mutability const變數指派給object/array後不能指派給新的object/array,但可以修改原本的object/array ```javascript const secondConst = {}; secondConst.weapon = "wakizashi"; ``` ### 5.5.3 Registering identifiers within lexical environments JS執行程式碼的兩個時期 1. 創建lexical environment,將所有在這個lexical environment的變數和function註冊起來 1. 這個lexical environment是function? 是的話,建立arguments物件和參數 2. 這個lexical environment是function或global environment? 是的話,註冊所有在其他function外的函式宣告式 3. 這個lexical environment是block environment? 是的話,註冊這裡面的let和const變數;不是的話,註冊function外的var變數,以及block外的let和const變數;這些變數的值都為`undefined`,如果前面註冊過了,就留下原本的內容 2. 執行程式碼 所以,可以提前使用宣告式宣告的function,但不能使用指派給變數的function ```javascript // 可提前使用 function fun(){} // 不可提前使用 var myFunExpr = function(){}; var myArrow = (x) => x; ``` ### Listing 5.10 ```javascript assert(typeof fun === "function", "We access the function"); var fun = 3; assert(typeof fun === "number", "Now we access the number"); function fun(){} assert(typeof fun == "number", "Still a number"); ``` 實際處理順序是 ```javascript // 準備階段 function fun(){} var fun // 失敗,因為fun這個identifier已經註冊過了 // 執行階段 assert(typeof fun === "function", "We access the function"); var fun = 3; assert(typeof fun === "number", "Now we access the number"); function fun(){} // 這段會跳過,不會執行,所以不會對fun造成影響 assert(typeof fun === "number", "Still a number"); ``` ### 讀書會補充 1. 是否能用一句話解釋閉包? - 解決當一個function消失後,別的function要怎麼拿到這個消失的function內的變數 - 但是要事先做一些處理才能產生閉包的特性 - 閉包是一個工具,存在就是要為了區隔scope 2. JS引擎要怎麼確定不再需要某個變數? 3. identifier是什麼意思? - 當它不是字串、數字、運算符等他已知的東西,他就是identifier,然後JS會去找這個東西是什麼 4. 用閉包做一個計數器 - 注意function撰寫的當下能夠看到的變數是誰 5. 阿傑的寫法:把function declaration寫在最後面,使用function的hoisting,因為會包成function就是為了不想看到裡面的東西 ## Chapter 7 ### 7.1 Understanding prototypes `Object.setPrototypeOf(繼承者, 被繼承者)` 物件的原型也是internal屬性[[prototype]],不能被取得 yoshi有一個property叫[[prototype]],這個屬性指向hattori ![image](https://hackmd.io/_uploads/HkweOp0N1l.png) ### Listing 7.2 ```javascript function Ninja(){} // 每一個function都有一個prototype屬性可以使用 // 設定Ninja的prototype有swingSword屬性 Ninja.prototype.swingSword = function(){ return true; }; const ninja2 = new Ninja(); // ninja2有swingSword這個method,且可以執行這個method console.log(Boolean(ninja2.swingSword)); // true console.log(ninja2.swingSword()); // true ``` ![image](https://hackmd.io/_uploads/ryxtTaAVkl.png) 1. `function Ninja`有一個屬性prototype指向Ninja的prototype(就命名為Ninja prototype) 2. Ninja prototype有一個屬性constructor指回`function Ninja` 3. 利用`Ninja.prototype.swingSword`對Ninja prototype設定method 4. 實體ninja2有一個無法取得的屬性[[prototype]]指向Ninja prototype > 什麼時候會用到prototype? 想知道傳來的物件(例如querySelector傳回來的東東)是什麼型別(class)的時候。 > 例如用 `ninja2.constructor.name` 拿到Ninja,而知道ninja2的型別是Ninja > [name=Chris] ### 7.2.1 Instance properties 如果實體的method和prototype的method同名,會優先使用實體的method(甚至根本不會去訪問prototype) 缺點:創建實體的時候就會創建一份method,但明明是同一個功能,卻要多花記憶體去儲存 所以method最好都設定在prototype上就好 ![image](https://hackmd.io/_uploads/S1IPb00Nkl.png) > - 重點不是在記憶體,而是不要重複coding,並把資料管理得有層次感 > - js是個不能控制記憶體的語言,所以告訴我這件事省不省記憶體沒有意義 > [name=Chris] 但如果這個method是用來取得在實體上的(偽)私有變數,就只能把method掛在constructor上 ```javascript function Ninja2(){ const swung = false; // 不用this的私有變數 } Ninja2.prototype.swingSword = function(){ // 設定在prototype上,但內容是想要取得實體的變數 return swung; }; const ninja2 = new Ninja2(); ninja2.swingSword() // ReferenceError: swung is not defined ``` > 如何定義什麼是真的私有變數? 這個變數在this身上,但外部存取不到,但可以透過內部的getter/setter存取得到。 > [name=Chris] ### Listing 7.4 ```javascript function Ninja(){ this.swung = true; } const ninja1 = new Ninja(); Ninja.prototype.swingSword = function(){ return this.swung; }; assert(ninja1.swingSword(), "Method exists, even out of order."); Ninja.prototype = { pierce: function() { return true; } } // 沒有改到ninja1的[[prototype]]指向,所以還是可以呼叫 assert(ninja1.swingSword(), "Our ninja can still swing!"); ``` ![image](https://hackmd.io/_uploads/B1NzQ4eHke.png) 如果在這時候再new一個ninja2出來,他的prototype就會指向有pierce的那個object了 ### 7.2.3 constructor ### Listing 7.5 找到一個物件當初建構它的constructor,可以用`instanceof`或是屬性`.constructor` ```javascript ninja instanceof Ninja // true ninja.constructor === Ninja // 往 prototype 找 constructor 這個屬性嗎? // true ``` 可以用實體的.constructor去取得constructor然後new一個新的實體 ```javascript const ninja2 = new ninja.constructor(); ninja2 instanceof Ninja // true ninja === ninja2 // false ``` the constructor property of an object can be changed 測試 ```javascript function Ninja2(){} const ninja2 = new Ninja2(); ninja2 instanceof Ninja2 // true ninja2.constructor === Ninja2 // true // 把constructor換掉,換成Samurai function Samurai(){} ninja2.constructor = function Samurai() {} // 這段寫錯了,這樣是指向一個新的function不是舊的Samurai ninja2 instanceof Ninja2 // 用instanceof還是可以知道原本是從哪裡建構來的 // true ninja2.constructor === Ninja2 // 但.constructor就不行 // false // 用ninja2的constructor建構一個實體samurai1 // 結果samurai1既不是從Ninja2建構而來 // 也不是從Samurai const samurai1 = new ninja2.constructor() samurai1 instanceof Ninja2 // false samurai1.constructor === Ninja2 // false samurai1 instanceof Samurai // false samurai1.constructor === Samurai // false // 超怪XD // 只知道他是從ninja2的constructor建構 samurai1 instanceof ninja2.constructor // true ``` ### 讀書會補充 1. `Object.setPrototypeOf(yoshi, hattori);` 等同於 `yoshi.__proto__ = hattori;` 只是js不希望你去操作到`.__proto__` 2. querySelector 和 getElementById 拿到的東西的型別不一樣 前者是ES6後出現的,拿到物件後無法改動 (immutable),後者可以 3. 先用`.constructor.name`得到型別名字,然後用`instanceof`,再看看兩個是不是都是true,是的話才表示沒有被改過。 ### Listing 7.8 要設定某建構函式的型別 (class),就對建構函式的prototype處理 `SubClass.prototype = new SuperClass();` ![image](https://hackmd.io/_uploads/BkTYMIBryl.png) ninja實體的[[prototype]]會指向Person() new出來的實體,並且ninja實體失去和Ninja prototype的連結 :question:為什麼instanceof Ninja還可以是true? 如果Ninja prototype上面有一個swingSword的function,ninja實體會有這個功能嗎? 不建議使用`Ninja.prototype = Person.prototype.` Any changes to the Ninja prototype will then also change the Person prototype (because they’re the same object). ### 7.3.1 The problem of overriding the constructor property ```javascript console.log(ninja.constructor === Ninja) // false ``` ![image](https://hackmd.io/_uploads/Sk5BvAStR.png) 對Ninja.prototype (也就是Person() new出來的實體) 設定一個constructor屬性 (不是原本在Ninja prototype的constructor屬性) 指回Ninja建構函式 ```javascript Object.defineProperty(Ninja.prototype, "constructor", { enumerable: false, // 且這個屬性不會被迭代到 // 為啥不能被迭代到? value: Ninja, writable: true }); var ninja = new Ninja(); console.log(ninja.constructor) // Ninja console.log(Ninja.prototype.constructor) // Ninja ``` :question:new Person()這個實體的constructor不就找不到Person prototype了嗎? 感覺這個實體是特殊用途,就跟Person()之後new的實體不一樣了 ### 7.3.2 The instanceof operator 查看這個實體的prototype chain上是否有這個function的prototype `ninja instanceof Ninja` 查看ninja的prototype chain上是否有Ninja的prototype 所以`ninja.__proto__`等於`new Person()` `Ninja.prototype`也等於`new Person()` #### instanceof的缺點 假設`Ninja.prototype = {}`,`ninja.__proto__`或`ninja.__proto__.__proto__....`就找不到這個空物件惹,導致`ninja instanceof Ninja`會是false ### 7.4 Using JavaScript “classes” in ES6 #### static method static: 只有class本身可以用,實體不行 沒有前綴的一般method: 只有實體可以用,class不行 ```javascript class Ninja{ constructor(name, level){ this.name = name; this.level = level; } swingSword() { return true; } static compare(ninja1, ninja2){ return ninja1.level - ninja2.level; } } var ninja1 = new Ninja("Yoshi", 4); console.log(ninja1.compare); // undefined console.log(ninja1.swingSword()); // true console.log(Ninja.compare) // function console.log(Ninja.swingSword) // undefined ``` ### 7.4.2 Implementing inheritance class的繼承 ```javascript "use strict" // 可能是強制在ES5的時候不能使用class, extends, super當變數? class Person { constructor(name){ this.name = name; } dance(){ return true; } } class Ninja extends Person { construct(name, weapon){ // Uses the super keyword to call // the base class constructor // 也就是把constructor接回去的部份 super(name); this.weapon = weapon; } wieldWeapon(){ return true; } } ``` ### 讀書會補充 - 什麼是物件導向? - 繼承 (Inheritance)、封裝 (Encapsulation, private實現了封裝)、動態連結 (Polymorphism) - JS有#private,盡量完備物件導向 - 找型別的SOP 1. typeof 2. constructor.name 3. instanceof null在第一步會回傳object,但在第二步會報錯(只有null在第二步會報錯) ![image](https://hackmd.io/_uploads/B1CDRd5Bye.png) - 什麼時候用`==`? 要找 undefined 或 null 才用 `== null` 如果給一個變數等於`undefined`,需要寫註解 - 為什麼要寫class? - 為了要得到別人的==function (行為)==,但自己不想寫,所以用繼承的 - 不要為了其他事繼承 - window.name 是 getter ## Chapter 8 ### Listing 8.2 get不用接收arguments所以()裡面不用寫東西 ```javascript const ninjaCollection = { ninjas: ["Yoshi", "Kuma", "Hattori"], get firstNinja() { // firstNinja等於屬性名稱 report("Getting firstNinja"); return this.ninjas[0]; }, set firstNinja(value){ // 跟get的屬性名稱一樣 report("Setting firstNinja"); this.ninjas[0] = value; } }; // 使用 console.log(ninjaCollection.firstNinja); // Yoshi ninjaCollection.firstNinja = "Hachi"; ``` 取用他就像是取用一般的屬性一樣,而不是用method需要加上() 但其實背後還是有function在執行 ### Listing 8.3 用class的寫法 :heavy_check_mark:發現有沒有加this都可以?? 不行捏 ![image](https://hackmd.io/_uploads/ByzQRySL1g.png) ```javascript class NinjaCollection { constructor(){ this.ninjas = ["Yoshi", "Kuma", "Hattori"]; } get firstNinja(){ report("Getting firstNinja"); return this.ninjas[0]; } set firstNinja(value){ report("Setting firstNinja"); this.ninjas[0] = value; } } ``` 如果只寫了getter但賦值給他 - nonstrict模式:不會報錯,只是沒有作用 - strict模式:會報錯,說你沒有寫setter > - 可以在nonstrict中,setter裡面丟一個錯誤出來,防止改動到這個值 > - 用程式語言說明「我不要被改」 > - 記得寫註解 > [name=Chris] getter和setter的使用時機:取用私有變數 但class沒有私有變數! > Because with object literals and classes our getter and setter methods aren’t created within the same function scope as variables that we could use for private object properties, we can’t do this. ```javascript function Ninja() { let _skillLevel = 0; Object.defineProperty(this, 'skillLevel', { get: () => { report("The get method is called"); return _skillLevel; }, set: value => { report("The set method is called"); _skillLevel = value; } }); } const ninja = new Ninja(); typeof ninja._skillLevel // undefined console.log(ninja.skillLevel === 0) // true ninja.skillLevel = 10; console.log(ninja.skillLevel === 10) // true ``` > 前綴命名的目的:彌補語言缺陷 > 告訴你這個是私有變數,或是不給你用,例如Vue的$$,用了他改版不負責 > [name=Chris] ### 讀書會補充 - 資料設計會影響到getter/setter的複雜度 - 把某個東西封裝起來的概念 - Object分成容器、物件的概念 - get、set是屬於物件的範疇 - Array用[]取值是屬於容器的範疇 - 抽象:不需要知道背後的運作,只要理解看得到的就好了 - 例如不需要知道電線怎麼跑,只要知道哪個開關可以控制 - 設計師要主客觀分離,理解使用者使用時的樣子 ### Listing 8.7 `get: (target, key)` `target`就是要保護的那個物件 `key`是屬性 例如emperor.name emperor就是`target` name就是`key` ```javascript const representative = new Proxy(emperor, { get: (target, key) => { report("Reading " + key + " through a proxy"); return key in target ? target[key] // 檢查被保護的物件內是不是有這個屬性 : "Don’t bother the emperor!" }, set: (target, key, value) => { report("Writing " + key + " through a proxy"); target[key] = value; } }); ``` Proxy可以對所有屬性做統一的處理,之前的getter/setter的寫法只能針對某一個屬性 :heavy_check_mark:客製化的處理? > JS的proxy會給人統一處理的感覺,但其實可以把Proxy理解成是自己寫的class。 > 在其他語言,Proxy是寫好這個物件專用的Proxy (handler),再把物件包起來,但JS是new Proxy的同時才定義getter/setter的內容。 [C++範例](https://ithelp.ithome.com.tw/articles/10330469) > 一般是做很多個handler,去靈活定義Proxy,包含可以取用method、屬性或拿到原始的物件 > 詳細可以看[Chris的筆記](https://dwatow.github.io/2022/11-10-pattern/proxy-in-js/) > [name=Chris] :heavy_check_mark:只能針對某一個物件? 多個物件可以共用proxy嗎? 做成function回傳new Proxy物件就可以了 Proxy的運作方式和getter/setter一樣 > 其實背後還是有function在執行 可以被proxy攔截的運算 例如:`in`運算子 > [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/has) ### 8.2.1 Using proxies for logging 理解成,要解決到處都是console.log()的debugging code的問題 原本是這樣寫 ```javascript const loggableNinja = new Proxy(ninja, { get: (target, property) => { console.log("Reading " + property); return target[property]; }, set: (target, property, value) => { console.log("Writing value " + value + " to " + property); target[property] = value; }, }); ``` ![image](https://hackmd.io/_uploads/HyKXTz5L1l.png) 可以把Proxy包在function的回傳值,就不用再宣告一個使用Proxy的變數 ```javascript function makeLoggable(target){ return new Proxy(target, { get: (target, property) => { // 設定一個方法,來取用原本的物件 by Chris if (property === "target") { return target } report("Reading " + property); return target[property]; }, set: (target, property, value) => { report("Writing value " + value + " to " + property); target[property] = value; } }); } let ninja = { name: "Yoshi"}; ninja = makeLoggable(ninja); // 用有Proxy的ninja取代原本的ninja ``` :heavy_check_mark:這樣是不是永遠無法存取原本的ninja? 即使我知道他還存在......如果是的話,這樣沒關係嗎? > 可以設定一個方法,來取用原本的物件 [name=Chris] ### 8.2.2 Using proxies for measuring performance 把function包進Proxy的作法 > [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy/apply) ```javascript function isPrime(number){ if(number < 2) { return false; } for(let i = 2; i < number; i++) { if(number % i == 0) { return false; } } return true; } isPrime = new Proxy(isPrime, { apply: (target, thisArg, args) => { console.time("isPrime"); // 為什麼要用apply // 因為isPrime剛好沒有使用到this,所以看不出來需要用.apply綁定this的必要性 by Chris const result = target.apply(thisArg, args); // 為什麼不是寫這樣? 試過這樣的輸出也是正確的 const result = target(args); console.timeEnd("isPrime"); return result; } }); isPrime(1299827); // 用平常呼叫function的方法呼叫就好 ``` Chris改寫,做一個快取 ```javascript function isPrime(number){ if(number < 2) { return false; } for(let i = 2; i < number; i++) { if(number % i == 0) { return false; } } return true; } isPrime = new Proxy(isPrime, { // 快取存放的地方 cache: { }, apply(target, thisArg, args) { console.log(this, thisArg); // this是這個Proxy,thisArg是當下的this,在全域執行thisArg就是window console.time("isPrime"); // 如果存過快取,就直接回傳快取的值 if (Object.keys(this.cache).includes(String(args[0]))){ console.timeEnd("isPrime"); return this.cache[args[0]] } const result = target.apply(thisArg, args); this.cache[args[0]] = result; console.timeEnd("isPrime"); return result; } }); // 第一次執行 isPrime(1299827); // 第二次執行 isPrime(1299827); ``` ![image](https://hackmd.io/_uploads/HJg-X9x_Je.png) 上面可以看到兩次執行時間差很多 ### 8.2.3 Using proxies to autopopulate properties ```javascript function Folder() { return new Proxy({}, { // 為什麼是空物件? 因為就單純沒有東西嗎XD? get: (target, property) => { report("Reading " + property); if(!(property in target)) { // 為什麼要new? target[property] = new Folder(); // 為什麼不直接呼叫 target[property] = Folder(); } return target[property]; } }); } const rootFolder = new Folder(); // 為什麼是用建構器 // 為了語意上好像回傳了一個物件 by Chris const rootFolder = Folder(); // 測試感覺沒有問題? ``` ### Chris補充 - 三大概念 1. new String就一定會回傳物件 (用new語意上就是回傳了一個物件) 2. String() 轉態用 3. String不當函式呼叫,就是namespace,裝很多static function - Method就是單純的namespace,裝了很多static function ## Chapter 9 ### 9.1.1 Creating arrays > arrays also exhibit a peculiar feature related to the length property: Nothing stops us from manually changing its value. 測試是不是真的沒辦法禁止更改length 改之前,length的writable是true ![image](https://hackmd.io/_uploads/BkImOa1P1e.png) 改之後,length的writable是false,看起來可以改? ![image](https://hackmd.io/_uploads/rJiq5Tywye.png) :warning:configurable是false的情況下,writable可以從true改成false!? > 不知道為什麼 但可以改length的原因,應該是他本身本來就必須被改 可以試試看writable:false的時候push東西進去看看 [name=Chris] > [鐵人文章](https://ithelp.ithome.com.tw/articles/10197826) ### 9.1.2 Adding and removing items at either end of an array ```javascript var lastNinja = ninjas.pop(); // ninjas:["Yagyu", "Kuma"] // 更改了原本的陣列 // lastNinja: "Hattori" // 回傳被丟掉的東西 ``` ### 9.1.4 findIndex只能找第一個符合的東西的index :heavy_check_mark:找到所有符合的東西的index? 看起來只能自己寫 不要期望標準函式庫能幫你作到所有事情,所以盡量用組合技 ### Listing 9.13 Array-like的物件 > [MDN](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/push#%E4%BB%A5%E9%A1%9E%E9%99%A3%E5%88%97%EF%BC%88array-like%EF%BC%89%E7%9A%84%E6%96%B9%E5%BC%8F%E6%93%8D%E4%BD%9C%E7%89%A9%E4%BB%B6) ```html <input id="first"/> <input id="second"/> ``` ```javascript const elems = { length: 0, add: function(elem){ Array.prototype.push.call(this, elem); }, gather: function(id){ this.add(document.getElementById(id)); }, find: function(callback){ return Array.prototype.find.call(this, callback); } }; ``` #### 測試 - 不加length也沒關係,因為這個method會自動幫忙加 ![image](https://hackmd.io/_uploads/BkpFC6xw1g.png) > 這包括了 length 可能不存在的狀況,在這個情況下 length 屬性也將被建立於物件中。 [name=MDN] - 改用apply也可以 ![image](https://hackmd.io/_uploads/SykJ-ReDyx.png) ### 讀書會補充 - Array method要注意會不會改變原本的陣列,如果是回傳新陣列,就可以達到immutable,也是functional programming的概念 - 資料結構,動態宣告記憶體位置,只記得開頭的地方,其他都是靠連結 - 關聯式容器:到每個元素的距離是一樣的,走訪所有內容難 - key就是找到元素的路徑 - value就是那個元素 - 循序式容器:走訪內容簡單 ### Listing 9.15 key只能放字串的問題 ```javascript const firstElement = document.getElementById("firstElement"); const secondElement = document.getElementById("secondElement"); const map = {}; map[firstElement] = { data: "firstElement"}; map[firstElement].data === "firstElement" // true map[secondElement] = { data: "secondElement"}; map[secondElement].data === "secondElement" // true // 因為會自動轉型成 map["[object HTMLDivElement]"] 所以兩次賦值其實是對一樣的key賦值 map[firstElement].data === "firstElement" // false ``` ![image](https://hackmd.io/_uploads/Hkz6Ugpvke.png) key甚至可以是一個物件! :heavy_check_mark:通常什麼時候會需要使用object來當作key? > key 的意思是「視為相同的特徵」如果物件本身視為不同,不管任何欄位 (id 或 name) 是否相同…就會用物件當 key 值。 > 也就是說,假設希望同一個元素,因為取得的時間不同,而想把他們視為是不同的東西,那就會用物件當 key 值 (因為他們其他的內容都一樣,沒辦法作為識別用)。 > [name=Chris] :heavy_check_mark:是不是沒辦法透過這個Map來取得object的資訊? > key 通常是已知,所以不會透過 map 才知道的結果。 > 如果物件本身沒有 id (識別值) 就必須用物件當 key,不過這麼用的例子很少。 > [name=Chris] :heavy_check_mark:其他語言為什麼同一個物件會被辨識為同一個key? > 其他語言例如Python,是以hash為特徵 (key) (hash: 把某一段資料透過演算法做出數位指紋,來自於資料是否相同),所以只要內容一樣,hash就會相同。 > [name=Chris] ### Listing 9.17 覺得因為key是兩個不同的實體,所以本來就會不一樣 以下測試 ```javascript const map = new Map(); const currentLocation = location.href; const firstLink = new URL(currentLocation); const secondLink = firstLink; map.set(firstLink, { description: "firstLink"}); map.set(secondLink, { description: "secondLink"}); map.get(firstLink).description === "firstLink"; // false ``` ![image](https://hackmd.io/_uploads/HkTyjl6Dyg.png) 所以不是key能夠放一樣的東西,只是內容很像而已 ### 9.2.3 ```javascript for(let item of directory){ item[0] // key item[1] // value } ``` ### 9.3.1 set用的是for of ```javascript for(let ninja of ninjas) { // ... } ``` ## appendix B ### Breakpoints ![image](https://hackmd.io/_uploads/B1ZesuNdkg.png) 左一:全部執行完 (如果有中斷點就會停在中斷點) 左二:step over (不會跳進function內) 左三:step into (和左二差在哪?會跳進function) 左四:跳出這個function (不會繼續在function內一步一步執行) 左五:連for迴圈都會跑進去 ![image](https://hackmd.io/_uploads/HyXb6_V_1e.png) breakpoint上面按右鍵選擇edit breakpoint > 不太用breakpoint,比較常用console [name=鵬化] ### Creating tests > Additionally, reproducibility ensures that our tests aren’t dependent on external factors, such as network or CPU loads. 如果不可再現,要怎麼知道是network或CPU的關係? ### QUnit and Jasmine QUnit:測試jQuery,最近還有在維護耶!! ```html <!DOCTYPE html> <html> <head> <link rel="stylesheet" href="qunit-2.24.1.css" /> <script src="qunit-2.24.1.js"></script> </head> <body> <div id="qunit"></div> <script> function sayHiToNinja(ninja) { return "Hi " + ninja; } QUnit.test("Ninja hello test", function (assert) { assert.ok(sayHiToNinja("Hatori") == "Hi Hatori", "Passed"); assert.ok(false, "Failed"); }); </script> </body> </html> ``` ![image](https://hackmd.io/_uploads/B1vTTjEOJg.png) Jasmine:感覺比較像Jest ### Code coverage 有幾成比例的code是沒有測試到的 實務上有在算這個嗎? [Blanket.js](https://github.com/alex-seville/blanket) [Istanbul](https://github.com/gotwarlost/istanbul) ### 讀書會補充 by 鵬化 - 網頁開發 - `$0` 點選某個element後,就可以取得這個element - 串API - Network - fetch/XHR: API - WS: websocket - Disable cache: 取消快取 圈起來的表示是快取 ![image](https://hackmd.io/_uploads/HkKAKAZY1l.png) - [responsively.app](https://responsively.app/) 可以一次秀全部尺寸的工具,加快開發速度 - [Claude](https://claude.ai/) AI 適合程式碼 - [GSAP](https://gsap.com/) JS animation library - 炫泡的網站:[版塊設計官網](https://blockstudio.tw/)