ECMAScript面向對象與原型

ECMAScript有兩種開發模式:1.函數式(過程化),2.面向對象(OOP)。面向對象的語言有一個標誌,那就是類的概念,而經過類能夠建立任意多個具備相同屬性和方法的對象。可是,ECMAScript沒有類的概念,所以它的對象也與基於類的語言中的對象有所不一樣。
一.建立對象
1.建立一個對象,而後給這個對象新建屬性和方法。程序員

var box=new Object();                        //建立對象
box.name1='xixi';                            //添加屬性
box.age=100;                                 //添加屬性
box.run=function(){
return this.name1+this.age+'運行中...';       //this表示當前做用域下對象
};
//this表示new Object()實例化出來的那個對象
//this要放在一個做用域下,好比box.run(){},這個是box做用域下的方法,方可用this來表示box自己
alert(box.run());                           //xixi100運行中...
alert(this.name1);                          //這裏的this表明的是window打印:undefined

上面建立了一個對象,而且建立屬性和方法,在run()方法裏的this,就是表明box對象自己。這種是JavaScript建立對象最基本的方法,但有個缺點,想建立一個相似的對象,就會產生大量的代碼。
2.多個相似對象聲明:建立另外一個對象box2並把第一個對象box賦值給box2(box2的屬性就共享box1的)數組

var box=new Object();                          //建立對象
box.name='xixi';                               //添加屬性
box.age=100;                        
box.run=function() {
    return this.name+this.age+'運行中...';    
};

var box2=box;                                  //獲得box的引用
box2.name='wang';                              //直接改變了name屬性
box2.age=200;                        
box2.run=function() {
    return this.name+this.age+'運行中...';    
};

alert(box.run());      //wang200運行中
alert(box2.run());     //用box.run()發現name也改變了:wang200運行中

3.工廠模式的方法:爲了解決多個相似對象聲明的問題,咱們可使用一種叫作工廠模式的方法,這種方法就是爲了解決實例化對象產生大量重複的問題。瀏覽器

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('wang',100);                //建立第一個對象
var box2 = createObject('shi',200);                 //建立第二個對象

alert(box1.run());                                  //保持獨立:wang100運行中
alert(box2.run());                                  //保持獨立:shi200運行中

alert(box1 instanceof Object);                      //true
alert(box2 instanceof Object);                      //true

工廠模式解決了重複實例化的問題,但還有一個問題,那就是識別問題,由於根本沒法搞清楚他們究竟是哪一個對象的實例。
4.構造函數:ECMAScript中能夠採用構造函數(構造方法)可用來建立特定的對象。類型於Object 對象。安全

function Box(name,age) {                        //建立一個對象,全部構造函數的對象其實就是Object
    this.name=name;                             //添加一個屬性
    this.age=age;            
    this.run=function () {                      //添加一個方法
        return this.name+this.age+'運行中...';
    };
};

function Desk(name, age) {                      //建立一個對象,全部構造函數的對象其實就是Object
    this.name = name;                           //添加一個屬性
    this.age = age;            
    this.run = function () {                    //添加一個方法
        return this.name + this.age + '運行中...';
    };
};

var box1 = new Box('wang',100);                 //實例化
var box2 = new Box('xixi',200);                 //實例化
var box3 = new Desk('kkk',500);                 //實例化

alert(box1 instanceof Box);                     //true
alert(box2 instanceof Box);                     //true
alert(box3 instanceof Box);                     //false。能夠識別了,由於box3是Desk對象的引用
alert(box3 instanceof Desk);                    //能夠識別:true

