ECMAScript 有兩種開發模式:1.函數式(過程化),2.面向對象(OOP)。面向對象的語言有一個標誌,那就是類的概念,而經過類能夠建立任意多個具備相同屬性和方法的對象。可是,ECMAScript 沒有類的概念,所以它的對象也與基於類的語言中的對象有所不一樣。javascript
js(若是沒有做特殊說明,本文中的js僅包含ES5之內的內容)自己是沒有class類型的,可是每一個函數都有一個prototype屬性。prototype指向一個對象,當函數做爲構造函數時,prototype則起到相似class的做用。html
建立一個對象,而後給這個對象新建屬性和方法。
java
var box = new Object(); //建立一個Object 對象 box.name = 'Lee'; //建立一個name 屬性並賦值 box.age = 100; //建立一個age 屬性並賦值 box.run = function () { //建立一個run()方法並返回值 return this.name + this.age + '運行中...'; }; alert(box.run()); //輸出屬性和方法的值
爲了解決多個相似對象聲明的問題,咱們可使用一種叫作工廠模式的方法,這種方法就是爲了解決實例化對象產生大量重複的問題。
app
function createObject(name, age) { //集中實例化的函數 var obj = new Object(); obj.name = name; obj.age = age; obj.run = function () { return this.name + this.age + '運行中...'; }; return obj; } var box1 = createObject('Lee', 100); //第一個實例 var box2 = createObject('Jack', 200); //第二個實例 alert(box1.run()); alert(box2.run()); //保持獨立
工廠模式解決了重複實例化的問題,可是它有許多問題,建立不一樣對象其中屬性和方法都會重複創建,消耗內存;還有函數識別問題等等。函數
構造函數的方法有一些規範:this
1)函數名和實例化構造名相同且大寫,(PS:非強制,但這麼寫有助於區分構造函數和
普通函數);
2)經過構造函數建立對象,必須使用new 運算符。spa
function Box(name, age) { //構造函數模式 this.name = name; this.age = age; this.run = function () { return this.name + this.age + '運行中...'; }; } var box1 = new Box('Lee', 100); //new Box()便可 var box2 = new Box('Jack', 200); alert(box1.run()); alert(box1 instanceof Box); //很清晰的識別他從屬於Box
構造函數能夠建立對象執行的過程:firefox
1)當使用了構造函數,而且new 構造函數(),那麼就後臺執行了new Object();
2)將構造函數的做用域給新對象,(即new Object()建立出的對象),而函數體內的this 就
表明new Object()出來的對象。
3)執行構造函數內的代碼;
4)返回新對象(後臺直接返回)。prototype
注:3d
1)構造函數和普通函數的惟一區別,就是他們調用的方式不一樣。只不過,構造函數也是函數,必須用new 運算符來調用,不然就是普通函數。
2)this就是表明當前做用域對象的引用。若是在全局範圍this 就表明window 對象,若是在構造函數體內,就表明當前的構造函數所聲明的對象。
這種方法解決了函數識別問題,但消耗內存問題沒有解決。同時又帶來了一個新的問題,全局中的this 在對象調用的時候是Box 自己,而看成普通函數調用的時候,this 又表明window。即this做用域的問題。
咱們建立的每一個函數都有一個prototype(原型)屬性,這個屬性是一個對象,它的用途是包含能夠由特定類型的全部實例共享的屬性和方法。邏輯上能夠這麼理解:prototype 經過調用構造函數而建立的那個對象的原型對象。使用原型的好處可讓全部對象實例共享它所包含的屬性和方法。也就是說,沒必要在構造函數中定義對象信息,而是能夠直接將這些信息添加到原型中。
function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型裏添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型裏添加方法 return this.name + this.age + '運行中...'; };
構造函數的聲明方式和原型模式的聲明方式存儲狀況以下:
因此,它解決了消耗內存問題。固然它也能夠解決this做用域等問題。
咱們常常把屬性(一些在實例化對象時屬性值改變的),定義在構造函數內;把公用的方法添加在原型上面,也就是混合方式構造對象(構造方法+原型方式):
var person = function(name){ this.name = name }; person.prototype.getName = function(){ return this.name; } var zjh = new person(‘zhangjiahao’); zjh.getName(); //zhangjiahao
下面詳細介紹原型:
每一個javascript對象都有一個原型對象,這個對象在不一樣的解釋器下的實現不一樣。好比在firefox下,每一個對象都有一個隱藏的__proto__屬性,這個屬性就是「原型對象」的引用。
因爲原型對象自己也是對象,根據上邊的定義,它也有本身的原型,而它本身的原型對象又能夠有本身的原型,這樣就組成了一條鏈,這個就是原型鏈,JavaScritp引擎在訪問對象的屬性時,若是在對象自己中沒有找到,則會去原型鏈中查找,若是找到,直接返回值,若是整個鏈都遍歷且沒有找到屬性,則返回undefined.原型鏈通常實現爲一個鏈表,這樣就能夠按照必定的順序來查找。
1)__proto__和prototype
JS在建立對象(不管是普通對象仍是函數對象)的時候,都有一個叫作__proto__的內置屬性,用於指向建立它的函數對象的原型對象prototype。以上面的例子爲例:
console.log(zjh.__proto__ === person.prototype) //true
一樣,person.prototype對象也有__proto__屬性,它指向建立它的函數對象(Object)的prototype
console.log(person.prototype.__proto__ === Object.prototype) //true
繼續,Object.prototype對象也有__proto__屬性,但它比較特殊,爲null
console.log(Object.prototype.__proto__) //null
咱們把這個有__proto__串起來的直到Object.prototype.__proto__爲null的鏈叫作原型鏈。以下圖:
2)constructor
原型對象prototype中都有個預約義的constructor屬性,用來引用它的函數對象。這是一種循環引用
person.prototype.constructor === person //true Function.prototype.constructor === Function //true Object.prototype.constructor === Object //true
3)爲加深對理解,咱們再舉一個例子:
function Task(id){ this.id = id; } Task.prototype.status = "STOPPED"; Task.prototype.execute = function(args){ return "execute task_"+this.id+"["+this.status+"]:"+args; } var task1 = new Task(1); var task2 = new Task(2); task1.status = "ACTIVE"; task2.status = "STARTING"; print(task1.execute("task1")); print(task2.execute("task2"));
execute task_1[ACTIVE]:task1
execute task_2[STARTING]:task2
構造器會自動爲task1,task2兩個對象設置原型對象Task.prototype,這個對象被Task(在此最爲構造器)的prototype屬性引用,參看下圖中的箭頭指向。
因爲Task自己仍舊是函數,所以其」__proto__」屬性爲Function.prototype, 而內建的函數原型對象的」__proto__」屬性則爲Object.prototype對象。最後Obejct.prototype的」__proto__」值爲null。
總結:
實例對象的__proto__指向,其構造函數的原型;構造函數原型的constructor指向對應的構造函數。構造函數的prototype得到構造函數的原型。
有時某種緣由constructor指向有問題,能夠經過
constructor:構造函數名;//constructor : Task
從新指向。
繼承是面向對象中一個比較核心的概念。其餘正統面嚮對象語言都會用兩種方式實現繼承:一個是接口實現,一個是繼承。而ECMAScript 只支持繼承,不支持接口實現,而實現繼承的方式依靠原型鏈完成。
在JavaScript 裏,被繼承的函數稱爲超類型(父類,基類也行,其餘語言叫法),繼承的函數稱爲子類型(子類,派生類)
屬性使用對象冒充(call)(實質上是改變了this指針的指向)繼承基類,方法用遍歷基類原型。
function A() { this.abc=12; } A.prototype.show=function () { alert(this.abc); }; //繼承A function B() { //繼承屬性;this->new B() A.call(this); //有參數能夠傳參數A.call(this,name,age) } //繼承方法;B.prototype=A.prototype; for(var i in A.prototype) { B.prototype[i]=A.prototype[i]; } //添加本身的方法 B.prototype.fn=function () { alert('abc'); }; var objB=new B(); var objA=new A();objB.show();
能夠實現多繼承。
主要是Desk.prototype = new Box(); Desk 繼承了Box,經過原型,造成鏈條。主要經過臨時中轉函數和寄生函數實現。
臨時中轉函數:基於已有的對象建立新對象,同時還沒必要所以建立自定義類型
寄生函數:目的是爲了封裝建立對象的過程
//臨時中轉函數 function obj(o) { //o表示將要傳遞進入的一個對象 function F() {} //F構造是一個臨時新建的對象,用來存儲傳遞過來的對象 F.prototype = o; //將o對象實例賦值給F構造的原型對象 return new F(); //最後返回這個獲得傳遞過來對象的對象實例 } //寄生函數 function create(box, desk) { var f = obj(box.prototype); f.constructor = desk; //調整原型構造指針 desk.prototype = f; } function Box(name) { this.name = name; this.arr = ['apple','pear','orange']; } Box.prototype.run = function () { return this.name; }; function Desk(name, age) { Box.call(this, name); this.age = age; } //經過寄生組合繼承實現繼承 create(Box, Desk); //這句話用來替代Desk.prototype = new Box(); var desk = new Desk('Lee',100); desk.arr.push('peach'); alert(desk.arr); alert(desk.run());
臨時中轉函數和寄生函數主要作的工做流程:
臨時中轉函數:返回的是基類的實例對象函數
寄生函數:將返回的基類的實例對象函數的constructor指向派生類,派生類的prototype指向基類的實例對象函數(是一個函數原型),從而實現繼承。
-------------------------------------------------------------------------------------------------------------------------------------
完
轉載需註明轉載字樣,標註原做者和原博文地址。
更多閱讀: