iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 10
0
自我挑戰組

Javascript 犀牛本-濃縮再濃縮 提煉再提煉系列 第 10

Day 10: 類別與模組(Part 1)

類別(class)與建構式(constructor)

  • 建構式是用來初始化新建物件的函式,利用new來呼叫它。
  • 呼叫建構式關鍵特色在於建構式的prototype特性會被用來當作新物件(object)的prototype。
    比較下面兩段程式碼:
function fn_1(from,to){
    var r = Object.create(fn_1.method); // 繼承fn_1.method 所有方法
    r.form = from;
    r.to = to;
    return r;
}
// fn.XXX -> 可以隨意命名,這裡暫且命名為method
fn_1.method={
    include: function(x){return this.form <= x && x <= this.to}
}

var callFn_1 = fn_1(1,3);
callFn_1.include(2) // return true;

function Fn_2(from,to){
    this.form = from;
    this.to = to;
}
// 所有的fn_2物件都會繼承自下面這個物件
// 名稱必須是prototype(強制)
Fn_2.prototype={
    incloud: function(x){return this.form <= x && x <= this.to}
}

var callFn_2 = new Fn_2(1,3);
callFn_2.incloud(2) // return true;

  • 有prototype這個物件,即認定此函式為建構式。所以大部分會說: Fn_2()建構式創建了Fn_2物件。
  • 當呼叫建構時(透過new)即繼承自Fn_2.prototype物件的所有特性。fn_1比較像是一般function的呼叫。
  • 一般來說,建構式是類別的定義,而類別class命名是以大寫為開頭所以函式命名為Fn_2非fn_2。

利用instanceof來判斷是否屬於某類別

var callFn_2 = new Fn_2(1,2); 新創一個Fn_2物件給callFn_2,所以callFn_2也繼承了Fn_2的prototype

  • 由上可知,若要判斷是否是屬於哪個物件可以用callFn_2 instanceof Fn_2判斷。
  • callFn_2 instanceof Fn_2 並非真的去檢查是否為Fn_2建立的物件,而是檢查callFn_2是否繼承自Fn_2.prototype。

constructor

建構式需要有prototype的特性,所以每個JavaScript function都具備prototype特性(是一個物件)且裡面僅有一個constructor(不可列舉),且constructor特性值是原來含有此prototype特性的物件。

var Fn = function() { console.log('function fn');}

下面的例子可以看出,prototype特性constructor的值是指向原來含有prototype的物件。

var Fn = function() { console.log('function fn');}

var p = Fn.prototype.constructor;

p === Fn // return true

如果去new一個新的function,先創建的物件(NewFn)會去繼承原函式(Fn)的prototype,放在NewFn原型鍊裡。

var Fn = function() { console.log('function fn');}

var NewFn = new Fn;
var NewFn_A =new Fn;

NewFn.constructor === Fn; //return true

// 比較兩個新創建物件
NewFn_A === NewFn // return false 兩物件並不相等
NewFn_A.constructor === NewFn.constructor //return true

  • NewFn_A.constructor === NewFn.constructor 兩者相等,可看出兩個新創的物件是繼承自同一個函式。

在下面的例子中因為prototype被覆寫了,所以裏頭並沒有constructor

function Fn_2(from,to){
    this.form = from;
    this.to = to;
}

Fn_2.prototype={
    incloud: function(x){return this.form <= x && x <= this.to}
}

所以要在新增原型方法通常會利用以下方式:

function Fn_2(from,to){
    this.form = from;
    this.to = to;
}

Fn_2.prototype.incloud = function(x){return this.form <= x && x <= this.to}

  • 這樣可以確保constructor存在。

類別擴充

在JavaScript裡prototype的繼承是動態的。當在新創建一個類別(class)時,會繼承原類別的prototype,在這之後如果有改變原類別的prototype,則新創建的類別所繼承的prototype也會跟著變。

function Fn_3(from,to){
    this.form = from;
    this.to = to;
}
Fn_3.prototype.incloud = function(x){return this.form <= x && x <= this.to}

var newFn_3 = new Fn_3;

console.dir(newFn_3);


Fn_3.prototype.toString = function(){return this.form+'~'+this.to}

console.dir(newFn_3);
  • 執行第9行時

  • 執行第14行時,當所繼承的prototype改變了,所創建類別的原型鍊也會跟著動態改變(並無重新new)

類別與型別

使用typeof XX 可取得類型以剛剛的範例還看newFn_3:
typeof newFn_3 return "object"
typeof Fn_3 return "function"

在大部分的情況,可能更想得到類別型別。
類似newFn_3類別型別是Fn_3

instanceof

A instanceof B 判斷A物件是否有B物件的prototype物件。
如果A是繼承B.prototype那A instanceof B return true。
instanceof 無法直接知道物件的類別,只能用來測試一個物件是否屬於所指定的類別,

function Fn_3(from,to){
    this.form = from;
    this.to = to;
}
Fn_3.prototype.incloud = function(x){return this.form <= x && x <= this.to}

var newFn_3 = new Fn_3;

newFn_3 instanceof Fn_3 //return true

constructor

使用constructor來取得所屬類別
以上面的例子為例:

newFn_3.constructor 


可以看出回傳的是Fn_3 function

並不是每個物件都有constructor特性。(例如今天一開始的那範例callFn_1就沒有contructor)

Duck typing

Duck typing的敘述:(出自James Whitcomb Riley)

When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.

如果一隻鳥走起來像鴨,游起來像鴨,叫起來跟鴨一樣,那就稱這隻鳥為鴨。

用下面簡單的例子說明:
傳入一個物件確認是否為object且有name特性和id特性,如果上述條件都即認定obj就是就是我們自訂的物件,並沒有去深入地確認內層是否一致。

function quacks(obj) {
  return obj != null && typeof object === 'object' &&
    'name' in obj && 'id' in obj
}

上一篇
Day 9: 函數 function (Part 2)
下一篇
Day 11: 類別與模組(Part 2)
系列文
Javascript 犀牛本-濃縮再濃縮 提煉再提煉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言