---
# System prepended metadata

title: JS-2

---

JS-2
===

## 函數
> - 定義函數: `function 函數名() { 函數體 }`
> - 調用函數: `函數名();`
`vi main.js`
```javascript=
/* 如果沒有用函數封裝起來, 
 * 而每次想要求1-10的和時, 都要寫一次下面那串code
 * 那如果要修改成1-20時, 每個都要改
 * 
 * 調用函數時, 只要改函數內容就好了
var sum = 0;
for (var i=1; i<=10; i++) {
  sum += i;
}
console.log(sum);
*/

/* 定義函數:
function 函數名() {
  函數體
}
*/
function sumFn() {
  var sum = 0;
  for (var i=1; i<=10; i++) {
    sum += i;
  }
  console.log(sum);
}

/* 調用函數:
函數名()
*/
// sumFn()
```
`main.html`
```htmlmixed=
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <!-- 引入js, 類似python的 import -->
    <script src='main.js'></script>
    <script>
      // 調用函數
        // 愛怎麼調, 就怎麼調
      sumFn(); // 55
      sumFn(); // 55
      sumFn(); // 55
    </script>
  </head>
  <body>
  </body>
</html>
```

### 參數
> - 形參: 沒有具體的值, 僅用來佔位用, 類似`var 變量;`
> - 實參: 對形參附值
> - 跟Python不一樣的地方在: JS沒有限定幾個形參就要幾個實参
```javascript=
/* 語法:
function 函數名(形參1, 形參2, ...) {
  // 函數體
}
function(實參1, 實參2,...)
*/
/* 把形參看成變量, 把實參看成對變量附值即可 */

  function getSum(num1, num2) {
    console.log(num1+num2)
  }
  getSum(1,2) // 3
  /* 變量視角
  var num1;
  var num2;
  num1=1;
  num2=2
  */
  getSum(100,200) // 300

// 傳變量
  var x = 10;
  var y = 11;
  getSum(x,y);

// 函數內部修改值, 不會影響外面實參的值, 
// 因為調用的時候是實參複製一份給形參
  /* 類似於
  a = 100 // 100
  b = a // 100
  b = 200 // 200
  a // 100 > 沒變
  */

  function getNum(a, b) {
    a = 100;
    b = 200;
    console.log(a,b);
  }
  var a = 1;
  var b = 2;
  getNum(a,b) // 100 200
  console.log(a,b)// 1 2

// Q. 求圓面積 pi * r * r
  function circus(r) {
    var pi = 3.1415926;
    console.log(pi*r*r);
  }
  circus(3);

// Q. 求三個數中的最大值
// 解一
  function getMax(num1, num2, num3) {
    if (num1>num2) {
      if (num1>num3) {
        console.log('MaxNum = ' + num1);
      } else {
        console.log('MaxNum = ' + num3);
      }
    } else {
      if (num2>num3) {
        console.log('MaxNum = ' + num2);
      } else {
        console.log('MaxNum = ' + num3);
      }
    }
  }
// 解二
  function getMax(num1, num2, num3) {
    if (num1>num2 && num1>num3) {
      console.log('MaxNum = ' + num1);
    } else if (num1>num2 && num1<num3) {
      console.log('MaxNum = ' + num3);
    } else if (num1<num2 && num2>num3) {
      console.log('MaxNum = ' + num2);
    }
  }

  getMax(2,1,4); // MaxNum = 2
  getMax(10,20,5); // MaxNum = 20
  getMax(30,20,5); // MaxNum = 30
  
// Q. 求n個數的最大值
  function getMax(arr) {
    var max = arr[0] // 假設第一個是最大的
    for (i=1; i<arr.length; i++) {
      if (max < arr[i]) {
        max = arr[i]
      }
    }
    console.log('MaxNum = ' + max);
  }

  arr = [123,1,56,23,55];
  getMax(arr);

// Q. 判斷一個數是否為質數
  function judgePrime(num1) {
    var isPrime = true; // 假設每個數都是質數
    for (i=2; i<num1; i++) {
      if (num1%i === 0) { // 如果沒有餘數, 表示可以被整除而不是質數
        isPrime = false; // 改為不是質數
        break; // 既然確定不是質數, 就不用再繼續判斷了
      }
    }
    if (isPrime) { // 輸出
      console.log('質數')
    } else {
      console.log('不是質數')
    }
  }
  judgePrime(4); // 不是質數
  judgePrime(5); // 質數
  judgePrime(6); // 不是質數
  judgePrime(7); // 質數
  
  
// 沒有限定幾個形參就要幾個實參
/* Python嚴格限定
 
 * 實參 > 形參
  In [16]: def test(): 
      ...:     print('haha') 
  In [17]: test(1,2,3) # test() takes 0 positional arguments but 3 were given
  
 * 形參 > 實參
  In [18]: def test(a,b,c): 
    ...:     print('haha') 
  In [19]: test() # test() missing 3 required positional arguments: 'a', 'b', and 'c'
*/


  function test(a,b,c) {
    console.log(a,b,c);
  }
  test() // undefined undefined undefined : 實參不足就都補undefined
  test(1) // 1 undefined undefined
  test(1,2,3,4) // 1 2 3: 實參超過也沒差
```

### 函數返回值
> - 函數沒有返回值時, 會返回undefined
> - return 後面可以不寫東西, 返回 undefined
> - return 後面跟的東西就是返回值
> - return 會結束函數執行
```javascript=
/* 語法: return
function 函數名(形參..) {
  // 函數體
  return 返回值; 
}
*/

// Q. 求一組數的最大值
  function getMax(arr) {
    var max = arr[0];
    for (i=1; i<arr.length; i++) {
      if (max < arr[i]) {
        max = arr[i];
      }
    }
    return max; // 返回
  }

  var arr = [32,55,23,4,11,2];
  var max = getMax(arr);
  console.log(max); // 55

// Q. 求一組數的最小值
  function getMin(arr) {
    var min = arr[0];
    for (i=1; i<arr.length; i++) {
      if (min > arr[i]) {
        min = arr[i];
      }
    }
    return min; // 返回
  }

  var arr = [32,55,23,4,11,2];
  var min = getMin(arr);
  console.log(min); // 2

// Q. 求階乘 5! = 5*4*3*2*1 = 120

  function getFactorial(num) {
    var multiply = 1;
    for (i=1 ; i<=num; i++) {
      multiply *= i;
    }
    return multiply;
  }

  var multiply = getFactorial(5); 
  console.log(multiply); // 120


// Q. 求 1!+2!+3!+..n!

  // !! 發現一個問題, 如果我兩個 i 都沒有用var 定義時, 答案會是127, 為何? 
  function getFactorial(num) {
    var multiply = 1;
    for (var i=1 ; i<=num; i++) {
      multiply *= i;
    }
    return multiply;
  }

  function getSum(n) {
    var sum = 0;
    for (var i=1; i<=n; i++) {
      /* 這樣調用了兩次, 效率不高, 拿個東西把getFactorial存起來
      sum += getFactorial(i);
      console.log(i, getFactorial(i))
      */
      multiply = getFactorial(i);
      sum += multiply;
      console.log(i, multiply);
    }
    return sum;
  }
  var resault = getSum(5);
  console.log(resault); // 153


// 函數沒有返回值時, 會返回undefined
/* Python 是返回None

  In [14]: def test(): 
      ...:     print('haha') 
  In [15]: print(test())                  
  haha
  None
  
*/
  function test(a,b) {
    console.log(a,b); // 5 6
  }
  var r = test(5,6);
  console.log(r); // undefined

// return 會結束函數執行
// return 後面可以不寫東西, 返回 undefined
  /* python是返回None
  In [12]: def test(): 
      ...:     return 
  In [13]: print(test())                  
  None
  */
  function test(a,b) {
    return;
    console.log(a,b); // 沒有執行
  }
  var r = test(5,6);
  console.log(r); // undefined
```


