# W_JS忍者
[toc]
# Watson infor
>我會閱讀所有章節,依據章節節錄重點,也當作個人複習
# page20
個人理解是一個資料結構,就像連連看
ex
```html=
<html>
<head>
<meta>
</head>
<body>
```
這樣就會向下圖

document是指目前網頁的文檔對像模型(DOM),它是一個全域對象,用於存取和操作HTML文檔的內容和結構。document物件提供了一系列的方法和屬性,使你能夠動態地取得和修改網頁的內容。
---
# page22
全域程式
# page23-24
- 瀏覽器在頁面建立階段遇到script節點 就會先停止html 並開始執行js
- js執行完script最後一行,便會繼續處理html來建立其他得DOM節點
### 第一次讀書會
CH.1
BY Chris said
複雜名詞物件導向 複雜動詞function programming
CH.2
習題 一個是只能執行一次 addevenlistener是可以除鋰多個事件
keyword 觀察者模式
WEB應用程式得生命週期有那兩個階段:頁面建立 事件處理
chris 比喻玩switch 開機與按按鈕 如果硬要說關機算一個 也就是有三個 but?
圖形化界面 這就是早期沒有畫面互動 到有畫面互動 所以跟歷史有關 有趣
js是非同步優先與鹽 是為了瀏覽器
忍者在寫得時候是ES7之前 所以沒有async await
proxy就像是東尼使塔客跟鋼鐵衣關西 proxy就像鋼鐵衣
---
# CH.3 初探頭等函式:定義與引述
==35.==
在JS,函式是頭等物件,或是稱他們為頭等公民。
## 3.1 使用函式與否得差異為何?
==36.==
因為他是主要得模組化執行單元。我們位頁面編寫得所有程式碼都會除存在函式內。
**js物件具有特性:**
- 藉由實質:{}
- 可以指派給變數、陣列、其他物件屬性
```javascript=
var ninja = {};
ninjaArray.push({});
ninja.data = {};
```
==37.==
- 可以作為引述傳遞給函式
```javascript=
function hide(ninja){
ninja.visibility = false;
}
hide({});
```
- 可作為函式回傳值
```javascript=
function returnNewNinja() {
return {};
}
```
- 可動態建立和指派得屬性
```javascript=
var ninja = {};
ninja.name = "Hanzo";
```
### 3.1.1 函式作為頭等物件
js函式擁有物件所有功能,因此也可被當成是物件般來處理
- 由實質建立
```javascript=
function ninjaFunction() {}
```
- 可以指派給變數、陣列、其他物件屬性
```javascript=
var ninjaFunction = function() {};
ninjaArray.push(function(){});
ninja.data = function(){};
```
==38.==
- 作為引數傳遞給其他函式
```javascript=
function call(ninjaFunction){
ninjaFunction();
}
call(function(){});
```
- 作為函式回傳值
```javascript=
function returnNewNinjaFunction() {
return function(){};
}
```
- 他們擁有可動態建立和指派屬性
```javascript=
var ninjaFunction = function(){};
ninjaFunction.name = "Hanzo";
```
所以物件可以做得事情,==函式都可做到==,函式也是物件,但卻是可以被呼叫得物件
> **js中得函式型程式設計**
> 這種設計風格的重點是透過撰寫還是來解決問題,此設計可以幫助我們寫出更易於測試、擴展和模組化得程式碼
頭等物件得其中一項特性,是他們可以作為==引數傳遞給函式==。對函式而言,這代表我們將一個函式作為引數傳遞給另一個函式,而在稍後得某個時間點,它可能會呼叫這個被傳入得函式,這是==回呼函式==得概念
### 3.1.2 回呼函式
==39.==
這個術語源自這樣得事實:我所建立得函式會在稍後某個適當得時機點,由其他程式碼==回呼(call back)==
```javascript=
function useless(ninjaCallback) {
return ninjaCallback();
}
```
這段"無用"得code展示了==將函式作為參數傳遞==給令一個函式,並隨後透過傳遞參數呼叫該函式的能力,下面更多示範
> 程式列表3.1 一個簡單得回呼範例
```javascript=
var text = "Domo arigato!";
report("Before defining functions");
function useless(ninjaCallback) {
report("In useless function");
return ninjaCallback();
}
function getText() {
report("In getText function");
return text;
}
report("Before making all the calls");
assert(useless(getText) === text,
"The useless function works! " + text);
report("After the calls have been made");
```
==40.==
此為執行後結果

==41.==
如圖詳細講解,getText函式作為引數傳給useless函式,這表示useless函式內部可以藉由ninjaCallback參數來指向getText函式。然後,藉由呼叫ninjaCallback(),我們執行了getText函式,getText函式被我們當作引數傳遞,而被useless函式呼叫

