# 程式品質、物件基礎 連結:<https://hackmd.io/@ntust/ncc-js-3> 聯絡:[contact@sheiun.me](mailto:contact@sheiun.me) --- ## 講者 - 四資管四 徐孟辰 --- ## 本次社課 程式品質 * 在 Chrome 中除錯 * 程式風格 * 註解 - 5min * 忍者流程式 * 使用 Mocha 進行自動化測試 * Polyfills --- ## 本次社課 物件基礎 * 物件(Object) * 垃圾收集(Garbage Collection) * 符號(Symbol)型態 * 物件方法 "this" * 物件轉型到基本 * 建構子,運算子 "new" --- ### 在 Chrome 中除錯 * "Sources" 面板 * 控制台(Console) * 中斷點(Breakpoints) * 除錯器命令(Debugger command) * 暫停 & 看看周遭(Pause and look around) * 追蹤執行(Tracing the execution) * 記錄(Logging) * 小結 --- #### "Source" 面板 ![Source](https://i.imgur.com/3KUBjLK.png) --- #### 控制台(Console) ![Console](https://i.imgur.com/x8IzNt4.png) --- #### 中斷點(Breakpoints) ![Breakpoints](https://i.imgur.com/Wcfi1r6.png) > Conditional breakpoints --- #### 除錯器命令(Debugger command) ```javascript= function hello(name) { let phrase = `Hello, ${name}!`; debugger; // <-- the debugger stops here say(phrase); } ``` --- #### 暫停 & 看看周遭(Pause and look around) ![JS](https://i.imgur.com/nT0VlgO.png) --- #### 記錄(Logging) ```javascript= for (let i = 0; i < 5; i++) { console.log("value,", i); } ``` --- #### 小結 三種方法可以暫停腳本 1. 中斷點 2. `debugger` 聲明式 3. 錯誤發生(如果打開開發工具並且按鈕![button](https://i.imgur.com/3LMlDGG.png)處於“打開”狀態)。 --- ### 程式風格 * 語法 * 函式擺放 * 風格指南 * 自動化 Linter * 小結 * 牛刀小試 --- #### 語法(Syntax) ![syntax](https://i.imgur.com/NawzmIt.png) > 沒有必須,只有風格偏好。 --- #### 語法 大括號 ```javascript= if (n < 0) {alert(`Power ${n} is not supported`);} ``` 有哪些問題?怎麼改比較好? #### 函式擺放 1. 先宣告再使用 ```javascript= // function declarations function createElement() { ... } // the code which uses them let elem = createElement(); ``` 2. 先使用後宣告 ```javascript= // the code which uses the functions let elem = createElement(); // --- helper functions --- function createElement() { ... } ``` 3. 混合:函數在首次使用時宣告。 --- #### 風格指南 * [Google JavaScript Style Guide](https://google.github.io/styleguide/javascriptguide.xml) * [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) * [Idiomatic.JS](https://github.com/rwaldron/idiomatic.js) * [StandardJS](https://standardjs.com/) * 還有非常多... --- #### 自動化 Linter * [JSLint](http://www.jslint.com/) – 最初的 Linters 之一。 * [JSHint](http://www.jshint.com/) – 比起 JSLint 有更多的設定。 * [ESLint](http://eslint.org/) – 也許是最新的。 --- #### 自動化 Linter 如何用? 1. 安裝 [Node.js](https://nodejs.org/) 2. 在命令列輸入 `npm install -g eslint` 安裝 ESLint (npm 是 JavaScript 的套件管理工具)。 3. 在根目錄建立 `.eslintrc` 檔案 4. 在編輯器或 IDE 中啟用 --- 範例 ```javascript= { "extends": "eslint:recommended", "env": { "browser": true, "node": true, "es6": true }, "rules": { "no-console": 0, "indent": ["warning", 2] } } ``` --- #### 小結 > 閱讀流行的風格指南將使你能夠了解有關程式風格趨勢和最佳實踐的最新想法。 --- #### 牛刀小試 下面的程式風格有什麼問題? ```javascript= function pow(x,n) { let result=1; for(let i=0;i<n;i++) {result*=x;} return result; } let x=prompt("x?",''), n=prompt("n?",'') if (n<=0) { alert(`Power ${n} is not supported, please enter an integer number greater than zero`); } else { alert(pow(x,n)) } ``` --- ### 註解 * 壞的註解 * 好的註解 * 小結 --- #### 壞的註解 ```javascript= // This code will do this thing (...) and that thing (...) // ...and who knows what else... very; complex; code; ``` --- #### 好的註解 ```javascript= /** * Returns x raised to the n-th power. * * @param {number} x The number to raise. * @param {number} n The power, must be a natural number. * @return {number} x raised to the n-th power. */ function pow(x, n) { ... } ``` --- #### 小結 要註解的 * 總體架構,高級視圖。 * 函式用法 * 重要解法,尤其是程式不那麼顯而易見 避免註解 * 說明*程式如何運作、程式在幹嘛* * 僅在無法使程式如此簡單和可自我描述以至於不需要註解時,才將註解加入。 --- ### 忍者流程式 > 許多人嘗試遵循忍者之路。很少成功。 * 簡潔是機智的靈魂 * 一字變數 * 使用縮寫 * 飛得更高。是抽象的。 * 注意力測試 * 智能同義詞 --- #### 接上頁 * 重用名稱 * 底線很好玩 * 展現你的愛 * 重疊外部變數 * 到處都有副作用! * 函式強大! * 小結 --- #### 簡潔是機智的靈魂 ```javascript= // taken from a well-known javascript library i = i ? i < 0 ? Math.max(0, len + i) : i : 0; ``` --- #### 一字變數 > 「道隱無名。夫唯道,善貸且成。」--老子 `a`, `b`, `c` `i`, `j` `x`, `y`, `z` --- #### 使用縮寫 * `list` → `lst`. * `userAgent` → `ua`. * `browser` → `brsr`. * …等等 --- #### 飛得更高。是抽象的。 > 大方無隅, > 大器晚成, > 大音希聲, > 大象無形。 * 所有的變數理想名稱都是 `data` * 用變數型態來命名變數 `str` `num` * …但還是沒有辦法使用這樣的變數名怎麼辦?只需加一個數字:`data1`,`item2`,`elem5`… --- #### 注意力測試 在這提供眾多方法的其中一種 – 使用相似的變數名,像是 `date` 和 `data`。 --- #### 智能同義詞 > 「學而不思則罔,思而不學則殆。」--孔子 顯示在螢幕上的函式,你可以用 `display` 開頭。 顯示使用者名字的,你可以用 `show` 開頭。 --- #### 重用名稱 > 「始制有名,名亦既有,夫亦將知止,知止所以不殆。」--老子(道德經) 只在必要時宣告新的變數名。 ```javascript= function ninjaFunction(elem) { // 20 lines of code working with elem elem = clone(elem); // 20 more lines, now working with the clone of the elem! } ``` --- #### 底線很好玩 在變數前加上底線 `_` 或 `__` ,例如: `__data` 或 `__value` 一箭雙雕,不僅程式可讀性變差,而且開發者要花更久的時間了解底線的涵義,但他其實並沒有任何意思。 --- #### 展現你的愛 使用 `super..` 、 `mega..` 、 `nice..` 開頭的字展現你程式的強大。 --- #### 重疊外部變數 > 「夫處明者不見暗中一物,而處暗者能見明中區事。」--關尹子 ```javascript= let user = authenticateUser(); function render() { let user = anotherValue(); ... ...many lines... ... ... // <-- a programmer wants to work with user here and... ... } ``` --- #### 到處都有副作用! * 一個非常漂亮的技巧是除了主要任務外,還向他們增加“有用的”動作。 `isReady()` 竟然將 ready 參數變成了 false * 另一種令人驚訝的方法是返回非標準結果。 `if (checkPermission(..))` checkPermission 竟然不是回傳 true/false --- #### 函式強大! > 「大道泛兮,其左可右。」--老子(道德經) `validateEmail(email)` 不只檢查 email 還可以幫你顯示提示在前端,真的很棒呢! * 將多個動作加入到一起可以保護您的代碼避免重用。 --- #### 小結 * 遵循其中的一些,您的程式將充滿驚喜。 * 遵循其中的許多內容,您的程式將真正成為您的程式,沒有人願意修改它。 * 全神貫注,對於尋求啟發的年輕開發人員,您的程式將成為寶貴的課程。 --- ### 使用 Mocha 進行自動化測試 * 為什麼我們需要測試? * 行為驅動開發(BDD) * 開發 "pow":規範 * 開發流程 * 實際行動 * 初步實現 * 改善規範 * 改善實現 * 巢狀描述 * 擴展規範 * 小結 * 牛刀小試 --- #### 為什麼我們需要測試? * 通過手動重新運行測試代碼時,很容易遺漏一些東西。 * 自動化測試意味著除了程式之外,測試是單獨編寫的。他們以各種方式運行我們的函式,並將結果與預期結果進行比較。 --- #### 行為驅動開發(BDD) [Behavior Driven Development](http://en.wikipedia.org/wiki/Behavior-driven_development) BDD 三合一:測試,文檔和範例。 --- #### 開發 "pow":規範 ```javascript= describe("pow", function() { it("raises to n-th power", function() { assert.equal(pow(2, 3), 8); }); }); ``` * `describe("title", function() { ... })` * `it("use case description", function() { ... })` * `assert.equal(value1, value2)` --- #### 開發流程 1. 編寫了初始規格,並測試了最基本的功能。 2. 建立初始實作。 3. 為了檢查它是否有效,我們運行規格的測試框架 [Mocha](http://mochajs.org/)(稍後將有更多詳細資訊)。功能未完成時,將顯示錯誤。我們會進行更正,直到一切正常。 4. 現在,我們可以使用測試進行初始工作。 5. 我們在規格中增加了更多使用案例,可能尚未被實作支持,測試開始失敗。 6. 跳到 3.,更新實作,直到測試沒有錯誤。 7. 重複步驟 3-6,直到功能準備就緒。 --- #### 實際行動 接下來將以以下這些 JavaScript 函式庫進行測試 * [Mocha](http://mochajs.org/) * 核心框架 * [Chai](http://chaijs.com/) * 斷言函式庫 * [Sinon](http://sinonjs.org/) * 監視函式及模擬內置函數 <https://plnkr.co/edit/Coz2HZNfrPtUhbE5oXTv?p=preview> --- #### 初步實現 ```javascript= function pow(x, n) { return 8; // :) we cheat! } ``` --- #### 改善規格 ```javascript= describe("pow", function() { it("raises to n-th power", function() { assert.equal(pow(2, 3), 8); assert.equal(pow(3, 4), 81); }); }); ``` ```javascript= describe("pow", function() { it("2 raised to power 3 is 8", function() { assert.equal(pow(2, 3), 8); }); it("3 raised to power 3 is 27", function() { assert.equal(pow(3, 3), 27); }); }); ``` > 一個測試檢查一件事 --- #### 改善實現 ```javascript= function pow(x, n) { let result = 1; for (let i = 0; i < n; i++) { result *= x; } return result; } ``` ```javascript= describe("pow", function() { function makeTest(x) { let expected = x * x * x; it(`${x} in the power 3 is ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } for (let x = 1; x <= 5; x++) { makeTest(x); } }); ``` --- #### 巢狀描述 ```javascript= describe("pow", function() { describe("raises x to power 3", function() { function makeTest(x) { let expected = x * x * x; it(`${x} in the power 3 is ${expected}`, function() { assert.equal(pow(x, 3), expected); }); } for (let x = 1; x <= 5; x++) { makeTest(x); } }); }); ``` --- #### 擴展規範 ```javascript= describe("pow", function() { // ... it("for negative n the result is NaN", function() { assert.isNaN(pow(2, -1)); }); it("for non-integer n the result is NaN", function() { assert.isNaN(pow(2, 1.5)); }); }); ``` --- #### 小結 > 在 BDD 中,規範優先,然後是實施。最後,我們同時擁有規範和代碼。 1. 作為測試 – 他們保證程式正確運行。 2. 作為文件 – 函式的標題用 describe 並用 it 說明函式的作用。 3. 作為範例 – 測試實際上是運作範例,顯示如何使用功能。 --- #### 牛刀小試 ```javascript= it("Raises x to the power n", function() { let x = 5; let result = x; assert.equal(pow(x, 1), result); result *= x; assert.equal(pow(x, 2), result); result *= x; assert.equal(pow(x, 3), result); }); ``` 有什麼問題? --- ### Polyfills > Mozilla:「自動補完函式庫」 * 巴別塔(Babel) --- #### 巴別塔(Babel) 1. Babel 是轉譯器 2. 填補缺少的部分 --- 本結結束 進入 Object 的領域 --- ### 物件(Objects) * 文字與屬性 * 中括弧 * 屬性值簡寫 * 存在檢查 * "for…in" 迴圈 * 參照複製 * 克隆和合併,`Object.assign` * 小結 * 牛刀小試 --- #### 文字與屬性 ```javascript= let user = { // an object name: "John", // by key "name" store value "John" age: 30 // by key "age" store value 30 }; ``` ```javascript= alert(user.name) delete user.age; ``` --- #### 中括弧 ```javascript= user.likes birds = true let user = {}; // set user["likes birds"] = true; // get alert(user["likes birds"]); // true // delete delete user["likes birds"]; ``` --- #### 中括弧 ```javascript= let fruit = prompt("Which fruit to buy?", "apple"); let bag = { [fruit]: 5, // the name of the property is taken from the variable fruit }; alert( bag.apple ); // 5 if fruit="apple" ``` > 保留字可以用作屬性名稱! --- #### 屬性值簡寫 ```javascript= function makeUser(name, age) { return { name: name, age: age // ...other properties }; } let user = makeUser("John", 30); alert(user.name); // John ``` ```javascript= function makeUser(name, age) { return { name, // same as name: name age // same as age: age // ... }; } ``` ```javascript= let user = { name, // same as name:name age: 30 }; ``` --- #### 存在檢查 ```javascript= alert( user.noSuchProperty === undefined ); // true means "no such property" ``` ```javascript= "key" in object ``` --- #### "for…in" 迴圈 ```javascript= let user = { name: "John", age: 30, isAdmin: true }; for (let key in user) { // keys alert( key ); // name, age, isAdmin // values for the keys alert( user[key] ); // John, 30, true } ``` > 若鍵值是數字則會按照順序 --- #### 參照複製 ```javascript= let message = "Hello!"; let phrase = message; ``` ```javascript= let user = { name: "John" }; let admin = user; // copy the reference ``` > 參照比較 > const 物件 --- #### 克隆和合併,`Object.assign` ```javascript= let user = { name: "John", age: 30 }; let clone = {}; // the new empty object // let's copy all user properties into it for (let key in user) { clone[key] = user[key]; } ``` ```javascript= Object.assign(dest, [src1, src2, src3...]) ``` --- #### 小結 * 屬性鍵必須是字符串或符號(通常是字符串)。值可以是任何類型。 * 點表示法:`obj.property`、中括弧表示法:`obj["property"]` * `delete obj.prop`、`"key" in obj`、`for (let key in obj)` * `Objects.assign` * Array, Date, Error, ... --- #### 牛刀小試 * 寫一個 isEmpty 函式檢查物件是否為空 ```javascript= let schedule = {}; alert( isEmpty(schedule) ); // true schedule["8:30"] = "get up"; alert( isEmpty(schedule) ); // false ``` * 寫一個函式把值加總 ```javascript= let salaries = { John: 100, Ann: 160, Pete: 130, Meow: "null" } ``` --- ### 垃圾收集(GC) * 可達性 * 一個簡單的例子 * 兩個參照 * 互連物件 * 無法到達的島 * 內部演算法 * 小結 --- #### 可達性 > 可以被訪問 - 有些情況會導致無法 GC - 目前函式的區域變數和參數。 - 當前嵌套調用鏈中其他函式的變數和參數。 - 全域變數。 - (還有一些其他的) - 可以透過一些鏈狀結構去訪問的值都代表是可達的 JavaScript 引擎中有一個稱為 Garbage Collector 的背景程序。它監視所有物件並刪除那些變得無法訪問的物件。 --- #### 一個簡單的例子 ```javascript= // user has a reference to the object let user = { name: "John" }; user = null; ``` ![](https://i.imgur.com/DGFUcUA.png) ![](https://i.imgur.com/sHuWwPI.png) --- #### 兩個參照 ```javascript= // user has a reference to the object let user = { name: "John" }; let admin = user; user = null; ``` ![](https://i.imgur.com/v4pwNR3.png) - admin 仍然可以被訪問 所以 Object 不會被回收 --- #### 互連物件 ```javascript= function marry(man, woman) { woman.husband = man; man.wife = woman; return { father: man, mother: woman } } let family = marry({ name: "John" }, { name: "Ann" }); ``` --- #### 物件結構 ![](https://i.imgur.com/LutVFrJ.png) --- #### 刪除參照 ```javascript= delete family.father; delete family.mother.husband; ``` ![](https://i.imgur.com/cm2LeFO.png) --- #### GC ![](https://i.imgur.com/eDanHJY.png) ![](https://i.imgur.com/PQ4x8v7.png) --- #### 無法到達的島 ```javascript= family = null; ``` ![](https://i.imgur.com/4Wb1u8s.png) --- #### 內部演算法 > BFS ![](https://i.imgur.com/95uOWjX.png) --- #### 接上頁 - 一些優化 - Generational collection - 物件分兩組,新的多檢查,老的少檢查 - Incremental collection - 先標記,分段檢查 - Idle-time collection - CPU 有空才收垃圾 --- * 小結 - 垃圾收集是自動執行的。我們不能強迫或阻止它。 - 當物件可訪問時,它們會保留在記憶體中。 - 被參照與可訪問不同:一堆相互鏈接的物件可能整體上變得不可訪問。 [tour of V8: Garbage Collection](http://jayconrod.com/posts/55/a-tour-of-v8-garbage-collection) --- ### 符號(Symbol)型態 * Symbols * "Hidden" 屬性 * 全域符號 * 系統符號 * 小結 --- #### Symbols ```javascript= let id1 = Symbol("id"); let id2 = Symbol("id"); alert(id1 == id2); // false ``` --- #### "Hidden" 屬性 ```javascript= let user = { // belongs to another code name: "John" }; let id = Symbol("id"); user[id] = 1; alert( user[id] ); // we can access the data using the symbol as the key ``` --- #### "Hidden" 屬性 - 迭代 ```javascript= let id = Symbol("id"); let user = { name: "John", age: 30, [id]: 123 }; for (let key in user) alert(key); // name, age (no symbols) // the direct access by the symbol works alert( "Direct: " + user[id] ); ``` --- #### 全域符號 Symbol.for & Symbol.keyFor ```javascript= // read from the global registry let id = Symbol.for("id"); // if the symbol did not exist, it is created // read it again (maybe from another part of the code) let idAgain = Symbol.for("id"); // the same symbol alert( id === idAgain ); // true // get symbol by name let sym = Symbol.for("name"); let sym2 = Symbol.for("id"); // get name by symbol alert( Symbol.keyFor(sym) ); // name alert( Symbol.keyFor(sym2) ); // id ``` --- #### 系統符號 - Symbol.hasInstance - Symbol.isConcatSpreadable - Symbol.iterator - Symbol.toPrimitive --- #### 小結 - Symbol 是唯一識別符 - 可以把資料藏在物件 --- ### 物件方法 "this" * 方法範例 * 方法中的 "this" * "this" 沒有限制 * 內部:參照型態 * 箭頭函式沒有 "this" * 小結 * 牛刀小試 --- #### 方法範例 ```javascript= let user = { name: "John", age: 30 }; user.sayHi = function() { alert("Hello!"); }; user.sayHi(); // Hello! ``` 儲存在物件裡的函式叫做方法 --- #### 方法中的 "this" ```javascript= let user = { name: "John", age: 30, sayHi() { // "this" is the "current object" alert(this.name); } }; user.sayHi(); // John ``` #### "this" 沒有限制 this 在 js 與其他語言不同,任何的 function 都可以使用 ```javascript= function sayHi() { alert( this.name ); } let user = { name: "John" }; let admin = { name: "Admin" }; // use the same function in two objects user.f = sayHi; admin.f = sayHi; ``` --- #### 內部:參照型態 ```javascript= let user = { name: "John", hi() { alert(this.name); }, bye() { alert("Bye"); } }; user.hi(); // John (the simple call works) // now let's call user.hi or user.bye depending on the name (user.name == "John" ? user.hi : user.bye)(); // Error! let hi = user.hi; hi(); // Error, because this is undefined ``` 物件有參照要連帶呼叫,否則 this 預設會是 undefined --- #### 箭頭函式沒有 "this" ```javascript= let user = { firstName: "Ilya", sayHi() { let arrow = () => alert(this.firstName); arrow(); } }; user.sayHi(); // Ilya ``` --- #### 小結 - 儲存在物件屬性中的函式稱為「方法」。 - `object.doSomething()` 方法用這種方式呼叫 - 方法可以用 _this_ 參照自身的物件 - this 是在 run-time 時才被定義的 - 聲明一個函式時,可以使用它,但是在呼叫前,它沒有任何的值 - 函式可以從 A 物件複製到 B 物件 - 在呼叫物件的方法時 object.method(),method 中的 this 指的就是這個 object --- #### 牛刀小試 - 1 格式檢查 ```javascript= let user = { name: "John", go: function() { alert(this.name) } } (user.go)() ``` PS. 有點小問題 --- #### 牛刀小試 - 2 解釋 "this" 的值 ```javascript= "use strict" let obj, method; obj = { go: function() { alert(this); } }; obj.go(); // (1) [object Object] (obj.go)(); // (2) [object Object] (method = obj.go)(); // (3) undefined (obj.go || obj.stop)(); // (4) undefined ``` --- #### 牛刀小試 - 3 物件的字面值 ```javascript= function makeUser() { return { name: "John", ref: this }; }; let user = makeUser(); alert( user.ref.name ); // What's the result? ``` --- #### 牛刀小試 - 4 計算機 ```javascript= let calculator = { // ... your code ... }; calculator.read(); alert( calculator.sum() ); alert( calculator.mul() ); ``` PS. 輸入 read 的時候要可以重複的用 prompt 輸入 a 跟 b 兩個值 --- #### 牛刀小試 - 5 鏈式 ```javascript= let ladder = { step: 0, up() { this.step++; }, down() { this.step--; }, showStep: function() { // shows the current step alert( this.step ); } }; ladder.up(); ladder.up(); ladder.down(); ladder.showStep(); // 1 ``` 修改 ladder 物件讓下面動起來 ```javascript= ladder.up().up().down().showStep(); // 1 ``` --- ### 物件轉型到原始型態 * ToPrimitive * Symbol.toPrimitive * toString/valueOf * 回傳型態 * 小結 --- #### ToPrimitive --- #### Symbol.toPrimitive Definition ```javascript= obj[Symbol.toPrimitive] = function(hint) { // must return a primitive value // hint = one of "string", "number", "default" }; ``` Example ```javascript= let user = { name: "John", money: 1000, [Symbol.toPrimitive](hint) { alert(`hint: ${hint}`); return hint == "string" ? `{name: "${this.name}"}` : this.money; } }; // conversions demo: alert(user); // hint: string -> {name: "John"} alert(+user); // hint: number -> 1000 alert(user + 500); // hint: default -> 1500 ``` --- #### toString/valueOf 出現 Symbol 以前舊的轉換方法 ```javascript= let user = { name: "John", money: 1000, // for hint="string" toString() { return `{name: "${this.name}"}`; }, // for hint="number" or "default" valueOf() { return this.money; } }; alert(user); // toString -> {name: "John"} alert(+user); // valueOf -> 1000 alert(user + 500); // valueOf -> 1500 ``` --- #### 回傳型態 - 無法控制 toString 是否完全返回字串 - 也無法控制 Symbol.toPrimitive 方法是否返回數值,當 hint 為 number 時 --- #### 小結 hint 分三種 - "string" (alert 或需要字串運算的時候) - "number" (數學) - "default" (少數運算子) 轉換演算法 1. 呼叫 obj[Symbol.toPrimitive](hint) 如果方法存在 2. 如果 hint 是字串先嘗試使用 obj.toString() 再 obj.valueOf() 3. 如果 hint 不是字串就 valueOf 再 toString --- ### 建構子,運算子 "new" * 建構函式 * 建構子模式測試:new.target * 從建構子回傳 * 建構子中的方法 * 小結 * 牛刀小試 --- #### 建構函式 ```javascript= function User(name) { // this = {}; (implicitly) this.name = name; this.isAdmin = false; // return this; (implicitly) } let user = new User("Jack"); alert(user.name); // Jack alert(user.isAdmin); // false ``` ```javascript= let user = { name: "Jack", isAdmin: false }; ``` --- #### 建構子模式測試:new.target ```javascript= function User() { alert(new.target); } // without "new": User(); // undefined // with "new": new User(); // function User { ... } function User(name) { if (!new.target) { // if you run me without new return new User(name); // ...I will add new for you } this.name = name; } let john = User("John"); // redirects call to new User alert(john.name); // John ``` --- #### 從建構子回傳 ```javascript= function BigUser() { this.name = "John"; return { name: "Godzilla" }; // <-- returns this object } alert( new BigUser().name ); // Godzilla, got that object function SmallUser() { this.name = "John"; return; // <-- returns this } alert( new SmallUser().name ); // John ``` --- #### 建構子中的方法 ```javascript= function User(name) { this.name = name; this.sayHi = function() { alert( "My name is: " + this.name ); }; } let john = new User("John"); john.sayHi(); // My name is: John /* john = { name: "John", sayHi: function() { ... } } */ ``` --- #### 小結 - 建構函式簡而言之就是建構子,但與一般函式不同的是他是大寫開頭的。 - 建構函式只能透過 `new` 呼叫 --- #### 牛刀小試 - 1 兩個函式 - 一個物件 ```javascript= function A() { ... } function B() { ... } let a = new A; let b = new B; alert( a == b ); // true ``` --- #### 牛刀小試 - 2 計算機 ```javascript= let calculator = new Calculator(); calculator.read(); alert( "Sum=" + calculator.sum() ); alert( "Mul=" + calculator.mul() ); ``` --- #### 牛刀小試 - 3 累加器 - 寫一個建構函式 `Accumulator(startingValue)` - 寫一個 `read()` 方法可以讓使用者輸入一個數值 ```javascript= let accumulator = new Accumulator(1); // initial value 1 accumulator.read(); // adds the user-entered value accumulator.read(); // adds the user-entered value alert(accumulator.value); // shows the sum of these values ``` --- ## 最後 參考來源:<https://javascript.info>、MDN
{"metaMigratedAt":"2023-06-15T00:12:16.161Z","metaMigratedFrom":"YAML","title":"喵喵用瀏覽器學現代 JavaScript - 程式品質、物件基礎","breaks":true,"description":"第 3 堂","contributors":"[{\"id\":\"b107bcc6-fc73-449e-a017-dde08de3353c\",\"add\":21338,\"del\":700}]"}
    269 views
   owned this note