javascript面向對象的寫法03


javascript面向對象的寫法03

 

js一些基礎知識的說明

prototype

首先每個js函數(類)都有一個prototype的屬性,函數是類。注意類有prototype,而普通對象沒有。
js中有一些常用的內置類。下面代碼打印內置類和自定義類的protytype
console.log(String.prototype);
console.log(Date.prototype);
console.log(Object.prototype);

function ClassA(name,job,born) {
}
console.log(ClassA.prototype);

prototype一般作用 

prototype作用可以擴展類,為類添加一些屬性或者方法的定義。
實際上prototype是類的一個特殊的固有的屬性,它指向的是一個簡單的普通的對象。因為被指向的是一個普通的對象,所以可以對prototype指向的對象增加屬性或修改屬性等。prototype指向的對象被更改過后,最后的效果類似把類的定義更改過。
修改prototype指向的對象實例。
Object.prototype.name = "111";    //為Object原型增加一個name屬性
function ClassA(name,job,born) {
}

ClassA.prototype.name = "123";    //自定義類原型也可以修改
ClassA.prototype.func1 = function(){};    //同樣可以修改原型的func1屬性為一個函數指針

prototype作用可以擴展類,比如為某個類的原型增加了一些屬性,那么該修改過的類所創建的所有對象都有此屬性。

function ClassA(name,job,born) {
	//此時的ClassA類沒有定義任何的屬性,用它new出來的對象自然是沒有name屬性
}
var objB = new ClassA();	
console.log(objB.name);         //輸出undefined

Object.prototype.name = "111";
ClassA.prototype.name = "123";	//prototype的修改對之前new出來的對象同樣生效

var objA = new Object();
console.log(objA.name);         //內置類的prototype修改也是可以的

console.log(objB.name);		//修改過prototype后便有這個屬性了

  

可以把prototype看做類的另外一半,把prototype和原本的類並在一起就相當於一個完整的類了。

function ClassA() {
    this.a = 100;
    this.b = 100;
}
ClassA.prototype = {
    'name': '123',
    'getName': function(){}
};

//類似於
function ClassA() {
    this.a = 100;
    this.b = 100;
    this.name = '123';
    this.getName = function(){};
}

js訪問一個對象的屬性時,先在自己的屬性中尋找,如果沒有繼而會去從類的prototype中找。自己定義的屬性是會覆蓋住類prototype定義的屬性。

function ClassA() {
    this.a = 100;
}
ClassA.prototype = {
    'a': 200,
};

var obj = new ClassA();
console.log(obj.a);        //輸出100

prototype還可以修改已有的類

比如修改內置Array類的prototype,添加一個方法。
Array(數組)沒有“是否包含”這樣的方法,可以自己擴展一個
Array.prototype.containsObject = function(e) {
    for (i=0; i<this.length; i++) {
        if (this[i] == e) {
            return true;
        }
    }
    return false;
};

使用prototype的好處

使用prototype可以擴展類從而使得類創建的對象擁有擴展的屬性或方法,類本身作為構造函數也可以為對象設置屬性。
function ClassA() {
    this.a = 100;
}
ClassA.prototype = {
    'b': 200
};

那么兩者有何區別呢?使用何種方式好。

首先類作為構造函數的寫法是同以下形式作用一樣,前面文章提到過。
function ClassA() {
    this.a = 100;
}

//兩者等同
function ClassA() {
}
//
var obj = new ClassA();
obj.a = 100;

//也就是如果有其他對象要創建的話就如下面一樣
var obj02 = new ClassA();
obj02.a = 100;
var obj03 = new ClassA();
obj03.a = 100;
也就是說類構造函數方式創建的對象,每個對象都有一份所有屬性的拷貝。這個無可厚非,對象的普通屬性值在不同對象會不同,因該是每個對象一份拷貝的。
但如果屬性是一個函數指針呢,也就是說每個對象的方法也都同普通屬性一樣,各自有一份拷貝。這個就顯得不合理,因為函數的具體內容是一樣的,只要存一個地方就行了,多少份拷貝沒有任何區別。
所以使用prototype可以有更加完善的類的方法的寫法。
function ClassA() {
    this.func1 = function() {};        //此方法每個類都有一份拷貝。
}
ClassA.prototype.func2 = function(){};        //此方法存在於類的prototype屬性中,只有一個地方有。

