建立對象(學習筆記) —— 《高級教程》

建立單個對象的缺點:用同一個接口建立不少對象,會產生大量的重複代碼。javascript

工廠模式就是爲了解決這個問題。java

工廠模式

解決了建立多個類似對象的問題es6

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語句

構造函數自己也是函數,只不過能夠用來建立對象this

用new操做符新建構造函數的實例,經歷4個步驟:spa

person1 和 person2 分別保存着 Person 的兩個不一樣實例,都有一個 constructor (構造函數) 屬性,指向 Personprototype

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 操做符來檢驗。code

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的新原型對象上的屬性,由於他們是兩個不一樣的對象。

下圖展現了重寫原型以前和重寫原型以後各個對象之間的關係。

 

 

原生對象的原型

全部原生引用類型(Object, Array, String等)都是在其構造函數的原型上定義了方法。

console.log(typeof Array.prototype.sort); // "function"

在原生對象的原型上添加方法

// 注意 es6 中已經實現了該方法
String.prototype.startsWith = function(text) { return this.indexOf(text) === 0; } console.log('Hello javascript'.startsWith('Hello')); // true

不推薦在原生對象的原型上添加方法,可能會意外的重寫原生方法。

 

原型對象的問題

缺點:

  • 省略了爲構造函數傳遞初始化參數
  • 全部屬性被實例共享——主要針對引用類型的屬性

 

看下面代碼

// 原型對象缺點
function Person() { } Person.prototype = { constructor: Person, name: 'Mike', age: 28, job: 'Teacher', friends: ['Emily', 'David'], sayName: function() { console.log(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.friends.push('Jennifer'); console.log(person1.friends); // ["Emily", "David", "Jennifer"]
console.log(person2.friends); // ["Emily", "David", "Jennifer"]
console.log(person1.friends == person2.friends); // true

person1 的friends 和 person2 的 friends 共享了,實際應用中,每一個實例應該都有本身的 friends纔對。

因此,出現瞭如下這種模式。

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

優勢:

  • 構造函數定義實例屬性,每一個實例都會有一份本身的實例屬性副本
  • 原型模式定義方法和共享屬性,實例可共享方法,節省內存
  • 支持傳參

改寫前面的例子:

// 組合模式
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ['Emily', 'David']; } Person.prototype = { constructor: Person, sayName: function() { console.log(this.name); } } var person1 = new Person('Mike', 28, 'teacher'); var person2 = new Person('Gray', 24, 'doctor'); person1.friends.push('Jennifer'); console.log(person1.friends); // ["Emily", "David", "Jennifer"]
console.log(person2.friends); // ["Emily", "David"]
console.log(person1.friends == person2.friends); // false
person1.sayName(); // Mike
person2.sayName(); // Gray

如今修改任一實例的 friends 屬性,都只會影響自身,不會影響到其餘實例的。

動態原型模式

組合模式中,構造函數和原型是分離的。

動態原型模式,將原型在構造函數初始化時,就在構造函數中初始化了。

function Person(name, age, job) { this.name = name; this.age = age; this.job = job; if (typeof this.sayName != 'function') { Person.prototype.sayName = function() { // 能夠當即在全部實例中體現
            console.log(this.name); } } } var person1 = new Person('Mike', 28, 'teacher'); person1.sayName(); // Mike

寄生構造函數模式

先來看一段代碼:

function Person(name, age, job) { var obj = new Object(); // <-- 建立一個 obj
    obj.name = name; obj.age = age; obj.job = job; obj.sayName = function() { console.log(this.name); } return obj; // <-- return obj
} var person1 = new Person('Mike', 28, 'teacher'); person1.sayName(); // Mike

看起來和構造函數沒什麼區別,就是將屬性都綁定在內部新建的 obj 上,而後返回這個 obj。

可是,請繼續看:

console.log(person1.constructor); // ƒ Object() { [native code] } 實例的構造函數是 Object 不是 Person
console.log(person1 instanceof Person); // false <-- 實例

構造函數中返回的對象與構造函數或構造函數的原型屬性之間沒有關係。不能依賴 instanceof 來肯定對象的類型。

建議:在可使用其餘模式時,不要用這種模式。

穩妥構造函數模式

穩妥對象(durable objects):沒有公共屬性;方法不引用 this。

穩妥對象適合在一些安全環境(禁止使用 this 和 new)和防止數據被其餘應用程序改動時使用。

function Person(name, age, job) { var obj = new Object(); obj.name = name; obj.age = age; obj.job = job; obj.sayName = function() { console.log(name); // <-- 方法不引用 this
 } return obj; } var person1 = Person('Mike', 28, 'teacher'); // 不使用 new 關鍵字
person1.sayName(); // Mike

其中 name 的值,只能經過 sayName 方法訪問。

 

注:以上全部的文字、代碼都是本人一個字一個字敲上去的,圖片也是一張一張畫出來的,轉載請註明出處,謝謝!

相關文章
相關標籤/搜索