程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 你不知道的JavaScript--Item37 面向對象高級程序設計

你不知道的JavaScript--Item37 面向對象高級程序設計

編輯:JAVA綜合教程

你不知道的JavaScript--Item37 面向對象高級程序設計


1. JS是基於原型的程序

建立一個簡單的面向對象的類。有屬性,有方法。

function Aaa(){
    this.name = '小明';
}
Aaa.prototype.showName = function(){
    alert( this.name );
};

var a1 = new Aaa();
a1.showName();

在JS的自身的對象中,也是new一個對象,然後調用方法,比如:

var arr = new Array();
arr.push();
arr.sort();

在JS源碼 : 系統對象也是基於原型的程序。比如:

function Array(){
    this.lenglth = 0;
}
Array.prototype.push = function(){};
Array.prototype.sort = function(){};

盡量不要去修改或者添加系統對象下面的方法和屬性,這樣會改變系統的方法。

var arr = [1,2,3];
Array.prototype.push = function(){}
arr.push(4,5,6);
alert( arr );

這樣修改後,push方法就失效了,覆蓋了JS本省的push方法。

那麼如何自己重寫Array的push方法呢?

var arr = [1,2,3];
Array.prototype.push = function(){
    //this : 1,2,3
    //arguments : 4,5,6

    for(var i=0;i

2.JS中的包裝對象

我們知道js的基本數據類型有 string, number, boolean, null, undefined, object.但是當你看到下面的代碼時,你發現了什麼?

var str = 'hello';

alert( typeof str );//String

str.charAt(0);
str.indexOf('e');

除了對象可以調用方法,為什麼基本數據類型也可以?這是因為在js中,string, number, boolean都有自己的包裝對象。String Number Boolean

var str = new String('hello');//通過構造函數構造的數據類型就是對象了,可以直接調用方法了

//alert( typeof str );

alert(str.charAt(1));

String.prototype.charAt = function(){};

那基本類型調用方法時,發生了什麼呢?

var str = 'hello';
str.charAt(0);  //基本類型會找到對應的包裝對象類型,然後包裝對象把所有的屬性和方法給了基本類型,然後包裝對象消失,包裝類型就和快遞員一樣。

比如要實現字符串的一個lastValue方法:

var str = 'hello';

String.prototype.lastValue = function(){
    return this.charAt(this.length-1);
};

alert( str.lastValue() ); //o

str通過原型繼承,使本身具有了lastValue()方法,所以可以直接調用,我們再來看個例子:

var str = 'hello';

str.number = 10;

alert( str.number );  //undefined

這裡為什麼是undefined,因為這裡包裝類型並沒有把number共享給str,只是自己有,而復制完消失,str.name沒有接受到值,變成undefined。

3. JS中的原型鏈

在js中,實例對象與原型之間的連接,叫做原型鏈,原型鏈之間的連接叫__proto__( 隱式連接 )

原型鏈的最外層是Object。

function Aaa(){

}
Aaa.prototype.num = 10;
var a1 = new Aaa();
alert(a1.num);

比如我們一般的定義一個對象Aaa,並在原型上定義了一個屬性num,新new的a1是怎麼訪問到原型上的屬性的呢?

這裡就是a1有一個__proto__屬性,指向Aaa.prototype原型對象,顧可以訪問到。

假如這樣呢?

function Aaa(){
    this.num = 20;
}
Aaa.prototype.num = 10;
var a1 = new Aaa();
alert(a1.num);//20

這裡有兩個num,彈出的是哪一個呢?這就是原型鏈的規則,先從本身對象找,本身對象沒有到原型對象上找,。。。。。一直找下去,何處是盡頭?知道找到object的原型,如果沒有就報錯,因為object的原型是null,這個原型連接起來的鏈就是一條原型鏈。看例子

function Aaa(){
    //this.num = 20;
}
//Aaa.prototype.num = 10;
Object.prototype.num = 30;

var a1 = new Aaa();
alert(a1.num);

4. 面向對象的一些常用屬性和方法

hasOwnProperty() : 看是不是對象自身下面的屬性
var arr = [];
arr.num = 10;
Array.prototype.num2 = 20;

//alert(  arr.hasOwnProperty('num')  );  //true

alert(  arr.hasOwnProperty('num2')  );  //false
constructor : 查看對象的構造函數
每個原型都會自動添加constructor屬性 For in 的時候有些屬性是找不到的 避免修改construtor屬性
function Aaa(){
}

var a1 = new Aaa();

alert( a1.constructor );  //Aaa

var arr = [];
alert( arr.constructor == Array );  //true

每個對象的原型的構造函數都自動指向本身,可以修改為別的,但是不建議修改:

function Aaa(){
}
//Aaa.prototype.constructor = Aaa;   //每一個函數都會有的,都是自動生成的

//Aaa.prototype.constructor = Array;

var a1 = new Aaa();
alert( a1.hasOwnProperty == Object.prototype.hasOwnProperty );  //true

說明hasOwnProperty 屬性是通過原型鏈,在Object.prototype原型上的。

有時候我們會不經意的就把原型修改了,這個時候我們就要把原型修正一下:

function Aaa(){
}

Aaa.prototype.name = '小明';//采用這種原型繼承,constructor沒有改變
Aaa.prototype.age = 20;

Aaa.prototype = {//采用json格式的對象,這就是相當於一個Object對象賦值,所以constructor變為了Object。
    constructor : Aaa,
    name : '小明',
    age : 20
};

var a1 = new Aaa();
alert( a1.constructor );

采用for in循環,是循環不到原型上constructor屬性的。

function Aaa(){
}

Aaa.prototype.name = 10;
Aaa.prototype.constructor = Aaa;

for( var attr in Aaa.prototype ){
    alert(attr);//只有name
}
instanceof : 運算符。對象與構造函數在原型鏈上是否有關系【對象是否是構造函數的一個實例】
function Aaa(){
}

var a1 = new Aaa();

//alert( a1 instanceof Object );  //true


var arr = [];

alert( arr instanceof Array );//true;
toString() : 系統對象下面都是自帶的 , 自己寫的對象都是通過原型鏈找object下面的。主要做一些解析,主要用於Array、Boolean、Date、Object、Number等對象
var arr = [];
alert( arr.toString == Object.prototype.toString ); //false*/

/*function Aaa(){
}
var a1 = new Aaa();
alert( a1.toString == Object.prototype.toString );  //true*/
- 把對象轉成字符串
/*var arr = [1,2,3];

    Array.prototype.toString = function(){
        return this.join('+');
    };

    alert( arr.toString() );  //'1+2+3'*/
做進制轉換,默認10進制
//var num = 255;
    //alert( num.toString(16) );  //'ff'
最重要一點是做類型判斷
//利用toString做類型的判斷 : 

/*var arr = [];

alert( Object.prototype.toString.call(arr) == '[object Array]' ); */ 
//'[object Array]'

類型判斷的常用方法:三種typeof是不行的,根本區分不開。假如判斷一個數據類型是數組?

alert( arr.constructor == Array );  

alert( arr instanceof Array );  

alert( Object.prototype.toString.call(arr) == '[object Array]' ); 

但是在一種很特殊的情況下,就只能用第三種,在iframe中創建一個數組,前兩種方法就會失效,但是在大多數情況下,還是可以的。

window.onload = function(){

    var oF = document.createElement('iframe');
    document.body.appendChild( oF );

    var ifArray = window.frames[0].Array;

    var arr = new ifArray();

    //alert( arr.constructor == Array );  //false

    //alert( arr instanceof Array );  //false

    alert( Object.prototype.toString.call(arr) == '[object Array]' );  //true   
};

5. JS對象的繼承

什麼是繼承?
在原有對象的基礎上,略作修改,得到一個新的對象不影響原有對象的功能。

其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。

構造函數、原型和實例的關系:每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針_proto_

1.原型繼承

實現原型鏈有一種基本模式,其代碼大致如下:

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};

function SubType(){
    this.subproperty = false;
}
//繼承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};

var instance = new SubType();
alert(instance.getSuperValue()); //true

以上代碼定義了兩個類型:SuperType 和SubType。每個類型分別有一個屬性和一個方法。它們的主要區別是SubType 繼承了SuperType,而繼承是通過創建SuperType 的實例,並將該實例賦給SubType.prototype 實現的。實現的本質是重寫原型對象,代之以一個新類型的實例。換句話說,原來存在於SuperType 的實例中的所有屬性和方法,現在也存在於SubType.prototype 中了。在確立了繼承關系之後,我們給SubType.prototype 添加了一個方法,這樣就在繼承了SuperType 的屬性和方法的基礎上又添加了一個新方法。這個例子中的實例以及構造函數和原型之間的關系如圖

\

我們沒有使用SubType 默認提供的原型,而是給它換了一個新原型;這個新原型就是SuperType 的實例。於是,新原型不僅具有作為一個SuperType 的實例所擁有的全部屬性和方法,而且其內部還有一個指針,指向了SuperType 的原型。

我們知道,所有引用類型默認都繼承了Object,而這個繼承也是通過原型鏈實現的,上面例子展示的原型鏈中還應該包括另外一個繼承層次。

\

可以通過兩種方式來確定原型和實例之間的關系instanceof操作符,isPrototypeOf()方法。

alert(instance instanceof Object); //true
alert(instance instanceof SuperType); //true
alert(instance instanceof SubType); //true

alert(Object.prototype.isPrototypeOf(instance)); //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance)); //true

原型繼承注意點:

(1)子類型有時候需要重寫父類型中的某個方法,或者需要添加超類型中不存在的某個方法。但不管怎
樣,給原型添加方法的代碼一定要放在替換原型的語句之後。

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//繼承了SuperType
SubType.prototype = new SuperType();
//添加新方法,一定放在原型替換之後,不然會被覆蓋掉的哦。
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
    //重寫超類型中的方法
    SubType.prototype.getSuperValue = function (){
        return false;
    };
    var instance = new SubType();
    alert(instance.getSuperValue()); //false

(2)通過原型鏈實現繼承時,不能使用對象字面量創建原型方法。因為這樣做就會重寫原型鏈,直接被json取代。

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//繼承了SuperType
SubType.prototype = new SuperType();
//使用字面量添加新方法,會導致上一行代碼無效
SubType.prototype = {
    getSubValue : function (){
        return this.subproperty;
    },
    someOtherMethod : function (){
        return false;
    }
};
var instance = new SubType();
alert(instance.getSuperValue()); //error!

原型鏈的問題

原型鏈雖然很強大,可以用它來實現繼承,但它也存在一些問題。其中,最主要的問題來自包含引用類型值的原型。包含引用類型值的原型屬性會被所有實例共享;在通過原型來實現繼承時,原型實際上會變成另一個類型的實例。於是,原來實例的屬性也就順理成章地變成了現在的原型屬性了。引用類型共享了數據。。。

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//繼承了SuperType
SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"

這個例子中的SuperType 構造函數定義了一個colors 屬性,該屬性包含一個數組(引用類型值)。SuperType 的每個實例都會有各自包含自己數組的colors 屬性。當SubType 通過原型鏈繼承了SuperType 之後,SubType.prototype 就變成了SuperType 的一個實例,因此它也擁有了一個它自己的colors 屬性——就跟專門創建了一個SubType.prototype.colors 屬性一樣。但結果是什麼呢?結果是SubType 的所有實例都會共享這一個colors 屬性。而我們對instance1.colors 的修改能夠通過instance2.colors 反映出來,就已經充分證實了這一點。

原型鏈的第二個問題是:在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。實際上,應該說是沒有辦法在不影響所有對象實例的情況下,給超類型的構造函數傳遞參數。

2.構造函數繼承(類式繼承)

這種技術的基本思想相當簡單,即在子類型構造函數的內部調用超類型構造函數。別忘了,函數只不過是在特定環境中執行代碼的對象,因此通過使用apply()和call()方法也可以在(將來)新創建的對象上執行構造函數

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
    //繼承了SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"

var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"

傳遞參數

相對於原型鏈而言,借用構造函數有一個很大的優勢,即可以在子類型構造函數中向超類型構造函
數傳遞參數。

function SuperType(name){
    this.name = name;
}
function SubType(){
    //繼承了SuperType,同時還傳遞了參數
    SuperType.call(this, "Nicholas");
    //實例屬性
    this.age = 29;
}

var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29

在SubType 構造函數內部調用SuperType 構造函數時,實際上是為SubType 的實例設置了name 屬性。為了確保SuperType 構造函數不會重寫子類型的屬性,可以在調用超類型構造函數後,再添加應該在子類型中定義的屬性。

借用構造函數的問題

如果僅僅是借用構造函數,那麼也將無法避免構造函數模式存在的問題——方法都在構造函數中定義,因此函數復用就無從談起了,而且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結果所有類型都只能使用構造函數模式。

3. 組合繼承

將原型鏈和借用構造函數的技術組合到一塊,從而發揮二者之長的一種繼承模式。其背後的思路是使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。這樣,既通過在原型上定義方法實現了函數復用,又能夠保證每個實例都有它自己的屬性。

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};

