參考資料

建構函式

Javascript原本就提供一系列建構函式,例如ObjectStringBooleanDate,Promise…etc。

PS:一般來說我們習慣用大寫來表示建構函式

建立Dog原型

建構函示其實與一般函示沒有差別,都是使用function來宣告,我們來簡單創建一個建構函式:

1
2
3
4
5
function Dog(name,size,color){
this.name=name
this.size=size
this.color=color
}

接著我們使用new運算子來透過這個Dog建構函示建立實體(instance)

1
2
3
const white=new Dog('小白','小型犬','白色')

console.log(white) //Dog {name: '小白', size: '小型犬', color: '白色'}

使用new運算子時,會進行以下動作:

  1. 創建一個空物件實體{}
  2. 接著將該函式的this指向步驟一創建的空物件
  3. 依序執行(e.g:this.name=name會在物件內創建一個name屬性,並且將參數name的值賦予到該屬性上)

經過以上動作,我們建立了一個建構函示Dog的原型,並且利用new運算子生成一個名叫小白的小型犬實體。

新增新的Array方法

使用原型的好處是,我們可以直接新增方法在原型上,而不需要每次都重新定義。在修改方法時也只要修改一個地方,就可以在所有的實例上調用修改後的方法。

我們都知道可以直接透過const ary1=[1,2,3,4]來建立一個陣列,接著我們要試著新增一個取出陣列最後一個元素的方法:

1
2
3
4
5
6
7
const ary1=[1,2,3,4,5]

Array.prototype.getLastElement=function(){
return this[this.length-1]
}

console.log(ary1.getLastElement()) //5

這邊需要注意,在定義原型方法時,需要使用函式表達式來定義,不要使用箭頭函式,否則this的指向可能會有錯誤

