建立單個對象的缺點:用同一個接口建立不少對象,會產生大量的重複代碼。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
用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 方法訪問。
注:以上全部的文字、代碼都是本人一個字一個字敲上去的,圖片也是一張一張畫出來的,轉載請註明出處,謝謝!