### arguments
> - JS 所有函數都內建一個 arguments 屬性
> - arguments 對象存儲了所有傳進來的實參
> - arguments 可以進行遍歷
> - 看起來類似 python 的 *args 的作用, 用在參數數量不固定時
```javascript=
/* 複習python *args, **kwargs
 * python參數有兩種:
 * - 位置參數(position argument)
 * - 關鍵字參數(keyword argument)
 * 
 * - *args 傳遞無名可變長參數列表(位置參數), 本質上就是 tuple
 * - **kwargs 傳遞關鍵字可變長參數列表(關鍵字參數), 本質上就是 dict
 * 
 * - 如果兩個同時使用時, 由於位置參數需要位置對應, 故*args 應該在**kwargs前面
 * 
    def fn(*args, **kwargs):
        pass
    fn(*args, **kwargs)
*/
 
// arguments 對象存儲了所有傳進來的實參
  function test() {
    console.log(arguments);
  }
  test(1,2,3) // Arguments(3) [1, 2, 3, ...]
              // 0: 1 > 有各種位置
              // 1: 2
              // 2: 3 
              // length: 3 > 也有長度屬性

// arguments 可以進行遍歷
// Q. 求任意個數的最大值
  function maxNum() {
    var max = arguments[0]
    for (var i=1; i<arguments.length; i++) {
      if (max < arguments[i]) {
        max = arguments[i];
      }
    }
    console.log(max);
  }
  maxNum(44,33,52,1,43,12) // 52

// Q. 求任意個數的和
  function getSum() {
    var sum = 0;
    for (i=0; i<arguments[i]; i++) {
      sum += arguments[i];
    }
    console.log(sum);
  }
  getSum(1,2,3,4,5); // 15
```
### 函數封裝練習
```javascript=
// Q. 求 Fibonacci 數列中第n個數是多少? (1,1,2,3,5,8,...)
  function getFib(n){
    var firstFib = 1;
    var secondFib = 1;
    var fib ;
    for (var i=3; i<=n; i++) {
      fib = firstFib + secondFib;
      firstFib = secondFib;
      secondFib = fib;
    }
    console.log(fib);
  }
  getFib(3); // 2
  getFib(4); // 3
  getFib(5); // 5
  getFib(6); // 8

// Q. 返回一個反轉陣列 [1,2,3,4,5] -> [5,4,3,2,1]
  function reverseArr(arr) {
    var newArr=[];
    for (i=arr.length-1; i>=0; i--) {
      newArr[newArr.length] = arr[i]
    }
    return newArr
  }
  var arr = [1,2,3,4,5];
  console.log(reverseArr(arr));
  var arr2 = ['toyz', '刷漢堡', '包鬼', '光頭哥哥', 'RIP'];
  console.log(reverseArr(arr2));

// Q. 冒泡排序, 由小至大
  function bubbleSort(arr) {
    for (i=1; i<arr.length; i++) {
      var isSort = true;
      for (j=0; j<=arr.length-i; j++) {
        if (arr[j] > arr[j+1]) {
          var temp = arr[j+1];
          arr[j+1] = arr[j];
          arr[j] = temp;

          isSort = false;
        }
      }
      if (isSort) {
        break;
      }
    }
    return arr;
  }

  var arr = [54,26,93,17,77,31,44,55,20];
  console.log(bubbleSort(arr));


// Q. 判斷某年份是否為閏年(能被4整除且不能被100整除, 或能被400整除)
  function judgeLeapYear(n) {
    if (n%4 === 0 && n%100 !== 0 || n%400 === 0) {
      console.log('閏年');
    } else {
      console.log('不是閏年');
    }
  }
  judgeLeapYear(400); // 閏年
  judgeLeapYear(88); // 閏年
  judgeLeapYear(200); // 不是閏年

// Q. 輸入年月日, 計算天數

  // 必須判斷是否為閏年
  function judgeLeapYear(n) {
    var resault = false;
    if (n%4 === 0 && n%100 !== 0 || n%400 === 0) {
      resault = true;
    }
    return resault;
  }

  // 累加天數
  function getDays(year, month, day) {
    var days = day; // 先把當月的天數加上去
    for (var i=1; i<month; i++){
    
      /* 用if的話太麻煩了, 
       * 這邊全部都是判斷 ===, 所以改用 switch 會簡潔很多
      if (i===1) {
        days += 31;
      }else if (i===3) {
        days += 31;
      }
      */
      
      switch (i) {
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
          days += 31;
          break;
        case 4:
        case 6:
        case 9:
        case 11:
          days += 30;
          break;
        case 2:
          if (judgeLeapYear(year)){
            days+=29;
          } else {
            days+=28;
          }
          break;
      }
    // console.log(days);
    }
    return days;
  }

  getDays(2019,11,19) // 323
```

### 匿名函數
> - 語法: `function() {函數體}`
> - 匿名函數不能單獨存在
>   - 可以賦值給一個變量(函數表達式)
>   - 可以匿名函數自調用(自調用函數)
```javascript=
// 匿名函數
/* JS 語法
  function() {
    // 函數體
  }
*/
  
/* 函數表達式
var fn = function() { # 後面是匿名函數
  // 函數體
}

fn()
*/

  var fn = function() {
    console.log('haha');
  }
  fn(); // haha
  
  /* Python匿名函數 lambda 參數: 式子
  a = lambda x,y: x+y # 匿名表達式
  a(1,2) # 3
  */


/* 自調用函數
 * 匿名函數用小括號刮起來, 變成一個表達式, 後面再加一個調用的小括號
(function() {
  // 函數體
})()
*/

  (function() {
    console.log('haha');
  })() // haha
```




### 函數是一種數據類型
> - 可作為參數
> - 可作為返回值
```javascript=
var fn = function() {}
console.log(typeof fn); // function

// 函數作為參數
  var fn = function() {
    console.log('haha')
  }
  var fn2 = function(fn) {
    fn();
  }
  var a = 100
  fn2(fn); // haha
  fn2(a); // 傳遞非函數進去 > fn is not a function
  
// 函數作為返回值
/* python
In [33]: def fn(a): 
    ...:     b = 100 
    ...:     def fn2(): 
    ...:         print(a+b) 
    ...:     return fn2 # 函數當成返回值
    
In [34]: fn(5)                          
Out[34]: <function __main__.fn.<locals>.fn2()>
In [35]: a = fn(5) 
In [36]: a()    
105

 * Python的閉包(帶有數據的大功能編寫)常用這個技巧
*/


  function test(a) {
    var b = 10;
    return function() {
      console.log(a+b);
    }
  }
  var fn = test(5);
  console.log(typeof fn); // function : 返回了一個函數
  fn(); // 15 : 調用返回的函數
```

- 函數中如果需要 window ，要用傳的
  - 避免如果壓縮後，window 這個單詞背壓縮成某個字，造成不是指向 window 對象
  - 減少查找，提高效率
```
;(function (window) {
    window.xxx = ooo
})(window);
```

- 立即執行函數最好接收一個真實的 undefined
  - 因為舊的瀏覽器 undefined 是可以被修改的，規範上他不是保留字
  - 為了避免 undefined 被人改過而產生問題，接收一個沒有傳參的形參就能保證作用域內的 undefined 是真正的 undefiend
```
;(function (undefined) {

})();
```






## JS 基本規範
```javascript=
// 命名規範
// - 命名要有意義
// - 變量常用名詞(代表一個東西)
// - 函數常用動詞(做一件事情)

// 變量規範
// - 操作符號前後要有空格
// var a = 1000;

// 註釋規範
// - 註釋後面空一格
   // xxxx 
   
// 空格規範, 換行規範
   var name = 'xx';
   if (true) {
   
   }
   
   for (var i = i; i <= 10; i++) {
   
   }
   
   function fn() {
   
   }
```

## 作用域
> - 作用域: 變量或函數起作用的地方
> - ECMAScript中, 作用域有兩種
>   - 全局作用域
>     - 全局作用域定義的『全局變量』, 任何地方都能訪問
>     - 關閉網站後, 全局變量所佔的內存會被回收
>   - 局部作用域
>     - 任何函數內部都有一個局部作用域, 
>     - 在局部作用域中定義的局部變量, 只有該函數內可用
>     - 函數執行完後，局部變量所佔內存會被回收
> - 塊級作用域
>   - 塊級: 任何一個()或{} 包起來的區域都屬於一個塊
>   - 在塊級內定義的東西, 塊級外不可以直接用
>   - ==**ES5 之前，都沒有塊級作用域**==
> - 作用域鏈
>   - 全局作用域> 0級鏈
>   - 函數內定義的東西 > 1級鏈
>   - 函數內定義函數, 內圈函數裡定義的東西 > 2級鏈
```javascript=
// 全局變量
  // 一般寫法
  var a = 10; // 全局變量
  function test() {
    console.log(a);
  }
  test(); // 10
  
  // 特殊寫法
  function test() {
    a = 10; // 沒有var, 執行後也是丟到全局變量, 不過很少人這樣寫
  }
  test();
  console.log(a); // 10
  
// 局部變量

  function test() {
    var a = 10;
    console.log(a); // 10
  }
  test();
  console.log(a); // a is not defined
  
// 塊級作用域
{
  var a = 10;
  console.log(a); // 10 
}
console.log(a); // 10 > 現在還沒有塊級作用域

// 作用域鏈
  function test() {
    var a = 10;
    function test2() {
      var b = 10;
    }
  }
  var c = 10;
  // 0級鏈: test, c
  // 1級鏈: a test2
  // 2級鏈: b
  
  
print(a)
a =10
```

## 預解析
> - Js是由Js解析器執行, Js解析器執行code會有兩個步驟:
>   1. 預解析
>   2. code執行

> - 預解析
>   - 把變量聲明提升到最上面, 只提升聲明, 不提升附值, 聲明時的值為 undefined
>   - 把函數聲明提升到最上面, 只提升聲明, 不提升調用
>   - 先提升var, 再提升func
>   - 預解析的過程中, 如果函數和變量同名, 函數優先
>   - 提升完後再 `放置` 可執行代碼

