2015年12月10日 星期四

JavaScript 物件使用的方式介紹

JavaScript 採用了簡單的基於物件的范型。一個物件就是一系列屬性的集合,一個屬性包含一個名字和一個值。一個屬性的值可以是函式,這種情況下屬性也被稱為方法。除了瀏覽器裡面預定義的那些物件之外,你也可以定義你自己的物件。

本章描述了怎樣使用物件,屬性,函式和方法,以及怎樣新增你自己的物件。
物件綜述

javascript 中的物件(物體),和其它編程語言中的物件一樣,可以比照現實生活中的物件(物體)來理解它。 javascript 中物件(物體)的概念可以比照著現實生活中實實在在的物體來理解。

在javascript中,一個物件可以是一個單獨的擁有屬性和類別型的實體。我們拿它和一個杯子做下類別比。一個杯子是一個物件(物體),擁有屬性。杯子有顏色,圖案,重量,由什麼材質構成等等。同樣,javascript物件也有屬性來定義它的特徵。
物件和屬性

一個 javascript 物件有很多屬性。一個物件的屬性可以被解釋成一個附加到物件上的變數。物件的屬性和普通的 javascript 變數基本沒什麼區別,僅僅是屬性屬於某個物件。屬性定義了物件的特徵(譯註:動態語言物件導向的鴨子類別型)。你可以通過點符號來訪問一個物件的屬性。

objectName.propertyName

和其他 javascript 變數一樣,物件的名字(可以是普通的變數)和屬性的名字都是大小寫敏感的。你可以在定義一個屬性的時候就給它賦值。例如,我們新增一個myCar的物件然後給他三個屬性,make,model,year。具體如下所示:

var myCar = new Object();
myCar.make = "Ford";
myCar.model = "Mustang";
myCar.year = 1969;

JavaScript 物件的屬性也可以通過方括號訪問. 物件有時也被叫作關聯陣列, 因為每個屬性都有一個用於訪問它的字串值。例如,你可以按如下方式訪問 myCar 物件的屬性:

myCar["make"] = "Ford";
myCar["model"] = "Mustang";
myCar["year"] = 1969;

一個物件的屬性名可以是任何有效的 JavaScript 字串,,或者可以被轉換為字串的任何東西,包括空字串。然而,一個屬性的名稱如果不是一個有效的 JavaScript 標識符(例如,一個有空格或短橫線,或者以數字開頭的屬性名),就只能通過方括號標記訪問。這個標記法在屬性名稱是動態判定(屬性名只有到執行時才能判定)時非常有用。例如:

var myObj = new Object(),
    str = "myString",
    rand = Math.random(),
    obj = new Object();

myObj.type              = "Dot syntax";
myObj["date created"]   = "String with space";
myObj[str]              = "String value";
myObj[rand]             = "Random Number";
myObj[obj]              = "Object";
myObj[""]               = "Even an empty string";

console.log(myObj);

你也可以通過存儲在變數中的字串來訪問屬性:

var propertyName = "make";
myCar[propertyName] = "Ford";

propertyName = "model";
myCar[propertyName] = "Mustang";

你可以在  for...in 語句中使用方括號標記以枚舉一個物件的所有屬性。為了展示它如何工作,下面的函式當你將物件及其名稱作為參數傳入時,顯示物件的屬性:

function showProps(obj, objName) {
  var result = "";
  for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
        result += objName + "." + i + " = " + obj[i] + "\n";
    }
  }
  return result;
}

因而,對於函式呼叫 showProps(myCar, "myCar") 將返回以下值:

myCar.make = Ford
myCar.model = Mustang
myCar.year = 1969

物件即全部

在 JavaScript 中,幾乎所有的東西都是物件。所有的原生類別型除了 null 與 undefined 之外都被當作物件。它們可以被賦予屬性(某些類別型的被賦予的屬性不能被持久化),並且它們都有物件的全部特徵。
枚舉一個物件的所有屬性