使用構造函數的方法,即解決了重複實例化的問題,又解決了對象識別的問題,但問題是,這裏並無new Object(),爲何能夠實例化Box(),這個是哪裏來的呢?使用了構造函數的方法,和使用工廠模式的方法他們不一樣之處以下:
(1)構造函數方法沒有顯示的建立對象(new Object());
(2)直接將屬性和方法賦值給this對象;
(3)沒有renturn語句。
構造函數的方法有一些規範:
(1)構造函數也是函數,但函數名第一個字母大寫
(2)必須new 構造函數名(),new Box(),而這個Box第一個字母也是大寫的
(3)必須使用new運算符
既然經過構造函數能夠建立對象,那麼這個對象是哪裏來的,new Object()在什麼地方執行了?執行的過程以下:
(1)當使用了構造函數,而且new構造函數(),那麼就後臺執行了new Object();
(2)將構造函數的做用域給新對象,(即new Object()建立出的對象),而函數體內的this就表明new Object()出來的對象。
(3)執行構造函數內的代碼;
(4)返回新對象(後臺直接返回)。
5.關於this的使用,this其實就是表明當前做用域對象的引用。若是在全局範圍this就表明window對象,若是在構造函數體內,就表明當前的構造函數所聲明的對象。
構造函數和普通函數的惟一區別,就是他們調用的方式不一樣。只不過,構造函數也是函數,必須用new運算符來調用,不然就是普通函數。函數

function Box(name,age) {               //建立一個對象,全部構造函數的對象其實就是Object
    this.name=name;                    //添加一個屬性
    this.age=age;            
    this.run=function () {             //添加一個方法
        return this.name+this.age+'運行中...';
    };
};

var box1=new Box('xixi',100);          //實例化
alert(box1.run());                     //打印:xixi100運行中

var user='wang';
alert(this.user);                      //全局,表明window:打印wang

5.對象冒充測試

function Box(name,age) {             //建立一個對象,全部構造函數的對象其實就是Object
    this.name=name;                  //添加一個屬性
    this.age=age;            
    this.run=function () {           //添加一個方法
        return this.name+this.age+'運行中...';
    };
};

var o= new Object();
Box.call(o,'xixi',100);               //對象冒充
alert(o.run());                       //打印:xixi100運行中...

6.探討構造函數內部的方法(或函數)的問題,首先看下兩個實例化後的屬性或方法是否相等。this

function Box(name,age) {           //nwe Function()惟一性。建立一個對象,全部構造函數的對象其實就是Object
    this.name=name;                //添加一個屬性
    this.age=age;            
    this.run=function () {         //添加一個方法
        return this.name+this.age+'運行中...';
    };
};

var box1=new Box('xixi', 100);     //傳遞一致//實例化後地址爲1
var box2=new Box('xixi', 100);     //傳遞一致//實例化後地址爲2

alert(box1.name == box2.name);     //true,屬性的值相等
alert(box1.age == box2.age);       //true,屬性的值相等
alert(box1.run() == box1.run());   //true,構造函數體內的方法的值是至關的
alert(box1.run == box2.run);       //false,由於他們比較的是引用地址

(1)能夠把構造函數裏的方法(或函數)用new Function()方法來代替,獲得同樣的效果,更加證實,他們最終判斷的是引用地址,惟一性。
(2)能夠經過構造函數外面綁定同一個函數的方法來保證引用地址的一致性spa

function Box(user,age) {          //建立一個對象,全部構造函數的對象其實就是Object
    this.user=user;               //添加一個屬性
    this.age=age;            
    this.run=run;
};


function run() {                  //把構造函數內部的方法經過全局來實現引用地址一致
    return this.user + this.age + '運行中...';
}

var box1=new Box('xixi', 100);    //傳遞一致
var box2=new Box('xixi', 100);    //傳遞一致

alert(box1.run == box2.run);       //false,由於他們比較的是引用地址
alert(box1.run());                 //xixi100運行中...                     
alert(run());                      //NaN運行中...

雖然使用了全局的函數run()來解決了保證引用地址一致的問題,但這種方式又帶來了一個新的問題,全局中的this在對象調用的時候是Box自己,而看成普通函數調用的時候,this
二.原型
1.咱們建立的每一個函數都有一個prototype(原型)屬性,這個屬性是一個對象,它的用途是包含能夠由特定類型的全部實例共享的屬性和方法。邏輯上能夠這麼理解:prototype經過調用構造函數而建立的那個對象的原型對象。使用原型的好處可讓全部對象實例共享它所包含的屬性和方法。也就是說,沒必要在構造函數中定義對象信息,而是能夠直接將這些信息添加到原型中。prototype

function Box() {}                    //構造函數函數體內什麼都沒有,這裏若是有,叫作實例屬性,實例方法

Box.prototype.name='xixi';           //原型屬性
Box.prototype.age=100;               //原型屬性
Box.prototype.run=function () {      //原型方法
    return this.name+this.age+'運行中...';
};

var box1=new Box();
var box2=new Box();
alert(box1.run());                   //xixi100運行中...
alert(box1.run==box2.run);           //true,方法的引用地址保持一致

(1)若是是實例方法,不一樣的實例化,他們的方法地址是不同的,是惟一的指針


(2)若是是原型方法,那麼他們地址是共享的,你們都是同樣

2.在原型模式聲明中,多了兩個屬性,這兩個屬性都是建立對象時自動生成的。__proto__ 屬性是實例指向原型對象的一個指針,它的做用就是指向構造函數的原型屬性constructor。經過這兩個屬性,就能夠訪問到原型裏的屬性和方法了。
(1)__proto__ 屬性

function Box() {}                     //構造函數函數體內什麼都沒有,這裏若是有,叫作實例屬性,實例方法

Box.prototype.name='xixi';            //原型屬性
Box.prototype.age=100;                //原型屬性
Box.prototype.run=function () {       //原型方法
    return this.name+this.age+'運行中...';
};

var box = new Box();
alert(box.prototype);                 //使用對象實例沒法訪問到prototype輸出:undefined
alert(box.__proto__);                 //這個屬性是一個指針指向prototype原型對象輸出:[object Object]
alert(Box.prototype);                 //使用構造函數名(對象名)訪問prototype原型對象輸出:[object Object]

注意:IE 瀏覽器在腳本訪問__proto__會不能識別,火狐和谷歌瀏覽器及其餘某些瀏覽器均能識別。雖然能夠輸出,但沒法獲取內部信息。
(2)原型屬性constructor

function Box() {}                    //構造函數函數體內什麼都沒有,這裏若是有,叫作實例屬性,實例方法

Box.prototype.name='xixi';           //原型屬性
Box.prototype.age=100;               //原型屬性
Box.prototype.run=function () {      //原型方法
    return this.name+this.age+'運行中...';
};

var box1=new Box();
alert(box1.constructor);            //構造屬性,能夠獲取構造函數自己:function Box {}
                                    //做用是被原型指針定位,而後獲得構造函數自己
                                    //其實就是對象實例對應的原型對象的做用    

(3)判斷一個對象是否指向了該構造函數的原型對象,可使用isPrototypeOf()方法來測試。

function Box() {}                     //構造函數函數體內什麼都沒有,這裏若是有,叫作實例屬性,實例方法

Box.prototype.name='xixi';            //原型屬性
Box.prototype.age=100;                //原型屬性
Box.prototype.run=function () {       //原型方法
    return this.name+this.age+'運行中...';
};

var box1=new Box();
//判斷一個對象實例(對象引用)是否是指向了原型對象,基本上,只要實例化了,他自動指向的
alert(Box.prototype.isPrototypeOf(box1));    //true

3.原型模式的執行流程:
(1)先查找構造函數實例裏的屬性或方法,若是有,馬上返回;
(2)若是構造函數實例裏沒有,則去它的原型對象裏找,若是有,就返回;
雖然咱們能夠經過對象實例訪問保存在原型中的值,但卻不能訪問經過對象實例重寫原型中的值。

function Box() {}                     //構造函數函數體內什麼都沒有,這裏若是有,叫作實例屬性,實例方法

