资讯专栏INFORMATION COLUMN

ES6时代,你真的会克隆对象吗?

xiaokai / 1669人阅读

摘要:原文你真的会克隆对象吗开始之前在开始聊克隆之前,我们还是先来看看数据类型。值通过函数生成,是独一无二的。同时,中规定了对象的属性名有两种类型,一种是字符串,另一种就是类型。返回一个数组,包含对象自身的所有属性的键名。

原文:你真的会克隆对象吗

开始之前

在开始聊克隆之前,我们还是先来看看js数据类型。js的数据类型分为基本数据类型复杂数据类型

基本数据类型:Number、Boolean、String、Null、String、Symbol(ES6 新增)

复杂数据类型:Object,其他引用类型(Array、Date、RegExp、Function、基本包装类型(Boolean、String、Number)、Math等)都是Object类型的实例对象

克隆:基本数据 => 复制这个变量;复杂数据 => 拷贝引用(网上的介绍很多,不深入了)

常见浅拷贝

对于对象的克隆,应该大多数人都能实现出来,可能深、浅拷贝都能想出好几种方式,我们先来聊聊浅拷贝。

一个常见的浅拷贝一般是下面这样:

function shallowCopy (obj) {
  if (typeof obj !== "object") {
    return
  }
  var newObj = obj instanceof Array ? [] : {}
  for (var key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = obj[key]
    }
  }
  return newObj
}

或者更严谨一点的实现数组的判断:

Object.prototype.toString.call(arr) === "[object Array]"

好像是没什么问题呢,毕竟经过了好多项目的检测,网上一搜就能出现一大堆。

但是,我们开头介绍数据类型的时候就已经说过了,ES6新增了Symbol类型,情况好像就有点不一样了

Symbol

Symbol是ES6中引入的原始数据类型。Symbol值通过Symbol函数生成,是独一无二的。同时,ES6中规定了对象的属性名有两种类型,一种是字符串,另一种就是 Symbol 类型。凡是属性名属于 Symbol 类型,就不会与其他属性名产生冲突。但是,随之而来的问题是,我们的for...in循环不能遍历出该属性

Symbol 作为属性名,该属性不会出现在for...infor...of循环中,也不会被Object.keys()Object.getOwnPropertyNames()JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。

Symbol类型,自然有遍历Symbol类型的方法。Object.getOwnPropertySymbols + for...in的组合起来好像是能满足我们要求的了。嗯,看起来还不错,但是似乎有点麻烦了,有没有更便捷一点的方式呢?或许新时代的男人---Reflect.ownKeys,要闪亮登场了,这个既能遍历字符串,又能遍历Symbol的死{{BANNED}}(请允许我这么夸他)。

Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举
Object.assign

这个时候熟悉ES6的人或许开始有疑问了,我们已经开始讨论SymbolReflect.ownKeys,为什么浅克隆不直接用Object.assign或者展开运算符(...)呢?

嗯,待我吃根火腿冷静冷静,好像你说的很对!Object.assign的确是能拷贝Symbol类型的呢。但是呢,但是呢,我们是一个有追求的猿类,多一种实现方式不是能让我们多了解一些坑吗?而且这种方式不是能让我们更灵活的实现不可预知的需求吗?对,没错,是这样子的...

Object.assign这个更完美的男人出来之后,好像浅拷贝部分也该结束了,正常来说,的确是这样。不过我们再仔细想想上面的两种方式,好像还是有点区别的呢。我们再来看看这两个男人:

Reflect.ownKeys返回一个数组,包含对象自身的所有属性,不管是属性名是Symbol或字符串,也不管是否可枚举

Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性

注意到了吗?这里面有一个是否可枚举的概念,这个时候是不是应该感慨我们知道怎么实现不可预知的需求了呢。

不可枚举

我们先看个例子:

var obj = Object.create({ foo: 1 }, {
  [Symbol()]: {
    value: 1,
    enumerable: false
  },
  bar: {
    value: 2,
    enumerable: false
  },
  [Symbol()]: {
    value: 3,
    enumerable: true
  },
  baz: {
    value: 4,
    enumerable: true
  }
})

Object.assign({}, obj)  // {baz: 4, Symbol(): 3}

唉,的确是这样呢!看来Object.assign也不是我们的理想归宿啊。我们再回过头来看看Reflect.ownKeys,上面挖的坑也该填了,我们在讲Symbol的时候,Object.getOwnPropertySymbols + for...in直接用Reflect.ownKeys替代了,在从可枚举的角度出发看看,好像哪里不对,for...in只能循环遍历对象自身的和继承的可枚举的属性,且不含 Symbol。头都大了吗?来来来,喝完这杯,还有一杯,继续接着来。这么多循环,我们来缕缕头绪:

for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。

Object.keys()返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。

Object.getOwnPropertyNames()返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。

Object.getOwnPropertySymbols()返回一个数组,包含对象自身的所有 Symbol 属性的键名。

Reflect.ownKeys()返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

终于清晰了,或许也该结束了吧。

慢着,好像上面的例子让我想到了什么!!!

属性描述符

我们在来思考一个例子:

const source = {
  get foo() { return 1 }
};
const target = {};

Object.assign(target, source) // { foo: 1 }

好像并不是我们想要的呢,遍历的方式好像也不适用了,这可怎么办。别急,还有Object.getOwnPropertyDescriptors可以用。

ES2017 引入了Object.getOwnPropertyDescriptors方法,返回指定对象所有自身属性(非继承属性)的描述对象

仔细阅读下文档,终于用Object.getOwnPropertyDescriptors+Object.getPrototypeOf成功了呢

Object.create(
  Object.getPrototypeOf(obj), 
  Object.getOwnPropertyDescriptors(obj) 
)

写到这里,浅拷贝部分也该结束了

结束语

可能实际项目中并不需要处理的这么细致,但是希望大家对各种遍历、实现一个浅拷贝以及ES6的一些知识有一个总结和一点新的认识吧,本来想继续写深拷贝的,无赖篇幅已经不短,加上长夜漫漫,我想睡觉,深拷贝的问题更复杂,我先放放,日后再说。

最后的最后,对这篇文章有兴趣的朋友,可以继续关注下一篇的深克隆,会更新的会更新的...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107076.html

相关文章

  • ES6时代真的克隆对象(二)

    摘要:多个窗口意味着多个全局环境,不同的全局环境拥有不同的全局对象,从而拥有不同的内置类型构造函数。比如,表达式会返回,因为属性得到的仅仅是构造函数,而且是可以被手动更改的,只是返回的构造函数的名字,它并不返回类名。 原文:ES6时代,你真的会克隆对象吗(二) 上一篇,我们从Symbol和是否可枚举以及属性描述符的角度分析了ES6下怎么浅拷贝一个对象,发表在掘金和segmentfault上(...

    BoYang 评论0 收藏0
  • 从-1开始的ES6探索之旅02:小伙子,对象咋来的?续篇 - 对象班(class)里来的?

    摘要:这是因为子类没有自己的对象,而是继承父类的对象,然后对其进行加工。 温馨提示:作者的爬坑记录,对你等大神完全没有价值,别在我这浪费生命温馨提示-续:你们要非得看,我也拦不住,但是至少得准备个支持ES6的Chrome浏览器吧?温馨提示-再续:ES6简直了,放着不用简直令人发指! 书接上回,即便是程序员,也还是能够通过自己的努力辛辛苦苦找到合适对象的,见前文《javascript对象不完全...

    incredible 评论0 收藏0
  • 白话es6系列二:真的声明变量

    摘要:新增了二个声明变量的关键字,和,再加上之前的,这样声明变量就有三个关键字了,大有三国鼎立之势。当的值为时,该变量不会被声明并初始化。如果上面的那个循环中用声明变量,那么循环完了,变量也就随时销毁,不能再被访问。 ES6新增了二个声明变量的关键字,let和const,再加上ES6之前的var,这样声明变量就有三个关键字了,大有三国鼎立之势。那到底用哪个来声明变量呢? var 首先,得说说...

    maybe_009 评论0 收藏0
  • 真的知道JS

    摘要:你真的知道吗是一门奇怪的语言,要真正掌握并不容易。废话不多说,来一个快速测试,道题目,看看你对是否真正掌握。操作符用来判断某个属性属于某个对象,可以是对象的直接属性,也可以是通过继承的属性。很多人会认为打印的是。 你真的知道JavaScript吗     JavaScript是一门奇怪的语言,要真正掌握并不容易。废话不多说,来一个快速测试,5道题目,看看你对JavaScript是否真正...

    Half 评论0 收藏0
  • JavasScript重难点知识

    摘要:忍者级别的函数操作对于什么是匿名函数,这里就不做过多介绍了。我们需要知道的是,对于而言,匿名函数是一个很重要且具有逻辑性的特性。通常,匿名函数的使用情况是创建一个供以后使用的函数。 JS 中的递归 递归, 递归基础, 斐波那契数列, 使用递归方式深拷贝, 自定义事件添加 这一次,彻底弄懂 JavaScript 执行机制 本文的目的就是要保证你彻底弄懂javascript的执行机制,如果...

    forsigner 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<