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

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

开始之前

在开始聊克隆之前,我们还是先来看看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的死变态(请允许我这么夸他)。

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的一些知识有一个总结和一点新的认识吧,本来想继续写深拷贝的,无赖篇幅已经不短,加上长夜漫漫,我想睡觉,深拷贝的问题更复杂,我先放放,日后再说。

最后的最后,作为一个前端界的小学生,第一次用掘金写文章,虽然使用掘金挺久了,发表文章还是有点慌,文章有什么错误之处,还请指正。同时对这篇文章有兴趣的朋友,可以继续关注下一篇的深克隆,会更新的会更新的...

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,142评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,298评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,068评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,081评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,099评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,071评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,990评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,832评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,274评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,488评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,649评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,378评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,979评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,625评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,643评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,545评论 2 352

推荐阅读更多精彩内容

  • 1.属性的简洁表示法 允许直接写入变量和函数 上面代码表明,ES6 允许在对象之中,直接写变量。这时,属性名为变量...
    雨飞飞雨阅读 1,134评论 0 3
  • 属性的简洁表示法 ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。 上面代码表明,ES6允...
    呼呼哥阅读 2,913评论 0 2
  • 本文为阮一峰大神的《ECMAScript 6 入门》的个人版提纯! babel babel负责将JS高级语法转义,...
    Devildi已被占用阅读 1,983评论 0 4
  • 三,字符串扩展 3.1 Unicode表示法 ES6 做出了改进,只要将码点放入大括号,就能正确解读该字符。有了这...
    eastbaby阅读 1,532评论 0 8
  • 1 要尊重儿童,不要急于对他作出或好或坏的评判。——卢梭 生活中都没有资格去评价孩子好或不好,因为我们有时做的还不...
    双人鱼莉阅读 356评论 6 7