Open yygmind opened 5 years ago
var test = { a: 100, b: 200 } Object.prototype.testfn=function(){ console.log('testfn') } for (let key in test) { console.log('test: ',test[key]) } console.log(Object.propertyIsEnumerable.call(test, "testfn"))
// 结果 test: 100 test: 200 test: ƒ (){ console.log('testfn') } false
testfn不可枚举,但是被遍历出来了
var test = { a: 100, b: 200 } Object.prototype.testfn=function(){ console.log('testfn') } for (let key in test) { console.log('test: ',test[key]) } console.log(Object.propertyIsEnumerable.call(test, "testfn"))
// 结果 test: 100 test: 200 test: ƒ (){ console.log('testfn') } false
testfn不可枚举,但是被遍历出来了
在原型链上propertyIsEnumerable不被考虑,尽管在for-in循环中可以被循环出来。 详情点击 Object.prototype.propertyIsEnumerable()
var a = "abc";
var b = {
v1: "def",
v2: true,
v3: 10,
v4: Symbol("foo"),
v5: null,
v6: undefined
}
var obj = Object.assign(a, b);
console.log(obj);
这个在chrome
控制台输出如下,字符串是能展开的,即有 0,1,2的key
String {"abc", v1: "def", v2: true, v3: 10, v4: Symbol(foo), …}
0: "a"
1: "b"
2: "c"
v1: "def"
v2: true
v3: 10
v4: Symbol(foo)
v5: null
v6: undefined
length: 3
__proto__: String
[[PrimitiveValue]]: "abc"
// 木易杨 var v1 = "abc"; var v2 = true; var v3 = 10; var v4 = Symbol("foo");
var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); // 原始类型会被包装,null 和 undefined 会被忽略。 // 注意,只有字符串的包装对象才可能有自身可枚举属性。 console.log(obj); // { "0": "a", "1": "b", "2": "c" }
这里的 v3 =10 应该是 ‘10’
数字的10不会被转化
直接用Object.prototype.assign = xxx,这样挂载的属性不也是不可枚举的吗,就不用defineProperty了
我个人觉得其实没必要加undefined
和null
的判断,我测试是无论用new Object()
还是Object()
都能直接将undefined
和null
转成一个空对象
真的很感谢木老师的文章。思路真的很清晰... 看老师的文章,总能一次就捋清楚
直接用Object.prototype.assign = xxx,这样挂载的属性不也是不可枚举的吗,就不用defineProperty了
Object.assign不是原型方法
我个人觉得其实没必要加
undefined
和null
的判断,我测试是无论用new Object()
还是Object()
都能直接将undefined
和null
转成一个空对象
我也测试了一下,确实返回了空对象。但在标准中,一直到 ES10 中的 ToObject 对于 null
,undefined
都是返回 TypeError。陷入迷茫...
if(typeof Object.assign2 != 'function')
为什么这样判断呀,假如有个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。
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!