原生Js实现Object.assign()
Object.assign()应用
Object.assign()主要是将所有可枚举属性的值从一个或多个源对象复制到目标对象,同时返回目标对象
语法如下所示:
Object.assign(target, ...sources)其中 target 是目标对象,sources 是源对象,可以有多个,返回修改后的目标对象 target。
如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后来的源对象的属性将类似地覆盖早先的属性。
示例1
我们知道浅拷贝就是拷贝第一层的基本类型值,以及第一层的引用类型地址。
// 第一步
let a = {
name: "advanced",
age: 18
}
let b = {
name: "muyiy",
book: {
title: "You Don't Know JS",
price: "45"
}
}
let c = Object.assign(a, b);
console.log(c);
// {
// name: "muyiy",
// age: 18,
// book: {title: "You Don't Know JS", price: "45"}
// }
console.log(a === c);
// true
// 第二步
b.name = "change";
b.book.price = "55";
console.log(b);
// {
// name: "change",
// book: {title: "You Don't Know JS", price: "55"}
// }
// 第三步
console.log(a);
// {
// name: "muyiy",
// age: 18,
// book: {title: "You Don't Know JS", price: "55"}
// }1、在第一步中,使用 Object.assign 把源对象 b 的值复制到目标对象 a 中,这里把返回值定义为对象 c,可以看出 b 会替换掉 a 中具有相同键的值,即如果目标对象(a)中的属性具有相同的键,则属性将被源对象(b)中的属性覆盖。这里需要注意下,返回对象 c 就是 目标对象 a。
2、在第二步中,修改源对象 b 的基本类型值(name)和引用类型值(book)。
3、在第三步中,浅拷贝之后目标对象 a 的基本类型值没有改变,但是引用类型值发生了改变,因为 Object.assign() 拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用地址。
示例2
String 类型和 Symbol 类型的属性都会被拷贝,而且不会跳过那些值为 null 或undefined 的源对象。
// 第一步
let a = {
name: "muyiy",
age: 18
}
let b = {
b1: Symbol("muyiy"),
b2: null,
b3: undefined
}
let c = Object.assign(a, b);
console.log(c);
// {
// name: "muyiy",
// age: 18,
// b1: Symbol(muyiy),
// b2: null,
// b3: undefined
// }
console.log(a === c);
// trueObject.assign()实现代码
要实现 Object.assign(),首先了解它的大概用法:
接受的第一个参数表示目标对象(浅拷贝的结果),如果是 null 或者 undefined,直接报错;如果是对象字面量或者数组,直接使用;如果是基本类型,则装箱为对应的对象。
如果只接受了第一个参数,则将其包装为对象直接返回;如果不止接受了第一个参数,比如说接受了第二,第三 …… 等多个参数,那么这些参数表示源对象,它们的自身可枚举属性会一一添加到目标对象上,属性同名则以靠后的对象为准,进行属性覆盖。
第一个参数往后的参数,如果是 null 或者 undefined,那么直接跳过;其余的情况则尝试找出它们的可枚举属性,但实际上,只有字符串、数组、对象字面量这些类型是具有可枚举属性的。
根据上面所讲的思路,实现的代码就是下面这样的:
function myAssign(target,...objs){
if(target === null || target === undefined){
throw new TypeError("can not convert null or undefined to object")
}
let res = Object(target)
objs.forEach(obj => {
'use strict'
if(obj != null && obj != undefined){
for(let key in obj){
if(Object.prototype.hasOwnProperty.call(obj,key)){
res[key] = obj[key]
}
}
}
})
return res
}
Object.defineProperty(Object,'myAssign',{
value: myAssign,
writable: true,
configurable: true,
enumerable: false
})需要注意的要点
为什么不直接通过 . 给 Object 添加 myAssign 方法?
Object.myAssign() 实际上是 Object 的一个静态方法,但是不要直接通过 . 添加,因为这种方式添加的方法是可以枚举的,而 assign() 方法不可枚举。所以这里使用 Object.defineProperty() 添加,同时设置该方法不可枚举、可读、可配置。
为什么要使用严格模式?
考察参数出现字符串的情况。下面这两种情况容易理解:
Object.assign({a:1},"cd")
// 把 "cd" 的可枚举属性 0 和 1 添加到目标对象上,最后得到 {a:1,0:“c”,1:"d"}
Object.assign("cd",{a:1})
// 把 {a:1} 的可枚举属性 a 添加到目标对象上,最后得到 String{“cd”,a:1}但如果是这种情况:
Object.assign("ab","cd")
// 报错 Cannot assign to read only property '0' of object '[object String]'这里尝试把 “cd” 的可枚举属性 0 和 1 添加到目标对象上,但问题是,目标对象 String{“ab”} 也有可枚举属性 0 和 1,而且是只读的,这意味着我们尝试去修改目标对象的只读属性,所以报错也就很合理了。但是,在非严格模式下,这种行为只会静默失败,为了让它真的抛出错误,必须声明使用严格模式。
为什么不使用 Reflect.ownKeys(obj)?
考虑目标对象和源对象都是数组的情况,使用 Reflect.ownKeys(obj)确实可以一次性获得 obj 的自身可枚举属性,但是这些属性除了数组索引之外,也包含数组长度,这会导致将源对象的数组长度作为目标对象的数组长度,但实际上,两者长度不一定相等。比如,Objetc.myAssign([1,2,3],[8,9]) 的结果将不是期望得到的 [8,9,3],而是 [8,9],因为目标对象的长度被覆盖了。
为什么不直接使用 obj.hasOwnProperty(key) ?
既然不能使用 Reflect.ownKeys(obj),那么就只有先使用 for(let key in obj) 获得 obj 的自身属性 + 原型链属性,再使用 obj.hasOwnProperty(key) 筛选出自身属性了。但是为什么不直接使用 obj.hasOwnProperty(key) 呢?
这是因为,我们对源对象的情况并不了解。一方面,它可能重写了 hasOwnProperty 方法;另一方面,它可能是基于 Object.create(null) 构建的,这样的对象不会从 Object 原型上继承 hasOwnProperty 方法。所以这里借用 Object 原型的 hasOwnProperty 方法,是最保险的方式。
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!