Box.prototype.name='xixi';            //原型屬性
Box.prototype.age=100;                //原型屬性
Box.prototype.run=function () {       //原型方法
    return this.name+this.age+'運行中...';
};
var box1=new Box();
box1.name='wang';                     //實例屬性,並無重寫原型屬性
alert(box1.name);                     //就近原則:wang

var box2=new Box();
alert(box2.name);                     //實例屬性不會共享,因此box2訪問不到實例屬性,就只能訪問原型:xixi

(3)屬性刪除:若是想要box1也能在後面繼續訪問到原型裏的值,能夠把構造函數裏的屬性刪除便可

function Box() {}                      //構造函數函數體內什麼都沒有,這裏若是有,叫作實例屬性,實例方法

Box.prototype.name='xixi';             //原型屬性
Box.prototype.age=100;                 //原型屬性
Box.prototype.run=function () {        //原型方法
    return this.name+this.age+'運行中...';
};
var box1=new Box();
box1.name='wang';                      //實例屬性,並無重寫原型屬性
alert(box1.name);                      //就近原則:wang
delete box1.name;                      //刪除實例中的屬性
alert(box1.name);                      //打印刪除後的實例:xixi

4.如何判斷屬性是在構造函數的實例裏,仍是在原型裏?可使用 hasOwnProperty()函數來驗證
(1)實例裏有返回true:

function Box() {}                    //構造函數函數體內什麼都沒有,這裏若是有,叫作實例屬性,實例方法

Box.prototype.name='xixi';            //原型屬性
Box.prototype.age=100;                //原型屬性
Box.prototype.run=function () {       //原型方法
    return this.name+this.age+'運行中...';
};
var box1=new Box();
box1.name = 'wang'
alert(box1.hasOwnProperty('name'));   //返回true

(2)實例裏沒有返回false:

function Box() {}                    //構造函數函數體內什麼都沒有,這裏若是有,叫作實例屬性,實例方法

Box.prototype.name='xixi';           //原型屬性
Box.prototype.age=100;               //原型屬性
Box.prototype.run=function () {      //原型方法
    return this.name+this.age+'運行中...';
};
var box1=new Box();

alert(box1.hasOwnProperty('name'));  //返回false

5.in操做符會在經過對象可以訪問給定屬性時返回true,不管該屬性存在於實例中仍是原型中。

function Box() {}                     //構造函數函數體內什麼都沒有,這裏若是有,叫作實例屬性,實例方法

Box.prototype.name='xixi';            //原型屬性
Box.prototype.age=100;                //原型屬性
Box.prototype.run=function () {       //原型方法
    return this.name+this.age+'運行中...';
};
var box1=new Box();

alert('name'in box1);                //無論實例屬性或原型屬性是否存在,只要有就返回true,兩邊都沒有,返回false

咱們能夠經過hasOwnProperty()方法檢測屬性是否存在實例中,也能夠經過in來判斷實例或原型中是否存在屬性。那麼結合這兩種方法,能夠判斷原型中是否存在屬性。

//判斷只有原型中有屬性,
function isProperty(object, property) {      //判斷原型中是否存在屬性
    return !object.hasOwnProperty(property) && (property in object)
}

function Box() {}                     //構造函數函數體內什麼都沒有,這裏若是有,叫作實例屬性,實例方法

Box.prototype.name='xixi';            //原型屬性
Box.prototype.age=100;                //原型屬性
Box.prototype.run=function () {       //原型方法
    return this.name+this.age+'運行中...';
};

var box1=new Box();
alert(isProperty(box1,'name'))        //true,若是原型有

6.爲了讓屬性和方法更好的體現封裝的效果,而且減小沒必要要的輸入,原型的建立可使用字面量的方式:

function Box() {}

//使用字面量的方式建立原型對象,這裏{}就是對象,是Object,new Object就至關於{}
Box.prototype = {               //使用字面量的方式
    name : 'xixi', 
    age : 100,
    run : function () {
        return this.name+this.age+'運行中...';
    }
};

