理清javascript中的面向對象(一)——原型繼承

與其它編程語言不同的是,javascript的面向對象並不是依賴於抽象的,而是經過原型鏈,將一個個具體的對象實例進行鏈接,位於原型鏈下游的對象實例能夠讀取/使用位於上游的對象實例的屬性/方法。
下文由簡及深,試圖一步步理清javascript面向對象的本質。javascript

萬物之源:javascript的原生類型——Object

javascript定義了最基礎的對象類型Object,而且爲這一對象類型定義了許多成員方法。其它許多原生對象類型,實際上都是繼承自Object,好比說FunctionDate等。想要生成一個Object對象類型的對象實例也不是一件什麼難事:java

var obj = {a: 2333};
//又或者是利用Object()這一構造函數
var obj = new Object();

實際上,更符合面向對象思想的應該是利用構造函數來生成對象實例。編程

生成對象實例的運算符new,以及構造函數constructor

如何使用new這一運算符來生成對象實例

在其它編程語言中,new每每也是用來生成對象實例的,用法通常是這樣:new ClassName[([arguments])],而生成出來的對象實例也被冠以「ClassName對象」這樣的稱謂;而javascript則大不相同,因爲原生沒有這一律念,所以構造函數便取而代之:new constructor[([arguments])],而稱謂也改成「constructor對象」或"constructor類型的對象",構造函數名直接就等同於數據類型了。瀏覽器

function A() {}    //有一函數(如果用來生成對象,則稱爲構造函數)名A。
var obj = new A();    //使用構造函數A來生成實例對象obj
console.dir(obj);    //打印實例對象obj

clipboard.png

從上圖可知,利用構造函數A生成的實例對象obj的數據類型爲A,另外,咱們留意到obj有且只有一個__proto__屬性,這是什麼呢?先別急,下面就說到。編程語言

詳述構造函數constructor

從上文可知,要想生成對象實例,必須使用構造函數(constructor),即使是var arr = [];var obj = {}這樣的形式,javascript也會在內部調用Function()Object()這樣預設的構造函數(因爲是預設的構造函數/數據類型,所以不需顯式指定)。函數

繼續沿用上述例子,此次咱們把構造函數A打印出來看看長什麼樣兒:性能

function A() {}   
var obj = new A(); 
console.dir(A);

clipboard.png

實際上,構造函數跟普通的函數並沒有二致,只是由於用途(用來生成對象實例),所以才冠以「構造函數」的大名。從上圖看,咱們略過一些與面向對象無關的屬性(arguments/caller/length/name),以及其函數做用域(<function scope>),剩下的就是與面向對象息息相關的倆屬性了:prototype__proto__。這__proto__分外眼熟,赫然也出如今對象實例obj裏,不過這裏仍是先跳過,咱們先說這prototype屬性。this

構造函數中的prototype屬性

特別註明是「構造函數中的prototype屬性」,是由於,對於通常的函數來講,prototype屬性沒什麼意義。prototype屬性指定了使用該構造函數生成的對象實例繼承了哪一個對象實例。
如上述的function A()prototype屬性指向了一個默認的Object類型的對象實例(在javascript中,變量只是對象實例的一個引用,所以此處用「指向」比較準確),那麼,用function A()做爲構造函數實例化的obj實際上就是繼承了那默認的Object類型的對象實例,雖然obj自己並無自定義的屬性/方法,可是能經過obj調用繼承回來的全部屬性/方法。spa

對象繼承的單向鏈條:原型鏈

既然構造函數的prototype屬性能指定繼承的對象實例,那麼只要咱們修改這prototype屬性,使其指向其它對象實例,那麼就能夠達到實現繼承任意對象的效果了,看下面代碼:prototype

var car = {    //一個普通的Object類型實例對象
    status: 'stop'
}
function audi() {}    //構造函數audi
audi.prototype = car;    //修改構造函數audi的prototype屬性,使其指向car
console.dir(audi);
var audiQ3 = new audi();    //利用構造函數audi生成的數據類型爲audi的實例對象
console.dir(audiQ3);

