1、Object.assign
Object.assign方法只會(huì)拷貝源對(duì)象自身的并且可枚舉的屬性到目標(biāo)對(duì)象。注意這里有個(gè)詞叫做可枚舉
Object.assign(target,...source)
1-1、基礎(chǔ)用法
const t1 = {a:1,b:2};
const t2 = {b:3,c:4};
const t3 = {c:5,d:6};
const t4 = Object.assign(t1,t2,t3);
此時(shí)t1和t4是相同的 {a: 1, b: 3, c: 5, d: 6}
t2、t3此時(shí)是不變的
Object.assign會(huì)改變目標(biāo)對(duì)象上的屬性和值,所以我們一般對(duì)對(duì)象進(jìn)行合并一般使用const result = Object.assign({},...source);給予目標(biāo)對(duì)象為一個(gè)空對(duì)象,這樣就可以不用改變已有的值了。
1-2、改變結(jié)果的屬性/屬性值,那么目標(biāo)對(duì)象是否會(huì)改變?
如果此時(shí)我們改變t4上面的屬性值,那么想想t1會(huì)不會(huì)被改變??
const t1 = {a:1,b:2};
const t2 = {b:3,c:4};
const t3 = {c:5,d:6};
const t4 = Object.assign(t1,t2,t3);
t4.d = 100;
console.log(t1)//想想為什么?
console.log(t4)
結(jié)果卻是都一起被改變了,為什么會(huì)被改變?我們知道在程序中,任何一個(gè)變量其實(shí)都被引用了一段16位進(jìn)制的空間地址,在上述程序中其實(shí)t4的引用了t1的空間地址,上述我們可以理解為
const t4 = t1;而t1在Object.assign運(yùn)算中已被改變了
1-3、給其中一個(gè)源對(duì)象增加原型鏈增加屬性/方法
猜想下給其中一個(gè)源對(duì)象增加原型鏈增加屬性/方法,那么會(huì)有什么不同呢?
const t1 = {a:1,b:2};
const t2 = {b:3,c:4};
const t3 = {c:5,d:6};
t2.__proto__.e = 100;
t2.__proto__.print = function(){console.log(this)};
const t4 = Object.assign(t1,t2,t3);
console.log(t1.__proto__);
console.log(t2.__proto__);
console.log(t3.__proto__);
console.log(t4.__proto__);
結(jié)果我們發(fā)現(xiàn)竟然發(fā)現(xiàn)所有的源對(duì)象上的原型鏈包括結(jié)果和目標(biāo)對(duì)象都有了新的屬性/方法這是為什么????
其實(shí)當(dāng)你真正的理解了什么是原型鏈這個(gè)結(jié)果就不意外了。
即使我們新建一個(gè)對(duì)象 不參與Object.assign運(yùn)算也會(huì)發(fā)現(xiàn)這個(gè)新建的對(duì)象的原型鏈也有了這個(gè)新的屬性
const t1 = {a:1,b:2};
const t2 = {b:3,c:4};
const t3 = {c:5,d:6};
t2.__proto__.e = 100;
t2.__proto__.print = function(){console.log(this)};
const t4 = Object.assign(t1,t2,t3);
const t5 = {test:100};//不參與運(yùn)算
console.log(t5.__proto__)其實(shí)都有了
console.log(new Object());其實(shí)都有了
1-4、什么是可枚舉
可枚舉值得是可以遍歷到的屬性,在這里的案例我們需要用到
Object.defineProperty的用法了
const t1 = { a: 1, b: 2 };
const t2 = { b: 3, c: 4 };
const t3 = Object.defineProperty({}, "c", {
value: 100
})
const t4 = Object.assign(t1, t2, t3);
console.log(t4); 此時(shí)這個(gè)值是多少?
我們可以知道
Object.defineProperty的可枚舉屬性enumerable默認(rèn)為false,所以此時(shí)t4的屬性c還是4,但如果設(shè)置為true,可枚舉
const t1 = { a: 1, b: 2 };
const t2 = { b: 3, c: 4 };
const t3 = Object.defineProperty({}, "c", {
value: 100,
enumerable:true//可枚舉
})
const t4 = Object.assign(t1, t2, t3)
那么這里的t4屬性c就會(huì)變?yōu)?00
這就是說拷貝可枚舉的屬性
1-5、不會(huì)拷貝原型上的屬性或者方法
當(dāng)然這個(gè)前提是你不能更改Object上的屬性和方法,因?yàn)檫@個(gè)
1-3的原因是一樣的,你改變了祖父上的屬性和方法,那么任何子類上都會(huì)繼承
function t1() {
this.a = 1;
this.b = 2
}
t1.prototype.c = 3;
t1.prototype.say = function () { };
const t2 = Object.assign({}, new t1());
console.log(t2);此時(shí)t2上沒有c、say的屬性
1-6、面試題
猜想下下面的結(jié)果會(huì)是什么?
const t1 = 123;
const t2 = "456";
const t3 = false;
const t4 = function () { };
const t5 = Object.assign({},t1, t2, t3, t4)
console.log(t5);
解析,Object.assign是復(fù)制對(duì)象,如果不是對(duì)象那么則會(huì)強(qiáng)制轉(zhuǎn)為對(duì)象
const r1 = new Number(t1);
const r2 = new String(t2);
const r3 = new Boolean(t3);
const r4 = new Function(t4);
for(let k in r1){console.log(k,r1[k]);}//沒打印
for(let k in r2){console.log(k,r2[k]);}//打印了
for(let k in r3){console.log(k,r3[k]);}//沒打印
for(let k in r4){console.log(k,r4[k]);}//沒打印
所以這道題的t5值真正的為t2的對(duì)象{0:4,1:5,2:6}
2、Object.create
Object.create方法創(chuàng)建一個(gè)新對(duì)象,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的__proto__。其實(shí)這個(gè)語法和接下來學(xué)習(xí)的Object.defineProperty語法有關(guān)系
2-1、語法
@param proto
新創(chuàng)建對(duì)象的原型對(duì)象。
@param propertiesObject
可選。需要傳入一個(gè)對(duì)象,該對(duì)象的屬性類型參照Object.defineProperties()的第二個(gè)參數(shù)。
如果該參數(shù)被指定且不為undefined,
該傳入對(duì)象的自有可枚舉屬性(即其自身定義的屬性,而不是其原型鏈上的枚舉屬性)
將為新創(chuàng)建的對(duì)象添加指定的屬性值和對(duì)應(yīng)的屬性描述符。
Object.create(proto,[propertiesObject])
2-2、基礎(chǔ)用法
let a = Object.create({b:1});
console.log(a);
我們會(huì)發(fā)現(xiàn)這個(gè)b屬性會(huì)在原型鏈上而不是本身的對(duì)象屬性上。