var box = new Box();
alert(box instanceof Box);        //true
alert(box instanceof Object);     //true
alert(box.constructor == Box);    //字面量方式,返回 false,不然,true
alert(box.constructor == Object); //字面量方式,返回 true,不然,false

使用構造函數建立原型對象和使用字面量建立對象在使用上基本相同,但仍是有一些區別,字面量建立的方式使用constructor屬性不會指向實例,而會指向 Object,構造函數建立的方式則相反。若是想讓字面量方式的constructor指向實例對象,那麼能夠這麼作:

function Box() {}

//使用字面量的方式建立原型對象,這裏{}就是對象,是Object,new Object就至關於{}
Box.prototype = {               //使用字面量的方式
    constructor : Box,            //強制指向Box
    name : 'xixi', 
    age : 100,
    run : function () {
        return this.name+this.age+'運行中...';
    }
};

var box = new Box();
alert(box instanceof Box);        //true
alert(box instanceof Object);     //true
alert(box.constructor == Box);    //true
alert(box.constructor == Object); //false

注意:字面量方式爲何constructor會指向Object?由於 Box.prototype={};這種寫法其實就是建立了一個新對象。而每建立一個函數,就會同時建立它prototype,這個對象也會自動獲取constructor屬性。因此,新對象的constructor重寫了Box原來的constructor,所以會指向新對象,那個新對象沒有指定構造函數,那麼就默認爲Object。
原型的聲明是有前後順序的,因此,重寫的原型會切斷以前的原型

function Box() {}

//使用字面量的方式建立原型對象,這裏{}就是對象,是Object,new Object就至關於{}
Box.prototype = {               //使用字面量的方式
    constructor : Box,            //強制指向Box
    name : 'xixi', 
    age : 100,
    run : function () {
        return this.name+this.age+'運行中...';
    }
};

//重寫了原型對象
Box.prototype = {
    age : 200                    //這裏不會保留以前原型的任何信息了。
                                //把原來的原型對象和構造函數對象實例以前的關係切斷了
};

var box = new Box();            //在這裏聲明
alert(box.name);                //box 只是最初聲明的原型輸出:undefined

原型對象不只僅能夠在自定義對象的狀況下使用,而ECMAScript 內置的引用類型均可以使用這種方式,而且內置的引用類型自己也使用了原型。

//查看sort是不是Array原型對象裏的方法
alert(Array.prototype.sort);             //sort就是Array 類型的原型方法:function sort() {[native code]}
alert(String.prototype.substring);       //substring就是String類型的原型方法:function substring() {[native code]}

//內置引用類型的功能擴展
String.prototype.addstring = function () {   //給 String 類型添加一個方法
    return this+'被添加了!';                //this 表明調用的字符串
};

var box ='xixi'; 
alert(box.addstring());                      //使用這個方法輸出:xixi被添加了!

注意:儘管給原生的內置引用類型添加方法使用起來特別方便,但咱們不推薦使用這種方法。由於它可能會致使命名衝突,不利於代碼維護
原型模式建立對象也有本身的缺點,它省略了構造函數傳參初始化這一過程,帶來的缺點就是初始化的值都是一致的。而原型最大的缺點就是它最大的優勢,那就是共享。
原型中全部屬性是被不少實例共享的,共享對於函數很是合適,對於包含基本值的屬性也還能夠。但若是屬性包含引用類型,就存在必定的問題

//原型的缺點
function Box() {}

Box.prototype = {
    constructor : Box, 
    name : 'xixi', 
    age : 100,
    family : ['哥哥','姐姐','妹妹'],               //添加了一個數組屬性
    run : function () {
        return this.name+this.age+'運行中...'
    }
};

var box1 = new Box();
alert(box1.family);                 //輸出:哥哥,姐姐,妹妹

box1.family.push('弟弟');            //在第一個實例修改後引用類型,保持了共享,輸出:哥哥,姐姐,妹妹,弟弟
alert(box1.family);

var box2 = new Box();
alert(box2.family);                 //共享了box1添加後的引用類型的原型,輸出:哥哥,姐姐,妹妹,弟弟

