一杯茶🍵时间,琢磨一下Object.assign!
Object.assign相信每一个前端人儿都有遇到过这个方法,那么它究竟是什么?Object对象的一个内置方法,它作用是所有可枚举属性的值
从一个或多个源对象分配到目标对象,并返回目标对象。
// 源码中方法参数格式如下
Object.assign(target: object, ...sources: any[]): any;
- target:目标对象。
- sources:需要拷贝的参数。
上面只是单纯从源码本身去简单呈现Object.assign方法的构成,所以为了更好的理解Object.assign这个方法,本篇文章接下来就用详细地深入地理解它的使用与封装!
Object.assign的使用
由最近在做一些后台管理业务常遇到需要使用Object.assign这个方法的前端逻辑,比如对表单对象的属性值进行拷贝,以前一直没有深入理解Object.assign这个方法,只知道它相当于浅拷贝
,但是它内部真正做了什么,真的是一窍不通😵,最近突然对它产生了🧐,所以今天花费一杯茶🍵时间,让我们一起探索它🔍。
了解之前它,我们是否都遇到下面这两个简单业务:
- 🚀 新增一条数据:保存绑定表单的属性值,但是后端必填的有些属性是没有绑定到表单的,需要额外添加进去的,这时候就需要用到
Object.assign
方法的浅拷贝,也就是把额外添加的数据通过Object.assign方法拷贝到表单对象。 - 🚀 编辑已存在的数据:保存已经存在的数据内容(但是需要修改的数据),由于数据已存在,也就是说当前数据是有了唯一的id,如果我们还是按照新增的那种逻辑去保存的话,就可能会出现两种相同的数据(后端新增与修改共用一个api的情况),所以我们也需要
Object.assign
方法的浅拷贝,也就是把从获取信息的api中拿到的数据id通过Object.assign拷贝到表单对象。
上面就是常见用到Object.assign的业务场景,那么从场景出发,我们先看看从实际开发角度的出发的使用例子:
let form = {
Id: null,
StudentId: null,
Name: null,
Age: null,
Remark: null,
};
const studentId = 1;
// 把form对象拷贝到newForm
const newForm = Object.assign({}, form);
// 把form对象拷贝到formValues,并把studentId值赋给新对象
const formValues = Object.assign({}, form, { StudentId: studentId });
从上面例子,我们有的朋友可能就会问❓newForm与form不是一样的吗🤔、❓formValues得到的值是什么🤔
我们一个个问题来,先看第一个问题真的确定是一样吗?我们先打印一下看看看结果:
看图说话,我们就笃定这不是一样的吗,我们先考虑一个问题变量的存储,这就涉及到了变量值与引用地址了,这里就不一一深入,我们应该都知道对象属性值相同与引用地址相同两个特点同时成立
才能确定对象变量相等,那我们先来分别判断一下这两个对象是否相等:
console.log(Object.is(form, newForm));// false 只能判断这两个对象的引用地址是否一致
console.log(JSON.stringify(form) === JSON.stringify(newForm));// true 判断这两个对象内容是否一致
通过对象内置方法is判断对象引用地址是否一致,通过JSON方法转化判断对象属性值是否相等,从打印结果来看,很明显看出Object.assign方法的作用就是赋值给目标对象
,其实这就是浅拷贝的特点:只拷值不拷引用地址
。
一波操作下来终于会了吧!既然我们解决了第一个问题:newForm与form是不一样的,我们接着去探索一下第二个问题:formValues得到的值是什么🤔?
从上图我们可以看出其实Object.assign方法把studentId的值拷贝赋给form中的StudentId属性值,但是我们有没有想过假如把Object.assign方法中的参数位置互换一下位置结果又如何?我们来看看:
// 打印后发现formValues与form的值是一致,都是初始化状态
const formValues = Object.assign({}, { StudentId: studentId }, form);
为什么值会是一致?都是初始化状态,因为这也是Object.assign方法的一个特点:以后面的参数为主
。可能有朋友更调皮一点,假如前后参数的对象属性不一样(有不一致的属性)结果又如何?我们看看:
const formValues = Object.assign({}, { StudentId: studentId, Tutor: '路灯下的光' }, form);
从上图可以看出,其实Object.assign也可以把前后不一致的属性值合并一起,这样就可以实现,把有值的对象属性拷贝给没有值的对象属性。
当然上面的例子可能还无法让大家信服,我们再看看一个例子:
let source = {
a: {
b: 1
},
c: 1
};
let target = Object.assign({}, source);
source.a.b = 2;
source.c = 3
console.log('source',source)
console.log('target',target)
从上面例子,可以看出,如果对象中的属性是基础类型,Object.assign执行的是深拷贝
,source的改变不会导致目标对象的改变,如果对象中的属性是引用类型,Object.assign执行的是浅拷贝
,source的改变会导致目标对象的改变。
-
小结一下:
🏷 Object.assign可以理解为对象中的属性是基础类型,Object.assign执行的是深拷贝;对象中的属性是引用类型,Object.assign执行的是浅拷贝。
🏷 Object.assign除了第一个参数为目标对象,后面参数都是需要拷贝的对象。
🏷 Object.assign拷贝对象是从第二个参数开始,后面对象融合前面对象,如果存在一致的对象属性,属性值以后面对象参数属性为主。
Object.assign的封装
上面我们已经学习了Object.assign方法的使用,接下来我们将从源码封装角度去实现Object.assign这个方法,代码如下:
const assign = function (target) {
for (let i = 1; i < arguments.length; i++) { //arguments为全部的对象集合
let source = arguments[i]; //单个对象
//遍历一个对象的自身和继承来的属性,
//常常配合hasOwnProperty筛选出对象自身的属性
for (let key in source) {
//使用call方法,避免原型对象扩展带来的干扰
///判断是否为source自身的属性。
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
从上面代码中可以看出,拷贝的关键点在target[key] = source[key]
;也就是不管target中是否有source的属性,有就加进去,没有就把值拷贝到target中。我们再看看实际例子:
let form = {
Id: null,
StudentId: null,
Name: null,
Age: null,
Remark: null,
};
const studentId = 1;
const formValues = assign({}, { StudentId: studentId, Tutor: '路灯下的光' }, form);
const newForm = assign({}, form);
console.log('form=', form);
console.log('formValues=', formValues);
console.log(Object.is(form, newForm))// false;
从结果上可以看出form对象中没有Tutor这个属性,所以没有覆盖掉它的值,而StudentId的值被form中的属性覆盖掉了,同时拷贝后的对象并不会拷贝应用地址,显而易见,原生的Object.assign方法的功能都可以在封装的assign中呈现。
-
小结一下:
🏷 遍历函数参数,通过遍历内置变量arguments实现。
🏷 遍历参数,直接赋值给目标对象target。
转载自:https://juejin.cn/post/7221048707239985212