---
# System prepended metadata

title: W_JS忍者
tags: [javascript]

---

# W_JS忍者

[toc]
# Watson infor
>我會閱讀所有章節，依據章節節錄重點，也當作個人複習

# page20
個人理解是一個資料結構，就像連連看
ex 
```html=
<html>
<head>
    <meta>   
</head>
<body>
```
這樣就會向下圖
![Screenshot from 2024-05-23 14-50-33](https://hackmd.io/_uploads/ByE8Pv3QR.png)

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.==
此為執行後結果
![image](https://hackmd.io/_uploads/ryomGJAuC.png)

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

![image](https://hackmd.io/_uploads/SkAalJAOA.png)

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中，兩種最常用得函式定義方法，是使用函式宣告和函式表達式

#### 函式宣告

![image](https://hackmd.io/_uploads/SymV_I0O0.png)
- 必要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表達是一部份，如果呼叫，我們可以先給函式表達式指派給一個變數，之後可以使用該變數來呼叫

### 立即函式

![image](https://hackmd.io/_uploads/rJhpDNkK0.png)

左為標準函式呼叫，右為立即呼叫得函式表達式 ==??==
識別項 ==??==
- 左：在最基本得函式呼叫中，我們是透過識別項來指定呼叫得函式
- 右：首先建立一個函式，然後立即呼叫這個新建立得函式，這種作法稱為立即呼叫得函式表達式(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");

```
---
![image](https://hackmd.io/_uploads/Bypl6IkFR.png)
- 零或多個參數需要使用括號，只有一個參數括號可有可無
- 必要箭頭運算子
- 如果箭頭函式得主體只有一個表達式，那即是函式回傳值
- 如果主體是一段程式區塊，就和標準函式一樣，若沒有return敘述句，則回傳值是undefined;反之，回傳值就是return敘述句所回傳的值

==56.==
## 3.4 引數(argument)與函式參數(parameter)
- 參數是我們在函式定義中所列出得變數
- 引數是當我們呼叫函式時傳遞給他的值

==57.==
![image](https://hackmd.io/_uploads/HyKheukY0.png)
閱讀＠

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

==58.==
![image](https://hackmd.io/_uploads/ByKyGdytR.png)
解釋圖內文＠

### 3.4.1 不定參數
🥷：ES6加入

==59.==
> 程式列表3.7 使用不定參數
![image](https://hackmd.io/_uploads/HJfFLdyF0.png)

在參數名稱之前加上省略符號`...`便會將其轉換為==不定參數得參數陣列==，剩餘得傳入引數將會包含在其中

==??==
在圖中multiMax使用了四個引數來呼叫:multiMax(3,1,2,3)，第一個引數值3被指派給maltiMax函式得first。由於第二個參數是==不定參數==，所有剩餘得值(1,2,4)都放在一個新陣列remainingNumber中，接者，我們對陣列做降冪排序，在取出最大得數

🥷：只有最後一個函式參數可以是不定參數，若放置在非最後得參數將會得到警告
`SyntaxError: parameter after rest parameter.`

==60.==
## 3.4.2 預設參數
🥷：ES6加入
![image](https://hackmd.io/_uploads/BJ2rEo1FR.png)
當我們呼叫函式並且忽略相應對應得引數值時，正如範例中得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
![image](https://hackmd.io/_uploads/B12SyJeLJx.png)

<!-- 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

```
![Screenshot from 2024-12-31 11-52-15](https://hackmd.io/_uploads/SkGicyb8kx.png)
> 產生閉包

==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(代理)來控制對物件得存取
![image](https://hackmd.io/_uploads/BJxVwa_Ikl.png)
==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


