建立物件學習 —— 《高階教程》
建立單個物件的缺點:用同一個介面建立很多物件,會產生大量的重複程式碼。
工廠模式就是為了解決這個問題。
工廠模式
解決了建立多個相似物件的問題
function createPerson(name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function() { console.log(this.name) } return o; } var person1 = createPerson('Mike', 28, 'xxx'); console.log(person1); person1.sayName(); var person2 = createPerson('Mike', 24, 'aaa'); console.log(person2); person2.sayName();
缺點:無法解決物件識別的問題——怎樣知道一個物件的型別
建構函式模式
ECMAScript中的建構函式可以用來建立特定型別的物件。
function Person(name, age , job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { console.log(this.name); } } var person1 = new Person('Mike', 28, 'teacher'); console.log(person1); person1.sayName(); var person2 = new Person('Danie', 24, 'doctor'); console.log(person2); person2.sayName();
與工廠模式的區別:
- 沒有顯示的建立物件
- 將屬性和方法賦值給了this物件
- 沒有return語句
建構函式本身也是函式,只不過可以用來建立物件
用new操作符新建建構函式的例項,經歷4個步驟:
person1 和 person2 分別儲存著 Person 的兩個不同例項,都有一個 constructor (建構函式) 屬性,指向 Person
console.log(person1.constructor == Person); // true console.log(person2.constructor == Person); // true
console.log(person1.constructor == Object); // false console.log(person2.constructor == Object); // false
person1 和 person2 都是 Person 的例項,也是 Object 的例項,可以通過 instanceof 操作符來檢驗。
console.log(person1 instanceof Person); // true console.log(person2 instanceof Person); // true console.log(person1 instanceof Object); // true console.log(person2 instanceof Object); // true
缺點:每個建構函式中的方法都要在新的例項上建立一遍。
console.log(person1.sayName == person2.sayName); // false
將相同的方法移到外部:
function sayName() { // 將相同的方法移到建構函式的外部 console.log(this.name); } function Person(name, age , job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } var person1 = new Person('Mike', 28, 'teacher'); console.log(person1); person1.sayName(); var person2 = new Person('Danie', 24, 'doctor'); console.log(person2); person2.sayName();
// 此時不同例項上的方法就相等了 console.log(person1.sayName == person2.sayName); // true
缺點:需要在全域性作用域定義很多函式,沒有封裝性可言
原型模式
好處: 所有物件例項可共享它所包含的屬性和方法
function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); var person2 = new Person(); person1.sayName(); person2.sayName(); console.log(person1.sayName == person2.sayName); // true
下圖以上面程式碼為例,展示了Person建構函式、Person的原型屬性,及兩個例項之間的關係。
在實現中,無法訪問 [[Prototype]],可以用 isPrototypeOf() 方法來確定物件之間是否有這種關係。
console.log(Person.prototype.isPrototypeOf(person1)); // true console.log(Person.prototype.isPrototypeOf(person2)); // true
通過 Object.getPrototypeOf() 方法,訪問原型物件上的屬性
console.log(Object.getPrototypeOf(person1) == Person.prototype); // true console.log(Object.getPrototypeOf(person1).name); // Mike
修改例項屬性
// 原型模式 修改例項屬性 function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); person1.name = 'Gray'; person1.job = 'doctor'; var person2 = new Person(); person1.sayName(); // Gray person2.sayName(); // Mike console.log(person1); // Person {name: "Gray", job: "doctor", __proto__: Object} console.log(person2); // Person {__proto__: Object}
修改例項屬性為 null, 不會恢復指向原型的連結
function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); person1.name = 'Gray'; var person2 = new Person(); person1.sayName(); // Gray person2.sayName(); // Mike person1.name = null; // 不會恢復指向原型的連結 person1.sayName(); // null
刪除例項屬性 會重新恢復指向原型物件的連結
// 原型模式 刪除例項屬性 會重新恢復指向原型物件的連結 function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); person1.name = 'Gray'; var person2 = new Person(); person1.sayName(); // Gray person2.sayName(); // Mike delete person1.name; // 會重新恢復指向原型物件的連結 person1.sayName(); // Mike
hasOwnProperty() 檢測一個屬性存在於例項中還是存在於原型中。
// 原型模式 hasOwnProperty() function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); console.log(person1.hasOwnProperty('name')); // false person1.name = 'Gray'; console.log(person1.hasOwnProperty('name')); // true var person2 = new Person(); console.log(person2.hasOwnProperty('name')); // false delete person1.name; console.log(person1.hasOwnProperty('name')); // false
Object.keys() 獲得物件上所有可列舉的例項屬性
// Object.keys() function Person() { } Person.prototype.name = 'Mike'; Person.prototype.age = 28; Person.prototype.job = 'teacher'; Person.prototype.sayName = function() { console.log(this.name); } console.log(Object.keys(Person.prototype)); // ["name", "age", "job", "sayName"] var person1 = new Person(); console.log(Object.keys(person1)); // [] person1.name = 'Gray'; console.log(Object.keys(person1)); // ["name"]
更簡單的原型語法
function Person() { } Person.prototype = { // prototype 的 constructor 屬性不再指向 Person name: 'Mike', age: 28, job: 'Teacher', sayName: function() { console.log(this.name); } } var person1 = new Person(); console.log(person1)
以上方式,Person.prototype 的 constructor 屬性將不再指向 Person。
對比下面兩張圖:
Person.prototype.name = 'Mike'; // 方式建立的
constructor 會指向 Person
Person.prototype = {}; // 物件字面量方式建立的
constructor 不會指向 Person
通過 instanceof 還能返回正確的結果,但是 constructor 已經不能確定物件的型別了。
console.log(person1 instanceof Object); // true console.log(person1 instanceof Person); // true console.log(person1.constructor == Object); // true console.log(person1.constructor == Person); // false
如果 constructor 很重要,可以顯示指定
// 顯示指定 constructor function Person() { } Person.prototype = { // 通過指定 constructor屬性,指向 Person constructor: Person, // 以這種方式重設,會使它的[[Enumerable]]特性被設定為true name: 'Mike', age: 28, job: 'Teacher', sayName: function() { console.log(this.name); } } var person1 = new Person(); console.log(person1); // 打印出的結果見下圖
相容 ECMAScript5的瀏覽器引擎
Object.defineProperty()
function Person() { } Person.prototype = { name: 'Mike', age: 28, job: 'Teacher', sayName: function() { console.log(this.name); } } Object.defineProperty(Person.prototype, 'constructor', { enumerable: false, value: Person });
原型的動態性
先建立例項,再在Person的原型物件上加方法,例項也可以呼叫。
// 原型的動態性 function Person() { } var person1 = new Person(); Person.prototype.sayHi = function() { console.log('Hi'); } person1.sayHi(); // Hi
重寫原型物件
function Person() { } var person1 = new Person(); Person.prototype = { // 通過指定 constructor屬性,指向 Person constructor: Person, name: 'Mike', age: 28, job: 'Teacher', sayName: function() { console.log(this.name); } } console.log(person1); person1.sayName(); // person1.sayName is not a function
可以看到,person1的原型物件上沒有Person的新原型物件上的屬性,因為他們是兩個不同的物件。
下圖展示了重寫原型之前和重寫原型之後各個物件之間的關係。
未完待續...
注:以上所有的文字、程式碼都是本人一個字一個字敲上去的,圖片也是一張一張畫出來的,轉載請註明出處,謝謝!