2-3、我們加上第二參數(shù)
當(dāng)然你要熟悉Object.defineProperty的用法
let a = Object.create({ a: "a" }, {
b: {
value: "b",
writable : true
},
c: {
value: "c",
configurable:false
},
d: {
enumerable:false,
value: "d"
}
})
console.log(a);

2-4、思考題
const t1 = Object.create(null)
console.log(t1);
此時(shí)你會(huì)發(fā)現(xiàn)當(dāng)前這個(gè)t1連原型都沒有了
3、Object.defineProperty
我相信這個(gè)應(yīng)該有不少人都熟悉它,因?yàn)?code>vue2.x的響應(yīng)式原理用的就是該屬性,當(dāng)然
vue3.x已經(jīng)全面改成Proxy了,當(dāng)然這個(gè)不在本章節(jié)范圍內(nèi)。
3-1、語法
@param obj 要定義屬性的對(duì)象。
@param prop 要定義或修改的屬性的名稱。
@descriptor 要定義或修改的屬性描述符。
Object.defineProperty(obj, prop, descriptor)
3-2 、descriptor詳解
其實(shí)
Object.defineProperty真正的靈魂就是descriptor這個(gè)參數(shù)了
該參數(shù)有六個(gè)屬性:
value:該屬性對(duì)應(yīng)的值。
configurable:該屬性是否可以被刪除,默認(rèn)值為false,不可被刪除
enumerable:該屬性是否可以被枚舉,默認(rèn)值為false,不可被枚舉(也就是不能被循環(huán)遍歷到);想到這個(gè)應(yīng)該能想到Object.assign了吧
writable:該屬性是否可以被重新賦值,默認(rèn)置為false,不可被改寫
get屬性的 getter 函數(shù),如果沒有 getter,則為 undefined。當(dāng)訪問該屬性時(shí),會(huì)調(diào)用此函數(shù)。
set屬性的 setter 函數(shù),如果沒有 setter,則為 undefined。當(dāng)屬性值被修改時(shí),會(huì)調(diào)用此函數(shù)。
3-3、用法
要注意的是
writable、value不能和get、set方法同時(shí)出現(xiàn)
const t1 = Object.defineProperty({},"a",{
value:"1",
enumerable:true,//可枚舉
configurable : true ,//可刪除
writable : true,//可賦值
})
console.log(t1);
//有g(shù)et、set方法
let tValue = 1;
const t1 = Object.defineProperty({},"a",{
enumerable:true,//可枚舉
configurable : true ,//可刪除
get(){
return tValue
},
set(v){
tValue = v
}
})
console.log(t1);
3-4、面試題
請(qǐng)?jiān)O(shè)計(jì)當(dāng)前程序讓它滿足一下的判斷條件
if(a === 1 && a=== 2 && a === 3){
console.log("條件判斷正確");
console.log(a)?
}
答案:
要想一個(gè)變量同時(shí)滿足三個(gè)條件常規(guī)操作肯定不行,我們知道每次訪問變量其實(shí)就是
調(diào)用了getter方法獲取這個(gè)值,二全局變量都在window對(duì)象上所以
let _a = 0;
Object.defineProperty(window,"a",{
get (){
return ++_a;
}
})
為什么這樣就可以了?因?yàn)槲覀兠看芜M(jìn)行
a變量比較都是在獲取這個(gè)a的值,但是我們的getter方法每次訪問都會(huì)加1.
4、Object.entries
Object.entries意義:方法返回一個(gè)給定對(duì)象自身可枚舉屬性的鍵值對(duì)數(shù)組(白話就是,這個(gè)方法返回一個(gè)數(shù)組,且每個(gè)數(shù)組元素就是該對(duì)象的key和value),請(qǐng)注意必須是一個(gè)可枚舉的屬性/方法才能轉(zhuǎn)換,但是它不會(huì)轉(zhuǎn)換原型上的屬性/方法
4-1、用法
let a = {a:"1",b:"b",c:"測(cè)試"};
let r = Object.entries(a);
console.log(r);這是一個(gè)二維數(shù)組