//調用func2的時候,obj對象自身沒有,於是到類的prototype中尋找。
var obj = new ClassA();
obj.func2();

注意這兩種寫法的區別

function ClassA() {
    
}
//第一種方式擴展方法
ClassA.prototype.func1 = function() {};
ClassA.prototype.func2 = function() {};

//第二種,注意此種方法會有一些影響
ClassA.prototype = {
    "func1": function() {},
    "func2": function() {}
};
最關鍵的地方在於前者ClassA.prototype.func1是對ClassA.prototype的func1屬性做操作,字面意思是如果有則修改為,沒有則添加一個func1屬性。
而后者則是將整個ClassA.prototype指向給替換掉了。替換成了一個{}對象了,{}對象是內置Object類的對象。
看這句更好理解ClassA.prototype = new ClassB()。
后者替換了類原始的prototype屬性,后面會提到這種的影響,這個影響應當注意一下,在什么時候會有副作用自己要有所掌控。

constructor

每個js對象都有一個constructor屬性,類也有,因為類也是對象,把類當對象使用的時候跟普通對象沒區別。
constructor指向的是創建自身對象的構造函數,也就是自身的構造器是哪個。之前提過函數是對象的構造器,同時可以當成類來看待。所以constructor也可以理解成指向創建自己的類,也就是對象自己是由哪個類創建的。
function ClassA() {
}

var obj = new ClassA();
console.log(obj.constructor);     //這里輸出的就是ClassA函數,也就是ClassA類了。

console.log(ClassA.constructor);     //類(函數)也是對象,也有constructor。它的是Function

原型對象和原型鏈

每個對象都有它自己的原型對象。所謂的原型對象就是上面所說的,對象的類(構造函數)的prototype屬性所指的那個對象。
請看下面的例子
function ClassA() {
}
var obj = new ClassA();

//obj的原型對象是什么? 是obj構造函數的prototype屬性所指向的對象

obj.constructor == obj構造函數
obj.constructor.prototype == obj構造函數的prototype屬性所指向的對象 == obj的原型對象

類(函數)也是對象,它也有自己的原型對象。
注意:函數的原型對象不是它的prototype屬性所指的對象。而是它構造函數的prototype所指向的對象。請看下面例子
function ClassA() {
}
//ClassA是對象,它的原型對象是什么?

ClassA.prototype !=  ClassA的原型對象 
ClassA.constructor.prototype == ClassA的原型對象 
函數的構造函數是Function類,因此類的原型對象是Function.prototype所指向的對象。
記住普通對象的原型對象公式:obj.constructor.prototype
原型鏈就很簡單了,就是對象的原型對象也有原型對象,這樣一直向上就是原型鏈的意思了。
當訪問對象的某個屬性時,如果當前對象沒有此屬性,那么就從它的原型對象中去尋找,如果再沒有,就往原型鏈的方向查找每一個原型對象,直到Object.prototype指向的對象(頂層的原型對象了)。再沒有拋錯。
如何從任意一個對象(obj)找到它的祖輩的原型對象?
obj.constructor.prototype.constructor.prototype        //錯誤
obj.constructor.constructor.prototype                  //正確

對象靠內部的__proto__屬性和原型對象關聯。每個對象有個__proto__屬性,此屬性名字前有下划線就表明他是個內部變量,外部不可訪問更加不能修改。

obj.__proto__ == obj.constructor.prototype == obj的原型對象

 

call和apply方法

最簡單,call方法會執行一個函數。但是與簡單的函數執行有點不同,下面會提到。

//最簡單的使用call,調用一個普通的函數
function func1() {
    console.log(func1);
}
func1.call();

call如果帶參數則情況不同,call函數的第一個參數有特殊意義,它會將第一個參數傳給被調用的函數里面,並且將被調函數里面的this指針切換到這個參數。

function func1() {
    console.log(this);    //這種情況下,這里的this指向的是下面的a。因為下面使用func1.call調用並傳遞了第一個參數a。
}
var a = {'b': 100};
func1.call(a);         //func1函數會被執行,並且對象a會被傳遞到func1中,作為func1中this的執行。

如果call沒有第一個參數,則默認傳遞window對象。

看一個稍微復雜點的例子,如果能明白各個方法的調用情況就對call理解了。

function A() {
    this.bb = function(){
        console.log(this);
    };
}

