雖然Object構造函數與對象字面量都能建立單個對象, 但這些方式都有明顯的缺點: 使用同一個接口建立不少對象, 會產生大量重複代碼。javascript
var obj = {}; //對象字面量 var obj = new Object(); //對象構造函數(對象構造器)
這種模式抽象了對象具體建立的過程(相似其它語言的類)html
function createPerson (name, age, job) { var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function () { alert (this.name); } return o; } var person1 = createPerson("汪淼", 50, "納米"); var person2 = createPerson("楊冬", 20, "基礎物理學");
工廠模式解決了建立多個對象的問題, 確沒有解決對象識別問題(如何知道一個對象的類型)由於使用該模式並無給出對象的類型java
建立自定義構造函數意味着未來能夠將它的實列類型標識爲一種特定的類型。(更優勢)
這種方式定義的函數是定義在global中的編程
function Person (name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert (this.name); } } var person1 = new Person("汪淼", 50, "納米"); var person2 = new Person("楊冬", 20, "基礎物理學");
與工廠模式區別安全
高級編程對象處的說法 1.建立(或者說構造)一個全新的對象。 2.將構造函數的做用域賦值給新對象(所以this指向了新對象) 3.執行構造函數的代碼(爲這個新對象添加屬性) 4.返回對象
person1.constructor == Person; person2.constructor == Person;
constructor 最初是用來標識對象類型的。但提到檢測對象類型,仍是使用 instanceofapp
person1 instanceof Person // true person2 instanceof Person // true
構造函數與其餘函數惟一區別。調用方式的不一樣。 用new 操做符調用就是做爲構造函數,不用則爲普通函數函數
// 構造函數 var person = new Person('羅輯', 20, '宇宙社會學'); person.sayName(); //羅輯 // 普通函數 Person('葉文潔', 20, '基礎物理學'); window.sayName(); //葉文潔 // 在另外一個對象做用域中調用 var o = new Object(); Person.call(o, '泰勒', 25, '面壁者'); //call() apply() 是會當即調用函數的 而bind() 則不會 o.sayName(); // 泰勒
使用構造函數的主要問題是每一個方法都要在每一個實例上從新建立一遍,建立多個完成相同任務的方法徹底沒有必要,浪費內存空間this
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = new Function("alert(this.name)"); // 等價於 // this.sayName = function () { // alert (this.name); // } }
function Person (name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName() { alert (this.name); } var person1 = new Person("汪淼", 50, "納米"); var person2 = new Person("楊冬", 20, "基礎物理");
sayName 放到了構造函數外部, 構造函數內部經過sayName指針指向sayName函數;
問題: 建立了全局函數sayNameprototype
因而這個自定義引用類型就沒有絲毫封裝性可言指針
咱們建立的每一個函數都有一個 prototype(原型屬性),該屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型全部實例共享的屬性和方法。
用原型對象,可讓全部實例共享它的屬性和方法。換句話說,沒必要在構造函數中定義對象實例的信息,而是能夠將這些信息直接添加到原型對象中
function Person() { } Person.prototype.name = '維德'; Person.prototype.age = '24'; Person.prototype.sayName = function() { alert(this.name); } var person1 = new Person(); person1.sayName(); // 維德 var person2 = new Person(); person2.sayName(); // 維德 alert(person1.sayName == person2.sayName); //true // @todo 原型鏈圖
function Person(){}; Person.prototype = { name: "丁怡", age: 50, job: '物流', sayName : function(){ console.log(this.name); } }; var person1 = new Person(); person1.sayName();// '丁怡' console.log(person1.constructor === Person);// false console.log(person1.constructor === Object);// true
這種語法 constructor 再也不指向Person,(每建立一個函數,就會同時建立prototype, 同時這個對象也會自動獲取constructor),而該方法本質上至關於徹底重寫了prototype的默認屬性,constructor 也變成了新對象的constructor屬性(指向Object構造函數),再也不指向Person函數。此時儘管instanceof操做符還能返回正確的結果,但經過constructor已經沒法肯定對象的類型了
var friend = new Person(); friend instanceof Object; // true friend instanceof Person; // true friend constructor Person; // false friend constructor Object; // true
function Person(){}; Person.prototype = { // 方法1:添加 constructor constructor: 'Person', name: "丁怡", age: 50, job: '物流', sayName : function(){ console.log(this.name); } }; // 方法2:經過defineProperty() 添加 Object.defineProperty(Person.prototype,'constructor',{ enumerable: false, value: Person });
不管何時,只要建立了一個新函數,就會根據一組特定的的規則爲該函數建立一個prototype屬性,這個屬性指向函數的原型對象。在默認狀況下,全部的原型對象都會自動獲取constructor(構造函數)屬性,這個屬性包含了一個指向prototype屬性所在函數數的指針
in 會查找 原型鏈 hasOwnProperty() 則只查找實例自己
function Person(){}; Person.prototype = { name: "丁怡", age: 50, job: '物流', sayName : function(){ console.log(this.name); } }; var person1 = new Person(); person1.sayName();// '丁怡' console.log(person1.constructor === Person);// false console.log(person1.constructor === Object);// true
var friend = new Person(); Person.prototype.sayHi = function() { alert('hi'); } friend.sayHi(); // hi (ok!)
重寫function原型以後
function Person() {} var friend = new Person(); Person.prototype = { constructor: Person, name: '程心', age: 21, job: '天體物理', sayName: fucntion() { alert(this.name); } }; friend.sayName(); //error 原型鏈重寫 @todo 原型鏈圖
js 全部原生的引用類型,都是採用這種模式(Object, Array, String)
原型模式問題在於引用類型值屬性會被全部的實例對象共享並修改,這也是不多有人單獨使用原型模式的緣由
function Person(){} Person.prototype = { constructor: Person, name: "bai", age: 29, job: "Software Engineer", friend : ["shelby","Court"], sayName: function(){ console.log(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends);//["shelby","Court","Van"]; alert(person2.friends);//["shelby","Court","Van"]; alert(person1.friends === person2.friends);//true
組合使用構造函數模式和原型模式是建立自定義類型的最多見方式。構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性,這種組合模式還支持向構造函數傳遞參數。實例對象都有本身的一份實例屬性的副本,同時又共享對方法的引用,最大限度地節省了內存。該模式是目前使用最普遍、認同度最高的一種建立自定義對象的模式
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ['丁怡', '汪淼']; } Person.prototype = { constructor: Person, sayName: function() { alert(this.name); } } var person1 = new Person("bai",29,"Software Engineer"); var person2 = new Person("hu",25,"Software Engineer"); person1.friends.push('楊冬'); alert(person1.friends);// ['丁怡', '汪淼', '楊冬']; alert(person2.friends);// ['丁怡', '汪淼']; alert(person1.friends === person2.friends);//false alert(person1.sayName === person2.sayName);//true
動態原型模式將組合模式中分開使用的構造函數和原型對象都封裝到了構造函數中,而後經過檢查方法是否被建立,來決定是否初始化原型對象
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 friend = new Person("bai",29,"Software Engineer"); friend.sayName();//'bai'
該模式的基本思想是建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建立的對象。該模式是工廠模式和構造函數模式的結合
寄生構造函數模式與構造函數模式有相同的問題,每一個方法都要在每一個實例上從新建立一遍,建立多個完成相同任務的方法徹底沒有必要,浪費內存空間
function Person(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ console.log(this.name); }; // o.sayName = new Function('console.log(this.name);'); return o; } var person1 = new Person("bai", 29, "software Engineer"); var person2 = new Person("hu", 25, "software Engineer"); //具備相同做用的sayName()方法在person1和person2這兩個實例中卻佔用了不一樣的內存空間 console.log(person1.sayName === person2.sayName);//false
使用new 操做符會默認返回新對象實例,這裏return 重寫了調用構造函數的時返回的值
所謂穩妥對象指沒有公共屬性,並且其方法也不引用this的對象。穩妥對象最適合在一些安全環境中(這些環境會禁止使用this和new)或者在防止數據被其餘應用程序改動時使用
穩妥構造函數與寄生構造函數模式類似,但有兩點不一樣:一是新建立對象的實例方法不引用this;二是不使用new操做符調用構造函數
function Person(name,age,job){ //建立要返回的對象 var o = new Object(); //能夠在這裏定義私有變量和函數 //添加方法 o.sayName = function(){ console.log(name); }; //返回對象 return o; } //在穩妥模式建立的對象中,除了使用sayName()方法以外,沒有其餘方法訪問name的值 var friend = Person("bai",29,"Software Engineer"); friend.sayName();//"bai"
與寄生構造函數模式類似,使用穩妥構造函數模式建立的對象與構造函數之間也沒有什麼關係,所以instanceof操做符對這種對象也沒有什麼意義
本文從使用Object構造函數與對象字面量建立一個對象開始提及,建立多個對象會形成代碼冗餘;使用工廠模式能夠解決該問題,但存在對象識別的問題;接着介紹了構造函數模式,該模式解決了對象識別的問題,但存在關於方法的重複建立問題;接着介紹了原型模式,該模式的特色就在於共享,但引出了引用類型值屬性會被全部的實例對象共享並修改的問題;最後,提出了構造函數和原型組合模式,構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性,這種組合模式還支持向構造函數傳遞參數,該模式是目前使用最普遍的一種模式。此外,一些模式下面還有一些解決特殊需求的拓展模式(寄生構造函數模式, 穩妥寄生模式)