JavaScript 創建對象的幾個方法

總結這幾天啃高程(Javascript 高級程序設計)中看到的幾個創建對象的方法,及其優缺點。

面向對象

ES 中有兩種屬性,分別爲數據屬性和訪問器屬性。

  • 數據屬性,包含數據值位置,可讀取寫入,含以下四個特性:

    • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,默认值:true
    • [[Enumerable]]:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,默认值:true
    • [[Writable]]:表示能否修改属性的值。可直接在对象上定义属性,默认值:true
    • [[Value]]:包含这个属性的数据值。读取属性值时,从这个位置读;写入属性值时,把新值保存在此,默认值:undefined。例:var person = { name: "Nick" }; [[Value]] = "Nick"
  • 訪問器屬性,不包含數據值,具有gettersetter 函數。分別作讀取和寫入時用,分別負責返回有效值和決定如何處理數據,具有四個屬性:
    • [[Configurable]]:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值:true
    • [[Enumerable]]:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值:true
    • [[Get]]:在读取属性时调用的函数。默认值:undefined
    • [[Set]]:在写入属性时调用的函数。默认值:undefined
  • 訪問器屬性,必須用Object.defineProperty() 定義;數據屬性的默認值,也需要使用前者的函數來定義。定義多屬性,在 ES5 中可以使用Object.defineProperties() 方法。讀取屬性,可以使用Object.getOwnPropertyDescriptor() 方法。

工廠模式

  • 函數封裝對象所有細節,但每次調用此類函數,均返回函數內所有屬性和方法
  • 解決創建多個相似對象的問題
  • 沒有解決對象識別問題(如何知道對象的類型?)
1
2
3
4
5
6
7
8
9
10
11
12
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

構造函數模式

  • 函數內不用 new 顯式創建對象;屬性方法直接賦值於 this;無 return 語句
  • this 巨坑,建議與原型模式組合使用。
  • 調用時四步驟
    • 創建新對象
    • 作用域賦值於新對象
    • 執行函數代碼(新對象添加屬性)
    • 返回新對象
1
2
3
4
5
6
7
8
9
10
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

原型模式

  • 利用函數中特有的prototype 屬性,它是一個指針,指向一個對象。對象包含特定類型的所有實例共享屬性及方法。
  • 需要區別單一賦值和屬性賦值的區別。(有無 constructor
  • 關於原型鏈,翻閱此鏈接瞭解詳情
1
2
3
4
5
6
7
function Person(){ }
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};

組合使用構造函數模式和原型模式

  • 構造函數模式用于定義實例屬性,而原型模式用于定義方法和共享的屬性。
  • 每個實例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。
  • 支持向構造函數传递参数;是用来定义引用类型的一种默认模式。
1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}

動態原型模式

  • 將所有信息封裝至構造函數中,在其中初始化原型。
  • 保持了同時使用構造函數和原型的優點
1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
//方法
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}

寄生構造函數模式

  • 函數寫法同工廠模式相同
  • 使用 new ,把使用的包裝函數叫做構造函數
  • 構造函數在不返回值的情況下,默認返回新對象實例
1
2
3
4
5
6
7
8
9
10
11
function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");

穩妥構造函數模式

  • 穩妥對象:沒有公共屬性,方法不應用 this 的對象
  • 適合在一些安全環境中,或者在防止數據被其他應用程序改動時使用
  • 遵循與寄生構造函數類似的模式,但有兩點不同
    • 新創建對象的實例方法不引用 this
    • 不使用 new 操作符調用構造函數
1
2
3
4
5
6
7
8
9
function Person(name, age, job){
var o = new Object();
//可以在这里定义私有变量和函数
o.sayName = function(){
alert(name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");

上例中,除了調用 sayName() 方法外,沒有其他方式能夠訪問其數據成員。

Class (ES6)

  • ES6 引入此概念,作爲對象的模板。可以看作只是一個語法糖。
  • class 寫法只是讓對象原型寫法更加清晰,更像面向對象編程的語法。
1
2
3
4
5
6
7
8
9
10
11
class Person{
constructor(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
sayName(){
alert(name);
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
  • typeof 某個 class 的時候,結果爲 function,可以看作構造函數的另一種寫法。
1
typeof Person // "function"
  • prototype 對象的 constructor 屬性,直接指向 class 本身,與 ES5 行爲一致。
1
Person.prototype.constructor === Person// true
  • class 內部所有定義的方法,均不可枚舉[[Enumerable]],下例中的 sayName 方法爲內部定義方法,不可枚舉,與 ES5 的行爲不一致。
1
2
Object.keys(Person.prototype) // []
Object.getOwnPropertyNames(Person.prototype) // ["constructor","sayName"]
0%