從 ECMAScript 5 開始,有三種原生的方法用於列出或枚舉物件的屬性:

    for...in 循環
    該方法依次訪問一個物件及其原型鏈中所有可枚舉的屬性。
    Object.keys(o)
    該方法返回一個物件 o 自身包含(不包括原型中)的所有屬性的名稱的陣列。
    Object.getOwnPropertyNames(o)
    該方法返回一個陣列,它包含了物件 o 所有擁有的屬性(無論是否可枚舉)的名稱。

在 ECMAScript 5 中,沒有原生的方法枚舉一個物件的所有屬性。然而,可以通過以下函式完成:

function listAllProperties(o){   
    var objectToInspect;   
    var result = [];
  
    for(objectToInspect = o; objectToInspect !== null; objectToInspect = Object.getPrototypeOf(objectToInspect)){
        result = result.concat(Object.getOwnPropertyNames(objectToInspect));
    }
  
    return result;
}

這在展示 「隱藏」(在原型中的不能通過物件訪問的屬性,因為另一個同名的屬性存在於原型鏈的早期)的屬性時很有用。可以通過在陣列中去除同名元素即可輕鬆地列出訪問的屬性。
新增新物件

JavaScript 擁有一系列預定義的物件。另外,你可以新增你自己的物件。從  JavaScript 1.2 之後,你可以通過物件初始化器(Object Initializer)新增物件。或者你可以新增一個建構式並使用該函式和 new 運算子初始化物件。
使用物件初始化器

除了通過建構式新增物件之外,你也可以通過物件初始化器新增物件。使用物件初始化器也被稱作通過字面值新增物件。物件初始化器與 C++ 術語相一致。

通過物件初始化器新增物件的語法如下:

var obj = { property_1:   value_1,   // property_# may be an identifier...
            2:            value_2,   // or a number...
            // ...,
            "property n": value_n }; // or a string

這裡 obj 是新物件的名稱,每一個 property_i 是一個標識符(可以是一個名稱、數字或字串字面量),並且每個 value_i 是一個其值將被賦予 property_i 的表達式。obj 與賦值是可選的;如果你不需要在其他地方引用物件,你就不需要將它賦給一個變數。(注意在接受一條語句的地方,你可能需要將物件字面量括在括號裡,從而避免將字面量與塊語句相混淆)

如果一個物件是通過在頂級scripts的物件初始化器新增的,則 JavaScript 在每次遇到包含該物件字面量的表達式時都會新增物件。同樣的,在函式中的初始化器在每次函式呼叫時也會被新增。

下面的語句只有當 cond 表達式的值為 true 時新增物件並將其賦給變數 x。

if (cond) var x = {hi: "there"};

下例新增了有三個屬性的 myHonda 物件。注意它的 engine 屬性也是一個擁有自己屬性的物件。

var myHonda = {color: "red", wheels: 4, engine: {cylinders: 4, size: 2.2}};

你也可以用物件初始化器來新增陣列。參見 array literals.

在 JavaScript 1.1 及更早版本中,你不能使用物件初始化器。你只能通過使用建構式或其他物件的函式來新增物件。參見 Using a constructor function.
使用建構式

作為另一種方式,你可以通過兩步來新增物件:

    通過新增一個建構式來定義物件的類別型。首字母大寫是非常普遍而且很恰當的慣用法。
    通過 new 新增物件範例。

為了定義物件類別型,為物件類別型新增一個函式以聲明類別型的名稱、屬性和方法。例如,你想為汽車新增一個類別型,並且將這類別物件稱為 car ,並且擁有屬性 make, model, 和 year,你可以新增如下的函式:

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}

注意通過使用 this 將傳入函式的值賦給物件的屬性。

現在你可以像這樣新增一個 mycar 物件:

var mycar = new Car("Eagle", "Talon TSi", 1993);