function ClassA() {
    console.log(this);
    this.bb();          //注意ClassA本身沒有bb這個方法。
}

var a = new A();
a.bb.call();         //調用a.bb方法。因為沒有第一個參數,則傳遞window到a.bb方法中,所以a.bb里面的this指針指向的是window
ClassA.call(a);      //調用ClassA函數,ClassA里面的this指針指向的是a對象。所以this有bb方法可以調用。

看下面的例子,因為call可以替換函數內的this對象,則可以達到如下效果。

function A() {
    this.attr = 100;
    this.func = function(){
    };
}

function B() {
    A.call(this);
}

var b = new B();
b.func();
console.log(b.attr);
上面的例子,類B沒有定義任何屬性和方法,但是它創建的對象b卻可以有類A中定義的屬性和方法。
A.call(this)調用的地方是類B內部。此時的this指向執行的是b對象,A.call(this)將a對象傳遞到函數A中,則此時A函數執行過程中的this指針指向了a對象,於是就給a賦值了屬性和方法。
通過這樣可以模擬出繼承的效果,類B繼承自類A。
apply
apply函數和call函數功能一樣只是參數格式不同。以下的代碼效果是一樣的
function func(a, b, c, d) {
}

func.call(obj, 1, 2, 3, 4);
func.apply(obj, [1, 2, 3, 4]);     //第二個參數必須是一個數組。數組里面內容依次是調用func的實參。

除了參數格式外,其他沒啥區別。但是因為參數格式不同,所以apply函數可以實現簡化代碼的作用。例如

function A(a, b, c) {
}

function B(a, b, c) {
    A.apply(this, arguments);   //這里arguments為[1,2,3]。所以就直接傳遞了,不用自己組裝。
}

var a = new B(1, 2, 3);
還有一個妙用也可以簡化代碼。
許多函數被定義成了單個參數形式,Math.max(1,2,3)這樣,使用apply則可以Math.max.apply(null, array)。當你獲得某個數組的引用的時候,不需要講數組通過下標一個個將參數再寫一次了。

new操作符

new用來創建一個對象,要寫面向對象方式的js必須用它。
他最簡單的用法就是new 后面跟一個函數名創建一個對象,如果不使用new關鍵字,那么函數就當做普通的函數對待。
function a(){}

var b = a();    //當做普通函數對待,此時b的值為undefine
var b2 = new a();    //此時b2為一個對象,一個object空對象,沒有什么其他自定義的屬性。

new內部執行的一些操作

1.創建一個簡單的object對象,空的對象就像{}。我們這里把它先叫做aa;
2.設置和原型對象的關聯,上面提過原型對象是函數的prototype屬性所指向的對象。
aa.__proto__ = Class.prototype;    //前面提到過,對象通過__proto__屬性和原型對象關聯。

3.執行constructor函數並且設置aa的constructor屬性。前面提到過constructor指向的是構造函數(類),constructor函數執行過程中,函數內部會創建一個this指針,this指向到第一步創建的對象中。這樣constructor構造函數內部就可以使用this關鍵字了。

constructor.apply(aa, [args]);    //執行apply方法,傳遞第一步的對象為第一個參數,則后面的this就是指向的aa.

4.將第一步創建的aa對象,或者說是this指向的對象返回。如果構建函數本身有返回值,則情況會有所不同。

4.1普通情況下返回第一步創建的對象

function ClassA() {
    this.a = 100;
}
4.2如果構造函數本身有返回值,並且返回的是一個對象。
function ClassA() {
    this.a = 100;
    return {
        "b": 200
    };
}

var obj1 = new ClassA();
var obj2 = ClassA();    //此種情況下,ClassA被當作普通函數執行,它里面的this指針指向是全局的對象window

上面此種情況用new操作符和直接執行函數返回的結果是一樣的。都是{"b": 200}對象。使用new關鍵字的情況,前面幾個步驟創建的對象會拋棄。

4.3構造函數有返回值,但是返回的不是一個對象,而是一個簡單類型或是其他的,則跟沒返回值一樣,返回的還是this指針。
function ClassA() {
    this.a = 100;
    return 200;      //因為是返回的200是簡單類型,所以在使用new ClassA()的情況,此語句可以看做是被忽略了。
}

new關鍵字做的工作看以看似下面的幾條語句

var obj = {};
obj.__proto__ = constructor.prototype;
constructor.apply(obj, [args]);


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM