2019/4/7 11:21:33
1 對象相(xiàng)關的(de)一些語言特性
1.1 一切皆爲對象
JavaScript裏所有(yǒu)的(de)東西(xī)都(dōu)是對象. 對象是屬性的(de)集合. 數字, 字符串, 布爾值等原始值是"僞對象", 它們同樣擁有(yǒu)屬性, 但(dàn)是是在棧上(shàng)分(fēn)配并按值傳遞. 而其他(tā)的(de)對象是堆上(shàng)分(fēn)配并按引用(yòng)傳遞.
一個(gè)很重要的(de)概念是, 函數也(yě)是對象, 能(néng)夠作爲變量的(de)值, 返回值, 參數或者屬性的(de)值. 函數對象特殊的(de)地(dì)方是能(néng)通過"xxx()"語法執行(xíng)包含在xxx函數對象内的(de)代碼. 因爲這一特殊性, typeof xxx 将會(huì)返回function, 但(dàn)這隻是一種便利設施.
1.2 對象的(de)屬性可(kě)以動态添加和(hé)删除
var foo = new Object();
// 爲foo對象添加bar屬性
foo.bar = "foobar";
alert(foo.bar); //foobar
// 删除foo對象的(de)bar屬性
delete foo.bar;
alert(foo.bar); //undefined
1.3 除了宿主對象, 其它對象皆由構造函數創建
要有(yǒu)對象, 就先要有(yǒu)創建對象的(de)方法.
在C++/Java等語言, 這個(gè)方法就是實例化XXX類的(de)一個(gè)實例xxx.
而在JavaScript的(de)世界裏實際沒有(yǒu)類的(de)東西(xī), 當然仍然可(kě)以用(yòng)"類"和(hé)"實例"等慣用(yòng)語來描述JavaScript中類似的(de)行(xíng)爲, 但(dàn)其機制(zhì)是完全不同的(de). JavaScript的(de)對象是由構造函數創建的(de), 每個(gè)對象都(dōu)有(yǒu)constructor屬性表示創建該對象的(de)構造函數:
function Test() { this.a = "hello"; }
var test = new Test(); // 由Test構造函數創建
alert(test.constructor);
var o = { a: "hello" };
//實際相(xiàng)當于
var o_ = new Object();
o_.a = "hello"; // 由Object構造函數創建
alert(o.constructor);
構造函數也(yě)是對象, 那構造函數是由什(shén)麽創建? 内建的(de)Function函數:
function Test(a, b)
{
alert(a+b);
}
// 相(xiàng)當于:
Test = new Function(["a", "b"], "alert(a+b);");
Function函數又(yòu)是由什(shén)麽創建? 實際上(shàng)Function是本機代碼實現的(de)固有(yǒu)對象. 不過爲了一緻性, Function也(yě)有(yǒu)constructor屬性, 該屬性指向它自己. 接上(shàng)面的(de)代碼:
/* 輸出 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的(de)概念
prototype是構造函數的(de)一個(gè)屬性, 該屬性指向一個(gè)對象. 而這個(gè)對象将作爲該構造函數所創建的(de)所有(yǒu)實例的(de)基引用(yòng)(base reference), 可(kě)以把對象的(de)基引用(yòng)想像成一個(gè)自動創建的(de)隐藏屬性. 當訪問對象的(de)一個(gè)屬性時, 首先查找對象本身, 找到(dào)則返回; 若不, 則查找基引用(yòng)指向的(de)對象的(de)屬性(如(rú)果還找不到(dào)實際上(shàng)還會(huì)沿著(zhe)原型鏈向上(shàng)查找, 直至到(dào)根). 隻要沒有(yǒu)被覆蓋的(de)話(huà), 對象原型的(de)屬性就能(néng)在所有(yǒu)的(de)實例中找到(dào).
原型默認爲Object的(de)新實例, 由于仍是對象, 故可(kě)以給該對象添加新的(de)屬性:
// prototype默認爲new Object(); 爲了方便, 記爲p_obj
function Person(name) {
this.name = name;
}
// 爲 p_obj 增加 sayName 屬性
Person.prototype.sayName = function(){
alert(this.name);
}
var john = new Person("John"); // john 的(de) base reference指向p_obj
var eric = new Person("Eric"); // eric 的(de) base reference也(yě)是指向p_obj
// 注意sayName代碼中的(de)this将指向實例化後的(de)對象(this綁定)
john.sayName(); // john對象本身沒有(yǒu)sayName屬性, 于是訪問原型對象p_obj的(de)sayName屬性
eric.sayName(); // 訪問同一個(gè)原型對象p_obj的(de)sayName屬性
var tmp = Person.prototype;
tmp.boss = "David";
// 于這個(gè)運行(xíng)點, p_obj已經被修改
// 根據上(shàng)述屬性訪問流程, 新的(de)修改(boss屬性)能(néng)反映到(dào)所有(yǒu)的(de)實例, 包括已經創建和(hé)即将創建的(de)
alert("John's boss is " + john.boss);
alert("Eric's boss is " + eric.boss);
// hisCar和(hé)sayCar屬性将增加到(dào)john對象而不是p_obj對象..
john.hisCar = "Audi";
john.sayCar = function(){
alert(this.name + " has a car of " + this.hisCar);
}
john.sayCar();
// ..因此下一句将錯誤, 因爲eric對象本身和(hé)原型p_obj都(dōu)沒有(yǒu)sayName屬性
/* eric.sayCar(); */
2.2 原型鏈
除了能(néng)修改prototype指向的(de)對象, 還能(néng)修改prototype指向哪一個(gè)對象, 即爲prototype賦予一個(gè)不同的(de)對象. 這可(kě)以實現一種簡單的(de)繼承:
function Superman() {}
Superman.prototype.sayHello = function(){
alert("I'm a superman.");
}
function SupermanCan(skill){
this.skill = skill;
}
// 爲prototype賦予Superman的(de)實例..
SupermanCan.prototype = new Superman();
// ..再動态添加新的(de)屬性
SupermanCan.prototype.sayMore = function(){
this.sayHello(); // 調用(yòng)"父類"的(de)方法
alert("I can " + this.skill);
}
var david = new SupermanCan("fly");
// output: I'm a superman. I can fly
david.sayMore();
如(rú)果先實例化出一個(gè)對象, 再爲構造函數prototype賦予一個(gè)不同的(de)對象, 将會(huì): 已經創建的(de)對象的(de)基引用(yòng)不變, 将來創建的(de)對象的(de)基引用(yòng)爲新的(de)原型對象:
var f1 = {echo: function() { alert("sound"); } };
function Foo() {};
var foo = new Foo(); // foo的(de)基引用(yòng)指向Object實例
Foo.prototype = f1;
/* 未定義, 因爲這是"foo對象自己或者基引用(yòng)指向的(de)對象有(yǒu)echo屬性嗎?"
而不是"foo對象自己或者Foo.prototype指向的(de)對象有(yǒu)echo屬性嗎?" */
alert(foo.echo);
var foo2 = new Foo(); // foo2的(de)基引用(yòng)指f1對象
foo2.echo(); // output: sound
所有(yǒu)的(de)構造函數的(de)prototype都(dōu)不能(néng)爲空, 就是說Superman.prototype = null 會(huì)被解釋引擎無視; 另一方面, Object構造函數也(yě)有(yǒu)prototype屬性(該屬性是隻讀(dú)的(de), 可(kě)以爲原型增加屬性,但(dàn)不能(néng)賦予不同的(de)對象), 故因此可(kě)以有(yǒu)多層的(de)原型鏈, 但(dàn)原型鏈的(de)根必定會(huì)是Object.prototype . 這意味著(zhe)給Object.prototype增加屬性影響到(dào)所有(yǒu)對象:
Object.prototype.echo = function() {
alert("hello");
}
// echo屬性将增加到(dào)所有(yǒu)對象固有(yǒu)對象和(hé)自定義對象
var arr = new Array();
arr.echo();
Array.echo();
function ObjCons() {
this.dummy = "d";
}
var obj = new ObjCons();
obj.echo();
ObjCons.echo();
3. 構造函數和(hé)new的(de)實質
構造函數是一個(gè)地(dì)地(dì)道道的(de)函數, 一個(gè)函數之所以能(néng)成爲構造函數, 是因爲new運算符:
this.msg = "window";
function Test()
{
alert(this.msg);
}
Test(); // window
var test = new Test(); // undefined. 因爲test對象沒有(yǒu)定義msg屬性
二者區别在于如(rú)何切入對象: Test() 在某個(gè)對象(例子中爲window)的(de)上(shàng)下文(wén)上(shàng)執行(xíng)代碼, 即this指向這個(gè)對象; new Test()創建一個(gè)新對象, 并以這個(gè)新的(de)對象爲上(shàng)下文(wén)(this指向新對象)執行(xíng)代碼, 然後返回這個(gè)新對象.
假如(rú)有(yǒu)個(gè)函數:
function Test() {
var dummy = "have money";
this.wish = dummy;
doSomeThing();
}
結合以上(shàng)的(de)所有(yǒu)論述, 可(kě)以推測new Test()行(xíng)爲的(de)僞代碼表示爲:
創建一個(gè)新對象temp;
temp.constructor = Test;
temp.(base reference) = Test.prototype; // 這一句先于代碼體執行(xíng), 意味著(zhe)構造函數裏的(de)this.xxx能(néng)訪問原型對象的(de)屬性xxx
bind: this = temp; // 将this綁定到(dào)temp對象
// 開(kāi)始執行(xíng)函數代碼
var dummy = "have money";
this.wish = dummy; // 爲temp對象添加wish屬性
doSomeThing();
....
// 結束執行(xíng)函數代碼
return temp;
這個(gè)未必會(huì)符合内部的(de)二進制(zhì)實現, 但(dàn)卻能(néng)很好地(dì)解釋了JavaScript的(de)特性.
深圳市南山區南山街(jiē)道南海(hǎi)大(dà)道西(xī)桂廟路(lù)北陽光(guāng)華藝大(dà)廈1棟4F、4G-04
咨詢電話(huà):136 8237 6272
大(dà)客戶咨詢:139 0290 5075
業(yè)務QQ:195006118
技術(shù)QQ:179981967