About
隨著學(xué)習(xí)的深入,有時(shí)候不滿足僅僅看書看教程,喜歡在一些細(xì)節(jié)上鉆研,今天花時(shí)間研究了下JavaScript的new命令與對象深拷貝,研究所得記錄如下,如果有不正確的地方還請大佬指正,謝謝。
一、new命令
之前團(tuán)隊(duì)leader問我new命令的工作原理,當(dāng)時(shí)我就懵了,下來花時(shí)間看了阮一峰大神的ES5教程,終于弄懂了這個(gè)問題,現(xiàn)將感悟記錄如下
1. new命令工作原理
當(dāng)調(diào)用構(gòu)造函數(shù)時(shí)使用new命令,new命令會(huì)做如下操作:
- 創(chuàng)建一個(gè)空對象,作為將要返回的對象實(shí)例。
- 將這個(gè)空對象的原型
__proto__,指向構(gòu)造函數(shù)的prototype屬性。 - 將這個(gè)空對象賦值給函數(shù)內(nèi)部的
this關(guān)鍵字。 - 開始執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼。
2. 使用new命令應(yīng)該注意的地方
- 本來
new命令會(huì)產(chǎn)生一個(gè)新的對象,并讓構(gòu)造函數(shù)操作這個(gè)新的對象,并返回這個(gè)新的對象,所以構(gòu)造函數(shù)中不需要使用return語句,但是如果構(gòu)造函數(shù)中有return語句,并且return的是個(gè)對象,那么執(zhí)行了構(gòu)造函數(shù)就會(huì)返回return后面的對象而非新產(chǎn)生的對象,如果return后面是一個(gè)語句,那么new會(huì)忽略它而返回新的對象。 - 如果調(diào)用構(gòu)造函數(shù)時(shí)忘記使用
new命令,那么構(gòu)造函數(shù)就會(huì)對執(zhí)行構(gòu)造函數(shù)時(shí)的執(zhí)行上下文對象進(jìn)行操作。所以使用new是必要的。
3. 如何避免忘記使用new命令
我們可以通過使用嚴(yán)格模式來避免犯錯(cuò),但是一般情況下,我們也可以通過在聲明構(gòu)造函數(shù)時(shí)加入一些容錯(cuò)機(jī)制來避免忘記使用new造成的不良后果。
function Constructor(param) {
if ( new.target === Constructor){ // 判斷出使用了new命令,那么執(zhí)行構(gòu)造函數(shù)
this.name = dog
this.age = 24
} else { // 如果沒有使用new命令,那么重新調(diào)用構(gòu)造函數(shù)并且使用new命令
return new Constructor(param)
}
}
當(dāng)然也可以判斷if (this instanceOf Constructor) 。
二、深拷貝
眾所周知JavaScript中對復(fù)雜數(shù)據(jù)類型是采用引用的方式,即變量內(nèi)存儲(chǔ)的是數(shù)據(jù)的內(nèi)存地址而非真正的數(shù)據(jù),所以一旦修改了數(shù)據(jù),所有引用該數(shù)據(jù)的變量都會(huì)受影響。
淺拷貝的意思就是只拷貝第一層數(shù)據(jù),如果該數(shù)據(jù)還有引用型數(shù)據(jù),那么它仍舊不是完全獨(dú)立的,當(dāng)其引用的數(shù)據(jù)被修改,它仍舊會(huì)受到影響,比如:
a = [1,2,3,[4,5,6]]
b = [...a] // 對a進(jìn)行淺拷貝 b = [1,2,3,[4,5,6]]
b[0] = 100
console.log(a[0]) // 1 說明b數(shù)組的第一層已經(jīng)脫離了a數(shù)組
b[3][0] = 100
console.log(a[3][0]) // 100 說明b數(shù)組的第二層仍未脫離a數(shù)組,表示這并非真正的拷貝
1. 深拷貝需要注意的問題
從博客上看到其他作者寫的一些深拷貝的方法,一般都會(huì)有一些不完善的地方,比如:
1. 1 無法拷貝對象的構(gòu)造函數(shù)的prototype上的屬性
一般情況下,我們需要深拷貝對象時(shí)都是希望能夠完全復(fù)制一個(gè)對象,所以無論是原型屬性還是實(shí)例屬性我們需要完全復(fù)制,例如:
function Person(){}
Person.prototype.name = 'bing'
man = new Person() // {}
console.log(man.name) // 'bing'
上面的代碼中我們把一個(gè)屬性定義到構(gòu)造函數(shù)的原型上,我們通過實(shí)例能夠訪問到該屬性,但是如果我們不拷貝該屬性,那么這兩個(gè)對象就不是完全一樣的了。例如:
function deepCopy(obj) {
var result = Array.isArray(obj) ? [] : {}
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key]!==null) {
result[key] = deepCopy(obj[key]); //遞歸復(fù)制
} else {
result[key] = obj[key];
}
}
}
return result;
}
b = deepCopy(man)
console.log(b.name) // undefined
1.2 如果對象的某一屬性是對象本身
如果對象的某一屬性是對象本身,那么會(huì)造成無限循環(huán),例如:
b = {
'name': 'bing'
}
b.self = b
c = deepCopy(b) // Uncaught RangeError: Maximum call stack size exceeded
1.3 注意某些特殊對象
我們還應(yīng)該注意一些特殊的對象,比如:RegExp和Date
a = new Regexp(/a/i)
b = deepCopy(a)
console.log(a) // /a/i
console.log(b) // /(?:)/
1.4 注意某些不可枚舉的屬性
我們知道使用for...in是會(huì)跳過不可枚舉的屬性的,這樣會(huì)導(dǎo)致如果某些不可枚舉的屬性不能被拷貝致使目標(biāo)對象和源對象不一致
const a = new Object()
Object.defineProperty(a, 'name', {value: 'bing', enumerable: false})
a.age = 23
for (let i in a) { console.log(`${i}: ${a[i]}`)} // age: 23
通過上述代碼可以發(fā)現(xiàn)屬性name沒有被遍歷到,所以我們需要使用Object.getOwnPropertyNames()方法來獲取全部的key
1.5 注意symbol屬性
同樣,使用for...in也無法遍歷symbol屬性,所以我們需要使用Object.getOwnPropertySymbols()來獲取全部的symbol屬性
const a = new Object()
a.age = 23
sym = Symbol('foo')
a[sym] = 123
for (let i in a) { console.log(`${i}: ${a[i]}`)} // age: 23
1.6 注意拷貝屬性描述對象
假設(shè)源對象中某一屬性是禁止配置的,或者是不可枚舉的,如果我們只拷貝了屬性的value而沒有拷貝其屬性描述對象,那么目標(biāo)對象中該屬性的屬性描述對象會(huì)變成一個(gè)默認(rèn)的對象,從而導(dǎo)致目標(biāo)對象與源對象不同。
const a = new Object()
Object.defineProperty(a, 'name', {value: 'bing', enumerable: false})
const b = new Object()
b.name = a.name
Object.getOwnPropertyDescriptor(a,'name')
// {value: "bing", writable: false, enumerable: false, configurable: false}
Object.getOwnPropertyDescriptor(b,'name')
// {value: "bing", writable: true, enumerable: true, configurable: true}
通過上面的代碼我們可以發(fā)現(xiàn)通過賦值的辦法,name的屬性描述對象并沒有被拷貝,所以這里應(yīng)該采用Object.defineProperty()方法。
1.7 注意傳入的參數(shù)不是對象類型
如果傳入的不是對象這種復(fù)雜數(shù)據(jù)類型,我們應(yīng)該直接返回,因?yàn)楹唵螖?shù)據(jù)類型并非引用型。
三、較為完善的深拷貝算法
function deepCopy(obj) {
if (obj instanceof Date) { return new Date(obj) } // 解決特殊對象的拷貝
if (obj instanceof RegExp) { return new RegExp(obj)}
let result = new obj.constructor() // 解決無法拷貝原型
for (let key of Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj))) {
if (obj[key] !== obj) { // 解決無限循環(huán)
if (typeof obj[key] === 'object' && obj[key] !== null) {
result[key] = deepCopy(obj[key]); //遞歸復(fù)制
} else {
Object.defineProperty(result, key, Object.getOwnPropertyDescriptor(obj, key))
}
}
}
return result;
}
另外,如果我們非常了解被拷貝的對象,并且知道該對象中無特殊Unicode碼點(diǎn),函數(shù),以及正則表達(dá)式,我們也可以通過該方法:
function deepCopy (obj) {
return JSON.parse(JSON.stringify(obj))
}
結(jié)束語
本文可能會(huì)有一些不嚴(yán)謹(jǐn)?shù)牡胤?,因?yàn)樽髡弑救瞬攀鑼W(xué)淺,還請各位大佬批評指正,謝謝。