javascript
JavaScript中的原型和对象机制
1 對象相關的一些語言特性
1.1 一切皆為對象
JavaScript里所有的東西都是對象. 對象是屬性的集合. 數字, 字符串, 布爾值等原始值是"偽對象", 它們同樣擁有屬性, 但是是在棧上分配并按值傳遞. 而其他的對象是堆上分配并按引用傳遞.
一個很重要的概念是,?函數也是對象, 能夠作為變量的值, 返回值, 參數或者屬性的值. 函數對象特殊的地方是能通過"xxx()"語法執行包含在xxx函數對象內的代碼. 因為這一特殊性, typeof xxx 將會返回function, 但這只是一種便利設施.
1.2 對象的屬性可以動態添加和刪除
//?為foo對象添加bar屬性
foo.bar?=?"foobar";
alert(foo.bar);?//foobar
//?刪除foo對象的bar屬性
delete?foo.bar;
alert(foo.bar);?//undefined
1.3 除了宿主對象, 其它對象皆由構造函數創建
要有對象, 就先要有創建對象的方法.?
在C++/Java等語言, 這個方法就是實例化XXX類的一個實例xxx.
而在JavaScript的世界里實際沒有類的東西, 當然仍然可以用"類"和"實例"等慣用語來描述JavaScript中類似的行為, 但其機制是完全不同的. JavaScript的對象是由構造函數創建的, 每個對象都有constructor屬性表示創建該對象的構造函數:
var?test?=?new?Test();?//?由Test構造函數創建
alert(test.constructor);
var?o?=?{?a:?"hello"?};
//實際相當于
var?o_?=?new?Object();
o_.a?=?"hello";?//?由Object構造函數創建
alert(o.constructor);
構造函數也是對象, 那構造函數是由什么創建? 內建的Function函數:
function?Test(a, b){
????alert(a+b);
}
//?相當于:
Test?=?new?Function(["a",?"b"],?"alert(a+b);");
Function函數又是由什么創建? 實際上Function是本機代碼實現的固有對象. 不過為了一致性, Function也有constructor屬性, 該屬性指向它自己. 接上面的代碼:
/*?輸出?function?Function(){????????????[native?code]
????????}
*/
alert(Test.constructor);
alert(Test.constructor.constructor?===?Test.constructor);?//?true
alert(Test.constructor?===?Object.constructor);?//?true
2 原型prototype
2.1 prototype的概念
prototype是構造函數的一個屬性, 該屬性指向一個對象. 而這個對象將作為該構造函數所創建的所有實例的基引用(base reference),?可以把對象的基引用想像成一個自動創建的隱藏屬性. 當訪問對象的一個屬性時, 首先查找對象本身, 找到則返回; 若不, 則查找基引用指向的對象的屬性(如果還找不到實際上還會沿著原型鏈向上查找, ?直至到根).?只要沒有被覆蓋的話, 對象原型的屬性就能在所有的實例中找到.
原型默認為Object的新實例, 由于仍是對象, 故可以給該對象添加新的屬性:
function?Person(name)?{
????this.name?=?name;
}
//?為?p_obj?增加?sayName?屬性
Person.prototype.sayName?=?function(){
????alert(this.name);
}
var?john?=?new?Person("John");?//?john?的?base?reference指向p_obj
var?eric?=?new?Person("Eric");??//?eric?的?base?reference也是指向p_obj
//?注意sayName代碼中的this將指向實例化后的對象(this綁定)
john.sayName();?//?john對象本身沒有sayName屬性,?于是訪問原型對象p_obj的sayName屬性
eric.sayName();?//?訪問同一個原型對象p_obj的sayName屬性
var?tmp?=?Person.prototype;
tmp.boss?=?"David";
//?于這個運行點,?p_obj已經被修改
//?根據上述屬性訪問流程,?新的修改(boss屬性)能反映到所有的實例,?包括已經創建和即將創建的
alert("John's?boss?is?"?+?john.boss);
alert("Eric's?boss?is?"?+?eric.boss);
//?hisCar和sayCar屬性將增加到john對象而不是p_obj對象..
john.hisCar?=?"Audi";
john.sayCar?=?function(){
????alert(this.name?+?"?has?a?car?of?"?+?this.hisCar);
}
john.sayCar();
//?..因此下一句將錯誤,?因為eric對象本身和原型p_obj都沒有sayName屬性
/*?eric.sayCar();?*/
2.2 原型鏈
除了能修改prototype指向的對象, 還能修改prototype指向哪一個對象, 即為prototype賦予一個不同的對象. 這可以實現一種簡單的繼承:
Superman.prototype.sayHello?=?function(){
????alert("I'm?a?superman.");
}
function?SupermanCan(skill){
????this.skill?=?skill;
}
//?為prototype賦予Superman的實例..
SupermanCan.prototype?=?new?Superman();
//?..再動態添加新的屬性
SupermanCan.prototype.sayMore?=?function(){
????this.sayHello();?//?調用"父類"的方法
????alert("I?can?"?+?this.skill);
}
var?david?=?new?SupermanCan("fly");
//?output:?I'm?a?superman.?I?can?fly
david.sayMore();
如果先實例化出一個對象, 再為構造函數prototype賦予一個不同的對象, 將會: 已經創建的對象的基引用不變, 將來創建的對象的基引用為新的原型對象:
var?f1?=?{echo:?function()?{?alert("sound");?}?};function?Foo()?{};
var?foo?=?new?Foo();?//?foo的基引用指向Object實例
Foo.prototype?=?f1;
/*?未定義,?因為這是"foo對象自己或者基引用指向的對象有echo屬性嗎?"
???而不是"foo對象自己或者Foo.prototype指向的對象有echo屬性嗎?"?*/
alert(foo.echo);
var?foo2?=?new?Foo();?//?foo2的基引用指f1對象
foo2.echo();?//?output:?sound
所有的構造函數的prototype都不能為空, 就是說Superman.prototype = null 會被解釋引擎無視;? 另一方面, Object構造函數也有prototype屬性(該屬性是只讀的, 可以為原型增加屬性,但不能賦予不同的對象), 故因此可以有多層的原型鏈, 但原型鏈的根必定會是Object.prototype . 這意味著給Object.prototype增加屬性影響到所有對象:
Object.prototype.echo?=?function()?{????alert("hello");
}
//?echo屬性將增加到所有對象固有對象和自定義對象
var?arr?=?new?Array();
arr.echo();
Array.echo();
function?ObjCons()????{
????this.dummy?=?"d";
}
var?obj?=?new?ObjCons();
obj.echo();
ObjCons.echo();
3. 構造函數和new的實質
構造函數是一個地地道道的函數, 一個函數之所以能成為構造函數, 是因為new運算符:
function?Test()
{
????alert(this.msg);
}
Test();?//?window
var?test?=?new?Test();?//?undefined.?因為test對象沒有定義msg屬性
二者區別在于如何切入對象: Test() 在某個對象(例子中為window)的上下文上執行代碼, 即this指向這個對象; new Test()創建一個新對象, 并以這個新的對象為上下文(this指向新對象)執行代碼, 然后返回這個新對象.?
假如有個函數:
????var?dummy?=?"have?money";
????this.wish?=?dummy;
????doSomeThing();
????
}
結合以上的所有論述, 可以推測new Test()行為的偽代碼表示為:
??????創建一個新對象temp;
??????temp.constructor = Test;
??????temp.(base reference) = Test.prototype; // 這一句先于代碼體執行, 意味著構造函數里的this.xxx能訪問原型對象的屬性xxx
??????bind: this?=?temp; //?將this綁定到temp對象
??????// 開始執行函數代碼
??????var dummy = "have money";
??????this.wish = dummy; // 為temp對象添加wish屬性
??????doSomeThing();
??????....
??????// 結束執行函數代碼
??????return temp;
這個未必會符合內部的二進制實現, 但卻能很好地解釋了JavaScript的特性.
總結
以上是生活随笔為你收集整理的JavaScript中的原型和对象机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到被熊猫咬什么意思
- 下一篇: .net Json JavaScript