注意:數據共享的緣故,致使不少開發者放棄使用原型,由於每次實例化出的數據須要保留本身的特性,而不能共享。
7.構造函數+原型模式:能夠解決構造傳參和共享問題

//組合構造函數+原型模式
function Box(name, age) {        //保持獨立的用構造函數
    this.name = name;
    this.age = age;
    this.family = ['哥哥','姐姐','妹妹'];
}

Box.prototype = {                //保持共享的用原型
    constructor : Box,
    run : function () {
        return this.name+this.age+'運行中...'
    }
};

var box1 = new Box('xixi',100);
alert(box1.family);              //輸出:哥哥,姐姐,妹妹

box1.family.push('弟弟');         //輸出:哥哥,姐姐,妹妹,弟弟
alert(box1.family);

var box2 = new Box('wang',200);
alert(box2.family);                 //引用類型沒有使用原型,因此沒有共享。輸出:哥哥,姐姐,妹妹

注意:這種混合模式很好的解決了傳參和引用共享的大難題。是建立對象比較好的方法。
8.動態原型模式:無論你是否調用了原型中的共享方法,它都會初始化原型中的方法,而且在聲明一個對象時,構造函數+原型部分讓人感受又很怪異,最好就是把構造函數和原型封裝到一塊兒。爲了解決這個問題,咱們可使用動態原型模式。

//動態原型模式
function Box(name,age) {                              //將全部信息封裝到函數體內
    this.name = name;
    this.age = age;
    this.family = ['哥哥','姐姐','妹妹'];
    
    if (typeof this.run != 'function') {              //if判斷this.run是否存在
        alert('原型初始化開始');                        //測試用
        Box.prototype.run = function() {
            return this.name + this.age + '運行中...'
        };
        alert('原型初始化結束');                        //測試用
    }
}
//原型的初始化,只要第一次初始化,就能夠了,不必每次構造函數實例化的時候都初始化
var box1 = new Box('xixi',100);
var box2 = new Box('wang',200);

當第一次調用構造函數時,run()方法發現不存在,而後初始化原型。當第二次調用,就不會初始化,而且第二次建立新對象,原型也不會再初始化了。這樣及獲得了封裝,又實現了原型方法共享,而且屬性都保持獨立。
注意:使用動態原型模式,要注意一點,不能夠再使用字面量的方式重寫原型,由於會切斷實例和新原型之間的聯繫。
9.寄生構造函數:各類方式對象建立的方法,若是這幾種方式都不能知足需求,可使用一開始那種模式:寄生構造函數。

//寄生構造函數=工廠模式+構造函數
function Box(name,age) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.run = function () {
        return this.name+this.age + '運行中...'
    };
    return obj;
}

var box1 = new Box('xixi',100);    
alert(box1.run());                 //xixi100運行中...

var box2 = new Box('wang',200);
alert(box2.run());                 //wang100運行中...

寄生構造函數,其實就是工廠模式+構造函數模式。這種模式比較通用,但不能肯定對象關係,因此,在可使用以前所說的模式時,不建議使用此模式。
在什麼狀況下使用寄生構造函數比較合適呢?假設要建立一個具備額外方法的引用類型。因爲以前說明不建議直接String.prototype.addstring,能夠經過寄生構造的方式添加。

function myString(string) {
var str = new String(string);
str.addstring = function () {
return this + ',被添加了!';
};
return str;
}
var box = new myString('xixi'); //比直接在引用原型添加要繁瑣好多
alert(box.addstring());         //xixi,被添加了!

10.穩妥構造函數:在一些安全的環境中,好比禁止使用this和new,這裏的this是構造函數裏不使用this,這裏的new是在外部實例化構造函數時不使用 new。這種建立方式叫作穩妥構造函數。

//穩妥構造函數
function Box(name, age) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.run = function () {
        return this.name + this.age + '運行中...'    /直接打印參數便可
    };
    return obj;
}