該新增了 mycar 並且將指定的值賦給它的屬性。因而 mycar.make 的值是字串 "Eagle", mycar.year 的值是整數 1993,依此類別推。

你可以通過呼叫 new 新增任意數量的 car 物件。例如:

var kenscar = new Car("Nissan", "300ZX", 1992);
var vpgscar = new Car("Mazda", "Miata", 1990);

一個物件的屬性值可以是另一個物件。例如,假設你按如下方式定義了 person 物件:

function Person(name, age, sex) {
  this.name = name;
  this.age = age;
  this.sex = sex;
}

然後按如下方式新增了兩個 person 範例:

var rand = new Person("Rand McKinnon", 33, "M");
var ken = new Person("Ken Jones", 39, "M");

那麼,你可以重寫 car 的定義以包含一個擁有它的 owner 屬性,如:

function Car(make, model, year, owner) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
}

你可以按如下方式新增新物件:

var car1 = new Car("Eagle", "Talon TSi", 1993, rand);
var car2 = new Car("Nissan", "300ZX", 1992, ken);

注意在新增新物件時,上面的語句將 rand 和 ken 作為 owner 的參數值,而不是傳入字串字面量或整數值。接下來你如果想找出 car2 的擁有者的姓名,你可以訪問如下屬性:

car2.owner.name

注意你總是可以為之前定義的物件增加新的屬性。例如,語句

car1.color = "black";

為 car1 增加了 color 屬性,並將其值設為 "black." 然而,這並不影響其他的物件。想要為某個類別型的所有物件增加新屬性,你必須將屬性加入到 car 物件類別型的定義中。
使用 Object.create 方法

物件也可以用 Object.create 方法新增。該方法非常有用,因為它允許你為新增的物件選擇其原型物件,而不用定義一個建構式。該函式更多的訊息及詳細用法,參見 Object.create method
繼承

所有的 JavaScript 物件繼承於至少一個物件。被繼承的物件被稱作原型,並且繼承的屬性可能通過建構式的 prototype 物件找到。
物件屬性索引

在 JavaScript 1.0 中,你可以通過名稱或序號訪問一個屬性。但是在 JavaScript 1.1 及之後版本中,如果你最初使用名稱定義了一個屬性,則你必須通過名稱來訪問它;而如果你最初使用序號來定義一個屬性,則你必須通過索引來訪問它。

這個限制發生在你通過建構式新增一個物件和它的屬性(就像我們之前通過 Car 物件類別型所做的那樣)並且顯式地定義了單獨的屬性(如 myCar.color = "red")之時。如果你最初使用索引定義了一個物件屬性,例如 myCar[5] = "25",則你之可只能通過 myCar[5] 引用它。

這條規則的例外是從與HTML對應的物件,例如 forms 陣列。對於這些陣列的元素,你總是既可以通過其序號(依據其在文件中出現的順序),也可以按照其名稱(如果有的話)訪問它。舉例而言,如果文件中的第二個 <form> 標籤有一個 NAME 屬性且值為 "myForm",訪問該 form 的方式可以是 document.forms[1],document.forms["myForm"]或 document.myForm。

為物件類別型定義屬性

你可以通過 prototype 屬性為之前定義的物件類別型增加屬性。這為該類別型的所有物件,而不是僅僅一個物件增加了一個屬性。下面的程式碼為所有類別型為 car 的物件增加了 color 屬性,然後為物件 car1 的 color 屬性賦值:

Car.prototype.color = null;
car1.color = "black";

參見 JavaScript Reference 中 Function 物件的 prototype 屬性 。
定義方法

一個方法 是關聯到某個物件的函式,或者簡單地說,一個方法是一個值為某個函式的物件屬性。定義方法就像定義普通的函式,除了它們必須被賦給物件的某個屬性。例如:

objectName.methodname = function_name;

var myObj = {
  myMethod: function(params) {
    // ...do something
  }
};

這裡 objectName 是一個已經存在的函式,methodname 是方法的名稱,而 function_name 是函式的名稱。

你可以在物件的上下文中像這樣呼叫方法:

object.methodname(params);

你可以在物件的建構式中包含方法定義來為某個物件類別型定義方法。例如,你可以為之前定義的 car 物件定義一個函式格式化並顯示其屬性:

function displayCar() {
  var result = "A Beautiful " + this.year + " " + this.make
    + " " + this.model;
  pretty_print(result);
}

這裡 pretty_print 是一個顯示橫線和一個字串的函式。注意使用 this 指代方法所屬的物件。

你可以在物件定義中通過增加下述語句將這個函式變成 car 的方法:

this.displayCar = displayCar;

因此,car 的完整定義看上去將是:

function Car(make, model, year, owner) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.owner = owner;
  this.displayCar = displayCar;
}

然後你可以按如下方式為每個物件呼叫 displayCar 方法:

car1.displayCar();
car2.displayCar();

這樣導致如下圖所示的輸出結果:

Image:obja.gif

Figure 7.1: Displaying method output.
通過 this 引用物件

JavaScript 有一個特殊的關鍵字 this,它可以在方法中使用以指代當前物件。例如,假設你有一個名為 validate 的函式,它根據給出的最大與最小值檢查某個物件的 value 屬性:

function validate(obj, lowval, hival) {
  if ((obj.value < lowval) || (obj.value > hival))
    alert("Invalid Value!");
}

然後,你可以在每個元素的 onchange 事件處理器中呼叫 validate,並通過 this 傳入相應元素,程式碼如下:

<input type="text" name="age" size="3"
  onChange="validate(this, 18, 99)">

總的說來, this 在一個方法中指呼叫的物件。

當與 form 屬性一起使用時,this 可以指代當前物件的父窗體。在下面的例子中,窗體 myForm 包含一個 Text 物件和一個按鈕,當用戶點擊按鍵,Text 物件的值被設為窗體的名稱。按鈕的 onclick 事件處理器使用 this.form 以指代其父窗體,即 myForm。

<form name="myForm">
<p><label>Form name:<input type="text" name="text1" value="Beluga"></label>
<p><input name="button1" type="button" value="Show Form Name"
     onclick="this.form.text1.value = this.form.name">
</p>
</form>

定義 getter 與 setter

一個 getter 是一個獲取某個特定屬性的值的方法。一個 setter 是一個設定某個屬性的值的方法。你可以為預定義的或用戶定義的物件定義 getter 和 setter 以支持新增的屬性。定義 getter 和 setter 的語法採用物件字面量語法。

JavaScript 1.8.1 note

Starting in JavaScript 1.8.1, setters are no longer called when setting properties in object and array initializers.

下面的  JS shell 語句描述了getters 和 setters 是如何為用戶定義的物件 o 工作的。 JS shell 是一個套用程式,允許開發者以批處理或交互的方法測試 JavaScript。在 Firefox 中,你可以通過Ctrl+Shift+K 組合按鍵呼叫 JS shell。

js> var o = {a: 7, get b() {return this.a + 1;}, set c(x) {this.a = x / 2}};
[object Object]
js> o.a;
7
js> o.b;
8
js> o.c = 50;
js> o.a;
25

o 物件的屬性如下:

    o.a — 數字
    o.b — 返回 o.a + 1 的 getter
    o.c — 由  o.c 的值所設置 o.a 值的 setter

Please note that function names of getters and setters defined in an object literal using "[gs]et property()" (as opposed to __define[GS]etter__ below) are not the names of the getters themselves, even though the [gs]et propertyName(){ } syntax may mislead you to think otherwise. To name a function in a getter or setter using the "[gs]et property()" syntax, define an explicitly named function programmatically using Object.defineProperty (or the Object.prototype.__defineGetter__ legacy fallback).

