一瘩欺、創(chuàng)建對象有幾種方法
請大家盡可能多的找到創(chuàng)建對象的方法,如有補充歡迎在評論區(qū)留言討論死宣。更多參考詳見:JavaScript創(chuàng)建對象的7種方法
第一種方式:對象字面量表示法
var o1 = {
name: 'o1'
};
var o2 = new Object({
name: 'o2'
});
缺點:字面量對象中默認原型鏈指向Object讼载,用同一個接口創(chuàng)建很多對象會產(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);
那么钙姊,在控制臺輸入o1
, o2
, o3
, o4
,會發(fā)生什么埂伦?
-
o1
,o2
輸出 Object 對象{ name: 'o1' }
煞额、{ name: 'o2' }
-
o3
輸出 M 對象{ name: 'o3' }
-
o4
是個空對象{}
-
o1
,o2
,o3
,o4
均有name屬性
二、原型系統(tǒng)的“復(fù)制操作”的實現(xiàn)思路
淺拷貝
并不是真的去復(fù)制一個原型對象沾谜,而是使得新對象持有一個原型的引用;
深拷貝
另一個是切實地復(fù)制對象膊毁,從此兩個對象再無關(guān)聯(lián)。
三基跑、理解原型婚温、構(gòu)造函數(shù)、實例媳否、原型鏈
原型
JavaScript 是基于原型的編程語言的代表栅螟,它利用原型來描述對象。JavaScript 并非第一個使用原型的語言篱竭,在它之前力图,self、kevo 等語言已經(jīng)開始使用原型來描述對象了掺逼。 Brendan 最初的構(gòu)想是將 JavaScript 設(shè)計成一個擁有基于原型的面向?qū)ο竽芰Φ?scheme 語言吃媒,而基于原型系統(tǒng)的獨特之處是提倡運行時的原型修改。
對象可以有兩種成員類型:
- 實例成員:直接存在于對象實例中
- 原型成員:從對象原型繼承
”基于類“和”基于原型“的編程之間的比較:
基于類 | 基于原型 |
---|---|
提倡使用一個關(guān)注分類和類之間關(guān)系的開發(fā)模型 | 提倡編碼人員關(guān)注一系列對象實例的行為 |
先有類坪圾,再從類去實例化一個對象 | 將對象劃分到最近的使用方式相似的原型對象 |
類之間的關(guān)系:繼承晓折、組合等 | 通過復(fù)制的方式創(chuàng)建新對象 |
往往與語言的類型系統(tǒng)整合,形成一定編譯時的能力 | 一些語言中兽泄,復(fù)制一個空對象漓概,就是創(chuàng)建一個全新的對象 |
JavaScript的原型:
拋開JavaScript模擬Java類的復(fù)雜語法,如 new病梢、Function Object胃珍、函數(shù)的 prototype 屬性等。其原型系統(tǒng)可以用以下兩條概括:
- 如果所有對象都有私有字段 [[prototype]]蜓陌,就是對象的原型觅彰;
- 讀一個屬性,如果對象本身沒有钮热,則會繼續(xù)訪問對象的原型填抬,直到原型為空或者找到為止。
判斷對象是否包含特定的實例成員:hasOwnProperty("成員的名稱")
確定對象是否包含特定的屬性:使用in
操作符(既搜索實例又搜索原型)
ES6中隧期,JavaScript提供能直接訪問操縱原型的三個方法:
-
Object.create
根據(jù)指定的原型創(chuàng)建新對象飒责,原型可以是null
-
Object.getPrototypeOf
獲得一個對象的原型 -
Object.setPrototypeOf
設(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ù)和實例
實例:對象就是一個實例遣臼。
構(gòu)造函數(shù):任何一個函數(shù)只要被new操作了,該函數(shù)就可以被叫做構(gòu)造函數(shù)拾并。
上圖對應(yīng)關(guān)系闡述:
函數(shù)都有prototype屬性揍堰,指的就是原型對象。(聲明一個函數(shù)時JS引擎會為這個構(gòu)造函數(shù)自動添加prototype屬性嗅义,該屬性會初始化一個空對象屏歹,也就是原型對象。)
-
原型對象怎么區(qū)分出被哪一個構(gòu)造函數(shù)所引用芥喇?
原型對象中有一個構(gòu)造器constructor西采,會默認聲明的函數(shù)凰萨,即通過constructor來確定是被哪一個構(gòu)造函數(shù)引用继控。
上圖工作原理代碼演示:
上面的例子中o3
是實例,M
是構(gòu)造函數(shù)
//構(gòu)造函數(shù)和原型對象的關(guān)系
M.prototype.constructor === M //true
//實例和原型對象之間的關(guān)系
o3.__proto__ === M.prototype //true
原型鏈
原型鏈就是js中數(shù)據(jù)繼承的繼承鏈胖眷。在訪問一個實例的屬性的時候武通,先在實例本身中找,如果沒找到珊搀,再從這個實例對象向上找構(gòu)造這個實例的相關(guān)聯(lián)的對象冶忱,還沒找到就再往上找,這個相關(guān)聯(lián)的對象又有創(chuàng)造它的上一級的原型對象境析。以此類推囚枪,到Object.prototype終止(整個原型鏈的頂端),原型鏈通過prototype
和__proto__
屬性進行向上查找劳淆。
原型對象和原型鏈之間起的作用:
構(gòu)造函數(shù)中增加很多屬性和方法链沼。當有多個實例,想共用一個方法沛鸵,不能每個實例都拷貝一份(若每個實例都要拷貝一份括勺,會占內(nèi)存,沒有必要)曲掰,多個實例之間有相同的方法時要考慮存到共同的東西上疾捍,這個共同的東西就是原型對象。這就是JS引擎支持的原型鏈的功能栏妖,任何一個實例對象通過原型鏈找到它上面的原型對象乱豆,上面的實例和方法都可以被共享。
var M = function(name){this.name = name;};
M.prototype.say = function() {
console.log('say hi');
}
var o5 = new M('o5');
控制臺輸出:
o3.say()
say hi
o5.say()
say hi
注意:
構(gòu)造函數(shù)才會有prototype吊趾,對象是沒有prototype 的宛裕。
-
實例對象才有
__proto__
屬性房官。特殊的,函數(shù)即是函數(shù)也是對象续滋,也有
__proto__
屬性:M.__proto__ === Function.prototype //true
其他的:
Array.__proto__ === Function.prototype //true
let arr = [1,2,3,4] arr.__proto__ === Array.prototype //true
Array.prototype.__proto__ === Object.prototype //true
Object.prototype.__proto__ === null //true
四翰守、instanceof 原理
原理:實例對象的__proto__
屬性和構(gòu)造函數(shù)沒什么關(guān)聯(lián),其實是引用的原型對象疲酌。instanceof用來判斷實例對象的屬性和構(gòu)造函數(shù)的屬性是不是同一個引用蜡峰。
原型對象上可能還會有原型鏈,用實例對象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òu)造函數(shù)直接生成的湿颅,比instanceof更嚴謹
o3.__proto__.constructor === M //true
o3.__proto__.constructor === Object //false
五、new運算符
定義:JavaScript 的 new 運算符創(chuàng)建一個繼承于其運算數(shù)的原型的新對象粥诫,然后調(diào)用該運算數(shù)油航,把新創(chuàng)建的對象綁定給this。
按照慣例怀浆,打算與 new 結(jié)合使用的函數(shù)命名應(yīng)該首字母大寫谊囚,并謹慎使用 new 。
new運算接收一個構(gòu)造器和一組調(diào)用參數(shù)执赡,其工作原理為:
new 后面加上構(gòu)造函數(shù)(構(gòu)造器)镰踏,一個新對象被創(chuàng)建,它繼承自構(gòu)造函數(shù)原型對象Foo.prototype屬性
-
將this和調(diào)用參數(shù)傳給構(gòu)造器:
構(gòu)造函數(shù) Foo 被執(zhí)行的時候沙合,相應(yīng)的傳參會被傳入奠伪,同時上下文this 會被指定為這個新實例。(new Foo() 在不傳遞任何參數(shù)的時候可以寫成 new Foo首懈。)
如果構(gòu)造函數(shù)返回了一個對象绊率,那么這個對象會取代整個 new 出來的結(jié)果。換句話說究履,如果構(gòu)造函數(shù)沒有任何返回對象滤否,那么new出來的結(jié)果為第一步創(chuàng)建的新對象;有返回對象挎袜,直接返回顽聂。
實現(xiàn)一個new運算符效果:
var newFunc = function (func) {
//1.創(chuàng)建空對象,關(guān)聯(lián)指定構(gòu)造函數(shù)原型對象
var a = Object.create(func.prorotype);
//2.把上下文轉(zhuǎn)移給b對象
var b = func.call(a);
//3.判斷執(zhí)行之后的結(jié)果是不是對象類型
if (typeof b === 'object') {
return b;
} else {
return a;
}
}
控制臺輸出:
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ù)對象在語法上跟類變得相似紊搪,但是,它客觀上提供了兩種方式全景,一是在構(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 屬性指向的對象滞伟,它是從這個構(gòu)造器構(gòu)造出來的所有對象的原型揭鳞。
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 運算是唯一一個可以指定 [[prototype]] 的方法(當時的 mozilla 提供了私有屬性 proto野崇,但是多數(shù)環(huán)境并不支持),所以亩钟,當時已經(jīng)有人試圖用它來代替后來的 Object.create乓梨,我們甚至可以用它來實現(xiàn)一個 Object.create 的不完整的 pollyfill,見以下代碼:
Object.create = function(prototype){
var cls = function(){}
cls.prototype = prototype;
return new cls;
}
這段代碼創(chuàng)建了一個空函數(shù)作為類清酥,并把傳入的原型掛在了它的 prototype上扶镀,最后創(chuàng)建了一個它的實例,根據(jù) new 的行為焰轻,這將產(chǎn)生一個以傳入的第一個參數(shù)為原型的對象臭觉。
六、補充問題
為什么o4直接拿不到name屬性?
Object.create方法創(chuàng)建的對象是用原型鏈連接的辱志,當JS引擎查找o4是一個空對象蝠筑,這個空對象上是沒有name屬性的,name屬性在它的原型對象上荸频,也就是說菱肖,Object.create方法是把參數(shù)中的對象作為一個新對象的原型對象賦給o4的客冈,o4本身不具備這個屬性旭从。
o4.__proto__ === p
true
總結(jié):原型鏈真的很重要,值得反復(fù)理解推敲场仲。