Travel in Time.

克服JS 的奇怪部分 #2:認識 Object 和 function

Published on
understanding-the-weird-part

物件

  • 可以用 new Object{} 來建立物件,但不建議使用前者(詳見第五章)
  • Object 可以有屬性(property)和方法(method)
  • Object 參考的是位置,會取得位置對應的屬性/方法,用 .[] 來設定或取得都可以,前者比較簡潔(dot/bracket notation)
var person = {}

person['firstName'] = 'Tony'
person.lastName = 'Alice'
// 建立 "firstName" 屬性的記憶體,object 會參考到 "firstName" 的位址

greet(person)
// 將物件作為參數傳入 function greet

greet({ firstName: 'Cathy', lastName: 'Jenny' })
// 也可以在傳入時才建立物件,JS 實際上在執行時會先處理過

補充:JSON 和 object literal syntax 的關係

過去傳送 XML 耗費不必要的資源,所以有了 JavaScript object notation,形式上和 object literal syntax 相似,但還是有不同之處,比如屬性一定得包在 "" 引號

{
  "data": {
    "basic": {
      "name": "John",
      "email": "",
      "create_time": 1612247227
    }
  }
}

如果需要轉換兩者,只需透過以下方式

  • JSON.stringify(object) 轉換成 JSON 字串
  • JSON.parse(JSON) 轉換成物件

函式是物件的一種

在 JS 裡面,function 是 object 的一種,可以像其他型別一樣被設定成變數、被傳入其他地方,被快速創造出來使用,這樣的 function 被稱作是 First-class function

  • 是一種特殊的物件
  • 可以將 function 存成變數
  • 可以將 function 當成參數代入另一 function
  • 可以在一個 function 中回傳另一個 function
  • 跟物件一樣有屬性(property)

註:code property 和 name property 備註文字寫錯,要對調位置

當 function 被視為物件時,它還有兩個特別的屬性

  1. name property:function 的名稱,如果沒有的話就是匿名函式(anonymous function)
  2. code property:想像 functions 只是用來裝程式碼的容器,function 可被呼叫、新的執行環境會被建立,將執行裡面的程式碼
function greet() {
  // 建立名為 greet 的函式
  console.log('Hello') // code property
}

greet.language = 'english' // 因為是 object,可以添加屬性
greet() // name property 加上 () => 呼叫函式
console.log(greet) // 印出 code property 內容

參考資料:[筆記] JavaScript 中函式就是一種物件 ─ 談談 first class function


函式:表達式和聲明式

先來認識表達式和聲明式的差別

  1. 表達式(a unit of code that results in a values)

輸入的那串程式,執行後直接回傳一個值,通常會把表達式回傳的值存成變數,但並不是一定要存。

例如下面都是表達式,會回傳一個值,可以把它們指給一個變數

1 + 2 // 回傳 3
a === 3 // a === 3 會回傳 true
3 // 回傳 3
  1. 聲明式:會執行程式碼,但不是直接回傳值
const b = if (a === 3) {}  // 不能用變數接 if 聲明 ❌

if 指令就是函式聲明,它並不會直接回傳一個值,也不能將它指定為一個變數


Function 是物件的一種,它可以透過 expression 或 statements 兩種方式來建立函式:

函式聲明式 Function Statements

不會直接回傳任何的值

函式聲明會被提升(hoisting),在創造階段就被放進記憶體儲存,所以我們可以在執行這段程式前,就先呼叫函式來使用而不會報錯

greet() // it works!

function greeting() {
  console.log('hello')
}
函式表達式 Function Expressions
const anonymousGreet = function () {
  // function object
  console.log('hello')
}
  • 同樣在創造階段被放進記憶體 => 匿名函數物件
  • 在上面的例子中,function() {...} 是函式表達式 => 被執行時會讓函數被創造出來,會回傳一個函數物件
  • anonymousGreet 變數知道它的位址,所以不需要再寫一個 name property 去參照它

因為一行一行執行,用 anonymousGreet 去指向函式位址,如果在還沒有被賦值前就呼叫的話,會報錯

anonymousGreet() // error!

const anonymousGreet = function () {
  console.log('hello')
}

anonymousGreet() // 變數已經指向它的位置,可以呼叫

by value 和 by reference 基本觀念

1. by value

在建立 primitive type 的變數,純值會是用拷貝 value 的方式,拷貝 a 的值填到 b 位址。

var a = 3 // 0x001
var b
b = a // 0x002

;(在不同記憶體位置上) => 彼此不會互相影響

2. by reference

object literal 方式來創造物件,指定給 c,那麼就會是 by value、一樣會在記憶體中給它個位置。

var c = { greeting: 'hello' } // 0x001

但如果今天要把變數 c 的值等同於 d 時,因為 = 運算子知道 c 是指向一個物件,所以它不會創造新的記憶體位置給 d,而是讓 d 指向跟 c 一樣的地方

var d
d = c // 0x001

而 function 的參數,同樣會是用 by reference 的做法,如果將 d 作為參數傳進下方的函式,d 和 c 自身都會被改變

function changeGreeting(obj) {
  obj.greeting = 'hola'
}
changeGreeting(d) // 同樣改變 d 和 c

參考資料:談談 JavaScript 中 by reference 和 by value 的重要觀念