javascript面向對象的寫法03
js一些基礎知識的說明
prototype
console.log(String.prototype); console.log(Date.prototype); console.log(Object.prototype); function ClassA(name,job,born) { } console.log(ClassA.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.containsObject = function(e) { for (i=0; i<this.length; i++) { if (this[i] == e) { return true; } } return false; };
使用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;
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() {} };
constructor
function ClassA() { } var obj = new ClassA(); console.log(obj.constructor); //這里輸出的就是ClassA函數,也就是ClassA類了。 console.log(ClassA.constructor); //類(函數)也是對象,也有constructor。它的是Function
原型對象和原型鏈
function ClassA() { } var obj = new ClassA(); //obj的原型對象是什么? 是obj構造函數的prototype屬性所指向的對象 obj.constructor == obj構造函數 obj.constructor.prototype == obj構造函數的prototype屬性所指向的對象 == obj的原型對象
function ClassA() { } //ClassA是對象,它的原型對象是什么? ClassA.prototype != ClassA的原型對象 ClassA.constructor.prototype == ClassA的原型對象
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);
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);
new操作符
function a(){} var b = a(); //當做普通函數對待,此時b的值為undefine var b2 = new a(); //此時b2為一個對象,一個object空對象,沒有什么其他自定義的屬性。
new內部執行的一些操作
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; }
function ClassA() { this.a = 100; return { "b": 200 }; } var obj1 = new ClassA(); var obj2 = ClassA(); //此種情況下,ClassA被當作普通函數執行,它里面的this指針指向是全局的對象window
上面此種情況用new操作符和直接執行函數返回的結果是一樣的。都是{"b": 200}對象。使用new關鍵字的情況,前面幾個步驟創建的對象會拋棄。
function ClassA() { this.a = 100; return 200; //因為是返回的200是簡單類型,所以在使用new ClassA()的情況,此語句可以看做是被忽略了。 }
new關鍵字做的工作看以看似下面的幾條語句
var obj = {}; obj.__proto__ = constructor.prototype; constructor.apply(obj, [args]);