JS基礎篇--面向對象與原型

建立對象

var box = new Object();//建立對象
box.name = 'Lee';      //添加屬性
box.age = 100;
box.run = function(){
    return this.name + this.age + "運行中";  //this 表示當前做用域下對象
}

// this 表示new Object()實例出來的那個對象
alert(box.run());

這就是建立對象最基本的方法,可是有個缺點,想建立一個相似的對象,就會產生大量的代碼。瀏覽器

工廠模式

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

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',20);
var box2 = createObject('Jack',30);
console.log(box1.run());
console.log(box2.run());

工廠模式解決了重複實例化的問題,但還有一個問題,那就是識別問題,由於根本沒法搞清他們究竟是哪一個對象的實例。函數

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

構造函數

ECAMScript中採用構造函數(構造方法)可用來建立特定的對象。相似於Object對象。測試

//構造函數
function Box(name,age){
    this.name = name;
    this.age = age;
    this.run = function(){
        return this.name + this.age +"運行中...";
    };
};

var box1 = new Box('Lee',100);
var box2 = new Box('Jack',200);

console.log(box1.run());
console.log(box2.run());

使用構造函數的方法,即解決了重複實例化的問題,又解決了對象識別的問題,但問題是,這裏並無new Object(),爲何能夠實例化Box(),這個是哪裏來的呢?this

使用了構造函數的方法,和使用工廠模式的方法他們不一樣之處以下:
1.構造函數方法沒有顯示的建立對象(new Objectt()),但它在後臺自動var obj = new Object();
2.直接將屬性和方法賦值給this對象,this就至關於obj;
3.沒有return語句,不須要返回對象引用,它是在後臺自動返回的。prototype

//構造函數
function Box(name,age){
    this.name = name;
    this.age = age;
    this.run = function(){
        return this.name + this.age +"運行中...";
    };
};

function Dack(name,age){
    this.name = name;
    this.age = age;
    this.run = function(){
        return this.name + this.age +"運行中...";
    };
};

var box1 = new Box('Lee',100);
var box2 = new Box('Jack',200);
var box3 = new Dack('MrLee',300);

console.log(box1.run());
console.log(box2.run());
console.log(box3.run());

//解決了對象識別問題
console.log(box1 instanceof Box); //true
console.log(box2 instanceof Box); //true
console.log(box3 instanceof Box); //false
console.log(box3 instanceof Dack);//true

對象冒充:使用call()方法指針

var o= new Object();
Box.call(o,'Lee',100);
console.log(o.run());

看下一個問題:code

var box1 = new Box('Lee',100); //實例化後地址爲1
 var box2 = new Box('Lee',100); //實例化後地址爲2

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

上面的代碼運行說明引用地址不同,那麼構造函數內的方法也能夠這樣寫:對象