var box1 = Box('xixi',100);   //直接調用函數
alert(box1.run());            //xixi100運行中...

var box2 = Box('wang',200);
alert(box2.run());            //wang200運行中...

三.繼承
1.繼承是面向對象中一個比較核心的概念。其餘正統面嚮對象語言都會用兩種方式實現繼承:一個是接口實現,一個是繼承。而ECMAScript只支持繼承,不支持接口實現,而實現繼承的方式依靠原型鏈完成。

//繼承,經過原型鏈實現
function Box() {                //被繼承的函數叫作超類型(父類,基類)
    this.name = 'xixi';
}

function Desk() {                //繼承的函數叫作子類型(子類,派生類)
    this.age = 100;
}

function Table() {
    this.level ='CCCCC';
}

2.原型鏈繼承

//經過原型鏈繼承,超類型實例化後的對象實例,賦值給子類型的原型屬性
//new Box()會將Box構造裏的信息和原型裏的信息都交給Desk
//Desk的原型,獲得的是Box的構造+原型裏的信息
Desk.prototype = new Box();     //Desc繼承了Box,經過原型,造成鏈條
Table.prototype = new Desk();   //繼續原型鏈繼承

var desk = new Desk();
alert(desk.age);                //打印:100
alert(desk.name);               //獲得被繼承的屬性打印:xixi

var table = new Table();
alert(table.level);             //打印:CCCCC
alert(desk.age);                //打印:100
alert(desk.name);               //獲得被繼承的屬性打印:xixi

3.就近原則繼承

function Box() {                //被繼承的函數叫作超類型(父類,基類)
    //this.name = 'xixi';
}

Box.prototype.name = 'wang';     //實例和原型中均包含name=wang

function Desk() {                //繼承的函數叫作子類型(子類,派生類)
    this.age = 100;
}

Desk.prototype = new Box();     //Desc繼承了Box,經過原型,造成鏈條

var desk = new Desk();
alert(desk.name);               ////就近原則,實例裏有,就返回,沒有就去查找原型打印:wang

注意:以上原型鏈繼承還缺乏一環,那就是Obejct,全部的構造函數都繼承自Obejct。而繼承Object是自動完成的,並不須要程序員手動繼承。

function Box() {                //被繼承的函數叫作超類型(父類,基類)
    this.name = 'xixi';
}

function Desk() {                //繼承的函數叫作子類型(子類,派生類)
    this.age = 100;
}

Desk.prototype = new Box();     //Desc繼承了Box,經過原型,造成鏈條

var box = new Box();
var desk = new Desk();
//子類型從屬於本身或者他的超類型
alert(desk instanceof Object);     //true
alert(desk instanceof Desk);       //true
alert(desk instanceof Box);        //true
alert(box instanceof Desk);        //false

在JavaScript裏,被繼承的函數稱爲超類型(父類,基類也行,其餘語言叫法),繼承的函數稱爲子類型(子類,派生類)。繼承也有以前問題,好比字面量重寫原型會中斷關係,使用引用類型的原型,而且子類型還沒法給超類型傳遞參數。
爲了解決引用共享和超類型沒法傳參的問題,咱們採用一種叫借用構造函數的技術,或者成爲對象冒充(僞造對象、經典繼承)的技術來解決這兩種問題。
4.借用構造函數繼承(對象冒充繼承)

//使用對象冒充繼承
function Box(name, age) {
    this.name = name;
    this.age = age;
    this.family = ['哥哥','姐姐','妹妹'];    //引用類型,放在構造裏就不會被共享 
}

function Desk(name, age) {
    Box.call(this,name,age)                    //對象冒充,對象冒充只能繼承構造裏的信息
}

var desk = new Desk('xixi',100);
alert(desk.family);                         //哥哥,姐姐,妹妹

desk.family.push('弟弟');                   
alert(desk.family);                         //哥哥,姐姐,妹妹,弟弟

var desk2 = new Desk('xixi',100);           //添加的新數據,只給desk,不給desk2
alert(desk2.family);                        //哥哥,姐姐,妹妹

5.組合繼承(結合前兩種):借用構造函數雖然解決了剛纔兩種問題,但沒有原型,複用則無從談起。因此,咱們須要原型鏈+借用構造函數的模式,這種模式成爲組合繼承。

function Box(name,age) {
    this.name = name;
    this.age = age;
    this.family = ['哥哥','姐姐','妹妹'];
}

Box.prototype.run = function () {
    return this.name + this.age+ '運行中...';
};
//構造函數裏的方法,放在構造裏,每次實例化,都會分配一個內存地址,浪費,因此最好放在原型裏,保證屢次實例化只有一個地址
function Desk(name,age) {
    Box.call(this,name,age)            //對象冒充
}

Desk.prototype = new Box();            //原型鏈繼承

var desk = new Desk('xixi',100);    //xixi100運行中...
alert(desk.run());

6.原型式繼承:這種繼承藉助原型並基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。

//臨時中轉函數
function obj(o) {                //o表示將要傳遞進入的一個對象 
    function F() {}                //F構造是一個臨時新建的對象,用來存儲傳遞過來的對象
    F.prototype = o;            //將o對象實例賦值給F構造的原型對象
    return new F();                //最後返回這個獲得傳遞過來對象的對象實例
}

//這是字面量的聲明方式,至關於var box=new Box();
var box = {                     //字面量對象
    name : 'xixi',
    age : 100,
    family : ['哥哥','姐姐','妹妹']
};

//box1就等於new F()
var box1 = obj(box);                //傳遞
alert(box1.name);                   //打印出:xixi
alert(box1.family);                 //打印出:哥哥,姐姐,妹妹

box1.family.push('弟弟');
alert(box1.family);                 //打印出:哥哥,姐姐,妹妹,弟弟

var box2 = obj(box);                //傳遞
alert(box2.name);                   //打印出:xixi
alert(box2.family);                 //引用類型的屬性共享了,打印出:哥哥,姐姐,妹妹,弟弟

7.寄生式繼承=原型式+工廠模式:目的是爲了封裝建立對象的過程。

//臨時中轉函數
function obj(o) {                      
    function F() {}                
    F.prototype = o;            
    return new F();            
}
//寄生函數
function create(o) {                //封裝建立過程,經過寄生函數直接調用臨時中轉函數
    var f = obj(o);
    f.run = function () {
        return this.name + '方法';  //一樣,會共享引用
    }
    return f;
}

var box = {
    name : 'xixi',
    age : 100,
    family : ['哥哥','姐姐','妹妹']
};

var box1 = create(box);
alert(box1.run());                  //打印出:xixi方法

組合式繼承是JavaScript最經常使用的繼承模式;但,組合式繼承也有一點小問題,就是超類型在使用過程當中會被調用兩次:一次是建立子類型的時候,另外一次是在子類型構造函數的內部。

8.寄生組合繼承:解決了兩次調用的問題

//臨時中轉函數
function obj(o) {                
    function F() {}                
    F.prototype = o;            
    return new F();            
}
//寄生函數
function create(box,desk) {
    var f = obj(box.prototype);
    f.constructor = desk;                //調整原型構造指針
    desk.prototype = f;
}

function Box(name,age) {
    this.name = name;
    this.age = age;
}

Box.prototype.run = function () {
    return this.name + this.age + '運行中...'
}

function Desk(name,age) {
    Box.call(this,name,age);            //對象冒充,第二次調用 Box
}
//經過寄生組合繼承來實現繼承
create(Box,Desk);                        //這句話用來替代Desk.prototype = new Box();

var desk = new Desk('xixi',100);
alert(desk.run());                     //xixi100運行中...
alert(desk.constructor);               //調整原型構造指針後指針也指向本身自己Desk輸出:function Desk(name,age) {
//                                                                                       Box.call(this,name,age);
// 
相關文章
相關標籤/搜索