顯示廣告
隱藏 ✕
看板 KnucklesNote
作者 Knuckles (站長 那克斯)
標題 [JS] 克服JS的奇怪部分 ch6 建立物件
時間 2016-11-28 Mon. 12:55:59


Udemy課程: JavaScript全攻略:克服JS的奇怪部分
https://www.udemy.com/javascriptjs/learn/v4/overview
上完第六章的心得筆記

章節6 建立物件

57. 函數建構子、「new」與 JavaScript 的歷史

除了用物件實體語法來建立物件外,
另一種建立物件的方法為使用 new 運算子

函數建構子(Function Contructors): 用來建立物件用的一般函數

參考以下例子
function Person(){
	
this.firstname = 'John';
	
this.lastname = 'Doe';
}

var john = new Person();
console.log(john); // 顯示 Person {firstname: "John", lastname: "Doe"}
使用 new 後面接一個函數時,
會建立一個空物件,接著執行這個函數
並將函數執行環境的 this 指向這個空物件
利用 this 來增加物件的屬性
最後不需使用 return,JS會自動回傳這個物件

所以我們可以得到 john 是一個設定了兩個屬性的物件

驗証一下函數是否真的有執行,執行時 this 是指向什麼
function Person(){
	
console.log('This function is invoked.');
	
console.log(this);
	
this.firstname = 'John';
	
this.lastname = 'Doe';
}

var john = new Person();
console.log(john); 
// 顯示結果:
// This function is invoked.
// Person {}
// Person {firstname: "John", lastname: "Doe"}
可以確認函數真的有被執行,且一開始的 this 為一個空物件

這個用來建立物件的函數,就叫做函數建構子

使用這個函數建構子就可以重覆建立相同的物件
function Person(){
	
this.firstname = 'John';
	
this.lastname = 'Doe';
}

var john = new Person();
console.log(john); // 顯示 Person {firstname: "John", lastname: "Doe"}

var jane = new Person();
console.log(jane); // 顯示 Person {firstname: "John", lastname: "Doe"}

想要每次建立的物件有不同的屬性值的話,可以在函數建構子加上輸入值
function Person(firstname, lastname){
	
this.firstname = firstname;
	
this.lastname = lastname;
}

var john = new Person('John', 'Doe');
console.log(john); // 顯示 Person {firstname: "John", lastname: "Doe"}

var jane = new Person('Jane', 'Doe');
console.log(jane); // 顯示 Person {firstname: "Jane", lastname: "Doe"}
這樣就可以在建立物件的同時設定屬性值了


58. 函數建構子與「.prototype」

因為函數也是一個物件,有自己的屬性值
其中一個屬性叫 prototype,只有函數是用做函數建構子時才會用到

當使用函數建構子建立一個物件時,已經設定這個物件的原型了
使用函數建構子的屬性 prototype 可以取得這個原型

function Person(firstname, lastname){
	
this.firstname = firstname;
	
this.lastname = lastname;
}

// 使用函數建構子的屬性 prototype 在原型新增一個函數 getFullName
Person.prototype.getFullName = function(){
	
return this.firstname + ' ' + this.lastname;
}

// 使用函數建構子建立的物件都有 getFullName 可以用
var john = new Person('John', 'Doe');
console.log(john.getFullName()); // 顯示 John Doe

var jane = new Person('Jane', 'Doe');
console.log(jane.getFullName()); // 顯示 Jane Doe

在物件已建立後再使用 .prototype 新增的函數
該物件一樣可以取用
function Person(firstname, lastname){
	
this.firstname = firstname;
	
this.lastname = lastname;
}

var john = new Person('John', 'Doe');

// 在物件已建立後才在原型上新增函數
Person.prototype.getFormalFullName = function(){
	
return this.lastname + ', ' + this.firstname;
}

// 物件一樣可以呼叫此函數
console.log(john.getFormalFullName()); // 顯示 Doe, John

我們也可以在函數建構子裡就新增成員函數,例如
function Person(firstname, lastname){
	
this.firstname = firstname;
	
this.lastname = lastname;
	
this.getFullName = function(){
	
	
return this.firstname + ' ' + this.lastname;
	
}
}
var john = new Person('John', 'Doe');
console.log(john.getFullName()); // 顯示 John Doe
但這樣每個建立的物件裡都會再建立一次這個成員函數

使用 prototype 來建立物件成員函數的話
這個函數只會在原型物件上建立一次
每個用函數建構子建立的物件,都可以用原型取得這個函數
不需要每個物件裡都加上這個函數,以節省記憶體



59. 危險小叮嚀:「new」與函數

在使用函數建構子建立物件時,如果忘了加上 new,
var john = Person('John', 'Doe');
函數一樣會執行,但不會回傳物件,john會變成 undefined
執行時不會在這行顯示有錯誤

所以一個良好的習慣是,
將要用做函數建構子的函數,開頭字母用大寫,
這樣看到大寫的字母就會想到要加上 new 了


60. 觀念小叮嚀:內建的函數建構子

JS已內建了一些函數建構子

例如 Number()
[圖]

使用 var a = Number(3)
a 就是一個數字物件,物件的原型有一些成員函數可以用
這些成員函數實際上是建立在 Number.prototype 上的

例如 String()
[圖]

b 不是一個單純的字串,而是一個字串物件

直接對字串取成員函數也是可以的
[圖]

因為JS會自動把字串轉為字串物件

所以如果我們想要對所有的字串加上一個成員函數時
可以像這樣
String.prototype.isLengthGreaterThan = function(limit){
	
return this.length > limit;
}

console.log("John".isLenghtGreaterThan(3)); // 顯示 true
在 String.prototype 加上成員函數後
所有的字串都可以直接呼叫這個函數來用了