function SubType(name, age){
    //繼承屬性
    SuperType.call(this, name);
    this.age = age;
}

//繼承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    alert(this.age);
};

var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

4.拓展:拷貝繼承

先理解一下對象的拷貝,對象的引用是地址引用,雙向改變。而基本數據類型是按值傳遞,不影響原來的數值。

var a = {
    name : '小明'
};

var b = a;

b.name = '小強';

alert( a.name );//小強

采用拷貝繼承的方式,將方法一個個拷貝過去就不會發生雙向繼承了。

var a = {
    name : '小明'
};

//var b = a;
var b = {};
extend( b , a );    
b.name = '小強';
alert( a.name );    

function extend(obj1,obj2){
    for(var attr in obj2){
        obj1[attr] = obj2[attr];
    }
}

通過這個對象的復制之後,就可以實現拷貝繼承了:

//繼承 : 子類不影響父類,子類可以繼承父類的一些功能 ( 代碼復用 )

//屬性的繼承 : 調用父類的構造函數 call

//方法的繼承 : for in :  拷貝繼承 (jquery也是采用拷貝繼承extend)

function CreatePerson(name,sex){   //父類
    this.name = name;
    this.sex = sex;
}
CreatePerson.prototype.showName = function(){
    alert( this.name );
};

var p1 = new CreatePerson('小明','男');
//p1.showName();


