这是JS 原生方法原理探究系列的第七篇文章。本文会介绍如何实现 Object.assign()
方法。
Object.assign()
的基本用法要实现 Object.assign()
,首先了解它的大概用法:
根据上面所讲的思路,实现的代码就是下面这样的:
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
方法,是最保险的方式。