面嚮對象語言有一個標誌,那就是它們都有類的概念,經過類能夠建立任意多個具備相同屬性和方法的對象。javascript
ECMAScript沒有類的概念,它的對象也與基於類的語言中的對象有所不一樣。ECMAScript把對象定義爲:java
無序屬性的集合,其屬性能夠包含基本值、對象或函數。安全
每一個對象實例都是基於一個引用類型建立的,這個引用類型能夠是ECMAScript原生類型,也能夠是開發者定義的類型。函數
咱們能夠經過Object構造函數或對象字面量來建立單個對象,但這些方式有個明顯的缺點:使用同一個接口建立不少對象,會產生大量的重複代碼。this
爲解決上述問題,可使用工廠模式建立對象。工廠模式抽象了建立具體對象的過程。prototype
因爲ECMAScript沒有類,能夠定義一種函數,用函數來封裝以特定接口建立對象的細節。例如:3d
function createStudent(name,age) { var obj = new Object(); obj.name = name; obj.age = age; obj.sayName = function(){ alert(obj.name); }; return obj; } var Bob = createStudent("Bob", 24); var Tom = createStudent("Tom", 28);
工廠模式雖然解決了建立多個類似對象的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。上面代碼,咱們的本意是建立一個Student類,Bob和Tom是Student類型,但實際上根本不存在Student類型,而Bob和Tom是Object類型。指針
構造函數可用來建立特定類型的對象,這意味着能夠將構造函數的實例標識爲一種特定的類型。code
構造函數與與工廠模式的不一樣之處在於:對象
使用new操做符調用構造函數時,會經歷如下4個步驟:
function Student(name,age) { this.name = name; this.age = age; this.sayName = function(){ alert(this.name); }; } var Bob = new Student("Bob", 24); var Tom = new Student("Tom", 28); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // false
因爲ECMAScript中的函數是對象,所以只要定義一個函數,就會實例化一個對象。這會致使使用構造函數模式建立對象時,構造函數中的每一個方法都要在每一個實例上從新建立一遍。這樣作浪費內存,下降執行效率。
咱們能夠把方法定義從構造函數內部移到外部,如:
function Student(name,age) { this.name = name; this.age = age; this.sayName = sayName; } function sayName(){ alert(this.name); } var Bob = new Student("Bob", 24); var Tom = new Student("Tom", 28); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // true
這樣會致使新的問題:sayName本應是Student的私有方法,如今卻能夠被任意調用,這破壞類的封裝特性。
當咱們建立一個函數時,就會同時建立它的prototype對象,這個prototype對象也會自動得到constructor屬性,這個屬性指向構造函數對象。
當咱們經過構造函數實例化一個對象時,實例對象內部將包含一個指針(內部屬性),它指向構造函數的原型對象。這個內部屬性稱爲[[Prototype]]
,在腳本中沒有標準的方式訪問該內部屬性。
原型中方法和屬性被其所有實例所共享。
function Student() { } Student.prototype.name = "xiaohong"; Student.prototype.age = 24; Student.prototype.sayName = function() { alert(this.name); }; var Bob = new Student(); var Tom = new Student(); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // true
前面的代碼中每添加一個屬性和方法就要敲一遍Student.prototype
,爲了減小沒必要要的輸入,也爲了從視覺上更好地封裝原型的功能,能夠用對象字面量重寫整個原型。
function Student() { } Student.prototype = { name = "xiaohong", age = 24, sayName = function() { alert(this.name); } }; var Bob = new Student(); var Tom = new Student(); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // true alert(Student.prototype.constructor == Student); // false alert(Student.prototype.constructor == Object); // true
重寫原型後,現有原型的constructor屬性再也不指向構造函數對象,而是指向對象字面量的構造函數Object。
因爲在原型中查找值的過程是一次搜索,所以咱們對原型對象所作的任何修改都可以當即從實例上反映出來——即便是先建立了實例後修改原型也照樣如此。
可是,若是建立實例後重寫原型,實例會因爲沒法查找到屬性或方法報錯。這是因爲實例對象會經過內部屬性[[Prototype]]
鏈接到原型,在原型中查找屬性,而重寫原型切斷了實例對象與原型對象之間的聯繫。
構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享屬性。
這種組合方式的優勢是:
function Student(name,age) { this.name = name; this.age = age; } Student.prototype = { sayName = function() { alert(this.name); } }; var Bob = new Student("Bob",24); var Tom = new Student("Tom",28); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // true
原型模式中構造函數和原型是分離,爲了把全部信息都封裝在構造函數中,可使用動態原型模式。
構造函數模式的主要缺點是同個方法要在不一樣實例對象中重複建立,浪費內存,因此引入了原型模式。動態原型模式經過檢查某個應該存在的方法是否有效,若是無效則在構造函數中初始化原型,這樣就解決方法對象重複建立的問題,而封裝性更好。
function Student(name,age) { this.name = name; this.age = age; // sayName不存在,則初始化原型 if (typeof this.sayName != "function") { this.sayName = function() { alert(this.name); }; } } var Bob = new Student("Bob",24); var Tom = new Student("Tom",28); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // true
寄生構造函數模式就是用new操做符調用工廠模式。因爲重寫了返回值,返回對象和構造函數及其原型沒有任何聯繫,沒法對象類型識別。
function Student(name,age) { var obj = new Object(); obj.name = name; obj.age = age; obj.sayName = function(){ alert(this.name); }; return obj; } var Bob = new Student("Bob", 24); var Tom = new Student("Tom", 28);
穩妥對象指沒有公共屬性,並且其方法也不引用this的對象。穩妥對象最適合在一些安全的環境中或者防止數據被其餘應用程序改動時使用
穩妥構造函數與寄生構造函數相似,但有兩點不一樣:
function Student(name,age) { var obj = new Object(); obj.sayName = function(){ alert(name); }; return obj; } var Bob = Student("Bob", 24); Bob.sayName();