如果是用在數字上的話,例如
Number.prototype.isPositive = function(){
	
return this > 0;
}

console.log(3.isPositive()); // 顯示錯誤: Invalid or unexpected token
原來數字不會自動轉成數字物件

所以最好不要直接對看起來像是純值的東西取用成員函數


61. 危險小叮嚀:內建的函數建構子

使用內建的函數建構子建立的物件,看起來像純值,但不是純值
可能會因此出現非預期的結果,例如
var a = 3;
var b = new Number(3);
console.log(a==b);  // 顯示 true
console.log(a===b); // 顯示 false
所以使用上要小心


62. 危險小叮嚀:陣列與 for in

for in 是用來跑一遍物件中所有名稱/值的迴圈

但也可以使用 for in 來跑一遍陣列中所有值,例如
var arr = ['John', 'Jane', 'Jim'];

for(var prop in arr){
	
console.log(prop + ': ' + arr[prop]);
}
//顯示結果:
//0: John
//1: Jane
//2: Jim
因為 arr 實際上也是個物件,陣列裡的值就是物件的屬性

如果載入了其他程式的關係改寫了 Array.prototype,例如
Array.prototype.myCustomFeature = 'cool!';

var arr = ['John', 'Jane', 'Jim'];

for(var prop in arr){
	
console.log(prop + ': ' + arr[prop]);
}
//顯示結果:
//0: John
//1: Jane
//2: Jim
//myCustomFeature: cool!
這可能就不是我們預期的結果了

所以要跑一遍陣列最好還是使用一般 for 迴圈比較安全
Array.prototype.myCustomFeature = 'cool!';

var arr = ['John', 'Jane', 'Jim'];

for(var i = 0; i < arr.length; i++){
	
console.log(i + ': ' + arr[i]);
}
//顯示結果:
//0: John
//1: Jane
//2: Jim


63. Object.create 與純粹的原型繼承

使用函數建構子建立物件的語法,
其實是在模仿其他語使用 new Class 建立物件的語法

在JS還有一種純粹的原型繼承來建立物件的方式
參考以下例子
var person = {
	
firstname: 'Default',
	
lastname: 'Default',
	
greet: function(){
	
	
return 'Hi ' + this.firstname;
	
}
}

var john = Object.create(person);
console.log(john); // 顯示 Object {}
console.log(john.greet()); // 顯示 Hi Default

john.firstname = 'John';
john.lastname = 'Doe';
console.log(john); // 顯示 Object {firstname: "John", lastname: "Doe"}
console.log(john.greet()); // 顯示 Hi John
使用物件實體語法建立一個物件 person,
然後執行 Object.create() 並傳入這個物件 person,
將新建立的物件存成 john

此時 person 就會是 john 的原型物件
而 john 還是一個空物件
要再將 john 自己的屬性值設定上去


有些舊瀏覽器的JS可能沒有 Object.create 能用
可以用 polyfill 的方式加上去

polyfill: 把執行環境可能缺少的程式補上去

//polyfill
if(!Object.create){
	
Object.create = function(o){
	
	
if(arguments.length > 1){
	
	
	
throw new Error('Object.create implementation'
	
	
	
+ ' only accepts the first parameter.');
	
	
}
	
	
function F() {}
	
	
F.prototype = o;
	
	
return new F();
	
}
}
用 if(!Object.create) 檢查執行環境有沒有這個函數可以用
沒有的話再建立一個

在建立的函數 Object.create,輸入值為一個物件 o
檢查輸入值若超過一個的話顯示錯誤訊息
接著建立一個空函數 F,用來當函數建構子
將函數建構子的屬性 prototype 設為輸入的物件 o

用 new F(),用函數建構子建立一個物件,
將物件的原型設為函數建構子的屬性 prototype
最後輸出這個物件


64. ES6 與類別

在JS的下一版 ES6,可以使用類別(class)來建立物件
類別是在其他程式語言中普遍使用的方式

使用方法像這樣
class Person{

	
constructor(firstname, lastname){
	
	
this.firstname = firstname;
	
	
this.lastname = lastname;
	
}

	
greet(){
	
	
return 'Hi ' + firstname;
	
}
}

var john = new Person('John', 'Doe');
使用關鍵字 class 來建立類別 Person

裡面使用函數 contructor 來當建構子,
用來在建立物件時設定屬性值

然後也可以在類別中建立成員函數 greet

要建立物件的時候,使用 new Person() 並傳入新物件的屬性值
就可以建立一個物件 john 了


從其他語言過來的人會覺得有類別能用很棒
但要注意的是,其他語言的類別,不是物件,只是一個定義
而JS中的類別,就是一個物件
所以其實是在用物件來建立物件

至於使用類別要怎麼設定物件原型呢,可以這樣
class InformalPerson extends Person{

	
constructor(firstname, lastname){
	
	
super(firstname, lastname);
	
}

	
greet(){
	
	
return 'Yo ' + firstname;
	
}
}
就像其他語言的類別可繼承其他類別一樣
建立類別 InformalPerson 時使用 extends 繼承類別 Person
這樣 InformalPerson 的原型物件就是 Person 了

在成員函數中可以使用 super 呼叫原型物件的成員函數
例如在建構子中呼叫 super,就是呼叫 Person 的建構子


就算下一版的JS有類別可以用,
但其實做的事情和使用函數建構子或是Object.create是一樣的



--
※ 作者: Knuckles 時間: 2016-11-28 12:55:59
※ 編輯: Knuckles 時間: 2016-11-29 12:49:36
※ 看板: KnucklesNote 文章推薦值: 0 目前人氣: 0 累積人氣: 882 
分享網址: 複製 已複製
r)回覆 e)編輯 d)刪除 M)收藏 ^x)轉錄 同主題: =)首篇 [)上篇 ])下篇