-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
【进阶4-2期】Object.assign 原理及其实现 #26
Comments
var test = { // 结果 testfn不可枚举,但是被遍历出来了 |
在原型链上propertyIsEnumerable不被考虑,尽管在for-in循环中可以被循环出来。 |
这个在
|
这里的 v3 =10 应该是 ‘10’ 数字的10不会被转化 |
#有个问题,原生Object.assign()的行为中,arguments位于后面的对象属性如果在目标对象中存在,则会进行覆盖,这里是不是没有针对这种情况做处理呢? |
直接用Object.prototype.assign = xxx,这样挂载的属性不也是不可枚举的吗,就不用defineProperty了 |
我个人觉得其实没必要加 |
真的很感谢木老师的文章。思路真的很清晰... |
Object.assign不是原型方法 |
我也测试了一下,确实返回了空对象。但在标准中,一直到 ES10 中的 ToObject 对于 |
if(typeof Object.assign2 != 'function') 为什么这样判断呀,假如有个 |
你的来信我一收到 谢谢哈 ~~~~
|
放心吧,我已经收到啦。
|
存收到,谢谢!
|
引言
上篇文章介绍了赋值、浅拷贝和深拷贝,其中介绍了很多赋值和浅拷贝的相关知识以及两者区别,限于篇幅只介绍了一种常用深拷贝方案。
本篇文章会先介绍浅拷贝
Object.assign
的实现原理,然后带你手动实现一个浅拷贝,并在文末留下一道面试题,期待你的评论。浅拷贝
Object.assign
上篇文章介绍了其定义和使用,主要是将所有可枚举属性的值从一个或多个源对象复制到目标对象,同时返回目标对象。(来自 MDN)
语法如下所示:
其中
target
是目标对象,sources
是源对象,可以有多个,返回修改后的目标对象target
。如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后来的源对象的属性将类似地覆盖早先的属性。
示例1
我们知道浅拷贝就是拷贝第一层的基本类型值,以及第一层的引用类型地址。
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
的源对象。Object.assign
模拟实现实现一个
Object.assign
大致思路如下:1、判断原生
Object
是否支持该函数,如果不存在的话创建一个函数assign
,并使用Object.defineProperty
将该函数绑定到Object
上。2、判断参数是否正确(目标对象不能为空,我们可以直接设置{}传递进去,但必须设置值)。
3、使用
Object()
转成对象,并保存为 to,最后返回这个对象 to。4、使用
for..in
循环遍历出所有可枚举的自有属性。并复制给新的目标对象(使用hasOwnProperty
获取自有属性,即非原型链上的属性)。实现代码如下,这里为了验证方便,使用
assign2
代替assign
。注意此模拟实现不支持symbol
属性,因为ES5
中根本没有symbol
。测试一下
针对上面的代码做如下扩展。
注意1:可枚举性
原生情况下挂载在
Object
上的属性是不可枚举的,但是直接在Object
上挂载属性a
之后是可枚举的,所以这里必须使用Object.defineProperty
,并设置enumerable: false
以及writable: true, configurable: true
。上面代码说明原生
Object
上的属性不可枚举。我们可以使用 2 种方法查看
Object.assign
是否可枚举,使用Object.getOwnPropertyDescriptor
或者Object.propertyIsEnumerable
都可以,其中propertyIsEnumerable(..)
会检查给定的属性名是否直接存在于对象中(而不是在原型链上)并且满足enumerable: true
。具体用法如下:上面代码说明
Object.assign
是不可枚举的。介绍这么多是因为直接在
Object
上挂载属性a
之后是可枚举的,我们来看如下代码。所以要实现
Object.assign
必须使用Object.defineProperty
,并设置writable: true, enumerable: false, configurable: true
,当然默认情况下不设置就是false
。所以具体到本次模拟实现中,相关代码如下。
注意2:判断参数是否正确
有些文章判断参数是否正确是这样的。
这样肯定没问题,但是这样写没有必要,因为
undefined
和null
是相等的(高程 3 P52 ),即undefined == null
返回true
,只需要按照如下方式判断就好了。注意3:原始类型被包装为对象
上面代码中的源对象 v2、v3、v4 实际上被忽略了,原因在于他们自身没有可枚举属性。
但是下面的代码是可以执行的。
原因很简单,因为此时
undefined
、true
等不是作为对象,而是作为对象 b 的属性值,对象 b 是可枚举的。这里其实又可以看出一个问题来,那就是目标对象是原始类型,会包装成对象,对应上面的代码就是目标对象 a 会被包装成
[String: 'abc']
,那模拟实现时应该如何处理呢?很简单,使用Object(..)
就可以了。到这里已经介绍很多知识了,让我们再来延伸一下,看看下面的代码能不能执行。
答案是否定的,会提示以下错误。
原因在于
Object("abc")
时,其属性描述符为不可写,即writable: false
。同理,下面的代码也会报错。
但是并不是说只要
writable: false
就会报错,看下面的代码。这里并没有报错,原因在于 JS 对于不可写的属性值的修改静默失败(silently failed),在严格模式下才会提示错误。
所以我们在模拟实现
Object.assign
时需要使用严格模式。注意4:存在性
如何在不访问属性值的情况下判断对象中是否存在某个属性呢,看下面的代码。
这边使用了
in
操作符和hasOwnProperty
方法,区别如下(你不知道的JS上卷 P119):1、
in
操作符会检查属性是否在对象及其[[Prototype]]
原型链中。2、
hasOwnProperty(..)
只会检查属性是否在myObject
对象中,不会检查[[Prototype]]
原型链。Object.assign
方法肯定不会拷贝原型链上的属性,所以模拟实现时需要用hasOwnProperty(..)
判断处理下,但是直接使用myObject.hasOwnProperty(..)
是有问题的,因为有的对象可能没有连接到Object.prototype
上(比如通过Object.create(null)
来创建),这种情况下,使用myObject.hasOwnProperty(..)
就会失败。解决方法也很简单,使用我们在【进阶3-3期】中介绍的
call
就可以了,使用如下。所以具体到本次模拟实现中,相关代码如下。
本期思考题
参考
进阶系列目录
交流
进阶系列文章汇总如下,内有优质前端资料,觉得不错点个star。
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
The text was updated successfully, but these errors were encountered: