建立對象,主要有工廠模式、構造函數模式、原型模式三種。本文主要分析了這三種模式的特色、利弊,以及一些細節問題。 參考:js高程 紅寶書(第四版)數組
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");
複製代碼
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
2. 用 new 調用構造函數會執行以下操做:函數
3. 構造函數的問題:性能
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
每一個函數都會建立一個 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 循環中使用。
若是要肯定某個屬性 是否存在於原型上,則能夠像下面這樣同時使用 hasOwnProperty()和 in 操做符:
function hasPrototypeProperty(object, name){
return !object.hasOwnProperty(name) && (name in object);
}
複製代碼
7. Object.keys():得到對象上全部可枚舉的實例屬性,返回一個字符串數組。
8. Object.getOwnPropertyNames():列出全部實例屬性,不管是否能夠枚舉。
let keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys); // "[constructor,name,age,job,sayName]"
複製代碼
注意,返回的結果中包含了一個不可枚舉的屬性 constructor。
這兩個靜態方法Object.values()和 Object.entries()接收一個對象,返回它們內容的數組。
function Person() {};
Person.prototype = {
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
}
};
複製代碼
這樣存在一個問題:Person.prototype 的 constructor 屬性就不指向 Person 了。
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
});
複製代碼
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]] 指針是在調用構造函數時自動賦值的,指向最初建立的原型。(原型被重寫後,即是另外一個原型對象了,會切斷最初原型與構造函數的聯繫)
(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
複製代碼