JS建立對象

建立對象,主要有工廠模式、構造函數模式、原型模式三種。本文主要分析了這三種模式的特色、利弊,以及一些細節問題。 參考:js高程 紅寶書(第四版)數組

1、工廠模式

function createPerson(name, age, job) { 
    let o = new Object(); 
    o.name = name; 
    o.age = age; 
    o.job = job; 
    o.sayName = function() { 
        console.log(this.name); 
    }; 
    return o; 
} 
let person1 = createPerson("Nicholas", 29, "Software Engineer"); 
let person2 = createPerson("Greg", 27, "Doctor");
複製代碼
  • 優勢:建立多個類似對象的問題
  • 缺點:沒有解決對象識別問題(即新建立的對象是什麼類型?)

2、構造函數模式

function Person(name, age, job){ 
    this.name = name; 
    this.age = age; 
    this.job = job; 
    this.sayName = function() { 
        console.log(this.name); 
    }; 
} 
let person1 = new Person("Nicholas", 29, "Software Engineer"); 
let person2 = new Person("Greg", 27, "Doctor"); 

person1.sayName(); // Nicholas 
person2.sayName(); // Greg
複製代碼

1. 構造函數模式 與 工廠模式 是比較像的,只是有以下區別:markdown

  • 沒有顯式地建立對象
  • 屬性和方法直接賦值給了 this
  • 沒有 return
  • 函數名 Person 的首字母是大寫

2. 用 new 調用構造函數會執行以下操做:函數

  • 內存中建立一個新對象。
  • 這個新對象內部的 [[Prototype]]_ 特性被賦值爲構造函數的 prototype 屬性
  • 構造函數內部的 this 被賦值爲這個新對象(即 this 指向新對象)。
  • 執行構造函數內部的代碼(給新對象添加屬性)。
  • 若是構造函數返回非空對象,則返回該對象;不然,返回剛建立的新對象

3. 構造函數的問題:性能

  • 其定義的方法在每一個實例上都建立一遍,不一樣實例的方法不是同一個 Function 實例(JS 中函數是對象,所以每次定義函數時,都會初始化一個對象。)
function Person(name, age, job){ 
    this.name = name; 
    this.age = age; 
    this.job = job; 
    this.sayName = new Function("console.log(this.name)"); // 邏輯等價
}
複製代碼

要解決這個問題,能夠把函數定義 轉移到 構造函數外部ui

function Person(name, age, job){ 
    this.name = name; 
    this.age = age; 
    this.job = job; 
    this.sayName = sayName; 
} 
function sayName() { 
    console.log(this.name); 
} 
let person1 = new Person("Nicholas", 29, "Software Engineer"); 
let person2 = new Person("Greg", 27, "Doctor"); 
person1.sayName(); // Nicholas 
person2.sayName(); // Greg
複製代碼

構造函數內 sayName 屬性中包含的只是一個指向外部全局函數的指針,因此 person1 和 person2共享了定義在全局做用域上的 sayName()函數。可是這樣處理也是有弊端的:this

  • 雖解決了相同邏輯的函數重複定義問題,可是全局做用域被搞亂了。若這個對象須要多個函數,就須要在全局做用域中定義多個函數。
  • 該問題可用原型模式來解決。

3、原型模式

每一個函數都會建立一個 prototype 屬性,這個屬性是一個對象,包含應該由特定引用類型的實例共享的屬性和方法。spa

function Person() {} 
    Person.prototype.name = "Nicholas"; 
    Person.prototype.age = 29; 
    Person.prototype.job = "Software Engineer"; 
    Person.prototype.sayName = function() { 
        console.log(this.name); 
    }; 
    let person1 = new Person(); 
    person1.sayName(); // "Nicholas" 
    
    let person2 = new Person(); 
    person2.sayName(); // "Nicholas" 
    
    console.log(person1.sayName == person2.sayName); // true
複製代碼

原型相關的方法總結:

1. isPrototypeOf():肯定 實例 和 原型對象 的關係;prototype

console.log(Person.prototype.isPrototypeOf(person1)); // true 
console.log(Person.prototype.isPrototypeOf(person2)); // true
複製代碼

2. Object.getPrototypeOf():返回參數的內部特性[[Prototype]]的值。指針

console.log(Object.getPrototypeOf(person1) == Person.prototype); // true 
console.log(Object.getPrototypeOf(person1).name); // "Nicholas"
複製代碼

3. Object.setPrototypeOf():向實例的私有特性[[Prototype]]寫入一個新值。會嚴重影響代碼性能,不推薦!能夠使用Object.create()代替code

4. Object.create():建立新對象,併爲其指定原型對象

let biped = { 
    numLegs: 2 
}; 
let person = Object.create(biped); 
person.name = 'Matt'; 
console.log(person.name); // Matt 
console.log(person.numLegs); // 2,訪問原型上的屬性
console.log(Object.getPrototypeOf(person) === biped); // true
複製代碼

5. hasOwnProperty()方法用於肯定某個屬性 是在實例上 仍是在原型對象上。屬性存在於調用它的對象實例上時返回 true

console.log(person1.hasOwnProperty("name")); // false 

person1.name = "Greg"; 
console.log(person1.name); // "Greg",來自實例
console.log(person1.hasOwnProperty("name")); // true
複製代碼

6. 原型和 in 操做符

in 操做符有兩種使用方式:單獨使用和在 for-in 循環中使用。

  • 在單獨使用時,in 操做符會在能夠經過對象訪問指定屬性時返回 true,不管該屬性是在實例上仍是在原型上。

若是要肯定某個屬性 是否存在於原型上,則能夠像下面這樣同時使用 hasOwnProperty()和 in 操做符:

function hasPrototypeProperty(object, name){ 
    return !object.hasOwnProperty(name) && (name in object); 
}
複製代碼
  • for-in 循環中使用 in 操做符時,能夠經過對象訪問能夠被枚舉的屬性都會返回,包括實例屬性和原型屬性。

7. Object.keys():得到對象上全部可枚舉的實例屬性,返回一個字符串數組。

8. Object.getOwnPropertyNames():列出全部實例屬性,不管是否能夠枚舉。

let keys = Object.getOwnPropertyNames(Person.prototype); 
console.log(keys); // "[constructor,name,age,job,sayName]"
複製代碼

注意,返回的結果中包含了一個不可枚舉的屬性 constructor。

4、對象迭代

這兩個靜態方法Object.values()和 Object.entries()接收一個對象,返回它們內容的數組。

1. 其餘原型語法:

function Person() {};

Person.prototype = {
    name: "Nicholas", 
    age: 29, 
    job: "Software Engineer", 
    sayName() { 
        console.log(this.name); 
    } 
};
複製代碼

這樣存在一個問題:Person.prototype 的 constructor 屬性就不指向 Person 了。

  • 在建立函數時,會自動建立它的 prototype 對象,同時會自動給這個原型的 constructor 屬性賦值。
  • 上面的寫法徹底重寫了默認的 prototype 對象,所以其 constructor 屬性也指向了徹底不一樣的新對象(Object 構造函數),再也不指向原來的構造函數。
let friend = new Person(); 
console.log(friend instanceof Object); // true 
console.log(friend instanceof Person); // true

console.log(friend.constructor == Person); // false 
console.log(friend.constructor == Object); // true
複製代碼

別急,稍做修改便可:

function Person() {} 

Person.prototype = { 
    // constructor: Person, 用這種方式恢復 constructor 屬性會建立一個[[Enumerable]]爲 true 的屬性。而原生 constructor 屬性默認是不可枚舉的 
    name: "Nicholas", 
    age: 29, 
    job: "Software Engineer", 
    sayName() { 
        console.log(this.name); 
    } 
}; 
// 恢復 constructor 屬性
Object.defineProperty(Person.prototype, "constructor", { 
    enumerable: false, 
    value: Person 
});
複製代碼

2. 原型的動態性

let friend = new Person(); 
Person.prototype.sayHi = function() { 
    console.log("hi"); 
}; 
friend.sayHi(); // "hi",沒問題!
複製代碼

原型被重寫後:

function Person() {} 
let friend = new Person(); 
Person.prototype = { 
    constructor: Person, 
    name: "Nicholas", 
    age: 29, 
    job: "Software Engineer", 
    sayName() { 
        console.log(this.name); 
    } 
}; 
friend.sayName(); // 錯誤
複製代碼

實例的 [[Prototype]] 指針是在調用構造函數時自動賦值的,指向最初建立的原型。(原型被重寫後,即是另外一個原型對象了,會切斷最初原型與構造函數的聯繫)

3. 原型的問題

(1)原型主要有這兩個問題:

  • 弱化了向構造函數傳遞初始化參數的能力,會致使全部實例默認都取得相同的屬性值。
  • 原型的最主要問題源自它的共享特性,原型上的全部屬性是在實例間共享的。

因此實際開發中一般 不單獨 使用原型模式

(2)原型共享特性 適用場景分析:

  • 對函數來講比較合適;
  • 對於包含原始值的屬性也還好,能夠在實例上添加同名屬性來簡單地遮蔽原型上的屬性;
  • 對於包含引用值的屬性就很糟糕了
function Person() {} 
Person.prototype = { 
    constructor: Person, 
    name: "Nicholas", 
    age: 29, 
    job: "Software Engineer", 
    friends: ["Shelby", "Court"],
    sayName() { 
        console.log(this.name); 
    } 
}; 
let person1 = new Person(); 
let person2 = new Person(); 
person1.friends.push("Van");   // friends屬性是原型上的,該屬性也會在其餘實例上體現出來
console.log(person1.friends); // "Shelby,Court,Van" 
console.log(person2.friends); // "Shelby,Court,Van" 
console.log(person1.friends === person2.friends); // true
複製代碼
相關文章
相關標籤/搜索