This JavaScript shell session illustrates how getters and setters can extend the Date prototype to add a year property to all instances of the predefined Date class. It uses the Date class's existing getFullYear and setFullYear methods to support the year property's getter and setter.

These statements define a getter and setter for the year property:

js> var d = Date.prototype;
js> d.__defineGetter__("year", function() { return this.getFullYear(); });
js> d.__defineSetter__("year", function(y) { this.setFullYear(y); });

These statements use the getter and setter in a Date object:

js> var now = new Date;
js> print(now.year);
2000
js> now.year = 2001;
987617605170
js> print(now);
Wed Apr 18 11:13:25 GMT-0700 (Pacific Daylight Time) 2001

Obsolete syntaxes

In the past, JavaScript supported several other syntaxes for defining getters and setters. None of these syntaxes were supported by other engines, and support has been removed in recent versions of JavaScript. See this dissection of the removed syntaxes for further details on what was removed and how to adapt to those removals.
小結

In principle, getters and setters can be either

    defined using object initializers, or
    added later to any object at any time using a getter or setter adding method.

When defining getters and setters using object initializers all you need to do is to prefix a getter method with get and a setter method with set. Of course, the getter method must not expect a parameter, while the setter method expects exactly one parameter (the new value to set). For instance:

var o = {
  a: 7,
  get b() { return this.a + 1; },
  set c(x) { this.a = x / 2; }
};

Getters and setters can also be added to an object at any time after creation using two special methods called __defineGetter__ and __defineSetter__. Both methods expect the name of the getter or setter as their first parameter, in the form of a string. The second parameter is the function to call as the getter or setter. For instance (following the previous example):

o.__defineGetter__("b", function() { return this.a + 1; });
o.__defineSetter__("c", function(x) { this.a = x / 2; });

Which of the two forms to choose depends on your programming style and task at hand. If you already go for the object initializer when defining a prototype you will probably most of the time choose the first form. This form is more compact and natural. However, if you need to add getters and setters later — because you did not write the prototype or particular object — then the second form is the only possible form. The second form probably best represents the dynamic nature of JavaScript — but it can make the code hard to read and understand.

Prior to Firefox 3.0, getter and setter are not supported for DOM Elements. Older versions of Firefox silently fail. If exceptions are needed for those, changing the prototype of HTMLElement (HTMLElement.prototype.__define[SG]etter__) and throwing an exception is a workaround.
With Firefox 3.0, defining getter or setter on an already-defined property will throw an exception. The property must be deleted beforehand, which is not the case for older versions of Firefox.
See also

    __defineGetter__
    __defineSetter__
    get
    set

刪除屬性

你可以用 delete 運算子刪除一個不是繼承而來的屬性。下面的例子說明如何刪除一個屬性:

//Creates a new object, myobj, with two properties, a and b.
var myobj = new Object;
myobj.a = 5;
myobj.b = 12;

//Removes the a property, leaving myobj with only the b property.
delete myobj.a;

如果一個全域變數不是用 var 關鍵字聲明的話,你也可以用 delete 刪除它:

g = 17;
delete g;

參見delete 以獲取更多訊息。
比較物件

在 JavaScript 中 objects 是一個引用類別型。將兩個引用相同的物件想比較會返回 true; 將兩個方法和屬性相同的物件相比較,會返回 false;

// fruit object reference variable
var fruit = {name: "apple"};

// fruitbear object reference variable
var fruitbear = {name: "apple"};

fruit == fruitbear // return false

fruit === fruitbear // return false

注意: "===" 運算子用來檢查數值是否相等: 1 === "1" // return false and 1 == "1" // return true

// fruit object reference variable
var fruit = {name: "apple"};

// fruitbear object reference variable
var fruitbear = fruit;  // assign fruit object reference to fruitbear object reference variable

// here fruit and fruitbear pointing to same object called fruit
fruit == fruitbear // return true

// here fruit and fruitbear pointing to same object called fruit
fruit === fruitbear // return true

沒有留言:

張貼留言