先來看看構造函數audi打印出來的結果:

clipboard.png

從audi.prototype.status能夠看出,此時的audi.prototype的確是指向{status: 'stop'}這個對象實例了。
那麼接下來看看利用修改後的構造函數audi所生成的對象實例audiQ3:

clipboard.png

咱們能夠發現audiQ3這一實例對象的數據類型是audi(與構造函數同名),另外,audiQ3其下只有__proto__這惟一一個成員屬性,繼續查看__proto__,赫然發現,裏面居然有status: "stop"。沒錯,__proto__屬性正是指向{status: 'stop'}這個對象實例的一個引用。

原型鏈的接點:__proto__

經過對象實例中的__proto__屬性,繼承的對象實例得以與被繼承的對象實例連接起來,因而,一環扣一環,造成了一條由對象實例、指向被繼承對象實例的引用所構成的鏈條:原型鏈。

原型鏈

因爲__proto__是由構造函數的prototype屬性決定的(也能夠說是prototype直接賦值給__proto__),所以咱們能夠經過修改prototype屬性來操縱這條原型鏈

再談構造函數

構造函數,主要用來在建立對象時初始化對象, 即爲對象成員變量賦初始值,那麼,javascript裏的構造函數,是怎麼實現這樣的功能的呢?如下面的DEMO做爲示例說明:

function car() {    //定義了一個名爲car的構造函數
    this.status = 'stop';    //爲往後使用car這一構造函數來生成的對象實例添加個status成員變量,並賦初始值'stop'
    this.start = function() {    //爲對象實例添加一個名爲start的成員方法
        this.status = 'running';
    }
}
var audiQ3 = new car();    //利用car生成一個對象實例,並將其賦給變量audiQ3
console.dir(audiQ3);
audiQ3.start();    //調用audiQ3的start方法
console.dir(audiQ3);

首先來看構造函數car,咱們看到this.status = 'stop';,這this是指代car這一function嗎?不是的,這個this其實是指向當前函數執行時的上下文環境,用在構造函數時,指的則是新生成的對象實例(在本DEMO中指的是audiQ3)。所以,只要利用this,就能在構造函數中,爲將來利用此構造函數生成的對象實例,添加成員屬性和成員方法了。

javascript原生支持的原型繼承方式:Object.create

ECMAScript 5定義了一種原生的原型繼承方式:Object.create,咱們能夠經過這種方式更簡便地實現原型繼承。

語法

Object.create(proto, [ propertiesObject ])

參數

proto 一個對象,做爲新建立對象的原型。
propertiesObject 可選。該參數對象是一組屬性與值,該對象的屬性名稱將是新建立的對象的屬性名稱,值是屬性描述符(這些屬性描述符的結構與Object.defineProperties()的第二個參數同樣)。注意:該參數對象不能是undefined,另外只有該對象中自身擁有的可枚舉的屬性纔有效,也就是說該對象的原型鏈上屬性是無效的。

示例

var car = {
    status: 'stop',
    start: function() {
        this.status = 'running'
    }
}
var audiQ3 = Object.create(car);
console.dir(audiQ3);

clipboard.png

怎麼樣,利用Object.create這種方法是否是很簡單就實現了原型繼承呢?實際上,這是ECMAScript 5給咱們作了一下封裝,至關於:

function (proto) {
  var constructor = function(){}
  constructor.prototype = proto;
  return new constructor();
}

瀏覽器兼容性修復

考慮到ECMAScript 5在IE上到IE10才徹底支持,所以咱們有必要對低版本的IE瀏覽器進行兼容,實際上也很簡單,對上面的代碼稍做修改便可:

if(typeof Object.create !== 'function') {
    Object.create = function(proto) {
      var constructor = function(){}
      constructor.prototype = proto;
      return new constructor();
    }
}
相關文章
相關標籤/搜索