```javascript=
// 神奇的問題
  console.log(a); // undefined > 沒有炸裂！因為預解析把聲明丟到前面了
  var a = 10;
  
  // 預解析後
    var a;
    console.log(a);
    a = 10;

// 神奇的問題
a(); // 10 > 也沒有炸裂! 因為預解析把聲明函數往上丟了
function a() {
  console.log(10);
}
  // 預解析後
  function a() {
    console.log(10);
  }
  a();
  
// 神奇的問題
  var a = 25;
  function abc() {
    console.log(a); 
    var a = 10;
  }
  abc(); // 
  
  // 預解析
  var a;
  function abc() {
    // 局部作用域 預解析
    var a;
    console.log(a);
    a = 10;
  }
  a = 25;
  abc(); // undefined
  
// 神奇的問題: 函數優先
  console.log(a); // a() { console.log('aaaaa'); }
  function a() {
    console.log('aaaaa');
  }
  var a = 1;
  console.log(a); // 1
  
  // 預解析
    var a; // var 高於 function
    function a() {
      console.log('aaaaa');
    }
    console.log(a); // a() { console.log('aaaaa'); }
    a = 1;
    console.log(a); // 1
    
// Q
  var a = 18;
  f1();
  function f1() {
    var b = 9;
    console.log(a); // undefined
    console.log(b); // 9
    var a = '123';
  }
  
  // 預解析
    var a;
    function f1() {
      var b;
      var a;
      b = 9;
      console.log(a); // undefined
      console.log(b); // 9
      a = '123';
    }
    a = 18;
    f1();
    
// Q 重要～
  f1();
  console.log(c);
  console.log(b);
  console.log(a);
  function f1() {
    var a = b = c = 9;
    console.log(a);
    console.log(b);
    console.log(c);
  }
  
  // 預解析
  function f1() {
    // var a = b = c = 9;
    var a; // 局部變量
    a = 9;
    
    // 沒有var, 全局變量!
    b = 9;
    c = 9;
    console.log(a);
    console.log(b);
    console.log(c);
  }
  f1(); // 9 9 9
  console.log(c); // 9
  console.log(b); // 9 
  console.log(a); // a is not defined
```


## 對象

### 對象是什麼
> - 對象是一個「具體」的事務, 具有特徵與行為
> - 「具體」事務:
>   - 手機 > 不是對象, 是一個類型(包含世界上所有的手機)
>   - 我手上這支手機 > 對象
> - 特徵與行為
>   - 特徵:
>     - 名詞, 描述這個對象
>     - Note7, 32G, 有裂痕, 原廠手機殼
>   - 行為
>     - 動詞, 表示對象可以幹嘛
>     - 上網, 打電話, 丟炸彈遊戲

### JS中的對象
> - JS對象是無序屬性 (類似dict> key:value,)
> - 由屬性和方法組成, 每個屬性方法用 `,` 隔開
>   - 屬性 = 特徵
>   - 方法 = 行為

> - 對象與函數的區別
>   - 對象: 封裝屬性方法
>   - 函數: 封裝一串code


> - 對象內方法調用對象的屬性值: `this`, 
>   - 類似 python 實例類裡的實例方法要調用實例屬性用 self
```javascript=

// 創建對象
  var myPhone = { // 對象: 我的手機
    label: 'Samsung', // 屬性(特徵): 三星Note7
    model: 'Note7', 
    callMom: function () { // 方法(行為): 打給老媽
      console.log(0987654321);
    }
  }

// 調用方法
  myPhone.callMom(); // 987654321 > 0 怎麼不見了=.=

// 獲取屬性
  console.log(myPhone.label); // "Samsung"
  console.log(myPhone.model); // "Note7"

// this 在方法中代表當前對象
  // 類似python的 self

  var bigBro = {
    name: 'machi',
    speak: function () {
      console.log(this.name + '底加');
    }
  }
  console.log(bigBro.name) // machi
  bigBro.speak(); // machi底加

  /* python: class
  class BigBro(object):
      def __init__(self):
        self.name = 'machi'
      def speak(self):
        print(self.name + '底加')
        
  print(BigBro().name) # machi
  BigBro().speak() # machi底加
  */
  
// 另外一種調用屬性的方式, 其實就是dict取值的方式 array['key']
  console.log(bigBro['name']); // machi
  console.log(bigBro['speak']); // f
  bigBro['speak']() // machi底加
  

// Q. 創造李星
var leeSin = {
  name: 'LeeSin',
  health: 575,
  healthRegen: 7.5,
  armor: 33,
  magicResist: 32.1,
  energy: 200,
  energyRegen: 50,
  attackDamage: 70,
  attackSpeed: 0.651,
  critDamage: 2,
  range: 125,
  rangeType: 'Melee',
  moveSpeed: 345,
  
  levelUp: function() {
    this.health += 85;
    this.healthRegen += 0.7;
    this.armor += 3.7;
    this.magicResist += 1.25;
    this.attackDamage += 3.2;
  },
  Q: function() { // 略, 打個名字意思意思就好了
    console.log('虎嘯龍吟／震驚百里');
  },
  W: function() {
    console.log('鐵璧金身／易筋洗髓');
  },
  E: function() {
    console.log('狂風暴雨／分筋錯骨');
  },
  R: function() {
    console.log('神龍擺尾');
  }
}

console.log(leeSin.name); // LeeSin
console.log(leeSin.health); // 575

leeSin.levelUp();
console.log(leeSin.health); // 660
leeSin.Q(); // 虎嘯龍吟／震驚百里
```

### 對象創建
> #### 字面量 
>  - 11, '11', [], {}, true, ...
```javascript=
var dog = {
  name: '小黑',
  bark: function () {
    console.log(this.name + 'wonwon');
  }
}
dog.bark(); // 小黑wonwon
```
> #### new Object()
> - `Object` 是一個構造函數
>   - 就是 python 類的父層
> - 通過 `new` 的方式來調用 `Object`
>   - `new` 看起來是創建實例的意思,
>   - 也就是 python 的 \_\_new__方法
>     - \_\_new__是在創建類實例對象的過程中最先調用的方法, 並返回類實例對象
> - `new Object()` 創建一個空的對象
> - 透過動態添加來添加屬性方法

```javascript=
var dog = new Object(); // 創造一個對象
console.log(dog.name); // undefined: 對象裡面是空的, 當然也沒有name

// 動態添加屬性
dog.name = '小黑';
console.log(dog.name); // 小黑

// 動態添加方法
dog.bark = function() {
  console.log(this.name + 'wonwon');
}
dog.bark(); // 小黑wonwon
```

> #### 函數創建對象
> - 用一個函數放一些形參
> - 函數裡面創造一個空對象
> - 接著運用動態添加把東西丟進去
> - 最後返回對象
```javascript=
// 問題:
  // 當我想要創建多隻狗時, 一個一個創會有很多重複的內容
var dog1 = {
  name: '小黑',
  bark: function () {
    console.log(this.name + 'wonwon')
  }
}
var dog2 = {
  name: '小黃',
  bark: function () {
    console.log(this.name + 'wonwon')
  },
}
```
```javascript=
// 解決: 函數創建對象
  function creatDog(name, age) {
    var dog = new Object();
    dog.name = name;
    dog.age = age;
    dog.bark = function () {
      console.log(this.name + 'wonwon');
    }
    return dog;
  }
  var yellowDog = creatDog('小黃', 18);
  var blackDog = creatDog('小黑', 20);

  yellowDog.name // "小黃"
  yellowDog.age // 18
  yellowDog.bark() // 小黃wonwon

  blackDog.name // "小黑"
  blackDog.age // 20
  blackDog.bark() // 小黑wonwon


/* python 寫寫看
In [1]: def createDog(name, age): 
   ...:     class Dog(object): 
   ...:         def __init__(self, name, age): 
   ...:             self.name = name 
   ...:             self.age = age 
   ...:         def bark(self): 
   ...:             print(self.name + 'wonwon') 
   ...:     dog = Dog(name, age) 
   ...:     return dog 
   
In [2]: yellowDog = createDog('小黃', 18)

In [3]: yellowDog.name # '小黃'
In [4]: yellowDog.age # 18
In [5]: yellowDog.bark() # 小黃wonwon
*/
```
> #### 自定義構造函數
> - 命名首字母要大寫(Pascal Case)
> - 就是python的類
```javascript=
function CreateDog(name, age) {
  this.name = name;
  this.age = age;
  this.bark = function () {
    console.log(this.name + 'wonwon');
  }
}
var yellowDog = new CreateDog('小黃', 18);
var blackDog = new CreateDog('小黑', 20);
  // 就是調用 python 的 __new__
  // 返回一個實例對象

yellowDog.name // "小黃"
yellowDog.age // 18
yellowDog.bark() // 小黃wonwon

blackDog.name // "小黑"

/* python 對象
  In [1]: class Test(object): 
     ...:     def __init__(self, name, age): 
     ...:         self.name = name # 類屬性
     ...:         self.age = age 
     ...:     def bark(self): # 類方法
     ...:         print(self.name + 'wonwon') 

  In [2]: a = Test('小黃', 18) # 實例屬性

  In [3]: a.name # '小黃'
  In [4]: a.age # 18
  In [5]: a.bark() # 小黃wonwon

  In [6]: b = Test('小黑', 20)

  In [7]: b.name # '小黑'
  In [8]: b.age # 20
  In [9]: b.bark() # 小黑wonwon
*/
```

### new
> - new 做了什麼事
>   - 創建一個新對象
>   - 讓this指向該對象
>   - 執行構造函數(添加屬性方法等)
>   - 返回對象指向
```python
# python的 __new__ 函數
def __new__(cls, clsName, clsBases, clsDict):
    return type(clsName, clsBases, clsDict)
```

### this
```javascript=
// 函數中的 this 指向 Window
function fn() {
  console.log(this)
}
fn() // Window

// 方法中的 this 指向對象方法所屬的對象
var obj = {
  a: 100,
  fn: function () {
    console.log(this);
  }
}
obj.fn() // {a: 100, fn: ƒ}

// 構造函數中的 this 指向構造函數所創建的對象
function Fn() {
  a = 10;
  this.b = 100;
  console.log(this);
}
var cls = new Fn();  // Fn {b: 100}
```

