緣起
由於網頁的前端技術越來越熱門, 很多 Framework / Library (ex: jQuery, AngularJS / Bootstrap ... ) 或技術 (ex: / HTML5 / CSS3 / TypeScript / SCSS ...) 不斷出現. 但其中最主要的基礎還是在 HTML5 / CSS3 / Javascript.本文主要針對 Javascript 建立物件的方式及相關的注意事項, 作了整理; 畢竟, 想要看懂Javascript Framework (ex: jQuery) 的內容, 還是要有一些底子, 特別是物件的建立及的物件導向觀念,
筆者才疏學淺, 目前的功力只能寫到物件建立的方式; 至於物件導向的實作, 則需另外再花時間研究.
完整範例, 請由此下載.
基本共用方法
為了能夠列出物件所包含屬性 (properties) 的 key / value 值對, 故撰寫了一些基本的共用方法, 提供後續範例使用.
// ==================================================
// 註:
// 1. 本函式以 <signature> 模擬 overload 的情境
// 2. 本函式提供的2種處理方式, 在同一個頁面裡, 不可混用, 只能擇一使用
// 3. 本函式的第1個方式, 因為係以純文字模式輸出, 外層必須加上 <pre> </pre> 的 tag,
// 才能輸出 \t, \r 這類的控制字元, 達到階層的效果
// 4. 本函式的第2個方式 (採用 ul tag), 無法呈現階層性, 且在頁面需有以下元素
// <body>
// <ul id="foo"></ul>
// </body>
// ==================================================
function dispLog(msg, ul) {
/// <signature>
/// <summary>顯示訊息, using document.writeln()</summary>
/// <param name="msg" type="String">要顯示的訊息</param>
/// <returns type="void"></returns>
/// </signature>
/// <signature>
/// <summary>顯示訊息, using page "ul" element</summary>
/// <param name="msg" type="String">要顯示的訊息</param>
/// <param name="ul" type="Boolean">是否要與 id 為 foo 的 ul 標籤整合</param>
/// <returns type="void"></returns>
/// </signature>
var d = new Date();
if (ul === undefined) {
document.writeln(d.toISOString().substr(14, 9) + " " + msg);
}
else {
if (ul === false) {
document.writeln(d.toISOString().substr(14, 9) + " " + msg);
}
else {
//with pure Javascript
var obj = window.document.getElementById("foo");
var text = obj.innerHTML;
text += "<li>" + d.toISOString().substr(14, 9) + " " + msg + "</li>";
obj.innerHTML = text;
////with jQuery
//$("<li />").text(d.toISOString().substr(14, 9) + " " + msg).appendTo("#foo");
}
}
}
function iterateObjectAllProperties(obj, level, onlyOwnedProperties) {
/// <signature>
/// <summary>列舉物件的屬性 name, value 值對</summary>
/// <param name="obj" type="Object">被列舉的物件</param>
/// <param name="level" type="Number">第幾層 (物件的某個屬性可能也是物件, 要一併列舉)</param>
/// <returns type="void"></returns>
/// </signature>
/// <signature>
/// <summary>列舉物件的屬性 name, value 值對</summary>
/// <param name="obj" type="Object">被列舉的物件</param>
/// <param name="level" type="Number">第幾層 (物件的某個屬性可能也是物件, 要一併列舉)</param>
/// <param name="onlyOwnedProperties" type="Boolean">只列舉單純屬於該物件的屬性</param>
/// <returns type="void"></returns>
/// </signature>
function doOutput()
{
var msg = "";
//前置放 tabs
if (level !== 0) {
for (var i = 0; i < level ; i++) {
msg += "\t";
}
}
if (obj[prop] === null) {
msg += "obj[" + prop + "] = " + null;
dispLog(msg);
} else {
try {
msg += "obj[" + prop + "] = " + obj[prop].toString();
} catch (e) {
msg += "obj[" + prop + "] = " + e.message;
}
dispLog(msg);
}
//如果是子物件, 就遞迴 ...
if (typeof (obj[prop]) == "object" && obj[prop] != null) {
level++;
iterateObjectAllProperties(obj[prop], level, onlyOwnedProperties);
level--;
}
}
var listAll = false;
for (var prop in obj) {
msg = "";
if (onlyOwnedProperties === undefined) {
listAll = true;
} else {
if (onlyOwnedProperties === false) {
listAll = true;
}
}
//如果全部列舉
if (listAll == true) {
doOutput();
} else {
//如果只列舉物件本身
if (obj.hasOwnProperty(prop)) {
doOutput();
}
}
}
}
function iterateObjectProperties(obj, onlyOwnedProperties) {
/// <signature>
/// <summary>列舉物件的屬性 name, value 值對</summary>
/// <param name="obj" type="Object">被列舉的物件</param>
/// <returns type="void"></returns>
/// </signature>
/// <signature>
/// <summary>列舉物件的屬性 name, value 值對</summary>
/// <param name="obj" type="Object">被列舉的物件</param>
/// <param name="onlyOwnedProperties" type="Boolean">只列舉單純屬於該物件的屬性</param>
/// <returns type="void"></returns>
/// </signature>
iterateObjectAllProperties(obj, 0, onlyOwnedProperties);
}
方式一: Object Literal
使用時機:
建立一個單純的 Javascript 物件, 類似 C# 的匿名物件範例1.1 -- 利用 Object Literal 建立物件 (有點類似 C# 的 anonymous 物件)
// =================================================
// 以 Object Literal 的方式建立物件
// =================================================
function createByObjectLiteral() {
/// <summary>利用 Object Literal 建立物件 (有點類似 C# 的 anonymous 物件) </summary>
var empty = {}; // An object with no properties (Object.prototype)
var point = { x: 0, y: 0 }; // Two properties
var point2 = { x: point.x, y: point.y + 1 }; // More complex values
var book = {
"main title": "JavaScript", // 屬性名稱含有 空白, "-", 或 保留字,,
'sub-title': "The Definitive Guide", // 必須以雙引號括住
"for": "all audiences", //
author: { // 屬性本身為子物件
firstname: "David", //
surname: "Flanagan" //
},
//出版商資訊
publisher: {
name: "O'Reilly",
pressContacts: ["North America", "Japan", "Taiwan"] //陣列也是物件
},
email: "maureen@oreilly.com"
}
document.write("<pre>");
iterateObjectProperties(empty);
document.write("</pre>");
document.write("<pre>");
iterateObjectProperties(point);
document.write("</pre>");
document.write("<pre>");
iterateObjectProperties(point2);
document.write("</pre>");
document.write("<pre>");
iterateObjectProperties(book);
document.write("</pre>");
//輸出結果:
//31:47.830 obj[x] = 0
//31:47.830 obj[y] = 0
//31:47.830 obj[x] = 0
//31:47.831 obj[y] = 1
//31:47.831 obj[main title] = JavaScript
//31:47.831 obj[sub-title] = The Definitive Guide
//31:47.831 obj[for] = all audiences
//31:47.831 obj[author] = [object Object]
//31:47.832 obj[firstname] = David
//31:47.832 obj[surname] = Flanagan
//31:47.832 obj[publisher] = [object Object]
//31:47.832 obj[name] = O'Reilly
//31:47.832 obj[pressContacts] = North America,Japan,Taiwan
//31:47.833 obj[0] = North America
//31:47.833 obj[1] = Japan
//31:47.833 obj[2] = Taiwan
//31:47.833 obj[email] = maureen@oreilly.com
}
方式二: Constructor Function
使用時機:
建立一個 Javascript 物件的範本, 再依實際需要, 以 new 敍述建立一個或多個物件實體 (instances). 類似 C# 的 Class; 但必須說明的是 Javascript 是以原型為基礎 (Prototype-Based) 的程式語言, 與其它以類別為基礎 (Class-Based) 的程式語言不同.範例2.1 -- 利用 new 建立物件後, 再新增該物件的屬性
// =================================================
// 以 Constructor Function 的方式建立物件
// =================================================
function createByConstructor01() {
/// <summary>利用 new 建立物件後, 再新增該物件的屬性</summary>
var o = new Object(); // Create an empty object: same as {}.
var a = new Array(); // Create an empty array: same as [].
var d = new Date(); // Create a Date object representing the current time
var r = new RegExp("js"); // Create a RegExp object for pattern matching.
var objCard = new Object(); // objCard 係以 Object 為範本, 建立一個物件
objCard.name = "Jasper"; // 加入專屬於 objCard 的屬性
objCard.age = 47;
objCard.phones = ["02-2123-4567", "02-2321-7654"];
objCard.email = "jasper@abc.com";
document.write("<pre>");
iterateObjectProperties(objCard);
document.write("</pre>");
//輸出結果:
// 56:59.116 obj[name] = Jasper
// 56:59.131 obj[age] = 47
// 56:59.147 obj[phones] = 02-2123-4567,02-2321-7654
// 56:59.159 obj[0] = 02-2123-4567
// 56:59.175 obj[1] = 02-2321-7654
// 56:59.189 obj[email] = jasper@abc.com
}
範例2.2 -- 利用 constructor function (類似 Class) + new 建立物件
// =================================================
// 以 Constructor Function 的方式建立物件
// =================================================
function createByConstructor02() {
/// <summary>利用 constructor function (類似 Class) + new 建立物件 </summary>
// ---------------- 較單純的 (帶一個簡單的 method) -----------------------
function Shape(color, borderThickness) {
this.color = color;
this.borderThickness = borderThickness;
this.describe = function () {
document.write("<pre>");
dispLog("I am a " + this.color + " shape, with a border that is " + this.borderThickness + " thick");
document.write("</pre>");
}
}
var shape = new Shape("red", 2.0);
shape.describe();
document.write("<pre>");
iterateObjectProperties(shape);
document.write("</pre>");
// ---------------- 較複雜的 -----------------------
function Phones(office, mobiles) {
///<summary>定義 Phones 的建構函式</summary>
this.office = office;
this.mobiles = mobiles;
}
function Contactor(name, phones, email) {
///<summary>定義 ContactInfo 的建構函式</summary>
this.name = name;
this.phones = phones;
this.email = email;
}
//建立物件, 並同時給值
var contactor01 = new Contactor("Jasper", new Phones("02-2123-4567", ["0912-345-678", "0912-876-543"]), "jasper@abc.com");
//建立物件, 後續才給值
var contactor02 = new Contactor();
contactor02.name = "Judy";
contactor02.phones = new Phones();
contactor02.phones.office = "02-2321-7654";
contactor02.phones.mobiles = null;
contactor02.email = "judy@abc.com";
document.write("<pre>");
iterateObjectProperties(contactor01);
document.write("</pre>");
document.write("<pre>");
iterateObjectProperties(contactor02);
document.write("</pre>");
//輸出結果:
//39:42.343 I am a red shape, with a border that is 2 thick
//
//39:42.362 obj[color] = red
//39:42.372 obj[borderThickness] = 2
//39:42.382 obj[describe] = function () {
// document.write("");
// dispLog("I am a " + this.color + " shape, with a border that is " + this.borderThickness + " thick");
// document.write("");
//
//04:49.864 obj[name] = Jasper
//04:49.875 obj[phones] = [object Object]
//04:49.886 obj[office] = 02-2123-4567
//04:49.895 obj[mobiles] = 0912-345-678,0912-876-543
//04:49.907 obj[0] = 0912-345-678
//04:49.933 obj[1] = 0912-876-543
//04:49.942 obj[email] = jasper@abc.com
//
//04:49.959 obj[name] = Judy
//04:49.970 obj[phones] = [object Object]
//04:49.979 obj[office] = 02-2321-7654
//04:49.988 obj[mobiles] = null
//04:50.001 obj[email] = judy@abc.com
}
以第2個範例而言, 經由 Visual Studio 2013 進行逐步除錯 (圖 2.2.1 及 圖 2.2.2), 可以發現有 __proto__ 這個屬性, 指向其上層的物件. 例如: shape --> Shape --> Object --> Null (圖 2.2.3). 但這個只是初步的結論,
實際上, __proto__ 指向的是一個 prototype 物件; 更準確的說, 應該是建構函式的 prototype 屬性.
![]() |
| 圖 2.2.1: Javascript __proto__ 架構示意圖 |
![]() |
| 圖 2.2.2: Javascript __proto__ 架構示意圖 |
![]() |
| 圖 2.2.3: Javascript __proto__ 架構示意圖 |
補充觀念: prototype
參考連結: Understanding JavaScript Object Creation Patterns在進行下一個物件建立方式之前, 先針對 prototype 作一下說明,
就方式二的最末段說明: "實際上, __proto__ 指向的是一個 prototype 物件; 更準確的說, 應該是建構函式的 prototype 屬性", 寫一下程式作驗證
範例3.1 -- 用以說明 prototype 的觀念
// =================================================
// 用以說明 prototype 的觀念
// =================================================
function testPrototype01() {
/// <summary>用以說明 prototype 的觀念 </summary>
function Shape(color, borderThickness) {
this.color = color;
this.borderThickness = borderThickness;
this.describe = function () {
document.write("<pre>");
dispLog("I am a " + this.color + " shape, with a border that is " + this.borderThickness + " thick");
document.write("</pre>");
}
}
var shape01 = new Shape("red", 2.0);
var shape02 = new Shape("blue", 3.0);
document.write("<pre>");
iterateObjectProperties(shape01, true);
iterateObjectProperties(shape02, true);
document.write("</pre>");
//
document.write("<pre>");
//以下的 shape01 及 shape02 的 __proto__ 都指向 Shape.prototype
dispLog("shape01.__proto__ === Shape.prototype ? " + (shape01.__proto__ === Shape.prototype));
dispLog("shape02.__proto__ === Shape.prototype ? " + (shape02.__proto__ === Shape.prototype));
dispLog("shape01.__proto__ === shape02.prototype ? " + (shape01.__proto__ === shape02.__proto__));
dispLog("");
//以下的 Shape.prototype.__proto__ === Object.prototype, 且 Shape.__proto__ === Function.prototype
//說明: Shape 其實是一個建構函式, 所以其 __proto__ 會指向 Function.prototype;
// 而 Function.prototype.__proto__ 才會指向 Object.prototype
dispLog("Shape.prototype.__proto__ === Object.prototype ? " + (Shape.prototype.__proto__ === Object.prototype));
dispLog("Shape.__proto__ === Function.prototype ? " + (Shape.__proto__ === Function.prototype));
dispLog("Function.prototype.__proto__ === Object.prototype? " + (Function.prototype.__proto__ === Object.prototype));
//
document.write("</pre>");
//輸出結果:
//00:08.843 obj[color] = red
//00:08.860 obj[borderThickness] = 2
//00:08.874 obj[describe] = function () {
// document.write("<pre>");
// dispLog("I am a " + this.color + " shape, with a border that is " + this.borderThickness + " thick");
// document.write("</pre>");
//}
//00:08.888 obj[color] = blue
//00:08.904 obj[borderThickness] = 3
//00:08.923 obj[describe] = function () {
// document.write("<pre>");
// dispLog("I am a " + this.color + " shape, with a border that is " + this.borderThickness + " thick");
// document.write("</pre>");
//}
//
//00:08.950 shape01.__proto__ === Shape.prototype ? true
//00:08.966 shape02.__proto__ === Shape.prototype ? true
//00:08.978 shape01.__proto__ === shape02.prototype ? true
//00:08.994
//00:09.007 Shape.prototype.__proto__ === Object.prototype ? true
//00:09.021 Shape.__proto__ === Function.prototype ? true
//00:09.038 Function.prototype.__proto__ === Object.prototype? true
}
![]() |
| 圖 3.1.1 Javascript: prototype 示意圖 |
但 prototype 到底有什麼好處呢? 其實, 它最主要在於可擴充現行建構函式 (or 類別) 的功能, 茲以範例3.2 作說明. 這個範例看來跟範例3.1 很像, 但事實上是有區別的, 將 describe() 方法由建構函式, 移到 Shape.prototype
範例3.2 -- 用以說明 prototype 的觀念 (利用 prototype, 加入擴充方法)
// =================================================
// 用以說明 prototype 的觀念
// =================================================
function testPrototype02() {
/// <summary>用以說明 prototype 的觀念 (利用 prototype, 加入擴充方法) </summary>
function Shape(color, borderThickness) {
this.color = color;
this.borderThickness = borderThickness;
}
//在 Shape.prototye 加入一個擴充方法, 這個方法可以由多個 instances 呼叫
Shape.prototype.describe = function () {
document.write("<pre>");
dispLog("I am a " + this.color + " shape, with a border that is " + this.borderThickness + " thick");
document.write("</pre>");
}
var shape01 = new Shape("red", 2.0);
var shape02 = new Shape("blue", 3.0);
document.write("<pre>");
iterateObjectProperties(shape01, true);
iterateObjectProperties(shape02, true);
document.write("</pre>");
//每一個 instances 都可以呼叫經由 建構函式的 prototype 作擴充的方法
shape01.describe();
shape02.describe();
//輸出結果:
//00:09.139 obj[color] = red
//00:09.152 obj[borderThickness] = 2
//00:09.171 obj[color] = blue
//00:09.189 obj[borderThickness] = 3
//
//04:21.626 I am a red shape, with a border that is 2 thick
//04:21.657 I am a blue shape, with a border that is 3 thick
}
如上的說明, 我們也可以為 String 加上一些原來沒有的功能, 例如: reverse() ...
如果仔細查看, 可以發現在各個 prototype 物件上, 其實還有一個 constructor 的屬性, 指向建構函式.
範例3.3 -- 用以說明 prototype 的觀念 (constructor 屬性)
// =================================================
// 用以說明 prototype 的觀念
// =================================================
function testPrototype03() {
/// <summary>用以說明 prototype 的觀念 (利用 prototype, 加入擴充方法) </summary>
function Shape(color, borderThickness) {
this.color = color;
this.borderThickness = borderThickness;
}
//在 Shape.prototye 加入一個擴充方法, 這個方法可以由多個 instances 呼叫
Shape.prototype.describe = function () {
document.write("<pre>");
dispLog("I am a " + this.color + " shape, with a border that is " + this.borderThickness + " thick");
document.write("</pre>");
}
var shape01 = new Shape("red", 2.0);
var shape02 = new Shape("blue", 3.0);
document.write("<pre>");
dispLog("Shape.prototype.constructor === Shape ? " + (Shape.prototype.constructor === Shape));
dispLog("Shape.prototype.constructor = " + Shape.prototype.constructor);
dispLog("Function.prototype.constructor = " + Function.prototype.constructor);
dispLog("Object.prototype.constructor = " + Object.prototype.constructor);
document.write("</pre>");
//輸出結果:
//21:38.524 Shape.prototype.constructor === Shape ? true
//21:38.540 Shape.prototype.constructor = function Shape(color, borderThickness) {
// this.color = color;
// this.borderThickness = borderThickness;
//}
//
//21:38.556 Function.prototype.constructor =
//function Function() {
// [native code]
//}
//
//21:38.592 Object.prototype.constructor =
//function Object() {
// [native code]
//}
}
![]() |
| 圖 3.3.1 Javascript: prototype 示意圖 |
方式三: Object.create() method
使用時機: 藉由某個物件的 prototype, 建立一個單純的 Javascript 物件. 此方式不需經由 Constructor Function 即可建立物件 (Object.create() is an excellent choice for creating an object without going through its constructor.) 但因為不經由 Constructor Function, 所以有些原本以為可正常運作的寫法, 但其實無法運作 ...
範例4.1 -- Object.create(), 看起來可以運作, 但實際上運作會發生預期之外的執行結果
// =================================================
// 採用 Object.create()
// =================================================
function createByCreateFunc01() {
/// <summary>採用 Object.create() (錯誤的示範) </summary>
function Car(desc) {
this.desc = desc;
this.color = "red";
}
Car.prototype = {
getInfo: function () {
return 'A ' + this.color + ' ' + this.desc + '.';
}
};
//instantiate object using the constructor function
var car = Object.create(Car.prototype);
car.color = "blue";
//以下會出現 undefined, 主因在於 Object.create() 是利用 prototype, 但不經由 constructor 作初始化
//The description is lost. So why is that? Simple; the create() method only uses the prototype and
//not the constructor. Hence, Object.create() is an excellent choice for creating an object without
//going through its constructor.
document.write("<pre>");
dispLog(car.getInfo()); //displays 'A blue undefined.' ??!
document.write("</pre>");
//輸出結果:
//51:34.411 A blue undefined.
}
範例4.2 -- 修正上述錯誤的範例
// =================================================
// 採用 Object.create()
// =================================================
function createByCreateFunc02() {
/// <summary>採用 Object.create() (修正因為沒有經過 constructor 作初始化, 造成 undefined 的問題) </summary>
//var Car = Object.create(null); //this is an empty object, like {} ==> Actually, this is not true:
//var Car = Object.create(null); //this is an empty object - no toString() etc.
//var Car = Object.create(Object.prototype); // this is like {}
//var Car = Object.create(null); //this is an empty object - no toString() etc.
var Car = Object.create(Object.prototype); //this is an object - include toString() ... methods
Car.prototype = {
getInfo: function () {
return 'A ' + this.color + ' ' + this.desc + '.';
}
};
var car = Object.create(Car.prototype, {
//value properties
color: { writable: true, configurable: true, value: 'red' },
//concrete desc value
rawDesc: { writable: false, configurable: true, value: 'Porsche boxter' },
// data properties (assigned using getters and setters)
desc: {
configurable: true,
get: function () {
return this.rawDesc.toUpperCase();
},
set: function (value) {
this.rawDesc = value.toLowerCase();
}
}
});
car.color = 'blue';
document.write("<pre>");
dispLog(car.getInfo()); //displays 'A blue PORSCHE BOXTER.'
document.write("</pre>");
//輸出結果:
//20:40.488 A blue PORSCHE BOXTER.
}
範例4.3 -- 一個來自MDN的範例
// =================================================
// 採用 Object.create()
// =================================================
function createByCreateFunc03() {
/// <summary>採用 Object.create() (Mozila 的範例) </summary>
// Shape - superclass
function Shape() {
this.x = 0;
this.y = 0;
}
// superclass method
Shape.prototype.move = function (x, y) {
this.x += x;
this.y += y;
dispLog('Shape moved to x:' + x + ' y:' + y);
//console.info('Shape moved.');
};
// Rectangle - subclass
function Rectangle() {
Shape.call(this); // call super constructor.
}
// subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var rect = new Rectangle();
document.write("<pre>");
dispLog('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle)); // true
dispLog('Is rect an instance of Shape? ' + (rect instanceof Shape)); // true
//console.log('Is rect an instance of Rectangle? ' + (rect instanceof Rectangle)); // true
//console.log('Is rect an instance of Shape? ' + (rect instanceof Shape)); // true
rect.move(1, 1); // Outputs, 'Shape moved to x:1 y:1'
document.write("</pre>");
//輸出結果:
//30:41.466 Is rect an instance of Rectangle? true
//30:41.484 Is rect an instance of Shape? true
//30:41.499 Shape moved to x:1 y:1
}
範例 4.3 有點複雜, 筆者還不是很能了解, 但推測是利用 Object.create() 建構類別之間的繼承關係 ...






沒有留言:
張貼留言