function CreateStar(name,sex,job){  //子類

    CreatePerson.call(this,name,sex);

    this.job = job;

}

//CreateStar.prototype = CreatePerson.prototype;

extend( CreateStar.prototype , CreatePerson.prototype );

CreateStar.prototype.showJob = function(){
};

var p2 = new CreateStar('黃曉明','男','演員');

p2.showName();


function extend(obj1,obj2){
    for(var attr in obj2){
        obj1[attr] = obj2[attr];
    }
}

6.實例:用繼承實現拖拽


    <script type="text/javascript"> window.onload = function(){ var d1 = new Drag('div1'); d1.init(); var d2 = new childDrag('div2'); d2.init(); } function Drag(id){ this.obj = document.getElementById(id);//定義拖拽的對象 this.disX = 0;//水平邊距 this.disY = 0;//垂直邊距 } Drag.prototype.init = function(){//拖拽函數 var that = this; this.obj.onmousedown = function(e){ var e = e || window.event; that.fnDown(e); document.onmousemove = function(e){ var e = e || window.evnet; that.fnMove(e); } document.onmouseup = function(){ that.fnUP(); } return false; } } Drag.prototype.fnDown = function(e){ //clientX 返回當事件被觸發時,鼠標指針的水平坐標 //offsetLeft:獲取對象相對於版面左側位置 this.disX = e.clientX - this.obj.offsetLeft;//鼠標位置到obj左邊位置 this.disY = e.clientY - this.obj.offsetTop; } Drag.prototype.fnMove = function(e){ this.obj.style.left = e.clientX - this.disX + 'px'; this.obj.style.top = e.clientY - this.disY +'px'; } Drag.prototype.fnUP = function(){ document.onmousedown= null; document.onmousemove = null; } //定義子類,子類繼承父類,但是與父類有不同,不能拖拽脫離可視區域 function childDrag(id){ Drag.call(this,id);//屬性用call,apply } // 方法用for in extend(childDrag.prototype,Drag.prototype); //重寫父類的方法 childDrag.prototype.fnMove = function(e){ var leftX = e.clientX - this.disX; var topY = e.clientY - this.disY; var clientW = document.documentElement.clientWidth - this.obj.offsetWidth; var clientH = document.documentElement.clientHeight - this.obj.offsetHeight; if (leftX < 0){ leftX = 0; }else if (leftX >clientW) { leftX = clientW; } if (topY < 0 ) { topY = 0; }else if (topY> clientH) { topY = clientH; } this.obj.style.left = leftX + 'px'; this.obj.style.top = topY + 'px'; } function extend(obj1, obj2){//拷貝函數 for(var attr in obj2){ obj1[attr] = obj2[attr]; } } </script>

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved