咱們所熟知的面嚮對象語言如 C++、Java 都有類的的概念,類是實例的類型模板,好比Student
表示學生這種類型,而不表示任何具體的某個學生,而實例就是根據這個類型建立的一個具體的對象,好比zhangsan
、lisi
,由類生成對象體現了抽象模板到具體化的過程,這叫作基於類的面向對象方式,而 JavaScript 沒有類的概念,是基於原型的面向對象方式(雖然 Es6 增長了 class
,實質是對原型方式的封裝)。總結起來就是如下兩點:編程
面嚮對象語言的第一個特性毫無疑問是封裝,在 JS 中,封裝的過程就是把一些屬性和方法放到對象中「包裹」起來,那麼咱們要怎麼去封裝屬性和方法,或者說怎麼去建立對象呢(後文統一說建立對象)?下面用逐步推動的方式闡述:設計模式
對象字面量 --> 工廠模式 --> 構造函數 --> 原型模式 --> 構造函數+原型模式
JS中建立對象最原始的方式有兩種:函數
var person = { name: "leon", age: "20", greeting: function() { alert('Hi!'); } }
Object
實例添加屬性方法var person = new Object(); person.name = "leon"; person.age = "20"; person.greeting = function() { alert('Hi!'); };
工廠模式是編程領域一種廣爲人知的設計模式,它抽象了建立具體對象的過程。JS 中建立一個函數,把建立新對象、添加對象屬性、返回對象的過程放到這個函數中,用戶只需調用函數來生成對象而無需關注對象建立細節,這叫工廠模式:this
function createPerson(name, age) { var person = new Object(); person.name = name; person.age = age; person.greeting = function() { alert('Hi!'); }; return person; } var person1 = createPerson("leon", "20");
JS 中構造函數與其餘函數的惟一區別,就在於調用它的方式不一樣。任何函數,只要經過new
操做符來調用,那它就能夠做爲構造函數。來看下面的例子:prototype
function Person(name, age) { this.name = name; this.age = age; this.greeting = function() { alert('Hi!'); }; // return this; } var person1 = new Person("leon", "20"); var person2 = new Person("jack", "21");
經過構造函數new
一個實例經歷了四步:設計
this
綁定到新對象上;return this;
)。而經過構造函數建立的對象都有一個constructor
屬性,它是一個指向構造函數自己的指針,所以就能夠檢測對象的類型啦。:指針
alert(person1.constructor === Person) //true alert(person1 instanceof Person) // true
可是仍然存在問題:code
alert(person1.greeting == person2.greeting) //false
同一個構造函數中定義了greeting()
,而不一樣實例上的同名函數倒是不相等的,意味着這兩個同名函數的內存空間不一致,也就是構造函數中的方法要在每一個實例上從新建立一次。這顯然是不划算的。對象
終於講到了原型模式,JS 中每一個構造函數都有一個prototype
屬性,這個屬性是一個指針,指向原型對象,而這個原型對象包含了這個構造函數全部實例共享的屬性和方法。而實例對象中有一個proto
屬性,它指向原型對象,也就是構造函數.prototype == 原型對象 == 對象._proto_
,那麼對象就能夠獲取到原型對象中的屬性和方法啦。同時,全部對象中都有一個constructor
屬性,原型對象的constructor
指向其對應的構造函數。ip
使用原型,就意味着咱們能夠把但願實例共享的屬性和方法放到原型對象中去,而不是放在構造函數中,這樣每一次經過構造函數new
一個實例,原型對象中定義的方法都不會從新建立一次。來看下面的例子:
function Person() { } Person.prototype.name = "leon"; Person.prototype.age = "20"; Person.prototype.greeting = function() { alert('Hi!'); }; var person1 = new Person(); var person2 = new Person(); alert(person1.name); //"leon" alert(person2.name); //"leon" alert(person1.greeting == person2.greeting); //true
name
都同樣了,咱們顯然不但願全部實例屬性方法都同樣,它們仍是要有本身獨有的屬性方法。而且若是原型中對象中有引用類型值,實例中得到的都是該值的引用,意味着一個實例修改了這個值,其餘實例中的值都會相應改變。另外 JS 中還定義了一些與原型相關的屬性,這裏羅列一下:
Object.getPrototypeOf()
,取得實例的原型對象。Object.getPrototypeOf(person1);
isPrototypeOf()
,判斷是否是一個實例的原型對象。Person.prototype.isPrototypeOf(person1);
hasOwnProperty()
,檢測一個屬性是否存在於實例中person1.hasOwnProperty("name");
in
,判斷一個屬性是否存在於實例和原型中。"name" in person1;
最後一種方式就是組合使用構造函數和原型模式,構造函數用於定義實例屬性,而共享屬性和方法定義在原型對象中。這樣每一個實例都有本身獨有的屬性,同時又有對共享方法的引用,節省內存。
function Person(name, age) { this.name = name; this.age = age; } Person.prototype = { constructor: Person, nationality: "China", greeting: function() { alert(this.name); } } var person1 = new Person("leon", "20"); var person2 = new Person("jack", "21"); alert(person1.greeting == person2.greeting) //true
上面代碼中用對象字面量的形式重寫了原型對象,這樣至關於建立了一個新的對象,那麼它的constructor
屬性就會指向Object
,這裏爲了讓它繼續指向構造函數,顯示的寫上了constructor: Person
這種構造函數與原型模式混成的模式,是目前在 JS 中使用最爲普遍的一種建立對象的方法。