---
# System prepended metadata

title: 0413 變數及函式宣告與範圍、Ruby 續 0412
tags: [JavaScript, Ruby / Rails]

---

# 0413 變數及函式宣告與範圍、Ruby 續 0412
###### tags: `JavaScript` `Ruby / Rails`
- MDN (類似 JS 官方手冊) [連結位置](https://developer.mozilla.org/en-US/)
## 印個 Hello World (其實是123)
在終端機中輸入 `$ node -v` 檢查 node 版本
- node 是啥？
  - 一個 Runtime (執行環境)
  - 其實各個瀏覽器也都是 Runtime
- `console.log()` 會印出結果但是沒有回傳值
- ==在 JS 中，要在最後面加分號就全部加，不然就全部不加==
```shell=
$ code ttt.js  # 建立一個 ttt.js 檔案然後開啟 VScode 編輯器
```
```javascript=
// 在檔案中輸入
console.log('123');
// 存檔
```
回到終端機
```shell=
$ node ttt.js # 用 node 當 runtime 執行 ttt.js
## 得到 123
```
## JS 宣告變數與範圍
### var 變數宣告
- 以==一個 function==為存活範圍
- 可重複宣告
```javascript=
x = 2;
console.log(x);
// 不要這樣做變數指定，這樣寫會汙染全域變數

// 在 ruby 裡用下面的得到相同結果
a = 2
puts a 
```
```javascript=
var a = 1; // 宣告 a 變數為 1 
console.log(a);
```

- Execution Context 執行環境
  - 只要有某個方法執行就會產生進入一個執行環境
  - 方法完成執行之後那個環境就會消失，包含裡面做的所有事情

```javascript=
var a = 2;  // 在整個程式的執行環境宣告變數

function hello() {
  var b = 1 ;     // 在方法中宣告變數
  console.log(b); // 在function裡執行才能印出
}

hello(); // 執行方法必須加()

console.log(a); // 得到 2
console.log(b); // 壞掉 (因為沒有進入 hello 方法的小泡泡裡面)

console.log(hello); // 得到一個 hello function
```
- 在 JS 裡面，要呼叫方法就==一定要在後面加小括號==

### Scope Chain 範圍鏈
```javascript=
a = 2;  // 指定 a = 2 在全域物件裡面直接插旗指定
var c = 3;  // 在執行環境大泡泡中宣告變數

function hello() {
  var b = 1;      // 在方法中宣告變數
  console.log(b); // 在 function 裡執行才能印出
  console.log(a); // 在 function 裡找不到會往外找
  console.log(c); // 在 function 裡找不到會往外找
}

hello(); 
// 印出 
// 1
// 2
// 3
console.log(a); // 印出 2 
console.log(b)  // 出錯
console.log(c)  // 印出 3
```
- 比較在 ruby 中的變數範圍
```ruby=
a = 1

def b
  p a
end

b  # 執行後會出錯，因為 ruby 方法中找不到變數就會放棄
```
- 全域變數指定
```javascript=
function hello() {
  b = 2;      // 呼叫 hello 方法的時候指定下去
}

hello();
console.log(b);    // 得到 2
```
- 在 function 內已宣告變數，接著指定新變數
```javascript=
var a = 1;

function hello() {
  var a = 3;
  a = 2;      // 先找 function 裡面的，找不到才往外找指定的變數
  console.log(a);
}

hello();    // 得到 2
console.log(a);    // 得到 1
```
- function 裡有 function
```javascript=
var a = 1;

function hello() {
  var a = 2;
  function world(){
    var a = 3;
    console.log(a);
  }
}

hello();    // 沒印出東西，單純宣告變數就結束
world();    // 錯誤，因為world()被包在hello()裡面，所以呼叫不到
```
- function 裡有 function
  - 在 function 裡執行 function 裡的 function
```javascript=
var a = 1;

function hello() {
  var a = 2;
  function world(){
    var a = 3;
    console.log(a);
  }
  world();
}

hello();   // 印出 3    
```
- function 裡有 function
```javascript=
var a = 1;

function hello() {
  var a = 2;
  function world(){
    var a = 3;
  }
  world();
  console.log(a);
}

hello();   // 印出 2    
```
- function 裡有 function
```javascript=
function hello() {
  var a = 2;
  function world(){
    console.log(a);
  }
  world();
}

hello();   // 印出 2 ，往外找    
console.log(a); // 出錯
```
- function 裡有 function
```javascript=
function hello() {
  var a = 2;
  function world(){
    console.log(a);
  }
  world();
}

console.log(a)    // 出錯，因為 function hello 還沒被執行
hello();
```
### 變數提升 Variable Hoisting
```javascript=
console.log(a);
var a = 1;
// 得到 undefined，未定義（undefined 也是一個值）（不會出錯）
```
```javascript=
console.log(a);
// 出錯，得到錯誤訊息 a is not defined
```
```javascript=
var a;
console.log(a);

a = 2;
console.log(a);

// 印出 
// undefined
// 2
```
---
### JS 的定義期與執行期
( 編譯期&直譯期、建立期 Creation Phase & 執行期 Execution Phase )
- JS 啟動後 
  - 先進入定義期去找所有指定的變數與 function code，看需要什麼資源
    - (1A) 註冊名稱、(1B) 初始化
  - 再進入執行期由上往下執行原始碼
    - (2) 賦值 / 執行函式
  - 用 var 宣告變數時會執行 1A 跟 1B

```javascript=
if (false) {      // 判斷式不是 function，只是原始碼中的其中一段
  var a = 1          判斷式 or 判斷是不是？
}

console.log(a)    // 得到 undefined
```


### 超連結
* [var MDN解說](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Statements/var)
* [認識 JS (MDN)](https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/First_steps)



---

### let 變數宣告

- let 不做 1B 而是暫時讓這個變數不能使用，TDZ 的狀況，就會使這個值不能使用，
- TDZ 暫時死區（Temporal Dead Zone）
- 作用範圍為==一個 block==

* 如果要讓 TDZ 被解除，就是給他一個值

### const 常數宣告
- 定義之後==不能==修改
- 常數也不做 1B 
- 作用範圍為==一個 block==
```javascript=
const A = 1   // A 就是常數（通常會大寫，但不一定要大寫）
```
- 正常情況（做了 1A，然後在第 2 階段執行指定變數）
```javascript=
let a = 1
console.log(a)

// 1
```
- 因為沒有做 1B (初始化) 所以會出錯，但是在 1A 已經預留記憶體空間
```javascript=
console.log(a)
let a = 1

// ReferenceError: Cannot access 'a' before initialization
// 出錯，錯誤訊息如上示
```
---
## 函式 function
### 函式宣告
* 建立期：同時做完 1A 1B ( 定義 ) 2 ( 賦值 )
* 執行期：可被完整使用（ 呼叫 ）
* 在建立期也會為變數決定存活區間
* 匿名函式==不能==單獨存在
* 在 JS 裡面，要呼叫方法就==一定要在後面加小括號==
#### 普通函式宣告
```javascript=
function a() {
  console.log('aaa');
}

a();
// 印出 'aaa'
```
- 因為已經完成所有程序，所以可被完整呼叫
```javascript=
a();
function a() {
  console.log('aaa');
}
// 印出'aaa'
```
#### 宣告變數指向==匿名==函式 (anonymous function)
-  變數只會執行
    -  1A 跟 1B (var)
    -  1A (let, const)
```javascript=
var a = function () {    // 在 1A 1B 執行，但是還沒執行 2
  console.log('aaa');
}

a();
// 'aaa'
```
- 因為只有執行第一階段
```javascript=
a();    // 因為還沒執行 2 所以 a 是 undefined
var a = function () {    // 在 1A 1B 執行，但是還沒執行 2
  console.log('aaa');
}
// 錯誤，a is not a function

// 類似
var a
a();    // 因為還沒執行 2 所以 a 是 undefined
var a = function () {    // 在 1A 1B 執行，但是還沒執行 2
  console.log('aaa');
}
```
- 箭頭函式 Arrow function
  - 沒有自己的 this
```javascript=
var c = (x) => {
  console.log('ccc')
}
```

---
### JS 函式回傳值
- JS 不是預設最後一行就有回傳值，所以要記得 ==`return`== !!!!!
- 建立函式的 (?)
```javascript=
function f_to_c(temp){
   return (temp - 32)*5/9; 
}

console.log(f_to_c(20));
```

```javascript=
function a(tmp){
  return console.log(tmp);
}

a();    // undefined JS 沒有預設參數也不會出錯
a(1);    // 1
a(1, 2, 3);    // 1，多餘的參數會省略
```
```javascript=
// 因為 JS 的四捨五入只能讓數字四捨五入到整數，所以自己建一個函式操作
function my_round(num, n = 2) {
  return Math.round(num * (10 ** n)) / (10 ** n);  // 10 的 n 次方
}

function f_to_c(temp) {
  return my_round((temp - 32) / 1.8, 4);
}

console.log(f_to_c(120));
```

### 變數存活範圍
- let, const 作用以 block 為範圍，離開範圍就無效
  - block: 大括號裡 (if, function....)
- var 以 function 為範圍
```javascript=
function x() {
  if (true) {
    var a = 1;
    console.log(a);
  }
}
```
## JS 中的 this
### 全域變數
- 在 chrome 瀏覽器中為 window
- 在 node.js 中為 global
```javascript=
console.log(this) // this 指向全域物件
```

- 誰呼叫的，誰就是 this，動態執行的時候才會決定
```javascript=
function x() {
  console.log(this)
}

x()    // 隱藏了一個全域物件 global.x() 或是 window.x()
```
- 呼叫時沒有開頭的就當作全域物件呼叫
- 箭頭函式沒有自己的 this

==定義不是執行～～～～～==

[超連結](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/this)
## JS 中的物件
- 包在大括號裡面的 key: value 組合
```javascript=
let obj = {
  name: 'aaa',
  age: 123,
  hello: function() {
    console.log(this)
  }
}

obj.name
obj.hello()
```
```javascript=
let hero = {
  name: '一護',
  hp: 100,
  power: 5,
  healing: function(n = 5){
    this.hp += n
    if (this.hp >= 100) {    // 這邊的 this 等於物件本身
      this.hp = 100
    }
  },
  attack: function() {
    console.log('attack!!!!!')
    this.hp -= 5
  }
}

hero.attack()
hero.attack()
hero.healing()


// 印出
// name: '一護',
// hp: 95,
```
- 因為箭頭函式沒有 this，所以下面的程式碼中 this 會找不到東西而指向全域變數
```javascript=
let hero = {
  name: '一護',
  hp: 100,
  power: 5,
  healing: (n = 5) => {
    console.log(this.hp)  // this 指向全域變數
  }
}

hero.healing(20)  
```
### 用 JS 監控式改變 HTML 原始碼
- HTML 原始碼
```htmlembedded=
<div id="clickme">
  按我
</div>
```
- JS 原始碼
```javascript=
var d = document.querySelector('#clickme')    // 利用類似 CSS 選取器的方法選取元素
  d.addEventListener("click", function(){    
  this.innerHTML = "被按了"
})
// 建立監控動作 "Click" 及 Rollback function(修改文字)
// 得到效果是如果點擊到 "按我" 上面，文字會變成 "被按了"
```





---
## 續 0412 使用者登入介面
複習：模組跟類別是很像的，但是模組不能 new 不能繼承
類別方法中只能用類別方法
實體方法中只能用實體方法
兩者不能混用
模組方法就是類別方法，等級一樣
拉出來建立模組（感覺像是獨立的工具），裡面放類別方法就可以讓類別方法使用
 - 是類別方法還是單純的方法而已啊？=> 如果在模組裡面定義單純的方法 => 看起來單純但實際上是實體方法

建立在外面的模組（或類別）內有模組（或類別）方法的話，就可在任何地方呼叫使用
```ruby=
將下面這個地方換掉
=begin
private
  def encrypt(password)
    Digest::SHA1.hexdigest(password)
  end

  def salted(password)
    "123#{password}xx"
  end
=end

module Hasher    # 因為沒有想把它 new 出來，所以直接使用 module
  require 'digest'

  def self.encrypt(password)
    Digest::SHA1.hexdigest(password)
  end
  
  def self.salted(password)
    "123#{password}xx"
  end
  
=begin
  def self.salted(password, salt1, salt2)
    "123#{password}xx"
  end
=end

end

就可以接著在下面的類別中使用

class User < ApplicationRecord
  def self.login(params)
    email = params[:email]
    password = params[:password]

    salted_password = Hasher.salted(password)
    encrypted_password = Hasher.encrypted(salted_password)

    find_by(email: email, password: encrypted_password)
  end
```

```ruby=
class Cat
  def self.new
  end
  
  def self.a
    Cat.new.b 或 self.new.b 或 new.b
  end
    
  def b
  end
end


module
```

模組方法使用
```ruby=
module DD
  module CC
    def self.EE
    end
  end
end


Digest::SHA1.hexdigest(password)

```


## 環境變數建立與使用
較敏感的資訊會放在環境變數內
`
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
ENV.fetch
`

```shell=
$ CCC=123 rails c
```
部署系統的時候設定環境變數

### super 用法
```ruby=
class Animal
  def a
    puts "a in animal"
  end
end

class Cat < Animal
  def a
    super
  end
end

kitty = Cat.new
kitty.a
```

在方法中間插入 super => 呼叫上層的同名方法
如果後面沒有加小括號，代表把得到的引數全部往上丟，如果上層方法要的引數數量不同的話就會出錯
後面有加小括號，代表指定引數

有些程式語言支援 function overload (只要方法在特徵上不同就視為不同，不管他們名稱是否一樣)

```ruby=
class Animal
  def a(x, y, x)
    puts 'a in animal'
  end
  
class Cat < Animal
  def a(c, d)
   super(c, d, '123')
  end
end
```