### 遍歷屬性

```javascript=
/* python
fn = {'name': 'Toyz', 'age': 25, 'isBoy': True}
In [4]: fn = {'name': 'Toyz', 'age': 25, 'isBoy': True} 
   ...: for i in fn: 
   ...:   print(i + ': ' + str(fn[i])) 
                           
name: Toyz
age: 25
isBoy: True
*/

// 這裡有一個對象, 要怎麼遍歷屬性？
  var fn = {
    name: 'Toyz',
    age: 25,
    isBoy: true
  }

// for in 會遍歷 key 出來
  for (var key in fn) {
    console.log(key); // name age isBoy
  }
  
// 獲取值的方法
  // - fn.key > 只能指定固定的某個屬性
    for (var key in fn){
      console.log(key + ': ' + fn.key)
    }
    // name: undefined
    // age: undefined
    // isBoy: undefined
      // fn.key > 指定 key屬性的值, 可是沒有 key 屬性, 所以找無

  // - fn['key'] > 以 str 的形式帶入變量去 dict 內查找 
    for (var key in fn){
      console.log(key + ': ' + fn[key])
    }
    // name: Toyz > 'key' + ': ' + fn['key']
    // age: 25 
    // isBoy: true
    
// 循環附值
  var fn = {};
  for (i=0; i<=10; i++) {
    fn['第' + i + '個'] = i;
  }
  console.log(fn); // {第0個: 0, 第1個: 1, 第2個: 2, 第3個: 3, 第4個: 4, …}

  for (var key in fn) {
    console.log(key + ': ' + fn[key]);
  }
```
### 刪除屬性
`delete function['key']` 
```javascript=
/* python
In [13]: b = {'A': 1, 'B': 2}   
In [15]: b['A'] # 1
In [17]: del b['A']
In [18]: b # {'B': 2}
*/

// 刪除屬性
  var fn = {
      name: 'Toyz',
      age: 25,
      isBoy: true
    }
    
  fn['name'] // "Toyz"
  delete fn['name'] // true
  fn // {age: 25, isBoy: true}
  delete fn.age // true
  fn // {isBoy: true}
```

## 簡單數據類型與複雜數據類型區別
> - 簡單數據類型(值類型) string, number, boolean, undefined, null
>   - 存儲在棧中
>   - 變量存儲的是值本身, 所以又稱值類型

> - 複雜數據類型(引用類型) object
>   - 存儲在堆中
>   - 變量存儲的是引用, 所以又稱引用類型
   
> - ps. JS沒有棧堆的概念
 

```javascript=
// 簡單數據類型的存儲方法

var a = 10;        
var b = a;
a = 20;

    棧                   棧
  _______             _______
  |     |             |     |
  |     |             |     |
  |     |             |     |
b | 10  |           b | 10  |
a | 10  |     ->    a | 20  |
  |_____|             |_____|

```
```javascript=
// 複雜數據類型的存儲方法

var fn = function (name, age) {
  this.name = name;
  this.age = age;
  return this;
}
var p = fn('Toyz', 25);
var p2 = p;


        棧                               堆
   ____________                    ____________
   |          |                    |          | 
   |          |                    |          | 
   |          |                    |          | 
   |          |                    |          | 
p2 | 0x112233 |  -------> 0x112233 | name age | 
p  | 0x112233 |  ---------^        |          | 
   |__________|                    |__________| 
  

p.name // 'Toyz'
p.age // 25
p2.name // 'Toyz'

p2.name = 'GodJJ'
p2.name // 'GodJJ'
p.name // 'GodJJ'
  
```

```javascript=
// 簡單類型作為參數

function fn(x,y) {
  x+=1;
  y+=1;
  console.log(x, y);
}
var x = 10;
var y = 20;
fn(x,y);
console.log(x, y);

  // 預解析
  var x;   // x | undefined |
  var y;   // y | undefined |
  function fn(x,y) { // fn | 0x11aa33 | 
    x+=1;            // 0x11aa33 |       x+=1           |
    y+=1;            //          |       y+=1           |
    console.log(x,y) 
  }
  x = 10; // x | 10 |
  y = 20; // y | 20 |
  fn(x,y); // > 0x11aa44 
           // 0x11aa44 |   x = 10; y = 10; |
           //          |  x+=1; y+=1;      | > x = 11; y = 21;
  console.log(x,y)  // 10 20
  
        棧                               堆
   ____________                    __________________
   |          |                    |                 | 
   |          |                    |                 | 
   |          |                    | ______________  | 
fn | 0x11aa33 | ------->  0x11aa33 | |x=undefined; | | 
y  | undefined|                    | |y=undefined; | | 
x  | undefined|                    | |x+=1;        | |
   |          |                    | |y+=1;        | |
   |          |                    | |_____________| | 
   |__________|                    |_________________|   

x = 10, y = 20

         棧
    y  | 10 | 
    x  | 20 |

fn(10, 20) // > 0x11aa44

                    堆
             | ______________  | 
    0x11aa33 | |x=undefined; | | 
             | |y=undefined; | | 
             | |x+=1;        | |
             | |y+=1;        | |
             | |_____________| | 
             |  ______________ |
    0x11aa44 | |x=10;        | |  > console.log(x, y); // 11 21
             | |y=20;        | |    // 執行完後, 由於沒有變量指向這個地址,
             | |x+=1; /*11*/ | |    // 所以釋放記憶體
             | |y+=1; /*21*/ | |
             | |_____________| | 

```


```javascript=
// 複雜數據類型作為參數

function Person(name, age) {
  this.name = name;
  this.age = age;
}

var p = Person('Toyz', 28);

function fn(person) {
  person.name = 'GodJJ';
}

fn(p)
console.log(p.name);

  // 預解析
  var p; // p | undefined |
  function Person(name, age) { // Person |  0x001122  |
                               // 0x001122 | name = undefined; |
                               // 0x001122 | age = undefined;  |
    this.name = name;          // 0x001122 | this.name = name; |
    this.age = age;            // 0x001122 | this.age = age;   |
  }
  function fn(person) {    // fn | 0x001133 |
                           // 0x001133 |  person = undefined;   |
    person.name = 'GodJJ'; // 0x001133 |  person.name = 'GodJJ' |
  }
  
  p = new Person('Toyz', 28); // p | 0x001144 |
                              // 0x001144 | p.name = 'Toyz' |
                              // 0x001144 | p.age = 28      |
  fn(p); 
  console.log(p.name);




            棧                                   堆
        ____________                 __________________________
        |           |                | ______________________  |  
fn      | 0x001133  |-----> 0x001133 | |person = undefined;  | |
        |           |                | |person.name = 'GodJJ'| |
p       | undefined |                | |_____________________| |
        |           |                | ______________________  |
Person  | 0x001122  |-----> 0x001122 | |name = undefined;    | |
        |           |                | |age = undefined;     | |
        |           |                | |this.name = name;    | |
        |           |                | |this.age = age;      | |
        |           |                | |_____________________| |
        |__________ |                |_________________________|
        
              |      var p = new Person()        |
              v                                  v
        ____________                 __________________________
        |           |                | ______________________  |  
fn      | 0x001133  |-----> 0x001133 | |person = undefined;  | |
        |           |                | |person.name = 'GodJJ'| |
        |           |                | |_____________________| |
        |           |                | ______________________  |
Person  | 0x001122  |-----> 0x001122 | |name = undefined;    | |
        |           |                | |age = undefined;     | |
        |           |                | |this.name = name;    | |
        |           |                | |this.age = age;      | |
        |           |                | |_____________________| |
        |           |                | ______________________  |
        |           |                | |name = 'Toyz';       | |
        |           |                | |age = 28;            | |
p       | 0x001144  |-----> 0x001144 | |this.name = name;    | |
        |           |                | |  /* Toyz */         | |
        |           |                | |this.age = age;      | |
        |           |                | |  /* 28 */           | |
        |           |                | |_____________________| |
        |__________ |                |_________________________|

              |        fn(0x001144)            |
              v                                V
        |           |                | ______________________  |
        |           |                | |name = 'GodJJ';      | |
        |           |                | |age = 28;            | |
person  | 0x001144  |-----> 0x001144 | |this.name = name;    | |
        |           |                | |  /* GodJJ */        | |
        |           |                | |this.age = age;      | |
        |           |                | |  /* 28 */           | |
        |           |                | |_____________________| |

console.log(p.name) // console.log(0x001144.name) > 'GodJJ'



// 往下延伸

  var p; 
  function Person(name, age) { 
    this.name = name;       
    this.age = age;
  }
  function fn(person) { 
    person.name = 'GodJJ';
    person = new Person('Gear', 30); // 加這兩行的結果?
    console.log(person.name)
  }

  p = new Person('Toyz', 28);
  fn(p); 
  console.log(p.name);


        
                       fn(0x001144)           

        |           |                | ______________________  |
        |           |                | |name = 'GodJJ';      | |
        |           |                | |age = 28;            | |
person  | 0x001144  |-----> 0x001144 | |this.name = name;    | |
        |           |                | |  /* GodJJ */        | |
        |           |                | |this.age = age;      | |
        |           |                | |  /* 28 */           | |
        |           |                | |_____________________| |

              |  person = new Person('Gear', 30);  |
              v                                    v
              
        |           |                | ______________________  |
        |           |                | |name = 'Gear';       | |
        |           |                | |age = 30;            | |
person  | 0x001155  |-----> 0x001155 | |this.name = name;    | |
        |           |                | |  /* Gear */         | |
        |           |                | |this.age = age;      | |
        |           |                | |  /* 30 */           | |
        |           |                | |_____________________| |
        
console.log(person.name) // console.log(0x001155.name) > 'Gear'
console.log(p.name); // console.log(0x001144.name) > 'GodJJ'

// 陣列

  var i = [1,2,3];
  function fn(array) {
    array[0] = 6;
  }
  fn(i);
  console.log(i); // [6, 2, 3]

    __________               __________________
i  | 0x0011aa |    0x0011aa | 0x0011aa[0] =  1 | 
   |          |             | 0x0011aa[1] =  2 |
   |          |             | 0x0011aa[2] =  3 |
   |          |
fn | 0x0011bb |    0x0011bb | array = undefined |
   |          |             | array[0] = 6      |
   |__________|             |___________________|
   
fn(i) // fn(0x0011aa) > 0x0011cc

i     | 0x0011aa |   0x0011cc | array = 0x0011aa |
fn    | 0x0011bb |            | 0x0011aa[0] = 6  | // 0x0011cc 釋放

array | 0x0011aa |   0x0011aa | 0x0011aa[0] = 6  |
                              | 0x0011aa[1] = 2  |
                              | 0x0011aa[2] = 3  |
         
console.log(i); // console.log(0x0011aa) > [6,2,3]





// 冒泡排序

  function bubbleSort(array) {
    for (i=1; i<arr.length; i++) {
      var isSort = true;
      for (j=0; j<=arr.length-i; j++) {
        if (arr[j] > arr[j+1]) {
          var temp = arr[j+1];
          arr[j+1] = arr[j];
          arr[j] = temp;

          isSort = false;
        }
      }
      if (isSort) {
        break;
      }
    }
  }
  var arr = [54,26,93,17,77,31,44,55,20];
  console.log(bubbleSort(arr)); // undefined 沒有返回值
  console.log(arr) // [17, 20, 26, 31, 44, 54, 55, 77, 93]

  // 流程
     // 創建
             _____棧_____            _________堆_________
  bubbleSort | 0x0011aa |  0x0011aa | array = undefined |
                                    | xxx               |

  arr        | 0x0011bb |  0x0011bb |       [,,]        |

    // 運行函數
  bubbleSort(arr) // bubbleSort(0x0011bb) > 0x0011cc

           ________堆_________________    
  0x0011cc | array = 0x0011bb         |
           | /*直接對0x0011bb修改排序*/ | 
    // 0x0011cc 沒有人指向 > 釋放
           
              ___棧______
  bubbleSort | 0x0011aa |
  arr        | 0x0011bb |
  array      | 0x0011bb |
```

## 內置對象

> - JS 組成: ECMAScript BOM DOM
>   - ECMAScript:
>     - 變量, 註釋, 數據類型, 操作符, 
>       流程控制語句 (判斷與循環), 數組, 對象, 構造函數, 內置對象

> - JS 對象種類: 自定義對象, 內置對象, 瀏覽器對象
>   - ECMAScript: 自定義對象, 內置對象
>     - 內置對象: Math, Array, Date, ...
>       - 這東西太多了, 需要的時候可以去 [MDN](https://wiki.developer.mozilla.org/zh-CN/) 找


## Math
> - Math不是一個構造函數
> - 使用上就是 `Math.xxx`

> - 複習: 構造函數調用 `var xx = new Xoo()`
```javascript=
var MyMath = {
  Pi: 3.14,
  max: function () {
    pass
  }
}
MyMath.PI
MyMath.max()
```

### Math.PI
```javascript=
// 圓面積
function circleArea(radius) {
  return 2 * Math.PI * radius 
}
circleArea(3);
```

### Math.random()
> 隨機產生 \[0~1\) 的 float
> - `[`: 包含等於
> - `)`: 不包含等於
> - `[0~1)`: 0 >= x > 1
```javascript=
// 求 [10~100) 隨機float

function getRandomFNumber(min, max) {
  return Math.random() * (max-min) + min
}
getRandomFNumber(10, 100);

// 求 [10~100) 隨機整數
function getRandomINumber(min, max) {
  return parseInt(Math.random() * (max-min) + min)
}
getRandomINumber(10, 100);

// 求 [10~100] 隨機整數

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max-min+1) + min)
}
getRandomInt(10, 100)
  // Math.random() * (91) + 10
    // 0 + 10
    // 99.9999 * 91 + 10 = 100.99909000000001
  // Math.floor(100.99909000000001) = 100
  
// 隨機生成RGB [0~255]
  // rgb(num, num, num)
function getRandomRGB() {
  var r = Math.floor(Math.random() * 256) // (255-0+1) + 0
  var g = Math.floor(Math.random() * 256)
  var b = Math.floor(Math.random() * 256)
  var rgb = 'rgb: (' + r + ' ,' + g + ' ,' + b + ')'
  return rgb
}
```

### Math.floor() / Math.ceil() / Math.round()
> - `Math.floor(Num)`: 向下取整
> - `Math.ceil(Num)`: 向上取整
> - `Math.round()`: 四捨五入取整
```javascript=
Math.floor(10.4) // 10
Math.floor(7.8) // 7

Math.ceil(10.4) // 11
Math.ceil(7.8) // 8

Math.round(7.8) // 8
Math.round(10.4) // 10
```

### Math.abs()
> `Math.abs(Num)`: 絕對值
```javascript=
Math.abs(10.4) // 10.4
Math.abs(-10.4) // 10.4
```

### Math.max() / Math.min()
> - `Math.max([value1[, value2[, ...]]])`
> - `Math.min([value1[, value2[, ...]]])`
>   - 中括號表示可選參數
```javascript=
// 沒有參數
Math.max() // -Infinity
Math.min() // Infinity

// 不能轉換成 num 得值
Math.max('google') // NaN

Math.max(23,41,5) // 41
Math.max(23,'41',5) // 41

// Q. 模擬Math.max() / Math.min()

// 19.11.22 第一版
var MyMath = {
  max: function (array) {
    var typeOfArr = typeof array
    if (typeOfArr == 'undefined') {
      var max = -Infinity
      return max
    }
    if (typeOfArr == 'number') {
      var max = arguments[0]
      for (i=1; i<arguments.length; i++) {
        if (max < arguments[i]) {
          max = arguments[i];
        }
      }
      return max
    }
    if (typeOfArr == 'object') {
      var max = array[0]
      for (i=1; i<array.length; i++) {
        if (max < array[i]) {
          max = array[i];
        }
      }
      return max
    }
  },
  min: function (array) {
    var typeOfArr = typeof array;
    if (typeOfArr == 'undefined') {
      var min = Infinity;
      return min
    }
    if (typeOfArr == 'number') {
      var min = arguments[0];
      for (i=1; i<arguments.length; i++) {
        if (min > arguments[i]) {
          min = arguments[i];
        }
      }
      return min
    }
    if (typeOfArr == 'object') {
      var min = array[0];
      for (i=1; i<array.length; i++) {
        if (min > array[i]) {
          min = array[i];
        }
      }
      return min
    }
  },
}



// 19.11.23 第二版
var MyMath = {
  max: function () {
    var arr = [];
    var max;
    // 判斷有沒有東西, 沒有就-無限大
    if (arguments.length === 0) { 
      return -Infinity
    }
    // 把 arguments 不是數字可是可以轉的東西都轉出來塞到arr裡
    for (i=0; i<arguments.length; i++) {
      //console.log(Number(arguments[i]));
      if (isNaN(Number(arguments[i])) === true) {
        return NaN
      } else {
        arr[i] = Number(arguments[i]);
      }
      //console.log(arr);
    }
    // 開始比大小
    max = arr[0];
    for (i=1; i<arr.length; i++) {
      if (max < arr[i]) {
        max = arr[i];
      }
    }
    return max
    },
  min: function () {
    var arr = [];
    var min;
    // 判斷有沒有東西, 沒有就-無限大
    if (arguments.length === 0) { 
      return Infinity
    }
    // 把 arguments 不是數字可是可以轉的東西都轉出來塞到arr裡
    for (i=0; i<arguments.length; i++) {
      //console.log(Number(arguments[i]));
      if (isNaN(Number(arguments[i])) === true) {
        return NaN
      } else {
        arr[i] = Number(arguments[i]);
      }
      //console.log(arr);
    }
    // 開始比大小
    min = arr[0];
    for (i=1; i<arr.length; i++) {
      if (min > arr[i]) {
        min = arr[i];
      }
    }
    return min
    }
}

/* NaN == NaN // false , 被這個搞很久, 原來NaN不能比
    if (Number(arguments[i]) === NaN) {
      return NaN
    }
*/
```

## Date
> - 構造函數
> - 調用要先創造一個實例(對象) `var now = new Date()`

> - Date 對象基於 1970年1月1日（UTC）起經過的毫秒數
> - 就是 python 的 time.time()
```javascript=
// 空構造函數, 獲取「當下」時間
  // new Date();
var now = new Date();
now // Sat Nov 23 2019 09:47:21 GMT+0800 (Taipei Standard Time)


// 轉換格式
// 傳入毫秒值
  // new Date(value);
var time = new Date(1574473641456);
time // Sat Nov 23 2019 09:47:21 GMT+0800 (Taipei Standard Time)

// 傳入日期形式的string
  // new Date(dateString);
new Date(dateString);
var time = new Date('1993-1-1');
time // Fri Jan 01 1993 00:00:00 GMT+0800 (Taipei Standard Time)
var time = new Date('1993-1-1 1:1:1');
time // Fri Jan 01 1993 01:01:01 GMT+0800 (Taipei Standard Time)


// new Date(
//   year, monthIndex 
//   [, day [, hours [, minutes [, seconds [, milliseconds]]]]]
// );

var time = new Date(1993,1,1)
time // Mon Feb 01 1993 00:00:00 GMT+0800 (Taipei Standard Time)
var time = new Date(1993,1,1,1,1,1)
time // Mon Feb 01 1993 01:01:01 GMT+0800 (Taipei Standard Time)


// 獲取當前時間毫秒值
  // valueOf()
    // 這一般是JS內部在調用的, 很少在code上面寫 
    // This method is usually called internally by JavaScript 
    // and not explicitly in code.
var now = new Date();
now.valueOf(); // 1574476478855

  // getTime()
    // 跟 valueOf 一樣, 只是這是寫 code 在用的 
    // This method is functionally equivalent to the valueOf() method.
now.getTime() // 1574476478855

  // now()
    // 兼容性問題, HTML5 才能用
    // 靜態方法
var num = Date.now() // 靜態方法 vs 實例方法 new Date().getTime()
num // 1574476885575


  // Number 轉換
var now = new Date();
now // Sat Nov 23 2019 10:45:33 GMT+0800 (Taipei Standard Time)
    // 這東西可以轉換
       Number(now); // 1574477133395
    // 簡寫
       // + 取正
       // 其實內部調用了 valueOf()
       + now; // 1574477133395
  // 整理
var now = Number(new Date());
var now = + new Date();




// 轉換日期顯示格式
  var now = new Date()
    // 以下這些方法可能因瀏覽器不同而不同
    // 導致輸出不穩定
  now.toString() // "Sat Nov 23 2019 11:20:27 GMT+0800 (Taipei Standard Time)"
  now.toDateString() // "Sat Nov 23 2019"
  now.toTimeString() // "11:20:27 GMT+0800 (Taipei Standard Time)"
    // toLocale 系列是根據使用的 os 來顯示當地的格式
  now.toLocaleString() // "11/23/2019, 11:20:27 AM"
  now.toLocaleDateString() // "11/23/2019"
  now.toLocaleTimeString() // "11:20:27 AM"


// get系列
  var now = new Date()
  now // Sat Nov 23 2019 11:31:05 GMT+0800 (Taipei Standard Time)
  now.getFullYear() // 2019
  now.getMonth() // 10 [0-11] *** 注意: 月份從 0 開始歐!!!
  now.getDate() // 23 [1-28/29/30/31] 
  now.getHours() // 11 [0-23]
  now.getMinutes() // 31 [0-59]
  now.getSeconds() // 5 [0-59]

  now.getTime() // 1574479865782
  now.getDay() // 6
  now.getMilliseconds() // 782

// Q. 自己寫一個獲取現在時間的類跟方法, 格式 xx/xx/xx xx:xx:xx

/* 第一版
  function MyDate() {
    this.now = function () {
      var getNow = new Date();
      console.log(getNow);
      var month = getNow.getMonth() + 1 // getMonth 從 0 開始, 所以要 +1

      var day = getNow.getFullYear() + '/' + month + '/' + getNow.getDate()
      var time = getNow.getHours() + ':' + getNow.getMinutes() + ':' + getNow.getSeconds()
      return day + ' ' + time;
    }
  }
  var test = new MyDate()
  test.now() // "2019/11/23 12:3:43"
  */

// 第二版
  function MyDate() {
    this.now = function () {
      if (arguments.length !== 0) {
        console.error('不要亂塞東西好嗎');
          // console.error > 罵人用的 = .=
      }
      var now = new Date(),
          year = now.getFullYear(),
          month = now.getMonth() + 1,
          day = now.getDate(),
          hour = now.getHours(),
          minute = now.getMinutes(),
          second = now.getSeconds(),
          today = year + '/' + month + '/' + day,
          nowTime = hour + ':' + minute + ':' + second;

      return today + ' ' + nowTime
    }
  }
  var test = new MyDate()
  test.now() // "2019/11/23 12:21:48"

// 第三版
  function MyDate() {
    this.toTaiwanString = function () {
      var now = new Date(),
          year = now.getFullYear(),
          month = now.getMonth()+1,
          day = now.getDate(),
          hour = now.getHours(),
          minute = now.getMinutes(),
          second = now.getSeconds(),
          today,
          nowTime;

      // 個位數補零
      /* 註
      if (Number(month) < 10) {
        month = '0' + month
      }
      */
      month = month < 10 ? '0' + month: month;
      day = day < 10 ? '0' + day: day;
      hour = hour < 10 ? '0' + hour: hour;
      minute = minute < 10 ? '0' + minute: minute;
      second = second < 10 ? '0' + second: second;

      today = year + '/' + month + '/' + day;
      nowTime = hour + ':' + minute + ':' + second;
      return today + ' ' + nowTime
    }
  }


  // 註
    // str 會自動經過轉換後才比較, 不用自己轉
      '10' < 11 // true
      '10' < 9 //false

      'a' < 1 // false
      'a' > 1 // false
      isNaN('a') // true
      isNaN('1') // false
    // ? 三元運算符
      // 判斷式 ? 表達式1: 表達式2
      // 如果判斷試為 true, 執行表達式 1, 否則執行表達式 2
      if (3>1) {
        console.log('haha')
      } else {
        console.log('wowo')
      } // haha

      3>1 ? console.log('haha') : console.log('woow') // haha
    

// 看到一個案例的寫法
  function fn(a) {
    // 判斷參數是不是Date的實例對象
    // 變量 instanceof 構造函數 > 變量是否為構造函數的實例對象
    // console.log(a instanceof Date); > true || false
    if (!(a instanceof Date)) { // 不是D ate 的對象
      console.error('請塞Date的實例對象'); // 罵人
      return // 閃人
    }

    var year = a.getFullYear(),
        month = a.getMonth()+1,
        day = a.getDate(),
        hour = a.getHours(),
        minute = a.getMinutes(),
        second = a.getSeconds(),
        today,
        nowTime;
    month = month < 10 ? '0' + month: month;
    day = day < 10 ? '0' + day: day;
    hour = hour < 10 ? '0' + hour: hour;
    minute =  minute < 10 ? '0' + minute: minute;
    second = second < 10 ? '0' + second: second;

    today = year + '/' + month + '/' + day;
    nowTime = hour + ':' + minute + ':' + second;
    return today + ' ' + nowTime
  }

  var a = new Date()
  fn(a) // "2019/11/23 14:06:37"
  var b = [1,2,4]
  fn(b) // 請塞Date對象
  
  
// Q. 計算時間差, 返回相差 天/時/秒

function fn(start, end) {
  var subTime = end-start;
  subTime /= 1000;
  var day, hour, minute, second;
  
  // 註
  second = subTime % 60;
  minute = subTime / 60 % 60;
  hour = subTime / 60 / 60 % 24;
  day = subTime / 60 / 60 / 24;
  
  return day + '天/' + hour + '時/' + minute + '分/' + second + '秒'
}
var dayOne = new Date();
var dayTwo = new Date(2020,0,1); 
fn(dayOne, dayTwo); // 跨年倒數



// 註: 推導公式的過程

// 150顆水果, 一袋裝4個, 一箱裝7袋, 可以裝成幾箱幾袋剩幾個
  // 150 / 4 = 37袋2個
  // 37 / 7 = 5箱2袋
  // 所以150顆水果可以裝5箱2袋2個
  
    // 150 % 4 = 2 個
    // 150 / 4 % 7 = 2 袋
    // 150 / 4 / 7 = 5 箱
    
    // 秒 % 60 = 秒
    // 秒 / 60 % 60 = 分
    // 秒 / 60 / 60 % 24 = 時
    // 秒 / 60 / 60 / 24 = 天
```

## Array
> - 棧操作
>   - push()
>   - pop()
> - 隊列操作
>   - push()
>   - shift()
>   - unshift()
> - 排序
>   - reverse()
>   - sort()
> - 操作
>   - concat()
>   - splice()
>   - slice()
> - 位置
>   - indexOf()
>   - lastIndexOf()
> - 迭代
>   - every()、filter()、forEach()、map()、some()
> - 連接
>   - toString()
>   - join()
```javascript=
// 陣列字面量創建陣列
  var arr = [] 
  arr.length // 0
  arr[0] = 100
  arr.length // 1
  arr // [100]

  var arr2 = [1,2,3]
  arr2.length // 3

// 陣列的構造函數創列陣列
  var array = new Array()
  array.length // 0
  array[0] = 100 // 100
  array.length // 1
  array // [100]

  var array2 = new Array(1,2,3)
  array2 // [1, 2, 3]


// 判斷是否為陣列
  // 方法1: 變量 instanceof 構造函數
  var a = [1,2,3];
  a instanceof Array; // true
  var b = 1;
  b instanceof Array; // flase
  
  // 方法2: Array.isArray(變量)
   // 瀏覽器兼容問題: HTML5 才有
  Array.isArray(a) // true
  Array.isArray(b) // false
  
// valueOf() 返回對象本身
  console.log(a.valueOf()) // (3) [1, 2, 3]
  
  
// toString() 陣列轉成string, 用逗號隔開
  console.log(a.toString()) // 1,2,3
```

```javascript=
// 棧操作(先進後出)
// - push() : 進
  // 參數可以有多個
  // 返回值為改變後陣列的長度   ++arr.length
  var arr = [1,2,3];
  var arr2 = arr.push(4,5);
  console.log(arr); // (5) [1, 2, 3, 4, 5]
  console.log(arr2); // 5
  
// - pop() : 出
  // 返回值為改變前陣列的長度 // arr--
  var arr = [1,2,3];
  var arr2 = arr.pop();
  console.log(arr); // (2) [1, 2]
  console.log(arr2); // 3


// 隊列操作(先進先出)
// - push()
// - shift()
  // 取出陣列 [0] 的值, 返回值也是 [0] 的值
  var arr = [99,55,11]
  var first = arr.shift();
  console.log(first); // 99
  console.log(arr); // (2) [55, 11]
  
// - unshift()
  // 在 [0] 插入一個值, 返回值為要插入的值
  var arr = [99,55,11]
  var tmp = arr.unshift(4);
  console.log(tmp); // 4
  console.log(arr); // (4) [4, 99, 55, 11]


// 排序
// - reverse() 翻轉
  // 直接對數組本身翻轉排序

  var arr = ['a','4',true];
  var tmp = arr.reverse();
  console.log(arr); // (3) [true, "4", "a"]
  console.log(tmp); // (3) [true, "4", "a"]

  // Q. ['Toyz', 'GodJJ', 'Gear'] -> ["Gear", "GodJJ", "Toyz"]
    // 解一: reverse()
    var arr = ['Toyz', 'GodJJ', 'Gear'];
    var newArr = arr.reverse();
    console.log(newArr); // (3) ["Gear", "GodJJ", "Toyz"]
    console.log(arr); // (3) ["Gear", "GodJJ", "Toyz"]

    // 解二 還在想



// - sort() 排序
  // arr.sort([compareFunction])
  // 直接對數組本身修改排序
  
  var arr = ['a', '4', true];
  var tmp = arr.sort();
  console.log(tmp); // (3) ["4", "a", true]
  console.log(arr); // (3) ["4", "a", true]
  
  
  // 默認是對 「字符的編碼」(ASCII, Unicode) // 從小到大排序
    // 排序前先轉乘str, 再依據Unicode編碼來排序, 
    // 所以 100 會排在 9 的前面 
    // 所以排出來的東西可能較不實用
  var arr = [11,102,3,1,30];
  var tmp = arr.sort();
  console.log(arr); // (5) [1, 102, 11, 3, 30]
  
  // function compare(a,b)
    // compare(a,b) 的返回值小於0時, a會排在b前面
    // compare(a,b) 的返回值大於0時, b會排在a前面
 
    // 降序
  var arr = [11,102,3,1,30];
  /*function compare(a,b) {
    return b-a
  }
  arr.sort(compare)*/
  arr.sort(function (a,b) {
    return b-a
  })
  console.log(arr); // (5) [102, 30, 11, 3, 1]
  
  
  
    // 升序
  var arr = [11,102,3,1,30];
  /*function compare(a,b) {
    return a-b 
  }
  arr.sort(compare)*/
  arr.sort(function (a,b) {
    return a-b
  })
  console.log(arr); // (5) [1, 3, 11, 30, 102]


  var arr = ['apple', 'amazon', 'google', 'tesla']
  arr.sort(function (a,b) {
    return a.length-b.length
  })
  console.log(arr); // (4) ["apple", "tesla", "amazon", "google"]

// 冒泡排序
  function fn(a,b) {
    return a-b
  }

  function bubbleSort(arr, fn) {
    for (i=1; i<arr.length; i++) {
      var isSort = true;
      for (j=0; j<=arr.length-i; j++) {
        //if (arr[j] > arr[j+1]) {
        // 把值丟到 fn() 裡去相減, 大於0就表示前者比後者大, 
        // 跟上面那句一樣意思
        if (fn(arr[j], arr[j+1]) > 0) {
          var temp = arr[j+1];
          arr[j+1] = arr[j];
          arr[j] = temp;

          isSort = false;
        }
      }
      if (isSort) {
        break;
      }
    }
    return arr;
  }

  var arr = [54,26,93,17,77,31,44,55,20];
  console.log(bubbleSort(arr, fn));

  /*  python 思考
  def fn(a,b): 
      return a-b 

  def bubbleSort(arr,fn): 
      for i in range(1,len(arr)): 
          for j in range(len(arr)-i): 
          # if arr[j] > arr[j+1]:
          # 調用fn(), 把兩個值丟進去相減為正數的話就表示前者比後者大,
          # 跟原本寫的是一樣意思
          if fn(arr[j], arr[j+1]) > 0:
              arr[j], arr[j+1] = arr[j+1], arr[j] 
      return arr 

  a = [100, 23, 4, 1, 2, 11]  
  bubbleSort(a, fn) # [1, 2, 4, 11, 23, 100]
  */
  
  

// 清空陣列
  // = []
  var arr = [1,2,3];
  arr = [];
  console.log(arr); // []
  
  // .length = 0
  var arr = [1,2,3];
  arr.length = 0;
  console.log(arr); // []

  // .splice(start[, deleteCount[, item1[, item2[, ...]]]])
    // 刪除或替換當前陣列的某些項目
    // 返回值: 清掉了什麼
  var arr = [1,2,3];
  console.log(arr.splice(0)); // (3) [1, 2, 3]
  console.log(arr); []
  var arr = [1,2,3];
  console.log(arr.splice(0,1)); [1]
  console.log(arr); // (2) [2, 3]

// 操作方法
// concat() : 把參數拼接到陣列中

// slice(strat, end) : 從當前陣列中取得一個新陣列(不影響原陣列)
  // start 從 0 開始
  // end 從 1 開始

// --------------------位置方法--------------------------
  // indexOf(searchElement[, fromIndex])
    // -1: 找不到
    // 從前往後找, 找到馬上返回
    // 第二個參數是從第幾個開始找
  // lastIndexOf(searchElement[, fromIndex])
    // -1: 找不到
    // 從後往前找, 找到馬上返回
    
// Q. ['a', "c", "a", "z", "a", "x", "a"] 找到每個 'a' 的位置

  // 解一
  var arr = ['a', "c", "a", "z", "a", "x", "a"];
  function fn(arr) {
    var index = {}
    for (i=0; i<arr.length; i++) {
      if (arr[i] === 'a') {
        console.log(i)
      }
    }
  }
  fn(arr) // 0 2 4 6
  
  // 解二
    // indexOf, lastIndexOf
  var arr = ['a', "c", "a", "z", "a", "x", "a"];
  
  /* 測試
    arr.indexOf('a') // 1
    arr.lastIndexOf('a') // 5

    arr.indexOf('a',2) // 3 // 從 [2] 開始找 'a'
    arr.indexOf('a',4) // 5 // 從 [4] 開始找 'a'
  */
  
  var index = -1
  do {
    index = arr.indexOf('a', index+1);
    if (index !== -1) {
      console.log(index);
    }
  } while (index !== -1); // 0 2 4 6


  // !小發現!
  var arr = ['a', "c", "a", "z", "a", "x", "a"];

  do {
    var index;
    console.log(index); // undefined 0 2 4  6
    index = arr.indexOf('a', index+1);
    console.log(index); // 0         2 4 6 -1
  } while (index !== -1); 
  
    // undefined + 1 // NaN,
    // 應該是 NaN 而造成 NaN !== -1 的無限迴圈, 結果直接變成 0 , 
    // 應該是 indexOf() 裡面有做判斷把不是數字的參數轉成 0 來從 [0] 找



// 迭代方法, HTML5
  // every(), filter(), forEach(), map(), some()
  // 不影響原陣列
  
  // Q. [1500, 1200, 2000, 2100, 1800] , 超過兩千的刪除

    // 解一
    /*function fn(a) {
      return a <= 2000
    }
    console.log([1500, 1200, 2000, 2100, 1800].filter(fn)); 
        // (4) [1500, 1200, 2000, 1800]
    */
    console.log([1500, 1200, 2000, 2100, 1800].filter(function(a) {
      return a<=2000
    })); // (4) [1500, 1200, 2000, 1800]


    // 解二
    var array =  [1500,1200,2000,2100,1800];
    var tmp = [];
    for (var i = 0; i < array.length; i++) {
      if(array[i] <= 2000) {
        tmp.push(array[i]);
      }
    }
    console.log(tmp); // (4) [1500, 1200, 2000, 1800]
  
// 將陣列串成一個 String
  // join()
    // 如果有給參數的話就會用參數去連接各個陣列的元素,
    // 如果沒給參數的話就用逗號連接各個陣列的元素
      // 跟 toString 一樣 
    // 不影響原陣列

  // Q. ['Toyz', 'GodJJ', 'Gear'] -> 'Toyz | GodJJ | Gear'
    var arr = ['Toyz', 'GodJJ', 'Gear'];
    
    var newArr = arr.join(' | ');
    console.log(newArr); // Toyz | GodJJ | Gear // 用參數連接
    console.log(arr); // (3) ["Toyz", "GodJJ", "Gear"] // 不影響原陣列
    
    var newArr2 = arr.join();
    console.log(newArr2); // Toyz,GodJJ,Gear // 逗號連接

    console.log(arr.toString()) // Toyz,GodJJ,Gear
    

// Q. ['c', 'a', 'z', 'a', 'x', 'a'] 去除重複
  var arr = ['c', 'a', 'z', 'a', 'x', 'a'];

  // 統計各個元素的數量, 超過1的要特別處理
  var count = {}
  for (var i=0; i<arr.length; i++) {
    var item = arr[i];
    if (count[item]) { // 測試1
      count[item]++  // 測試2
    } else {
      count[item] = 1;
    }
  }
  console.log(count); // {c: 1, a: 3, z: 1, x: 1}
  
  // value === 1 的直接放行加入新陣列
  var newArr = [];
  for (key in count) { // 測試3
    if (count[key] === 1) {
      newArr.push(key);
    } else { // 超過的看新陣列有沒有, 沒有就放行
      if (newArr.indexOf(key) === -1) {
        newArr.push(key);
      }
    }
  }

  console.log(newArr); // (4) ["c", "a", "z", "x"]

```

## 基本包裝類型
> - 為了方便操作, JS提供了三個基本包裝類型, 亦即將基本類型包裝成複雜類型
>   - string
>   - number
>   - boolean
```javascript=
var str = '123abc';
str.length; // 6
str // "123abc"


// 問題: 他只是一個簡單數據類型(沒有屬性方法), 怎麼會有對象的方法呢 ?!
  // 基本包裝類型
  var strTmp = new String('123abc');
  console.log(strTmp); // String {"123abc"}
                         // 0: "1"
                         // 1: "2"
                         // 2: "3"
                         // 3: "a"
                         // 4: "b"
                         // 5: "c"
                         // length: 6
                         // __proto__: String
                         // [[PrimitiveValue]]: "123abc"
                           // PrimitiveValue 原始值
  // 在執行 str.length 時, 會創建一個臨時的對象
  var tmp = new String('123abc')
  tmp.length; // 6
  tmp = null; // 讓那個對象被釋放
  
  
// Number
  // 基本數據類型 
  12
  // 轉換類型
  console.log(Number('12')); // 12
  // 創建 Number 實例
  console.log(new Number('12')); // Number {12}
                                   // __proto__: Number
                                   // [[PrimitiveValue]]: 12


// Boolean
  // 基本數據類型 
  false
  // 轉換類型
  console.log(Boolean('123'));
    // 創建 Boolean 實例
  console.log(new Boolean(false)); // Boolean {false}
                                     //__proto__: Boolean
                                     //[[PrimitiveValue]]: false


// 陷阱題
  var b1 = new Boolean(false);
  var b2 = b1 && true;
  console.log(b2);  // true
    // 因為 b1 並不是 false, 而是一個具體的實力對象
    // 在經過 Boolean 轉換時, 因為不是 undefined 0 '' null NaN
    // 所以轉換後為 true
    // true && true 自然就是 true
```

### new String()

> #### string不可變
> - 每次改變str時, 都是在讓變量重新指向
> - 問題: 當我大量改變str的指向時, 會有效能問題(重新創建空間需要時間)
```javascript=
var str = '';
for (i=0; i<1000000; i++) {
  str += i;
}
console.log(str);
```

> #### string方法
> - 如上面原理所述, str調用方法時, 一樣會自動創建一個臨時對象來調用後銷毀
>   所以不用特別創建對象來使用這些方法
> - 由於 str 是不可變的, 所以執行玩方法後都會返回一個新的 str
```javascript=
// char 操作
  // charAt(位置)  獲取指定位置的char
var tmp = '123abc';
console.log(tmp.charAt(0)); // 1
console.log(tmp.charAt(4)); // b 

  // charCodeAt(位置)  獲取指定位置 char 的 ASCII
console.log(tmp.charCodeAt(0)); // 49
console.log(tmp.charCodeAt(4)); // 98


  // str[位置] 與charAt 相同效果, 但這個有瀏覽器限制(HTML5, IE8+)
console.log(tmp[0]); // 1
console.log(tmp[4]); // b 


// 操作
concat() // 拼接, 跟 + 有相同效果
  var a = 'haha'
  var b = 'wowo'
  a.concat(b) // "hahawowo"

slice()    	// 擷取 [start~end) 的 char
substring() 	// 擷取 [start~end) 的 char
substr(start, n)   		// 擷取從 start 開始的 n個字符


  // Q. '5566不能亡' 擷取 '56不能亡'
var str = '5566不能亡'
var newStr = str.substr(1,2) + str.substr(4);
console.log(newStr);


// 位置
// indexOf()   	
var a = '5566在5月6號時解散'
console.log(a.indexOf('5')); // 0
console.log(a.indexOf('7')); // -1
'abcd'.indexOf('')  // 0 //=> 空字串也等於 0 而非 -1
// lastIndexOf() 
// search() 
  // vs. indexOf: search 支持正則
  
var a = '5566在5月6號時解散'
console.log(a.search('5')); // 0
console.log(a.search('7')); // -1



  // Q. '5566在5月6號時解散, 我流了56滴淚水' 找所有 5 的位置
var str = '5566在5月6號時解散, 我流了56滴淚水';
var index = -1;
do {
  index = str.indexOf('5', index+1);
  if (index !== -1) {
    console.log(index);
  }
} while (index !== -1) // 0 1 5 17


// 去除空白   
trim()  	
  // 只去除str前後空白
  // 測試
  var str = '  55 66 不 能 亡  ';
  console.log('!' + str.trim() + '!'); // !55 66 不 能 亡!
  


// 轉換大小寫
  // Locale 可省
to(Locale)UpperCase() 	// 轉換大寫
  var a = 'haha';
  var b = a.toUpperCase(); 
  console.log(b) // HAHA
to(Locale)LowerCase() 	// 轉換小寫
  var c = b.toLowerCase;
  console.log(c) // haha

// 其他


// replace(要替換的東西, 替換成)
  // 只會替換一個就返回

  // 測試
  var str = '5566在5月6號時解散, 我流了56滴淚水'
  str = str.replace('5', '6') // "6566在5月6號時解散, 我流了56滴淚水"


  // Q. '5566在5月6號時解散, 我流了56滴淚水' 將5換成6
  var str = '5566在5月6號時解散, 我流了56滴淚水';
  var index = -1;
  do {
    index = str.indexOf('5', index+1);
    if (index !== -1) {
      // str = str.replace(str[index], '6');
      str = str.replace('5', '6');
    }
  } while (index !== -1)
  console.log(str) // 6666在6月6號時解散, 我流了66滴淚水







// split()
  // 根據參數來切割成陣列
  
var str = '5,5,6,6';
var arr = str.split(',');
console.log(arr); // (4) ["5", "5", "6", "6"] > 返回一個array的實例對象


// Q. '  55 66 不 能 亡  ' 去除所有空格
  // 解法一 replace(' ', '')

  var str = '  55 66 不 能 亡  ';
  var index = -1;
  do {
    index = str.indexOf(' ', index+1);
    if (index !== -1) {
      str = str.replace(' ', '');
    }
  } while (index !== -1);
  console.log(str); // 5566不能亡

  // 解法二
  var str = '  55 66 不 能 亡  ';
  str = str.split(' ');
  console.log(str) // (9) ["", "", "55", "66", "不", "能", "亡", "", ""]
  str = str.join(''); 
  console.log(str) // 5566不能亡


// Q. 'abcoefoxyozzopp' 判斷數量最多的char與各數
var str = 'abcoefoxyozzopp';

function fn(str) {
  var count = {};
  for (var i=0; i<str.length; i++) {
    var item = str[i];
    if (count[item]) {
      count[item]++;
    } else {
      count[item] = 1;
    }
  }
  console.log(count); // {a: 1, b: 1, c: 1, o: 4, e: 1, …}
  var max = 1;
  var char;
  for (key in count) {
    if (max < count[key]) {
      max = count[key];
      char = key;
    }
  }
  console.log('最多的char: ' + char + ', 有' + max + '個'); 
}
fn(str) // 最多的char: o, 有4個



// 'http://www.itheima.com/login?name=zs&age=18&a=1&b=2' 把參數轉成對象
  // ? 後面為參數, 每個參數用 & 分隔, key=value


var str = 'http://www.itheima.com/login?name=zs&age=18&a=1&b=2';

function getParam(str) {
  // 先把參數拿部份拿出來
  var str = str.split('?');
  // console.log(str);
    // ["http://www.itheima.com/login", "name=zs&age=18&a=1&b=2"]
  param = str[1];
  // console.log(param); // name=zs&age=18&a=1&b=2

  // 把每個參數拿出來
  param = param.split('&');
  // console.log(param); // (4) ["name=zs", "age=18", "a=1", "b=2"]

  // 個別拼成 key value
  var dict =  {};
  for (i=0; i<param.length; i++) {
    var tmpParam = param[i].split('=');
    var key = tmpParam[0];
    var value = tmpParam[1]
    dict[key] = value;
  }
  return dict;
}

getParam(str); // {name: "zs", age: "18", a: "1", b: "2"}
```


```javascript=

```





























