你不知道的Object

1、Object.assign

Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。注意这里有个词叫做可枚举
Object.assign(target,...source)

1-1、基础用法

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);
此时t1和t4是相同的 {a: 1, b: 3, c: 5, d: 6}
t2、t3此时是不变的

Object.assign会改变目标对象上的属性和值,所以我们一般对对象进行合并一般使用const result = Object.assign({},...source);给予目标对象为一个空对象,这样就可以不用改变已有的值了。

1-2、改变结果的属性/属性值,那么目标对象是否会改变?

如果此时我们改变t4上面的属性值,那么想想t1会不会被改变??

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)

结果却是都一起被改变了,为什么会被改变?我们知道在程序中,任何一个变量其实都被引用了一段16位进制的空间地址,在上述程序中其实t4的引用了t1的空间地址,上述我们可以理解为const t4 = t1;而t1在Object.assign运算中已被改变了

1-3、给其中一个源对象增加原型链增加属性/方法

猜想下给其中一个源对象增加原型链增加属性/方法,那么会有什么不同呢?

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__);

结果我们发现竟然发现所有的源对象上的原型链包括结果和目标对象都有了新的属性/方法这是为什么????
其实当你真正的理解了什么是原型链这个结果就不意外了。
即使我们新建一个对象 不参与Object.assign运算也会发现这个新建的对象的原型链也有了这个新的属性

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};//不参与运算
console.log(t5.__proto__)其实都有了
console.log(new Object());其实都有了

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); 此时这个值是多少?

我们可以知道Object.defineProperty的可枚举属性enumerable默认为false,所以此时t4的属性c还是4,但如果设置为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就会变为100

这就是说拷贝可枚举的属性

1-5、不会拷贝原型上的属性或者方法

当然这个前提是你不能更改Object上的属性和方法,因为这个1-3的原因是一样的,你改变了祖父上的属性和方法,那么任何子类上都会继承

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);此时t2上没有c、say的属性

1-6、面试题

猜想下下面的结果会是什么?

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是复制对象,如果不是对象那么则会强制转为对象

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的对象{0:4,1:5,2:6}

2、Object.create

Object.create方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。其实这个语法和接下来学习的Object.defineProperty语法有关系

2-1、语法

@param proto
        新创建对象的原型对象。
@param propertiesObject
    可选。需要传入一个对象,该对象的属性类型参照Object.defineProperties()的第二个参数。
    如果该参数被指定且不为undefined,
    该传入对象的自有可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)
    将为新创建的对象添加指定的属性值和对应的属性描述符。

Object.create(proto,[propertiesObject])

2-2、基础用法

let a = Object.create({b:1});
console.log(a);

我们会发现这个b属性会在原型链上而不是本身的对象属性上。


image.png

2-3、我们加上第二参数

当然你要熟悉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);
image.png

2-4、思考题

const t1 = Object.create(null)
console.log(t1);

此时你会发现当前这个t1连原型都没有了

3、Object.defineProperty

我相信这个应该有不少人都熟悉它,因为vue2.x的响应式原理用的就是该属性,当然vue3.x已经全面改成Proxy了,当然这个不在本章节范围内。

3-1、语法

@param obj 要定义属性的对象。
@param prop 要定义或修改的属性的名称。
@descriptor 要定义或修改的属性描述符。
Object.defineProperty(obj, prop, descriptor)

3-2 、descriptor详解

其实Object.defineProperty真正的灵魂就是descriptor这个参数了
该参数有六个属性:
value:该属性对应的值。
configurable:该属性是否可以被删除,默认值为false,不可被删除
enumerable:该属性是否可以被枚举,默认值为false,不可被枚举(也就是不能被循环遍历到);想到这个应该能想到Object.assign了吧
writable:该属性是否可以被重新赋值,默认置为false,不可被改写
get 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。
set属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。

3-3、用法

要注意的是writable、value不能和get、set方法同时出现

const t1 = Object.defineProperty({},"a",{
    value:"1",
    enumerable:true,//可枚举
    configurable : true ,//可删除
    writable : true,//可赋值
})
console.log(t1);
//有get、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、面试题

请设计当前程序让它满足一下的判断条件
if(a === 1 && a=== 2 && a === 3){
  console.log("条件判断正确");
  console.log(a)?
}
答案:
要想一个变量同时满足三个条件常规操作肯定不行,我们知道每次访问变量其实就是
调用了getter方法获取这个值,二全局变量都在window对象上所以
let _a = 0;
Object.defineProperty(window,"a",{
  get (){
    return ++_a;
  }
})

为什么这样就可以了?因为我们每次进行a变量比较都是在获取这个a的值,但是我们的getter方法每次访问都会加1.

4、Object.entries

Object.entries意义:方法返回一个给定对象自身可枚举属性的键值对数组(白话就是,这个方法返回一个数组,且每个数组元素就是该对象的key和value),请注意必须是一个可枚举的属性/方法才能转换,但是它不会转换原型上的属性/方法

4-1、用法

let a = {a:"1",b:"b",c:"测试"};
let r = Object.entries(a);
console.log(r);这是一个二维数组
image.png

4-2 、测试不能枚举的属性能否转换

let a = Object.defineProperties({},{
    a:{
        value:1
    },
    b:{
        value : 2,
        enumerable:true
    },
    c:{
        value : 3,
        enumerable : true
    }
})
console.log(Object.entries(a));

会发现属性a没有在数组中

4-3、将对象转为map对象

let a = {a:1,b:2,c:3};
let b=  new Map(a);//直接转换会报错的。
let b = new Map(Object.entries(a));//成功

4-4、测试原型上的属性、方法

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能打印但是却不能转换
}

会发现c和say都不会出现在结果中

5、Object.fromEntries

其实这个方法就是Object.entries的对应方法,一个是将对象转为数组,一个将数组转为json

5-1、用法

let a = [["a",1],["b",2],["c",3]]
console.log(Object.fromEntries(a));
//{a: 1, b: 2, c: 3}结果

5-2、将Map对象转为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冻结一个对象,并且返回源对象(返回值和源对象是一个引用),且这个冻结是浅冻结。
什么叫冻结?也就是这个对象不可被操作了

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、利用构造函数的原型链改变值

function Test(){
    this.a = 1;
    this.b = 2;
}
Test.prototype.c = 3
let t= new Test();
Object.freeze(t)
t.c = 5//即使是实例原型上的属性/或者方法也不能操作
console.log(t);
//我们改变构造函数的值
Test.prototype.c = 100;
console.log(t.c);//发现是可以修改的

如果一个对象被冻结了,其原型链上的方法/属性也不会被修改,但是可通过其构造函数的原型进行操作。

6-3、为什么说Object.freeze是浅冻结?

function Test(){
    this.a = 1;
    this.b = 2;
    this.d = {
        d:"这是子对象"
    }
    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);确实被改变了

通过这个案例我们会发现Object.freeze它只会冻结源对象的属性,但是他不会冻结源对象中的对象和数组

6-4、被冻结的对象属性即使通过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属性并不会改变包括_a
console.log(a);

6-5、编写深冻结方法

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判断一个对象是否被冻结

7-1、判断Object.freeze到底是不是浅冻结

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封闭对象它和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方法让一个对象变的不可扩展,也就是永远不能再添加新的属性。

let a = {};
Object.preventExtensions(a)
a.a = 1;
a.__proto__.b = 2;//增加原型链上的属性是可行的,但是切记不要这样做,你会发现新的对象上也会有该属性
console.log(a);
let b = {}
consoole.log(b) b属性也会存在的

10、总结Object.freeze、Object.seal、Object.preventExtensions;

Object.freeze:冻结对象。不可修改、不可删除、不可扩展
Object.seal:封闭对象。可修改、不可删除、不可扩展
Object.preventExtensions:禁止扩展对象。可修改、可删除、不可扩展

你可能感兴趣的:(你不知道的Object)