1
2
3
4
5
6
7
//錯誤範例
const ary2=[6,7,8,9]
Array.prototype.getFirstElement=()=>{
return this[0];
}
console.log(ary2.getFirstElemnt() //expect 6, but undefined
console.log(ary2.getLastElement() //9

因為在定義getFirstElement時使用箭頭函式,this指向window,所以this[0]會是undefined。

並且我們也同樣可以在ary2上使用先前所定義的getLastElement方法

新增Dog

[[prototype]]

先前我們有建立一個狗原型的實例white,當我們透過console.log(white)展開物件時,可以看到一個[[prototype]]顯示他是Dog原型,並且裡面存在一個constructor:function Dog(name,size,color),指向建構函示Dog本身

p.s:有些瀏覽器可能會使用__proto__來表示[[prototype]],並且在自己建立的原型產生的實例下,該實例的[[prototype]]在Safari會顯示Dog,但Firefox,Chrome,Arc等瀏覽器都是顯示為Object。這邊我認為Dog會比較恰當。

另外需要注意,這邊的[[prototype]]與我們前面在撰寫的Array.prototype這裡的prototype是不一樣的東西!

先前我們有在Array原型上新增我們自己的方法,現在我們嘗試在

(雖然在ES6之後有提供class搭配constructor來創建建構函式,但是這個寫法只是語法糖,實際上仍然是原型鏈)

創建物件的方法

  • 物件實字 {}
  • 建構函式 new Object

ps:建構函式產生的包裹物件可能會因為不同內容而使用不同的建構函式
e.g: new Object(1) >> Number({1})
e.g: new Object(‘1’) >> String({“1:”})
e.g: new Object({myName:’小明’}) >> Object { myName: “小明” }

所以建議還是使用物件實字的方式建立物件就好

物件取值

物件取值有兩種方式,先使用物件實字方式建立物件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const family={
name:'小明家',
deposit:1000,
members:{
mom:'老媽',
ming:'小明',
},
callFamily:function(){
console.log('聯絡小明家')
},
1:'只能用中括號取得此值',
'2':'屬性一律為字串',
'$#@!':'$#@!-string'
}

取值的方式主要有兩種,分別為:

  1. 點記法
  2. 中括號取值

點記法

以上方物件範例來說,我們需要取得小明家存款(deposit)的內容,可以使用family.deposit取得。如果要調用callFamily這個函式的話,則是使用family.callFamily()即可執行該函式。

點記法雖然很方便,但是他無法讀取數字或特殊符號的屬性,這時候就要使用中括號取值來解決。

中括號取值

中括號取值需要注意的地方在於,中括號內需要填入的是字串。

例如同樣要取得存款(deposit)的值時,我們需要改寫為family['deposit'],中括號必須填入字串。

同樣的,要取得1:'只能用中括號取得此值'的內容,需要使用family['1']來取得。但是這邊使用family[1]也可以,因為會自動將數值1轉型為字串1。

但是如果需要取得'$#@!':'$#@!-string'的內容時,就必須要輸入family['$#@!']

總結來說,因為物件的屬性必定是字串形式,所以在用中括號取值時一律使用字串就不會有問題了。

補充:
使用中括號取值時,也可以用變數的形式傳入字串。例如:

1
2
const foo='$#@!'
console.log(family[foo]) // "$#@!-string"

透過這種寫法可以增加程式碼撰寫時的彈性

新增屬性

一樣可以透過點記法或是中括號兩種方式增加

1
2
3
4
family.getMembers=function(){return this.members}
family['members']['dad']='老爸'

family.getMembers(); // Object { mom: "老媽", ming: "小明", dad: "老爸" }

刪除屬性

可以透過delete運算子 來刪除物件的屬性:

1
delete family.deposit //true

如果刪除成功的時候會回傳true,但是就算原本該物件就沒有被指定刪除的屬性,仍然會return true(e.g:delete family.ooxx >> true)

那麼什麼時候會回傳false呢? 如果操作無效的話就會回傳false。例如想要直接刪除family本身,但是因為family並不是一個物件的屬性,所以會回傳false,並且family也不會被刪除。

1
2
delete family //false
console.log(family) // {name:'小明家',....}

需要特別注意的地方是,當我們不使用var,const,let宣告的時候,實際上是在window底下新增該屬性,例如:

1
2
3
4
5
6
7
8
var foo='var宣告的foo'
function addBarUnderWindow(){
bar='新增在全域window底下的bar屬性'
}
addBarUnderWindow();
console.log(window) //這邊可以看到window下面多了一個bar的屬性
delete foo // false
delete bar // true

ESLint風格補充

有些撰寫風格會希望在物件內最後一個屬性後方再加上逗號,但是不加也不會有任何影響。

未定義之屬性

如果我們今天要存取沒有定義的屬性,會回傳undefined。假設我們又在該屬性下要新增物件屬性,會產生錯誤:

1
2
3
4
5
6
const family={
name:'小明家',
deposit:1000,
}
console.log(family.ooxx.foo) // Uncaught TypeError: family.ooxx is undefined
console.log('我沒有辦法被運行,因為前面出現錯誤了')

當出現錯誤時,後方的javascript程式碼都不會被運行。

要解決這個問題可以預先先定義好ooxx是一個空物件:

1
2
3
4
5
6
const family={
name:'小明家',
deposit:1000,
ooxx:{}
}
family.ooxx.foo='undefined下的屬性'

或是可以用可選串聯(?.)運算子來解決

1
2
3
4
5
6
const family={
name:'小明家',
deposit:1000,
}
console.log(family.ooxx?.foo) //undefined
console.log('我可以被運行')

可以發現只沒有出現錯誤,而是出現undefined。

另外,如果在取得全域(window)下屬性出現錯誤時,造成後方程式碼無法運行的狀況,也可以透過對window物件存取屬性來解決這個問題:

1
2
console.log(a) // 拋出錯誤
console.log('我不會被執行,因為a沒有定義,拋出錯誤');
1
2
console.log(window.a) //undefined
console.log('可以被執行');
0%