以下筆記參考自《你不知道的JavaScript上卷》
一.原型
1.[[Prototype]]
是什么?
- 它是
JavaScript
的對(duì)象的一個(gè)特殊的內(nèi)置屬性,其實(shí)就是順著原型鏈引用其他的對(duì)象。
2.Object.create()
方法
①該方法會(huì)創(chuàng)建一個(gè)對(duì)象反惕,并把這個(gè)對(duì)象(obj)
的[[Prototype]]
關(guān)聯(lián)到指定對(duì)象(person)
。
var person = {
name: 'Gerg'
};
var obj = Object.create(person);
//__proto__只是[[Prototype]]內(nèi)置屬性的一種表現(xiàn)形式,但非標(biāo)準(zhǔn)形式
obj.__proto__ === person;
>>>true
obj.name;
>>>"Gerg"
②現(xiàn)在obj
對(duì)象的[[Prototype]]
關(guān)聯(lián)到了person
锰蓬。雖然obj.a
并不存在,但是屬性訪問(wèn)仍能成功地(在person
中)找到了該屬性值"Gerg"
眯漩。
③如果person
中也找不到name
屬性芹扭,并且[[Prototype]]
不為空的話,就會(huì)繼續(xù)查找下去赦抖。
④這個(gè)過(guò)程會(huì)持續(xù)找到匹配的屬性名為止舱卡,或者查找完整條[[Prototype]]
鏈。如果是后者队萤,[[Get]]
操作的返回值就是undefined
轮锥。
⑤for...in
遍歷對(duì)象的原理與[[Prototype]]
鏈相似,任何可以通過(guò)原型鏈訪問(wèn)到(并且是可枚舉的要尔,Enumerable: true;
)的屬性都會(huì)被枚舉舍杜。使用in
操作符檢查屬性是否在對(duì)象存在時(shí)新娜,同樣會(huì)查找對(duì)象的整條原型鏈(無(wú)論屬性可枚舉與否)。
var person = {
name: 'Gerg'
};
var obj = Object.create(person);
Object.defineProperty(obj,'age',{
configurable: true,
enumerable: false,
writable: true,
value: 21
});
for(var v in obj){
console.log('found: ' + v);
}
>>>found: name
'name' in obj;
>>>true
'age' in obj;
>>>true
3.屬性設(shè)置
//準(zhǔn)備
var person = {};
var obj = Object.create(person);
//完整解析以下過(guò)程:
obj.name = 'asan';
①
var person = {};
var obj = Object.create(person);
obj.name = 'asan';
obj.name;
>>>"asan"
person.name;
>>>undefined
- 如果
name
屬性不是直接存在于obj
對(duì)象中既绩,[[Prototype]]
鏈就會(huì)向上遍歷概龄,類似[[Get]]
操作。如果整條原型鏈都找不到name
屬性饲握,name
屬性就會(huì)被直接添加到obj
對(duì)象上旁钧。
②
var person = {
name: 'Gerg'
};
var obj = Object.create(person);
obj.name;
>>>"Gerg"
obj.name = 'asan';
obj.name;
>>>"asan"
person.name;
>>>"Gerg"
- 如果
obj
對(duì)象中包含了名為name
的普通數(shù)據(jù)訪問(wèn)屬性(即通過(guò)原型鏈可訪問(wèn)到name
屬性),這條賦值語(yǔ)句只會(huì)修改已有的屬性互拾。
③
-
情況一:
[[Prototype]]
鏈上層存在名為name
的普通數(shù)據(jù)訪問(wèn)屬性歪今,并且沒(méi)有被標(biāo)記為只讀(也就是writable: true;
),那么當(dāng)在obj
對(duì)象上添加一個(gè)名為name
的新屬性颜矿,會(huì)屏蔽掉原來(lái)[[Prototype]]
上的同名屬性寄猩。
var person = {
};
var obj = Object.create(person);
var higherObj = Object.create(obj);
Object.defineProperty(higherObj,'name',{
writable: true,
value: 'pangzi'
});
obj.name = 'asan';
obj.name;
>>>"asan"
-
情況二:如果
[[Prototype]]
鏈上層存在name
屬性,但是它被標(biāo)記為只讀(writable: false;
)骑疆,那么無(wú)法修改已有屬性田篇,或者說(shuō)無(wú)法在obj
對(duì)象上創(chuàng)建屏蔽屬性。在嚴(yán)格模式下箍铭,代碼會(huì)出錯(cuò)泊柬。否則,這條賦值語(yǔ)句會(huì)被忽略诈火。
var person = {
};
var obj = Object.create(person);
Object.defineProperty(obj,'name',{
writable: false,
value: 'Gerg'
});
obj.name = 'asan';
obj.name;
>>>"Gerg"
-
情況三:如果在
[[Prototype]]
鏈上層存在name
屬性兽赁,并且它是一個(gè)setter
,那么就一定會(huì)調(diào)用這個(gè)setter
冷守。name
屬性不會(huì)被添加到(或者屏蔽)obj
對(duì)象刀崖,也不會(huì)重新定義name
這個(gè)setter
。
④
- 如果屬性名
name
既出現(xiàn)在obj
對(duì)象中拍摇,也出現(xiàn)在obj
的[[Prototype]]
鏈上層亮钦,那么又會(huì)發(fā)生屏蔽(obj
對(duì)象中的name
屬性屏蔽了higherObj
對(duì)象中的同名屬性),因?yàn)?code>obj.name總是選擇原型鏈中最底層的name
屬性充活。
var person = {
name: 'Gerg'
};
var obj = Object.create(person);
obj.name = 'qin';
var higherObj = Object.create(obj);
higherObj.name = 'pangzi';
obj.name = 'asan';
obj.name;
>>>"asan"
4.隱式屏蔽
var person = {
name: 'Gerg'
};
var obj = Object.create(person);
person.name;
>>>"Gerg"
obj.name;
>>>"Gerg"
person.hasOwnProperty('name');
>>>true
obj.hasOwnProperty('name');
>>>false
obj.name = 'asan';
obj.name;
>>>"asan"
person.name;
>>>"Gerg"
obj.hasOwnProperty('name');
>>>true
obj.name = 'asan';
首選會(huì)通過(guò)[[Prototype]]
查找屬性name
(在person
對(duì)象中找到同名屬性)蜂莉,用屬性值'asan'
將原來(lái)的'Gerg'
覆蓋掉,接著用[[Put]]
將'asan'
賦值給obj
中新創(chuàng)建的屏蔽屬性name
混卵。
5."類"
①繼承意味著復(fù)制操作映穗,JavaScript
(默認(rèn))并不會(huì)復(fù)制對(duì)象屬性。相反淮菠,JavaScript
會(huì)在兩個(gè)對(duì)象之間創(chuàng)建一個(gè)關(guān)聯(lián)男公,這樣以來(lái)荤堪,一個(gè)對(duì)象就可以通過(guò)委托訪問(wèn)另一個(gè)對(duì)象的屬性和函數(shù)合陵。
②委托這個(gè)術(shù)語(yǔ)更為精確地描述 JavaScript
中對(duì)象的關(guān)聯(lián)機(jī)制枢赔。
③new
會(huì)劫持所有普通函數(shù)并用構(gòu)造對(duì)象的形式來(lái)調(diào)用它。
④
function nothingSpecial(){
}
var obj = new nothingSpecial();
obj;
>>>nothingSpecial {}
-
nothingSpecial
只是個(gè)普通的函數(shù)拥知,當(dāng)它被new
操作符調(diào)用時(shí)踏拜,它就會(huì)構(gòu)造一個(gè)函數(shù)并賦值給obj
。
⑤JavaScript
中的構(gòu)造函數(shù):帶new
操作符的函數(shù)調(diào)用低剔。
⑥
function Foo(name){
this.fooName = name;
}
Foo.prototype.myName = function(){
return this.fooName;
};
var a = new Foo('a');
var b = new Foo('b');
a.myName();
>>>"a"
b.myName();
>>>"b"
a.__proto__ === Foo.prototype;
>>>true
6.回顧"構(gòu)造函數(shù)"
①一個(gè)普遍而又感覺(jué)理所應(yīng)當(dāng)?shù)?strong>錯(cuò)覺(jué):a.constructor === Foo;
意味著對(duì)象a
確實(shí)有個(gè)constructor
屬性指向Foo
速梗。
②其實(shí)這只不過(guò)是一種虛假的安全感而已。何以證明襟齿?
function Foo(){};
Foo.prototype = {};
var a = new Foo();
a.constructor === Foo;
>>>false
③那么真相到底是怎么樣的呢姻锁?
-
Foo.prototype
中的constructor
屬性只是Foo
函數(shù)在聲明時(shí)的默認(rèn)屬性。如果在聲明之后猜欺,你創(chuàng)建了一個(gè)新對(duì)象并替換了函數(shù)默認(rèn)的prototype
對(duì)象引用位隶,那么新對(duì)象就不會(huì)自動(dòng)獲得constructor
屬性。
④所以a.constructor === Foo;
整個(gè)過(guò)程應(yīng)該是這樣的:
a.__proto__ === Foo.prototype;//true
Foo.prototype.constructor === Foo;//true
⑤More:
function Foo(){};
Foo.prototype = {};
var a = new Foo();
a.constructor === Foo;
>>>false
a.constructor === Object;
>>>true
- 這是為啥开皿?
對(duì)象
a
中并沒(méi)有constructor
屬性涧黄,所以它會(huì)委托[[Prototype]]
鏈上的Foo.prototype
對(duì)象。但是這個(gè)對(duì)象中也沒(méi)有constructor
屬性(不過(guò)赋荆,默認(rèn)的Foo.prototype
對(duì)象中有個(gè)屬性K裢住),所以它會(huì)繼續(xù)向上委托窄潭,這次會(huì)委托給委托鏈頂端的Object.prototype
春宣。這個(gè)對(duì)象由constructor
屬性,所以指向內(nèi)置的Object
函數(shù)嫉你。
⑥修改上圖的錯(cuò)誤
⑦如果你閑的慌信认,當(dāng)然可以人工為Foo.prototype
添加一個(gè)constructor
屬性。
function Foo(){};
Foo.prototype = {};
var a = new Foo();
Object.defineProperty(Foo.prototype,'constructor',{
configurable: true,
//注意constructor屬性在默認(rèn)情況下是不可枚舉的
enumerable: false,
writable: true,
value: Foo
});
a.constructor === Foo;
>>>true
7.(原型)繼承
①兩種把Bar.prototype
關(guān)聯(lián)到Foo.prototype
的方法:
//ES6之前需要拋棄默認(rèn)的Bar.prototype
Bar.prototype = Object.create(Foo.prototype);
//ES6開(kāi)始可以直接修改現(xiàn)有的Bar.prototype
Object.setPrototypeOf(Bar.prototype,Foo.prototype);