JS最重要得特性之一,就是可以在程式中==任何地方建立函式==,這個特性還可在一個函式並不需要從程式碼中許多地方來引用時,避免全域命名空間被不必要得命名污染
==42.==
使用對比器進行排序sort
[MDN.sort](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
sort對陣列進行排序並回傳此陣列
在下方程式碼中將讓排序演算法用回呼得方式來呼叫這個函式,而當排序演算法需要進行比較時,就會呼叫這個回呼函式。如果傳入的值應該反轉順序
> 為了比較數字而不是字串,比較函式可以僅僅利用 a 減 b。以下函式將會升冪排序陣列 by MDN
```javascript=
var values = [0, 3, 2, 5, 7, 4, 8, 1];
values.sort(function(value1, value2){
return value1 - value2;
});
//result[0, 1, 2, 3, 4, 5, 7, 8]
```
==43.==
## 3.2 函式作為物件得有趣之處 (Chris 表示不有趣 :)
我們可以將==屬性附加==到函式上
```javascript=
var ninja = {};
ninja.name = "hitsuke";
var wieldSword = function(){};
wieldSword.swordType = "katana";
```
可以讓這像功能派上用場得場景:
- 把多個函式儲存在集合中,這可以讓我們輕鬆得管理彼此護有關連得函式。
- 讓函式可以記住之前計算所得到得舊值,進而提高後續呼叫時得執行效能
### 3.2.1 儲存函式
#### 3.2 將一組具唯一性得函式儲存起來
當管理回呼函式集合時,我們不想看到任何重複,因為單一事件將導致對同一個回呼函是的多次呼叫。可以將函式儲存在陣列中,然後對陣列資料像循環一次去檢查重複得函式,但這種作法==在校能上表現不佳==,下方將講解解決辦法:==??==
```javascript=
var store = {
nextId: 1, // 初始化 nextId 為 1
cache: {}, // 初始化 cache 為空對象
add: function(fn) { // 定義 add 方法
if (!fn.id) { // 檢查函數是否已經有 id 屬性
fn.id = this.nextId++; // 分配新的 id 給函數,並將 nextId 增加 1
this.cache[fn.id] = fn; // 將函數存儲在 cache 中,鍵為 id
return true; // 返回 true 表示函數已成功添加
}
}
};
function ninja() {} // 定義一個名為 ninja 的函數
assert(store.add(ninja), "Function was safely added.");
// 添加 ninja 函數到 store 中,返回 true,表示添加成功
assert(!store.add(ninja), "But it was only added once.");
// 再次嘗試添加 ninja 函數,由於已經有 id,所以返回 undefined,表示沒有添加成功
```
> note:add()可相加,可添加 ==??==
==45.==
### 3.2.2 自我記憶函式
> 建立一個函式,使奇能夠記住之前得計算值
>
我們可以將結果與函式參數一起儲存,當使用同一組參數進行另一次呼叫時,我們可以回傳先前儲存得結果,而不用重新計算。可提高效能
下方將以計算質數為例子
#### 程式列表3.3 記憶先前已計算得值
```javascript=
function isPrime(value) {
if (!isPrime.answers) {
isPrime.answers = {}; // 如果 isPrime 沒有 answers 屬性,則初始化為空對象,作為緩存
}
if (isPrime.answers[value] !== undefined) {
return isPrime.answers[value]; // 如果緩存中已存在該數值的計算結果,則直接返回
}
var prime = value !== 1; // 1 不是質數
for (var i = 2; i < value; i++) {
if (value % i === 0) {
prime = false; // 如果能被 i 整除,則不是質數
break; // 結束循環
}
}
return isPrime.answers[value] = prime; // 將計算結果存入緩存並返回
}
assert(isPrime(5), "5 is prime!"); // 測試 5 是不是質數
assert(isPrime.answers[5], "The answer was cached!"); // 確認 5 的計算結果已被緩存
```
==47.==
這種方法有兩個主要優點
- 藉由取得之前得值,讓終端使用者在呼叫函式時得到效能上得改善
- 它會在幕後運行,不須執行特殊請求或做任何初始化,來使這一切發生
缺點部份
- 任何暫存都是花費記憶空間來換取效能
- 純粹主義者可能認為暫存是一個不應該與業務邏輯==??==放在一起得事物,一個函式或一個方法應該只做一件事並且做好
- 很難對這樣得演算法進行負載測試或測量其效能,因為結果取決於之前對於函式提供得輸入值
## 3.3 定義函式
記住,作為頭等物件,函式就像字串和數字這樣得值一樣,是可以在語言中使用得值。
JS提供了幾種定義函是的方法,他們可以分為四類
- 函式宣告和函式表達式,了解他們差異可以幫助我們知道函是在什麼時候被呼叫:
```javascript=
function myFun(){ return 1;}
```
==48.==
- 箭頭函式,于ES6得新功能,解決回呼函式得常見問題,也更加簡潔得來定義函式
```javascript=
myArg => myArg*2
```
- 函式建構式,一種不太常用得方法,讓我們能夠從一個可以動態產生得字串中,動態建立一個新的函式
```javascript=
new Function('a', 'b', 'return a + b')
```
- 生成器函式,ES6中加入到JS得功能,讓我們建立以往不同得函式,是可以在應用程式執行時退出和充新加入他們,同時在這些不同得進入點之間保持它門得變數值
```javascript=
function* myGen(){ yield 1; }
```
### 3.3.1 函式宣告和函式表達式
在js中,兩種最常用得函式定義方法,是使用函式宣告和函式表達式
#### 函式宣告

- 必要function關鍵字開頭
- 必要得函式名稱
- 必要括號
- 逗號分隔參數名稱表
- 大括號,內部為函式主體,會置入零個或多個敘述句
每個函式宣告都必須符合使形式之外,還有一個條件:一個函式宣告必須獨立放置,作為一個單獨得js敘述句
### 程式列表3.4 函式宣告範例
```javascript=
function samurai() {
return "samurai here";
}
function ninja() {
function hiddenNinja() {
return "ninja here";
}
return hiddenNinja();
}
```
```javascript=
function ninja() {
function hiddenNinja() {
return "ninja here";
}
return hiddenNinja();
}
```
定義在另一個函式中得函式,這完全正常。
>在函式放在其他函適中,可能會引起一些關於範圍和識別像,解析得複雜問題,但現在先跳過他們,因為我們將在第五章中詳細討論
### 函式表達式
函是在js頭等物件,這代表他們可以藉由實值建立、指派給變數和屬性,並用做參數或回傳值傳給其他函式。
```javascript=
var a = 3;
myFunction(4);
```
我們也可以在相同得位置使用函式實值:
```javascript=
var a = function() {};
myFunction(function(){});
```
函式表達式定義:總是作為敘述句得函式,例如指派表達式得右值(翻譯問題),或作為另一個函式得參數
下面程式展示函式宣告與函式表達式之間差異
==51.==
|程式列表3.5 函式宣告與函式表達式|
```javascript=
function myFunctionDeclaration(){
function innerFunction() {}
}
var myFunc = function(){};
myFunc(function(){
return function(){};
});
(function namedFunctionExpression () {
})();
+function(){}();
-function(){}();
!function(){}();
~function(){}();
```
讀解釋@
==52.==
函式宣告與函式表達式差異:
- 函式宣告必須要加上名稱,因為他是獨立得,由於函式基本要求是它必須是可被呼叫得,所以唯一呼叫他的方式就是藉由名稱來呼叫
- 函式表達式名稱可有可無,他是js表達是一部份,如果呼叫,我們可以先給函式表達式指派給一個變數,之後可以使用該變數來呼叫
### 立即函式

左為標準函式呼叫,右為立即呼叫得函式表達式 ==??==
識別項 ==??==
- 左:在最基本得函式呼叫中,我們是透過識別項來指定呼叫得函式
- 右:首先建立一個函式,然後立即呼叫這個新建立得函式,這種作法稱為立即呼叫得函式表達式(aka IIFE:immediate invoked function expression)
==53.==
> 圍繞函式表達式得括號 重點擷取
> - 括號包裹函式表達式是語法上的需求。
> - JavaScript解析器需要區分函序宣告和函數表達式。
> - 如果沒有括號,解析器會將獨立語句中的函式視為沒有名稱函式宣告,導致錯誤。
> - 括號告訴解析器這是函式表達式而敘述句。
> - 另一種方法是直接用括號立即調用的函數。
在==51.==的程式碼中展示得4個表達式也是相同概念
```javascript=
+function(){}();
-function(){}();
!function(){}();
~function(){}();
//也可使用一元運算子來區分,無須在函式表達式周圍使用括號,而這些一元運算子得結果也不會處存在任何地方。
```
==54.==
## 3.3.2 箭頭函式
🥷:箭頭函式是在ES6加入
箭頭函式是表達式得,也是js語法糖
語法糖:更簡短、簡潔得寫法
以下是案例比較,這個例子把回呼函式表達式傳給陣列物件sort
```javascript=
//一般函式表達式
var values = [0, 3, 2, 5, 7, 4, 8, 1];
values.sort(function(value1,value2){
return value1 – value2;
});
//箭頭函式,相對簡潔 (lambda!!!!)
var values = [0, 3, 2, 5, 7, 4, 8, 1];
values.sort((value1,value2) => value1 – value2);
```
在上方差異中可以看到
- 沒有function關鍵字
- 沒有大括號
- 沒有return
- 新增箭頭運算子`=>`
簡單解讀
```javascript=
param => expression
```
接受一個參數param並返回一個表達是expression
> 程式列表3.6 比較箭頭函式與函式表達式
```javascript=
var greet = name => "Greetings " + name;
assert(greet("Oishi") === "Greetings Oishi", "Oishi is properly greeted");
var anotherGreet = function(name) {
return "Greetings " + name;
};
assert(anotherGreet("Oishi") === "Greetings Oishi", "Again, Oishi is properly greeted");
```
---

- 零或多個參數需要使用括號,只有一個參數括號可有可無
- 必要箭頭運算子
- 如果箭頭函式得主體只有一個表達式,那即是函式回傳值
- 如果主體是一段程式區塊,就和標準函式一樣,若沒有return敘述句,則回傳值是undefined;反之,回傳值就是return敘述句所回傳的值
==56.==
## 3.4 引數(argument)與函式參數(parameter)
- 參數是我們在函式定義中所列出得變數
- 引數是當我們呼叫函式時傳遞給他的值
==57.==

閱讀@
當引數作為呼叫函式得一部分時,這些引數按指定得順序分配給函式定義中得參數,第一位引數分配給第一個參數,第二引數給第二參數 以此類推
==58.==

解釋圖內文@
### 3.4.1 不定參數
🥷:ES6加入
==59.==
> 程式列表3.7 使用不定參數

在參數名稱之前加上省略符號`...`便會將其轉換為==不定參數得參數陣列==,剩餘得傳入引數將會包含在其中
==??==
在圖中multiMax使用了四個引數來呼叫:multiMax(3,1,2,3),第一個引數值3被指派給maltiMax函式得first。由於第二個參數是==不定參數==,所有剩餘得值(1,2,4)都放在一個新陣列remainingNumber中,接者,我們對陣列做降冪排序,在取出最大得數
🥷:只有最後一個函式參數可以是不定參數,若放置在非最後得參數將會得到警告
`SyntaxError: parameter after rest parameter.`
==60.==
## 3.4.2 預設參數
🥷:ES6加入

當我們呼叫函式並且忽略相應對應得引數值時,正如範例中得Fuma、Yoshi和Hattori,次時就會預設action為skulking,如果有使用傳入值,那預設值就會被取代
## 總結
# CH4.
## CH5 閉包與範圍
### 名詞定義
#### 閉包:
函式操作在函式不的變數
依照讀書會結論,閉包是一種工具,主要特徵是函式裡面又包裹了另一個函式,根據書本案例,將函式作為建構式去撰寫。其作用可偵測和使用外部得變數,
#### 私有變數:簡單來說就是變數建立在函式裡面,外部無法直接呼叫。
- 安全不會被修改
- 易維護,不會被外部影響
- 提高模組化設計
#### 執行背景空間堆疊:
簡單來說就是程式執行是有順序的,初期是執行全域背景空間,如果有呼叫就會依序排列並且印出。直到一切皆空
```javascript=
// 模擬背景執行堆疊行為的範例
function firstFunction() {
console.log("進入 firstFunction");
secondFunction();
console.log("退出 firstFunction");
}
function secondFunction() {
console.log("進入 secondFunction");
thirdFunction();
console.log("退出 secondFunction");
}
function thirdFunction() {
console.log("進入 thirdFunction");
console.log("thirdFunction: 執行中...");
console.log("退出 thirdFunction");
}
console.log("程式開始");
firstFunction();
console.log("程式結束");
//印出結果
程式開始
進入 firstFunction
進入 secondFunction
進入 thirdFunction
thirdFunction: 執行中...
退出 thirdFunction
退出 secondFunction
退出 firstFunction
```
#### 字彙環境 aka Code nesting
function包住得範圍 可以理解為圖層,一層一層嵌套。
# CH 7
==195==
7.3圖
prototype 可以存取到父層 反正我的理解是正確,就是可以瘋狂串接AKA原型鍊,假設有a、b、c三位物件,b親了c,b就可以存取c的東西,但是a也像要親c,如果a也想要取得c得內容,那可以透過間接接吻的方式,a親b,由於b親了c,所以已經有存取到c的吻,那a親b就是透過b取到c的吻了
#### 7.3 chatGPT改進版:
原型鏈(Prototype Chain)其實是 JavaScript 中物件的繼承機制。一個物件可以透過它的 prototype 屬性連結到另一個物件,從而繼承該物件的屬性或方法。就像你說的:
- b 親了 c:代表 b.__proto__ = c,b 的原型(__proto__)指向 c,b 就能「繼承」 c 的屬性或方法。
- a 想要親 c:a 其實親不到 c(因為沒有直接指向),但 a 可以親 b(a.proto = b)。
- 間接接吻:a 親了 b,而 b 親了 c,這條鏈就像「接力」,讓 a 能透過 b 找到 c 的內容。
用技術的語言來說:
當我們在物件 a 上尋找某個屬性或方法時,JavaScript 會先檢查 a 本身。如果找不到,就會沿著原型鏈(__proto__)一路向上尋找,直到找到目標或達到頂端(null)。
==208==
再者裡演示了建立prototype方法,建立 Person Ninja 兩個函式,其中Person有一個dance物件
- 用建構子屬性參照Person,所以Person原型會建立
- Ninja則是轉移原型到Person(new Person)
- 建立ninja去 new Ninja(這裡Ninja 有 new Person),所以ninja裡面的原型會是Person,所以可以存取Person裡面的東西
原理就是,由於ninja本身沒有dance,所以js環境會對ninja這物件本身做檢查,然後會一路往上找(前提是有綁定父親原型,而父親要有綁定祖父,這樣就會往上找物件),找到祖父有這個東西就會取用,這就是繼承的方式
by Chris
#### 擁有這三個條件才算物件導向,js少了,所以不是物件導向
- 封裝(Encapsulation)js缺這個
-
- 繼承
- prototype部分
- 動態連結
- 只不要重複使用函式或功能,而是取得別人創造的功能
#### 判斷型別三個方法 or SOP
- typeof(如果簡單型別爆出是object,那就可以在往下測試複雜型別)
- consturtor.name
- instanceof
#### == / === 用法
=== undefined || === null 等同於 == null
要找 undefined、null 才用 ==,其他就用 ===
#### object 與 class 差異,和寫 class 的原因
- object 主旨在要別的屬性
- class 主要在想要索取別人的function,但前提示對方也是class
## Object
Object.defined
---
## CH8

<!-- Chris 建立proxy 可以先想像鋼鐵人概念 p
-->
==230==
> get取值器 set設值器 是一種JS透過閉包來摹擬物件私有變數的一種方式
#### 它主要處理三大情境
> 以下為set優點描述
- 可以限制物件,不會犯下像是給錯資料類型(比方在set裡面建立正規表達式)
- 預防輸入垃圾值
- 針對輸入值判斷是否正確
- 希望可以紀錄所有在物件裡發生的變化
- 紀錄改變值的順序,會知道錯誤發生時機
- vuex pinia 會用到這功能(紀錄修改值順序)
- 在頁面某處顯示我們在物件設定的屬性質
- 針對畫面變更
==231==
提到 get set 方法可以應用在例如:日誌紀錄、資料驗證、或是修改UI(比方深色模式)
==232==
### 8.1.1 定義 get set 的兩種方法
- 透過物件實值或 ES6 的類別定義
- 使用內建Object.defineProperty方法
> 複習 Object.defineProperty
### 8.2 在物件實質裡定義get取值器 set設值器
```javascript=
const ninjaCollection = {
ninjas:["Chris","Watson","Jeremy"],
get firstNinja(){
return this.ninjas[0]
},
set firstNinja(name){
this.ninjas[0] = name;
}
}
ninjaCollection.firstNinja;//'Chris'
ninjaCollection.firstNinja = "Danny";//'Danny'
ninjaCollection;//['Danny', 'Watson', 'Jeremy']
```
==233==
### 圖8.1導讀
從8.2我們看到get set的行為
- get 回傳索引值為0的元素
- set 對該元素指派新值
==234==
在存取屬性時,真正取值的方法會被==隱性==呼叫
> ?:為什麼是隱性?(隱性轉型、顯性轉型)
在設值時,設值器也會在背地裡呼叫,然後修改集合裡索引值為0的ninja
> ?:這段是否在解釋==230==頁的這段"可以紀錄所有在物件裡發生的變化,紀錄改變值的順序,會知道錯誤發生時機"?(已解:理解沒問題)
綜合以上所談到,這種原生get set 可以讓我們用==一般的屬性操作方式==來存取屬性
==235==
### 圖8.3導讀
### 8.3 在ES6類別中定義 get set
```javascript=
class NinjaCollection {
constructor() {
this.ninjas = ["Fang", "Jami", "Tangerine"];
}
get firstNinja() {
return this.ninjas[0];
}
set firstNinja(name) {
this.ninjas[0] = name;
}
}
//用 new 搭 class 創建出新的實例(instance)
const ninjaCollection = new NinjaCollection();
ninjaCollection.firstNinja; //"Fang"
ninjaCollection.firstNinja = "Danny";//"Danny"
ninjaCollection;//["Danny", "Jamie", "Tangerine"]
```
此實驗證實即使使用class,還是能如預期運作
==236==
>注意:
>我們不一定需要為一個屬性同時定義 get set
>- 如果只需要讀取值,可以只用 get
>- 如果只需要設值,可以只用 set
>
>當只有設定get or set 其中之一,還會出現兩種情況
>- 在一般模式下,JS引擎會默默忽略任何get or set動作
> ``` javascript=
> const obj = {
> get value() {
> return 42;
> }
>};
>
>obj.value = 100; // 不會報錯,但無效
>console.log(obj.value); // 42
>
> ```
>- 在嚴格模式下,JS會報錯(如果是常數,不想被改動的話可以使用嚴格模式 or setter,告訴程式語言 我的程式不想被改 )
> ``` javascript=
>'use strict';
>
>const obj = {
> get value() {
> return 42;
> }
>};
>
>obj.value = 100; // 會直接報錯:TypeError: Cannot set >property value which has only a getter
> ```
---
JS 並沒有真正的私有變數,只能透過物件包住變數,做出閉包來模擬私有變數。如果要使用get set來達到私有變數的方法,這裡可以用Object.defineProperty方法。
### 8.4 用Object.defineProperty方法定義get set
```javascript=
function Ninja() {
//私有變數 (命名規則 可加上前綴 $ or _ ,這技巧是用來彌補語言缺陷)
let _skillLevel = 0;
Object.defineProperty(this, "skillLevel", {
get: () => {
return _skillLevel;
},
set: (value) => {
_skillLevel = value;
},
});
}
const ninja = new Ninja();
ninja._skillLevel; //無法直接存取私有變數,回傳undefined
ninja.skillLevel //0
ninja.skillLevel = 10 //10
```

> 產生閉包
==239==
### 8.5 用set來驗證屬性值
```javascript=
function Ninja() {
let _skillLevel = 0;
Object.defineProperty(this, "skillLevel", {
get: () => {
return _skillLevel;
},
set: (value) => {
if (!Number.isInteger(value)) {
throw new TypeError("wrong");
}
_skillLevel = value;
},
});
}
//建立了ninja instance
const ninja = new Ninja();
try {
ninja.skillLevel = "hello";
} catch (error) {
throw new Error("Wrong");
}
//Uncaught Error: Wrong
```
這裡實驗: set 設定了 if 判斷,當指派的值是整數,set 會正常發揮。如果非整數,則會報錯。如此一來,就可避免指派錯誤的型態
==240==
get set 還可以用來定義可計算屬性,其值是在每次存取時計算出來。
### 8.6 定義可計算屬性
```javascript=
//shogun將軍
const shogun = {
name: "Watson",
//家族
clan: "Li",
get fullTitle() {
return this.name + " " + this.clan;
},
set fullTitle(name) {
const segments = name.split(" ");
this.name = segments[0];
this.clan = segments[1];
},
};
shogun.name;//"Watson"
shogun.clan;//"Li"
shogun.fullTitle;//"Watson Li"
shogun.fullTitle = "Jeremy Kuo";//改值 name:"Jeremy",clan:"Kuo"
```
每當要取fullTitle,取值方法會把 name clan 屬性拼接在一起。
### 總結
> JavaScript 的物件是一組動態屬性的集合,我們可以自由新增、修改或刪除屬性。而 get 和 set 的作用,是為屬性提供一種控制存取的方式,允許我們在屬性被讀取或寫入時進行監測、驗證或計算。
#### 本章節提到的功能
- 用Object.defineProperty方法定義get set
- 物件實質裡定義get取值器 set設值器
- 用 class 定義 get set
- 用set來驗證屬性值
- 定義可計算屬性
#### 觀察到現象
- 有做法是在 set 裡設置Validate、if else、判斷輸入是否為正確的驗證碼
- get set 是對物件屬性的操作,可以透過get取得值,透過 set 來重新設定值
- 透過 Object.defineProperty 建立模擬私有變數,檢查get set 的 scopes 有產生閉包
#### 疑問
- 目前已知 get set 是為了控制物件屬性,直接改物件不是也行?為什麼需要get set?(已解)
- 做了實驗,寫了get set 在inspect 裡的scopes沒看到閉包,他不算閉包,只是某方面來說兩者挺像?(已解,在==236==)
#### Chris 語錄
`Date.now()`(unix timestamp) 是 integer 是一個抽象數字。而date顯示出來的字串日期,則是透過 get set 實做出來的結果。
set
- 改變值之前先動手腳
- 數學概念-定義域:某數值會有合理範圍
- 抽象存取方式
8.6 為例
這裡是抽象,舉水電,使用者只知道開關與結果,但是水電配置使用者不懂,所以除了呼叫以外就叫做抽象,其他為非抽象(使用者觀點)
```javascript=
const shogun = {
name: "Watson",
//家族
clan: "Li",
get fullTitle() {
return this.name + " " + this.clan;
},
set fullTitle(name) {
const segments = name.split(" ");
this.name = segments[0];
this.clan = segments[1];
},
};
```
defind:上方的設計是為了實現下方的抽象
```javascript=
shogun.name;//"Watson"
shogun.clan;//"Li"
shogun.fullTitle;//"Watson Li"
shogun.fullTitle = "Jeremy Kuo";//改值 name:"Jeremy",clan:"Kuo"
```
get
- 數學概念-值域:輸出值會檢視過,比方一天不會有25h,所以get會檢視過後才會輸出結果。
- 相同的值,可以經由自己的設定,調整成不同格式輸出。比方設定單位轉換,時間、尺寸、匯率(舉例:在get設定method,只要輸入NTD:1,就會印出已設定好的匯率)、資料過濾,哪個欄位顯示,拿到資料前作手腳(有點類似上次提到的,不想在重寫function,所以透過繼承取得基礎型別的code共用)。
### 8.2 使用proxy(代理)來控制對物件得存取

==242==
### 比較
#### get set
- get set 只能控制單一屬性的存取
- get set 可做出紀錄日誌、資料驗證、計算屬性。
#### proxy
- proxy 能夠控制與物件之間的所有互動,包括對方法的呼叫
- proxy 除了包含上述功能,還能很容易地為程式做效能剖析及量測,也可用來對屬性自動設值,避免空值異常,或是為DOM這類容器提供一層包裝,減少瀏覽器相容性問題
==243==
### 8.7 用 Proxy 建構器函式來建立代理
```javascript=
const emperor = {name: "Watson"}
//使用proxy建構器函式,將emperor物件包裝在 representative 代理物件裡。
const representative = new Proxy(emperor, {
get:(target,key)=>{
//判斷目標物件是否有key值
return key in target ? target[key]
//此處為proxy功能,對get設置機關,有問題就會觸發訊息
:"Don't bother the emperor !"
},
set:(target, key, value)=>{
target[key] = value;
return true
}
})
//直接呼叫
emperor.name;//"Watson"
//藉由proxy代理物件呼叫
representative.name;//"Watson"
//呼叫不存在的屬性
emperor.nickName;//undefined
//藉由proxy呼叫不存在屬性,會跳出在get設定中的警告訊息
representative.nickName;//"don't bother the emperor !"
//透過proxy新增屬性
representative.nickName = "càitóu"
//呼叫emperor新增的屬性
emperor.nickName;//"càitóu"
//藉由proxy呼叫新增屬性
representative.nickName;//"càitóu"
```
- target 目標物件本身,也就是emperor
- key 就是屬性名稱
- value 屬性的值
>?:直接用set也可以設值,那proxy用意?此處尚無體會proxy的點,還是機關部分
==244==
### 在8.7裡面,proxy 作用像是為 get 設立了一個機關,而機關的功能是
- 如果目標物件確實有指定屬性,就會回傳屬性
- 如果目標物件裡沒有指定屬性,就會回傳警告訊息(沒proxy會傳undefined)
### 關於使用直接呼叫和proxy呼叫差別(可參照圖8.4)
```javascript=
//直接呼叫
emperor.name;//"Watson"
//藉由proxy代理物件呼叫
representative.name;//"Watson"
```
- 如果直接呼叫emperor.name屬性,會回傳"Watson"
- 如果使用 proxy ,get會被隱性呼叫,由於目標物件有找到name屬性,所以也會回傳"Watson"
==245==
>讀 注意:
### 8.7程式碼,如果直接對物件存取不存在的屬性
```javascript=
//呼叫不存在的屬性
emperor.nickName;//undefined
//藉由proxy呼叫不存在屬性,會跳出在get設定中的警告訊息
representative.nickName;//"don't bother the emperor !"
```
- 直接存取 emperor.nickName 會得到 undeined
- 透過 proxy 來存取則會啟動==取值動作處置器==
> 導讀圖8.4
### 設置新屬性
透過 proxy 來設置新屬性`representative.nickName = "càitóu"
`。此設值是透過 proxy 完成,所以會觸發設值機關,記錄一段訊息並且指派一個新的屬性給emperor。當然也是可以使用物件本來新增值
==246==
使用 proxy 要點在於啟動機關來控制目標物件的存取
>導讀四點舉例
下面稍微提一下
proxy 機關無法對相等運算子(== or ===),instanceof,typeof進行複寫操作。以相等運算子為例,在比較兩個物件時,不應該對該物件有存取行為。基於類似理由,我們也不能為instanceof, typeof設置機關。
### 8.2.1 使用代理來記錄日誌
本篇章針對日誌探討,在運作程式時,想了解運作原理,或是找出bug,這時就需要取得資訊,知道程式何時編輯,以及編輯內容等
#### 8.8 未使用代理
>?:個人覺得書上的範例不太直覺,用物件集合方式來比較,下面我用我自己的方式
```javascript=
const ninja = {
_name: "Yoshi",
get name() {
console.log(`Getting property "name": ${this._name}`);
return this._name;
},
set name(value) {
console.log(`Setting property "name" to: ${value}`);
this._name = value;
},
//當我新增Weapon 我就要在重寫一次 get set
get weapon() {
console.log(`Getting property "weapon": ${this._weapon}`);
return this._weapon;
},
set weapon(value) {
console.log(`Setting property "weapon" to: ${value}`);
this._weapon = value;
},
};
ninja.name; // Getting property "name": Yoshi
ninja.weapon = "sword"; // Setting property "weapon" to: sword
console.log(ninja.weapon); // Getting property "weapon": sword
```
#### 8.9 使用代理
```javascript=
function makeLoggable(target) {
return new Proxy(target, {
get: (target, property) => {
console.log(`hey, I'm calling u "${property}:${target[property]}"`);
return target[property];
},
set: (target, property, value) => {
console.log(`ohh, We add "${property}:${value}" in the object`);
target[property] = value;
},
});
}
//這邊建立物件
let ninja = { name: "Yoshi" };
//丟到function裏面,成爲target
ninja = makeLoggable(ninja);
ninja.name;
ninja.weapon = "sword";
```
以上面兩個日誌範例的差別
- get set 如果新增物件屬性,只能針對特定屬性"手動更新",不能動態改變日誌內容,這樣就會每次新增一個物件屬性就要在重寫一次get set。
- proxy 則可以依據以設定好內容搭配樣板字面值自動更新日誌,我只要寫一次get set就好,其他交給proxy的參數 property 與 value
### 8.2.2 使用代理來測量效能
主要用來測試函式的效能,而不需要更改函式的內容
#### 8.10 使用代理來測量效能
>?:這裏proxy的部分不懂,需要解惑
```javascript=
//使用代理來測量效能
function isPrime(num) {
num < 2 ? false : true;
for (let i = 2; i < num; i++) {
return num % i === 0 ? false : true;
}
}
console.log(isPrime(22)); //false
console.log(isPrime(23)); //true
isPrime = new Proxy(isPrime, {
//this apply is proxy handler
apply: (target, thisArg, args) => {
//開始計時
console.time("isPrime");
//idk what it is, only know it's collect result of true false
//後來經由測試,thisArg是空值 undefined
const result = target.apply(thisArg, args);
//停止計時器
console.timeEnd("isPrime");
return result;
},
});
isPrime(1299827);
```
==250==
### 8.2.3 藉由代理自動對屬性設值
```javascript=
function Folder() {
return new Proxy(
{},
{
get: (target, property) => {
console.log(`reading ${property}`);
if (!(property in target)) {
target[property] = new Folder();
}
return target[property];
},
}
);
}
const rootFolder = new Folder();
try {
rootFolder.ninjasDir.firstNinjaDir.ninjaFile = "yoshi.txt";
pass("an exception wasn't raised");
} catch (e) {
throw new Error("An exception has occurred");
}
```
>?:目前理解這運作原理,當get偵測到不存在的屬性,就會自動建立,new 出一個新的Folder (實例?)
>所以在下面看到當存取不存在的屬性時,不會報錯。面對空值異常,這是一個避免的工具
==252==
### 8.2.4 使用代理來實作陣列負數索引值
js 不支援負數索引,但是可以透過proxy來模擬這功能
#### 8.12 藉由代理來模擬陣列負數索引值
```javascript=
function createNegativeArrayProxy(array) {
//判斷:如果不是陣列就拋出錯誤訊息
if (!Array.isArray(array)) {
throw new TypeError("Expected an array");
}
//回傳proxy,以陣列作爲代理對象
return new Proxy(array, {
//讀取到索引值就觸發取值機關
get: (target, index) => {
//將index 利用一元正號運算子的特性,將值轉換爲數字
//怕遇到轉型問題
index = +index;
//由於陣列索引不能使用負數來移動索引位置(會出現undefined),所以判斷,
//如果索引值小於 0(=負索引),我們將負索引轉換為對應的正索引:
// 計算方式是將陣列的長度加上目前的索引值 (target.length + index)。
//假設傳入 -1,程式會計算 3 + (-1) = 2,對應陣列中的索引 2, "hattori"。
// 這樣的邏輯類似 string.at(), -1 對應倒數第一個元素。
//const str = "hello";
//console.log(str.at(-1)); // "o" (倒數第一個字元)
return target[index < 0 ? target.length + index : index];
},
set: (target, index, val) => {
index = +index;
return (target[index < 0 ? target.length + index : index] = val);
},
});
}
const ninjas = ["yoshi", "kuma", "hattori"];
const proxiedNinjas = createNegativeArrayProxy(ninjas);
console.log(ninjas[0]);//yoshi
console.log(ninjas[1]);//kuma
console.log(ninjas[2]);//hattori
console.log(proxiedNinjas[0]);//yoshi
console.log(proxiedNinjas[1]);//kuma
console.log(proxiedNinjas[2]);//hattori
console.log(typeof ninjas[-1]);//undefined
console.log(typeof ninjas[-2]);//undefined
console.log(typeof ninjas[-3]);//undefined
console.log(proxiedNinjas[-1]);//hattori
console.log(proxiedNinjas[-2]);//kuma
console.log(proxiedNinjas[-3]);//yoshi
proxiedNinjas[-1] = "Hachi";
ninjas[1] = "Hachi"
```
==254==
### 8.2.5 使用代理的效能成本
proxy 是用來==控制物件存取動作==的代理人。我們可以在 proxy 中定義機關,每當對 proxy 物件進行了某些動作時,就會觸發它們。
proxy 可用來控制物件的存取與操作,並實現功能如效能測量、記錄日誌、自動設值及負數索引等。然而,proxy也有一些==缺點==,所有動作都必須透過 proxy ,因此無形中增加了一道間接層,讓我們在實作上述功能之餘,也產生了額外的處理動作,進而影響程式的效能。
為測試效能差異,我們可參考程式列表 8.12,比較普通陣列與 proxy 陣列的效能表現。
==255==
#### 8.13 檢查代理的效能限制
```javascript=
function measure(item) {
const starTime = new Date().getTime();
for (let i = 0; i < 500000; i++) {
item[0] === "yoshi";
item[1] === "kuma";
item[2] === "hattori";
}
return new Date().getTime() - starTime;
}
const ninjas = ["yoshi", "kuma", "hattori"];
const proxiedNinjas = createNegativeArrayProxy(ninjas);
console.log("Proxies are around", Math.round(measure(proxiedNinjas) / measure(ninjas)));
//Proxies are around 23
```
>?:以結果來看,在chrome 瀏覽器上,proxy 比普通陣列慢了23倍
### 總結
Proxy 是 JS 的一種工具,允許攔截和定義對目標物件的基本操作,所有動作都必須經過 proxy,當指定的行爲發生,便會觸發定義在proxy的機關。但是proxy執行速度不會,若頻繁執行,便會帶來效能上的負擔
#### 本章節提到的功能
- 記錄日誌
- 函式效能與proxy效能測試
- 資料驗證
- 自動爲屬性設值
- 陣列負數索引值
#### 提問
#### Chris 語錄
## CH9 處理資料集合
[Chris 分享](https://hackmd.io/@unayojanni/HyzWz9R0u/%2F%40unayojanni%2FryUNVTPyt)
==259-262==
講解陣列的特性,陣列是物件,當丟東西進去時,他不會阻攔,甚至事會幫你新增值。
```javascript=
const test = ["Chris", "Watson"];
test[2]//undefind
test;//["Chris", "Watson", undefined];
```
上面結果test索引2是 undefind
如果後續在設值進去就會取代undefined
```javascript=
test[2]="Jeremy"
test;//["Chris", "Watson", "Jeremy"];
```
==262==
這裏是在講test陣列的長度是2,所以test.length-1 = 2-1 = 1
後來會長成這樣test[1],而索引值1是"Watson"
```javascript=
const test = ["Chris", "Watson"];
test[test.length-1]//"Watson"
```
==263~266==
這裏在講陣列有四大方法可做到兩端新增或移除,會影響陣列長度
- push
- 在 array 最尾端增加資料
- unshift
- 在 array 最前端增加資料
- 會影響索引值
- pop
- 移除 array 最尾端資料
- shift
- 移除 array 最前端資料
- 會影響索引值
==266==
介紹delete方法,可以刪除陣列裡面的內容,但不會影響陣列長度
```javascript=
const name = ["Chris", "Watson", "Jeremy","Jami","Fang","Tangerine"];
delete name[1]
name.length == 6
name;//["Chris", undefined, "Jeremy","Jami","Fang","Tangerine"]
```
==267~268==
這裏介紹 splice 方法,很有趣,他會分割內容和影響長度
```javascript=
const name = ["Chris", "Watson", "Jeremy"];
let cut = name.splice(1,1)
cut.length == 1
cut;//["Watson"]
name.length == 2
name;//["Chris", "Jeremy"]
```
由上面程式碼可知 name 影響了,cut 使用賤方法取走了 name 的一個資料。
splice 有兩個引數
- 第一個是選中要拆的索引值
- 第二個是決定從索引開始算要刪除多少資料,假設寫2就會是從選定的索引值開始算,往後刪除兩個,如果只寫索引不寫後面的值,那這樣後面的全部都會倍拆掉
```javascript=
const name = ["Chris", "Watson", "Jeremy","Jami","Fang","Tangerine"];
let cut = name.splice(1)
cut.length == 5
cut;//["Watson","Jeremy","Jami","Fang","Tangerine"]
name;//['Chris']
```
==269==
這裏講解陣列有一個loop方法是 forEach