this.run = new Function("return this.name + this.age +'運行

如何讓他們的引用地址同樣,下面代碼:繼承

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

function run(){
    return this.name +this.age+"運行中...";
}
 var box1 = new Box('Lee',100); //實例化後地址爲1
 var box2 = new Box('Lee',100); //實例化後地址爲2
 console.log(box1.run == box2.run); //true //由於他們比較的是引用地址

把構造函數內部的方法經過全局來實現引用地址一致。
雖然使用了全局函數run()來解決了保證引用地址一致的問題,可是這種方式又帶來了一個新的問題,全局中的this在對象調用的時候是Box自己,而當普通函數調用的時候,this又表明window。

原型

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

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

var box1=new Box();
var box2=new Box();
console.log(box1.run == box2.run); //true
console.log(box1.prototype);//這個屬性是一個對象,訪問不到
console.log(box1.__proto__);//這個屬性是一個指針指向prototype原型對象。

若是是實例方法,不一樣的實例化,他們的方法地址是不同的,是惟一的。
若是是原型方法,那麼他們的地址是共享的,你們都同樣。

console.log(box1.constructor); //構造屬性,能夠獲取構造函數

PS:IE瀏覽器在腳本訪問__proto__會不能識別,火狐和谷歌及其餘某些瀏覽器能識別。雖然能夠輸出,可是沒法獲取內部信息。

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

console.log(Box.prototype.isPrototypeOf(box1)); //true //只要實例化對象,即都會指向

原型模式的執行流程:
1.先查找構造函數實例裏的屬性或方法,若是有,馬上返回;
2.若是構造函數實例裏沒有,則去它的原型對象裏找,若是有,就返回。

如何判斷屬性時構造函數的實例裏,仍是原型裏?可使用hasOwnProperty()函數來驗證:

console.log(box1.hasOwnProperty('name'));//若是實例裏有返回true,不然返回false

如何判斷屬性是原型裏的?

function Box(){
       
} 

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

function isProperty(object,property){
    return !object.hasOwnProperty(property) && (property in object);
}
var box1=new Box();
console.log(isProperty(box1,'name'));

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

使用字面量的方式建立原型對象,這裏的{}就是對象,是object,new Object就至關於{}

function Box(){} 

Box.prototype={
    name:'Lee',
    age:100,
    run:function(){
        return this.name+this.age+"運行中...";
    }
}

var box = new Box();
console.log(box.constructor == Box); //false

字面量建立的方式使用constructor屬性不會指向實例,而會指向Object,構造函數建立的方式則相反。
這裏的Box.prototype={}就至關於建立了一個新的對象,因此 box.constructor是Object。
如何讓box.constructor指向Box呢?

function Box(){} 

Box.prototype={
    constructor:Box,//直接強制指向便可
    name:'Lee',
    age:100,
    run:function(){
        return this.name+this.age+"運行中...";
    }
}

var box = new Box();
console.log(box.constructor == Box); //true

重寫原型,不會保留以前原型的任何信息,把原來的原型對象和構造函數對象的實例切斷了。

function Box(){} 

Box.prototype={
    constructor:Box,
    name:'Lee',
    age:100,
    run:function(){
        return this.name+this.age+"運行中...";
    }
}

//重寫原型
Box.prototype={
    age:200
}
var box = new Box();
console.log(box.name); //undefined

查看sort是不是Array原型對象裏的方法
alert(Array.prototype.sort);

在以下 判斷String原型對象裏是否有substring方法
alert(String.prototype.substring);

給String 添加addstring方法:

String.prototype.addstring=function(){
    return this+',被添加了!';
}
var box="Lee";
console.log(box.addstring());

注:原型模式建立對象也有本身的缺點,它省略了構造函數傳參初始化這一過程,帶來的缺點就是初始化的值都是一致的。而原型最大的缺點就是它最大的優勢,那就是共享。

原型中全部屬性是被不少實例共享的,共享對於函數很是合適,對於包含基本值的屬性也還能夠。但若是屬性包含引用類型,就存在必定的問題:

function Box(){}

Box.prototype={
    constructor:Box,
    name:'Lee',
    age:100,
    family:['哥哥','姐姐','妹妹'],
    run:function(){
        return this.name+this.age+"運行中...";
    }
};

var box1 = new Box();
console.log(box1.family); //'哥哥','姐姐','妹妹'
box1.family.push("弟弟");
console.log(box1.family);//'哥哥','姐姐','妹妹','弟弟'

var box2 = new Box();
console.log(box2.family);//'哥哥','姐姐','妹妹','弟弟'

從上面代碼能夠看出,在第一個實例修改後引用類型,保持了共享。box2.family共享了box1添加後的引用類型的原型。

爲了解決構造傳參和共享問題,能夠組合構造函數+原型模式:

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('Lee',100);
console.log(box1.family); //'哥哥','姐姐','妹妹'
box1.family.push("弟弟");
console.log(box1.family);//'哥哥','姐姐','妹妹','弟弟'



var box2 = new Box('Jack',200);
console.log(box2.family); //'哥哥','姐姐','妹妹' //引用類型沒有使用原型,因此沒有共享

動態原型模式

//把原型封裝到構造函數裏
function Box(name,age){
    this.name=name;
    this.age=age;
    this.family=['哥哥','姐姐','妹妹'];

    console.log('原型初始化開始');  //執行了兩次
    Box.prototype.run=function(){
        return this.name+this.age+"運行中...";
    }
    console.log('原型初始化結束'); //執行了兩次
}

//原型的初始化,只要第一次初始化就能夠了,不必每次構造函數實例化的時候都初始化
var box1 = new Box('Lee',100);
var box2 = new Box('Jack',200);

爲了只讓第一次初始化,那麼就判斷

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

    if(typeof this.run!='function'){
        console.log('原型初始化開始'); //執行了一次次
        Box.prototype.run=function(){
            return this.name+this.age+"運行中...";
        };
        console.log('原型初始化結束'); //執行了一次
    }
}

//原型的初始化,只要第一次初始化就能夠了,不必每次構造函數實例化的時候都初始化
var box1 = new Box('Lee',100);
var box2 = new Box('Jack',200);

寄生構造函數
若是以上都不能知足須要,可使用一下寄生構造函數。
寄生構造函數=工廠模式+構造函數

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('Lee',100);
var box2 = new Box('Jack',200);

穩妥構造函數

在一些安全的環境中,好比禁止使用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('Lee',100);
var box2 = Box('Jack',200);

繼承

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

function Box(){
    this.name="Lee";
}

function Jack(){
    this.age=100;
}

Jack.prototype = new Box();

var jack = new Jack();
console.log(jack.name); //Lee

爲了解決引用共享和超類型沒法傳參的問題,咱們採用一種叫借用構造函數的技術,或者成爲對象冒充(僞造對象、經典繼承)的技術解決這兩個問題。

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

Box.prototype.age=200;

function Jack(name){
    Box.call(this,name);
}

var jack = new Jack('Lee');

console.log(jack.name);//Lee
console.log(jack.age);//undefined

可是上面的代碼能夠看出,對象冒充沒有繼承原型鏈上的age屬性。因此要繼承Box的原型,就出現下面的組合繼承。
組合繼承便是原型鏈+借用構造函數的模式

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

Box.prototype.age=200;

function Jack(name){
    Box.call(this,name);
}

Jack.prototype = new Box();

var jack = new Jack('Lee');

console.log(jack.name);//Lee
console.log(jack.age);//200

原型式繼承

//臨時中轉函數
function obj(o){
    function F(){};
    F.prototype = o;
    return new F();
}

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

var box1 = obj(box);
console.log(box1.family);//'哥哥','姐姐','妹妹'
box1.family.push('弟弟');
console.log(box1.family);//'哥哥','姐姐','妹妹','弟弟'

var box2 = obj(box);
console.log(box2.family);//'哥哥','姐姐','妹妹','弟弟'

存在的問題就是引用類型共享了。

寄生式繼承
把原型式與工廠模式結合起來。

//臨時中轉函數
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 = new Box();
var box={
    name:'Lee',
    age:100,
    family:['哥哥','姐姐','妹妹']
};

var box1 = create(box);
console.log(box1.run());

寄生組合繼承

//臨時中轉函數
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); //對象冒充
}

//經過寄生組合繼承來實現繼承
create(Box,Desk); //這句話用來替代Desk.prototype = new Box();

var desk = new Desk('Lee',100);
console.log(desk.run());
相關文章
相關標籤/搜索