一、創(chuàng)建對(duì)象有幾種方法
請(qǐng)大家盡可能多的找到創(chuàng)建對(duì)象的方法,如有補(bǔ)充歡迎在評(píng)論區(qū)留言討論。更多參考詳見:JavaScript創(chuàng)建對(duì)象的7種方法
第一種方式:對(duì)象字面量表示法
var o1 = {
name: 'o1'
};
var o2 = new Object({
name: 'o2'
});
缺點(diǎn):字面量對(duì)象中默認(rèn)原型鏈指向Object,用同一個(gè)接口創(chuàng)建很多對(duì)象會(huì)產(chǎn)生大量冗余代碼。
第二種方式:使用顯式構(gòu)造函數(shù)
var M = function (name) {
this.name = name;
};
var o3 = new M('o3');
第三種方式:Object.create
var P = {
name: 'p'
};
var o4 = Object.create(P);
那么,在控制臺(tái)輸入o1, o2, o3, o4,會(huì)發(fā)生什么?
-
o1,o2輸出 Object 對(duì)象{ name: 'o1' }、{ name: 'o2' } -
o3輸出 M 對(duì)象{ name: 'o3' } -
o4是個(gè)空對(duì)象{} -
o1,o2,o3,o4均有name屬性
二、原型系統(tǒng)的“復(fù)制操作”的實(shí)現(xiàn)思路
淺拷貝
并不是真的去復(fù)制一個(gè)原型對(duì)象,而是使得新對(duì)象持有一個(gè)原型的引用;
深拷貝
另一個(gè)是切實(shí)地復(fù)制對(duì)象,從此兩個(gè)對(duì)象再無(wú)關(guān)聯(lián)。
三、理解原型、構(gòu)造函數(shù)、實(shí)例、原型鏈
原型
JavaScript 是基于原型的編程語(yǔ)言的代表,它利用原型來(lái)描述對(duì)象。JavaScript 并非第一個(gè)使用原型的語(yǔ)言,在它之前,self、kevo 等語(yǔ)言已經(jīng)開始使用原型來(lái)描述對(duì)象了。 Brendan 最初的構(gòu)想是將 JavaScript 設(shè)計(jì)成一個(gè)擁有基于原型的面向?qū)ο竽芰Φ?scheme 語(yǔ)言,而基于原型系統(tǒng)的獨(dú)特之處是提倡運(yùn)行時(shí)的原型修改。
對(duì)象可以有兩種成員類型:
- 實(shí)例成員:直接存在于對(duì)象實(shí)例中
- 原型成員:從對(duì)象原型繼承
”基于類“和”基于原型“的編程之間的比較:
| 基于類 | 基于原型 |
|---|---|
| 提倡使用一個(gè)關(guān)注分類和類之間關(guān)系的開發(fā)模型 | 提倡編碼人員關(guān)注一系列對(duì)象實(shí)例的行為 |
| 先有類,再?gòu)念惾?shí)例化一個(gè)對(duì)象 | 將對(duì)象劃分到最近的使用方式相似的原型對(duì)象 |
| 類之間的關(guān)系:繼承、組合等 | 通過(guò)復(fù)制的方式創(chuàng)建新對(duì)象 |
| 往往與語(yǔ)言的類型系統(tǒng)整合,形成一定編譯時(shí)的能力 | 一些語(yǔ)言中,復(fù)制一個(gè)空對(duì)象,就是創(chuàng)建一個(gè)全新的對(duì)象 |
JavaScript的原型:
拋開JavaScript模擬Java類的復(fù)雜語(yǔ)法,如 new、Function Object、函數(shù)的 prototype 屬性等。其原型系統(tǒng)可以用以下兩條概括:
- 如果所有對(duì)象都有私有字段 [[prototype]],就是對(duì)象的原型;
- 讀一個(gè)屬性,如果對(duì)象本身沒有,則會(huì)繼續(xù)訪問(wèn)對(duì)象的原型,直到原型為空或者找到為止。
判斷對(duì)象是否包含特定的實(shí)例成員:hasOwnProperty("成員的名稱")
確定對(duì)象是否包含特定的屬性:使用in操作符(既搜索實(shí)例又搜索原型)
ES6中,JavaScript提供能直接訪問(wèn)操縱原型的三個(gè)方法:
-
Object.create根據(jù)指定的原型創(chuàng)建新對(duì)象,原型可以是null -
Object.getPrototypeOf獲得一個(gè)對(duì)象的原型 -
Object.setPrototypeOf設(shè)置一個(gè)對(duì)象的原型
利用以上三個(gè)方法,我們可以完全拋開類的思維,利用原型來(lái)實(shí)現(xiàn)抽象和復(fù)用。
var cat = {
say(){
console.log("miao~miao~");
},
play(){
console.log("ball")
},
}
//布偶貓
var Ragdoll = Object.create(cat, {
say: {
writable: true,
configurable: true,
enumerable: true,
value: function () {
console.log("iao~iao~")
}
}
})
var anotherCat = Object.create(cat);
anotherCat.say(); //miao~miao~
var anotherRagdollCat = Object.create(Ragdoll);
Ragdoll.say(); //iao~iao~
構(gòu)造函數(shù)和實(shí)例
實(shí)例:對(duì)象就是一個(gè)實(shí)例。
構(gòu)造函數(shù):任何一個(gè)函數(shù)只要被new操作了,該函數(shù)就可以被叫做構(gòu)造函數(shù)。

上圖對(duì)應(yīng)關(guān)系闡述:
函數(shù)都有prototype屬性,指的就是原型對(duì)象。(聲明一個(gè)函數(shù)時(shí)JS引擎會(huì)為這個(gè)構(gòu)造函數(shù)自動(dòng)添加prototype屬性,該屬性會(huì)初始化一個(gè)空對(duì)象,也就是原型對(duì)象。)
-
原型對(duì)象怎么區(qū)分出被哪一個(gè)構(gòu)造函數(shù)所引用?
原型對(duì)象中有一個(gè)構(gòu)造器constructor,會(huì)默認(rèn)聲明的函數(shù),即通過(guò)constructor來(lái)確定是被哪一個(gè)構(gòu)造函數(shù)引用。
上圖工作原理代碼演示:
上面的例子中o3是實(shí)例,M是構(gòu)造函數(shù)
//構(gòu)造函數(shù)和原型對(duì)象的關(guān)系
M.prototype.constructor === M //true
//實(shí)例和原型對(duì)象之間的關(guān)系
o3.__proto__ === M.prototype //true
原型鏈
原型鏈就是js中數(shù)據(jù)繼承的繼承鏈。在訪問(wèn)一個(gè)實(shí)例的屬性的時(shí)候,先在實(shí)例本身中找,如果沒找到,再?gòu)倪@個(gè)實(shí)例對(duì)象向上找構(gòu)造這個(gè)實(shí)例的相關(guān)聯(lián)的對(duì)象,還沒找到就再往上找,這個(gè)相關(guān)聯(lián)的對(duì)象又有創(chuàng)造它的上一級(jí)的原型對(duì)象。以此類推,到Object.prototype終止(整個(gè)原型鏈的頂端),原型鏈通過(guò)prototype和__proto__屬性進(jìn)行向上查找。

原型對(duì)象和原型鏈之間起的作用:
構(gòu)造函數(shù)中增加很多屬性和方法。當(dāng)有多個(gè)實(shí)例,想共用一個(gè)方法,不能每個(gè)實(shí)例都拷貝一份(若每個(gè)實(shí)例都要拷貝一份,會(huì)占內(nèi)存,沒有必要),多個(gè)實(shí)例之間有相同的方法時(shí)要考慮存到共同的東西上,這個(gè)共同的東西就是原型對(duì)象。這就是JS引擎支持的原型鏈的功能,任何一個(gè)實(shí)例對(duì)象通過(guò)原型鏈找到它上面的原型對(duì)象,上面的實(shí)例和方法都可以被共享。
var M = function(name){this.name = name;};
M.prototype.say = function() {
console.log('say hi');
}
var o5 = new M('o5');
控制臺(tái)輸出:
o3.say()
say hi
o5.say()
say hi
注意:
構(gòu)造函數(shù)才會(huì)有prototype,對(duì)象是沒有prototype 的。
-
實(shí)例對(duì)象才有
__proto__屬性。特殊的,函數(shù)即是函數(shù)也是對(duì)象,也有
__proto__屬性:M.__proto__ === Function.prototype //true其他的:
Array.__proto__ === Function.prototype //truelet arr = [1,2,3,4] arr.__proto__ === Array.prototype //trueArray.prototype.__proto__ === Object.prototype //trueObject.prototype.__proto__ === null //true
四、instanceof 原理

原理:實(shí)例對(duì)象的__proto__屬性和構(gòu)造函數(shù)沒什么關(guān)聯(lián),其實(shí)是引用的原型對(duì)象。instanceof用來(lái)判斷實(shí)例對(duì)象的屬性和構(gòu)造函數(shù)的屬性是不是同一個(gè)引用。
原型對(duì)象上可能還會(huì)有原型鏈,用實(shí)例對(duì)象instanceof判斷原型的構(gòu)造函數(shù),這條原型鏈上的函數(shù)返回都是 true:
o3 instanceof M //true
//只要是原型鏈上都可以看作instanceof的構(gòu)造函數(shù)
o3 instanceof Object //true
解釋:
o3.__proto__=== M.prototype //true
M.prototype.__proto___=== Object.prototype //true
//判斷是哪個(gè)構(gòu)造函數(shù)直接生成的,比instanceof更嚴(yán)謹(jǐn)
o3.__proto__.constructor === M //true
o3.__proto__.constructor === Object //false
五、new運(yùn)算符
定義:JavaScript 的 new 運(yùn)算符創(chuàng)建一個(gè)繼承于其運(yùn)算數(shù)的原型的新對(duì)象,然后調(diào)用該運(yùn)算數(shù),把新創(chuàng)建的對(duì)象綁定給this。
按照慣例,打算與 new 結(jié)合使用的函數(shù)命名應(yīng)該首字母大寫,并謹(jǐn)慎使用 new 。
new運(yùn)算接收一個(gè)構(gòu)造器和一組調(diào)用參數(shù),其工作原理為:
new 后面加上構(gòu)造函數(shù)(構(gòu)造器),一個(gè)新對(duì)象被創(chuàng)建,它繼承自構(gòu)造函數(shù)原型對(duì)象Foo.prototype屬性
-
將this和調(diào)用參數(shù)傳給構(gòu)造器:
構(gòu)造函數(shù) Foo 被執(zhí)行的時(shí)候,相應(yīng)的傳參會(huì)被傳入,同時(shí)上下文this 會(huì)被指定為這個(gè)新實(shí)例。(new Foo() 在不傳遞任何參數(shù)的時(shí)候可以寫成 new Foo。)
如果構(gòu)造函數(shù)返回了一個(gè)對(duì)象,那么這個(gè)對(duì)象會(huì)取代整個(gè) new 出來(lái)的結(jié)果。換句話說(shuō),如果構(gòu)造函數(shù)沒有任何返回對(duì)象,那么new出來(lái)的結(jié)果為第一步創(chuàng)建的新對(duì)象;有返回對(duì)象,直接返回。
實(shí)現(xiàn)一個(gè)new運(yùn)算符效果:
var newFunc = function (func) {
//1.創(chuàng)建空對(duì)象,關(guān)聯(lián)指定構(gòu)造函數(shù)原型對(duì)象
var a = Object.create(func.prorotype);
//2.把上下文轉(zhuǎn)移給b對(duì)象
var b = func.call(a);
//3.判斷執(zhí)行之后的結(jié)果是不是對(duì)象類型
if (typeof b === 'object') {
return b;
} else {
return a;
}
}
控制臺(tái)輸出:
o6 = newFunc(M)
o6 instanceof M //true
o6 instanceof Object //true
o6.__proto__.constructor === M //true
M.prototype.walk = function() {
console.log('walk')
}
o6.walk()
walk
o3.walk()
walk
new 這樣的行為,試圖讓函數(shù)對(duì)象在語(yǔ)法上跟類變得相似,但是,它客觀上提供了兩種方式,一是在構(gòu)造器中添加屬性,二是在構(gòu)造器的 prototype 屬性上添加屬性。
用構(gòu)造器模擬類的兩種方法:
//第一種方法:直接在構(gòu)造器中修改this,給this添加屬性
function Cls1(){
this.p1 = 1;
this.p2 = function(){
console.log(this.p1);
}
}
var o1 = new Cls1;
o1.p2();
//第二種方法:修改構(gòu)造器的 prototype 屬性指向的對(duì)象,它是從這個(gè)構(gòu)造器構(gòu)造出來(lái)的所有對(duì)象的原型。
function Cls2(){
}
Cls2.prototype.p1 = 1;
Cls2.prototype.p2 = function(){
console.log(this.p1);
}
var o2 = new Cls2;
o2.p2();
沒有 Object.create、Object.setPrototypeOf 的早期版本中,new 運(yùn)算是唯一一個(gè)可以指定 [[prototype]] 的方法(當(dāng)時(shí)的 mozilla 提供了私有屬性 proto,但是多數(shù)環(huán)境并不支持),所以,當(dāng)時(shí)已經(jīng)有人試圖用它來(lái)代替后來(lái)的 Object.create,我們甚至可以用它來(lái)實(shí)現(xiàn)一個(gè) Object.create 的不完整的 pollyfill,見以下代碼:
Object.create = function(prototype){
var cls = function(){}
cls.prototype = prototype;
return new cls;
}
這段代碼創(chuàng)建了一個(gè)空函數(shù)作為類,并把傳入的原型掛在了它的 prototype上,最后創(chuàng)建了一個(gè)它的實(shí)例,根據(jù) new 的行為,這將產(chǎn)生一個(gè)以傳入的第一個(gè)參數(shù)為原型的對(duì)象。
六、補(bǔ)充問(wèn)題
為什么o4直接拿不到name屬性?
Object.create方法創(chuàng)建的對(duì)象是用原型鏈連接的,當(dāng)JS引擎查找o4是一個(gè)空對(duì)象,這個(gè)空對(duì)象上是沒有name屬性的,name屬性在它的原型對(duì)象上,也就是說(shuō),Object.create方法是把參數(shù)中的對(duì)象作為一個(gè)新對(duì)象的原型對(duì)象賦給o4的,o4本身不具備這個(gè)屬性。
o4.__proto__ === p
true
總結(jié):原型鏈真的很重要,值得反復(fù)理解推敲。