JavaScript中沒有類的概念,因此其在對象建立方面與面嚮對象語言有所不一樣。數組
JS中對象能夠定義爲」無序屬性的集合」。其屬性能夠包含基本值,對象以及函數。對象實質上就是一組沒有特定順序的值,對象中每一個屬性、方法都有一個名字,每一個名字都映射到了一個值,所以咱們能夠將對象想象稱爲一個散列表。
JS是一種基於對象的語言,對象的概念在JS體系中十分的重要,所以有必要清楚地瞭解一下JS中對象建立的經常使用方法及各自的侷限性。app
在說工廠模式建立對象以前,咱們不妨回顧一下JS中最基本的建立對象的方法,好比說我想建立一個student對象怎麼辦?最簡單地,new一個Object:函數
var student = new Object(); student.name = "easy"; student.age = "20";
這樣,一個student對象就建立完畢,擁有2個屬性name
以及age
,分別賦值爲"easy"
和20
。ui
若是你嫌這種方法有一種封裝性不良的感受,咱們也可使用對象字面量的方式來建立student對象:this
var sutdent = { name : "easy", age : 20 };
這樣看起來彷佛就完美了。可是立刻咱們就會發現一個十分尖銳的問題:當咱們要建立同類的student1,student2,…,studentn時,咱們不得不將以上的代碼重複n次。spa
var sutdent1 = { name : "easy1", age : 20 }; var sutdent2 = { name : "easy2", age : 20 }; ... var sutdentn = { name : "easyn", age : 20 };
能不能像工廠車間那樣,有一個車牀就不斷生產出對象呢?咱們看」工廠模式」。.net
JS中沒有類的概念,那麼咱們不妨就使用一種函數將以上對象建立過程封裝起來以便於重複調用,同時能夠給出特定接口來初始化對象:prototype
function createStudent(name, age) { var obj = new Object(); obj.name = name; obj.age = age; return obj; } var student1 = createStudent("easy1", 20); var student2 = createStudent("easy2", 20); ... var studentn = createStudent("easyn", 20);
這樣一來咱們就能夠經過createStudent函數源源不斷地」生產」對象了。看起來已經高枕無憂了,但貪婪的人類總有不知足於現狀的天性:咱們不只但願」產品」的生產能夠像工廠車間通常源源不斷,咱們還想知道生產的產品到底是哪種類型的。指針
好比說,咱們同時又定義了」生產」水果對象的createFruit()函數:code
function createFruit(name, color) { var obj = new Object(); obj.name = name; obj.color = color; return obj; } var v1 = createStudent("easy1", 20); var v2 = createFruit("apple", "green");
對於以上代碼建立的對象v一、v2,咱們用instanceof操做符去檢測,他們通通都是Object類型。咱們的固然不知足於此,咱們但願v1是Student類型的,而v2是Fruit類型的。爲了實現這個目標,咱們能夠用自定義構造函數的方法來建立對象。
在上面建立Object這樣的原生對象的時候,咱們就使用過其構造函數:
var obj = new Object();
在建立原生數組Array類型對象時也使用過其構造函數:
var arr = new Array(10); //構造一個初始長度爲10的數組對象
在進行自定義構造函數建立對象以前,咱們首先了解一下構造函數
和普通函數
有什麼區別。
其一,實際上並不存在建立構造函數的特殊語法,其與普通函數惟一的區別在於調用方法。對於任意函數,使用new操做符調用,那麼它就是構造函數;不使用new操做符調用,那麼它就是普通函數。
其二,按照慣例,咱們約定構造函數名以大寫字母開頭,普通函數以小寫字母開頭,這樣有利於顯性區分兩者。例如上面的new Array(),new Object()。
其三,使用new操做符調用構造函數時,會經歷(1)建立一個新對象;(2)將構造函數做用域賦給新對象(使this指向該新對象);(3)執行構造函數代碼;(4)返回新對象;4個階段。
瞭解了構造函數
和普通函數
的區別以後,咱們使用構造函數將工廠模式
的函數重寫,並添加一個方法屬性:
function Student(name, age) { this.name = name; this.age = age; this.alertName = function(){ alert(this.name) }; } function Fruit(name, color) { this.name = name; this.color = color; this.alertName = function(){ alert(this.name) }; }
這樣咱們再分別建立Student和Fruit的對象:
var v1 = new Student("easy", 20); var v2 = new Fruit("apple", "green");
這時咱們再來用instanceof操做符來檢測以上對象類型就能夠區分出Student以及Fruit了:
alert(v1 instanceof Student); //true alert(v2 instanceof Student); //false alert(v1 instanceof Fruit); //false alert(v2 instanceof Fruit); //true alert(v1 instanceof Object); //true 任何對象均繼承自Object alert(v2 instanceof Object); //true 任何對象均繼承自Object
這樣咱們就解決了工廠模式
沒法區分對象類型的尷尬。那麼使用構造方法來建立對象是否已經完美了呢?
咱們知道在JS中,函數是對象。那麼,當咱們實例化不止一個Student對象的時候:
var v1 = new Student("easy1", 20); var v2 = new Student("easy2", 20); ... var vn = new Student("easyn", 20);
其中共同的alertName()
函數也被實例化了n次,咱們能夠用如下方法來檢測不一樣的Student對象並不共用alertName()
函數:
alert(v1.alertName == v2.alertName); //flase
這無疑是一種內存的浪費。咱們知道,this對象是在運行時基於函數的執行環境進行綁定的。在全局函數中,this對象等同於window;在對象方法中,this指向該對象。在上面的構造函數中:
this.alertName = function(){ alert(this.name) };
咱們在建立對象(執行alertName函數以前)時,就將alertName()函數綁定在了該對象上。咱們徹底能夠在執行該函數的時候再這樣作,辦法是將對象方法移到構造函數外部:
function Student(name, age) { this.name = name; this.age = age; this.alertName = alertName; } function alertName() { alert(this.name); } var stu1 = new Student("easy1", 20); var stu2 = new Student("easy2", 20);
在調用stu1.alert()
時,this對象才被綁定到stu1上。
咱們經過將alertName()函數定義爲全局函數,這樣對象中的alertName屬性則被設置爲指向該全局函數的指針。由此stu1和stu2共享了該全局函數,解決了內存浪費的問題。
可是,經過全局函數的方式解決對象內部共享的問題,終究不像一個好的解決方法。若是這樣定義的全局函數多了,咱們想要將自定義對象封裝的初衷便幾乎沒法實現了。更好的方案是經過原型對象模式來解決。
在瞭解如何使用原型模式建立對象以前,有必要先搞清楚什麼是原型對象。
咱們建立的每個函數都有一個prototype屬性,該屬性是一個指針,該指針指向了一個對象。對於咱們建立的構造函數,該對象中包含能夠由全部實例共享的屬性和方法。以下如所示:
在默認狀況下,全部原型對象會自動包含一個constructor屬性,該屬性也是一個指針,指向prototype所在的函數:
在調用構造函數建立新的實例時,該實例的內部會自動包含一個[[Prototype]]指針屬性,該指針指便指向構造函數的原型對象。注意,這個指針關聯的是實例與構造函數的原型對象
而不是實例與構造函數
:
瞭解了原型對象以後,咱們即可以經過在構造函數原型對象中添加屬性和方法來實現對象間數據的共享了。例如:
function Student() { } Student.prototype.name = "easy"; Student.prototype.age = 20; Student.prototype.alertName = function(){ alert(this.name); }; var stu1 = new Student(); var stu2 = new Student(); stu1.alertName(); //easy stu2.alertName(); //easy alert(stu1.alertName == stu2.alertName); //true 兩者共享同一函數
以上代碼,咱們在Student的protptype對象中添加了name、age屬性以及alertName()方法。但建立的stu1和stu2中並不包含name、age屬性以及alertName()方法,而只包含一個[[prototype]]指針屬性。當咱們調用stu1.name
或stu1.alertName()
時,是如何找到對應的屬性和方法的呢?
當咱們須要讀取對象的某個屬性時,都會執行一次搜索。首先在該對象中查找該屬性,若找到,返回該屬性值;不然,到[[prototype]]指向的原型對象中繼續查找。
由此咱們也能夠看出另一層意思:若是對象實例中包含和原型對象中同名的屬性或方法,則對象實例中的該同名屬性或方法會屏蔽原型對象中的同名屬性或方法。緣由就是「首先在該對象中查找該屬性,若找到,返回該屬性值;」
擁有同名實例屬性或方法的示意圖:
上圖中,咱們在訪問stu1.name是會獲得」EasySir」:
alert(stu1.name); //EasySir
不少時候,咱們爲了書寫的方便以及直觀上的」封裝性」,咱們每每採用對象字面量直接重寫整個原型對象:
function Student() { } Student.prototype = { constructor : Student, name : "easy", age : 20, alertName : function() { alert(this.name); } };
要特別注意,咱們這裏至關於用對象字面量從新建立了一個Object對象,而後使Student的prototype指針指向該對象。該對象在建立的過程當中,自動得到了新的constructor屬性,該屬性指向Object的構造函數。所以,咱們在以上代碼中,增長了constructor : Student
使其從新指回Student構造函數。
原型模型在對象實例共享數據方面給咱們帶來了很大的便利,但一般狀況下不一樣的實例會但願擁有屬於本身單獨的屬性。咱們將構造函數模型和原型模型結合使用便可兼得數據共享和」不共享」。
咱們結合原型模式在共享方法屬性以及構造函數模式在實例方法屬性方面的優點,使用如下的方法建立對象:
//咱們但願每一個stu擁有屬於本身的name和age屬性 function Student(name, age) { this.name = name; this.age = age; } //全部的stu應該共享一個alertName()方法 Student.prototype = { constructor : Student, alertName : function() { alert(this.name); } } var stu1 = new Student("Jim", 20); var stu2 = new Student("Tom", 21); stu1.alertName(); //Jim 實例屬性 stu2.alertName(); //Tom 實例屬性 alert(stu1.alertName == stu2.alertName); //true 共享函數
以上,在構造函數中定義實例屬性,在原型中定義共享屬性的模式,是目前使用最普遍的方式。一般狀況下,咱們都會默認使用這種方式來定義引用類型變量。
引用自:http://blog.csdn.net/a153375250/article/details/51083245