4-2 、測(cè)試不能枚舉的屬性能否轉(zhuǎn)換
let a = Object.defineProperties({},{
a:{
value:1
},
b:{
value : 2,
enumerable:true
},
c:{
value : 3,
enumerable : true
}
})
console.log(Object.entries(a));
會(huì)發(fā)現(xiàn)屬性a沒有在數(shù)組中
4-3、將對(duì)象轉(zhuǎn)為map對(duì)象
let a = {a:1,b:2,c:3};
let b= new Map(a);//直接轉(zhuǎn)換會(huì)報(bào)錯(cuò)的。
let b = new Map(Object.entries(a));//成功
4-4、測(cè)試原型上的屬性、方法
function Test(){
this.a = 1;
this.b = 2
}
Test.prototype.c = 3;
Test.prototype.say=function(){}
console.log(Object.entries(new Test()));
for (let k in t) {
console.log(k);雖然c和say能打印但是卻不能轉(zhuǎn)換
}
會(huì)發(fā)現(xiàn)c和say都不會(huì)出現(xiàn)在結(jié)果中
5、Object.fromEntries
其實(shí)這個(gè)方法就是
Object.entries的對(duì)應(yīng)方法,一個(gè)是將對(duì)象轉(zhuǎn)為數(shù)組,一個(gè)將數(shù)組轉(zhuǎn)為json
5-1、用法
let a = [["a",1],["b",2],["c",3]]
console.log(Object.fromEntries(a));
//{a: 1, b: 2, c: 3}結(jié)果
5-2、將Map對(duì)象轉(zhuǎn)為json
let a = new Map();
a.set("a",1)
a.set("b",2)
a.set("c",3)
console.log(Object.fromEntries(a));
6、Object.freeze
Object.freeze凍結(jié)一個(gè)對(duì)象,并且返回源對(duì)象(返回值和源對(duì)象是一個(gè)引用),且這個(gè)凍結(jié)是淺凍結(jié)。
什么叫凍結(jié)?也就是這個(gè)對(duì)象不可被操作了
6-1、用法
let a = { a: 1, b: 2, c: 3 };
Object.freeze(a)
a.a = 2//不可改
console.log(a);
a.d = 4;//不可增
console.log(a);
delete a.c;//不可刪
console.log(a);
6-2、利用構(gòu)造函數(shù)的原型鏈改變值
function Test(){
this.a = 1;
this.b = 2;
}
Test.prototype.c = 3
let t= new Test();
Object.freeze(t)
t.c = 5//即使是實(shí)例原型上的屬性/或者方法也不能操作
console.log(t);
//我們改變構(gòu)造函數(shù)的值
Test.prototype.c = 100;
console.log(t.c);//發(fā)現(xiàn)是可以修改的
如果一個(gè)對(duì)象被凍結(jié)了,其原型鏈上的方法/屬性也不會(huì)被修改,但是可通過其構(gòu)造函數(shù)的原型進(jìn)行操作。
6-3、為什么說Object.freeze是淺凍結(jié)?
function Test(){
this.a = 1;
this.b = 2;
this.d = {
d:"這是子對(duì)象"
}
this.e = ["a","b","c"]
}
Test.prototype.c = 3
let t= new Test();
//我們是不可以直接改變d的值
t.d = "d"
console.log(t);
//但是我們可以改變d屬性里面的值
t.d.d = "我改變了值"
console.log(t);確實(shí)被改變了
通過這個(gè)案例我們會(huì)發(fā)現(xiàn)
Object.freeze它只會(huì)凍結(jié)源對(duì)象的屬性,但是他不會(huì)凍結(jié)源對(duì)象中的對(duì)象和數(shù)組
6-4、被凍結(jié)的對(duì)象屬性即使通過getter/setter方法也不能被修改刪除
let a = { _a: 1, b: 2, get a() {
return this._a;
}, set a(v) {
this._a = v;
} }
Object.freeze(a)
a.a = 3;//a屬性并不會(huì)改變包括_a
console.log(a);
6-5、編寫深凍結(jié)方法
Object.deepFreeze = function (obj) {
//let keys = Object.keys(obj);
//注意Object.keys拿不到不可枚舉的key而getOwnPropertyNames能拿到
let keys = Object.getOwnPropertyNames(obj);
keys.forEach(k => {
if (typeof obj[k] === "object" && obj[k] != null) {
Object.deepFreeze(obj[k])
}
})
return Object.freeze(obj);
}
7、Object.isFrozen
Object.isFrozen判斷一個(gè)對(duì)象是否被凍結(jié)
7-1、判斷Object.freeze到底是不是淺凍結(jié)
let a = {
a:1,
b:2,
c:{
c:3,
d:4
}
}
Object.freeze(a);
console.log(Object.isFrozen(a.c))//false
Object.freeze(a.c);
console.log(Object.isFrozen(a.c))//true
8、Object.seal、Object.isSealed
Object.seal封閉對(duì)象它和Object.freeze差不多,但是它是可以修改值得
let a = {
a:1,
b:2,
}
Object.prototype.c = 3;
Object.seal(a)
a.d = 5;//不可增加
delete a.a//不可刪除
a.b = 100;//可以修改
a.__proto__.c = 333;//可以修改
console.log(a);
8-1、為什么說Object.seal是淺封閉?
let a = {
a:1,
b:2,
c:{
c:3
}
}
a.__proto__.d = 4;
Object.seal(a);
//為true
console.log(Object.isSealed(a));
//為false
console.log(Object.isSealed(a.c));
a.c.d = 5;
console.log(a.c);//比沒有添加d屬性
Object.seal(a.c);
//為true
console.log(Object.isSealed(a.c));
8-2、編寫深封閉
Object.deepSeal = function (obj) {
//let keys = Object.keys(obj);
//注意Object.keys拿不到不可枚舉的key而getOwnPropertyNames能拿到
let keys = Object.getOwnPropertyNames(obj);
keys.forEach(k => {
if (typeof obj[k] === "object" && obj[k] != null) {
Object.deepSeal (obj[k])
}
})
return Object.seal(obj);
}
9、Object.preventExtensions
Object.preventExtensions方法讓一個(gè)對(duì)象變的不可擴(kuò)展,也就是永遠(yuǎn)不能再添加新的屬性。
let a = {};
Object.preventExtensions(a)
a.a = 1;
a.__proto__.b = 2;//增加原型鏈上的屬性是可行的,但是切記不要這樣做,你會(huì)發(fā)現(xiàn)新的對(duì)象上也會(huì)有該屬性
console.log(a);
let b = {}
consoole.log(b) b屬性也會(huì)存在的
10、總結(jié)Object.freeze、Object.seal、Object.preventExtensions;
Object.freeze:凍結(jié)對(duì)象。不可修改、不可刪除、不可擴(kuò)展
Object.seal:封閉對(duì)象??尚薷摹⒉豢蓜h除、不可擴(kuò)展
Object.preventExtensions:禁止擴(kuò)展對(duì)象??尚薷摹⒖蓜h除、不可擴(kuò)展