Js深浅拷贝原理

更新日期: 2019-09-13阅读: 2.3k标签: 原理

如果拷贝的值是基本数据类型,拷贝的是基本类型的值。如果是引用类型拷贝的是内存地址。浅拷贝只解决了第一层的问题,拷贝第一层的基本类型值,以及第一层的引用类型地址。

也就是说:只能保证第一层数据为基本数据类型时,不会随原数据改变。原数据中包含子对象时,随原数据变化。


1.1 浅拷贝的使用场景

1. Object.assign()

该方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

let a = {
  name: 'owenli',
  book: {
    title:'前端进阶之路',
    price: '66'
  }
}
let b = Object.assign({}, a)
console.log('b原始值')
console.log(b)
a.name = '啦啦啦'
a.book.price = '88'
console.log('修改 name 和 price 后')
console.log(a)
console.log(b) // b 中的book.price也是 88。

注意:Object.assign() 不是深拷贝。

2. 展开运算符

let c = {...a}
//...
console.log('展开运算符...')
console.log(c)
console.log(a)

展开运算符的效果和 Object.assign 相同。

另外,slice 、concat 等也是浅拷贝。


二、深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。

2.1 使用场景

1. JSON.parse(JSON.stringify(object))

let a = {
  name: 'owenli',
  book: {
    title: '啦啦啦',
    price: '44'
  }
}
let b = JSON.parse(JSON.stringify(a)) // 深拷贝
console.log(a)

a.name = '啦啦啦'
a.book.price = '66'

console.log(a)
console.log(b)
// a 和 b 之间互不影响。

注意:undefined、Symbol 和 函数三种情况会直接忽略。不能处理 new Date() 和正则表达式。


三、浅拷贝 Object.assigin 的原理及实现

Object.assign(target, ...sources)

其中 target 是目标对象,sources 是源对象,可以有多个,返回修改后的目标对象 target。

如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖。后来的源对象的属性将类似地覆盖早先的属性。

3.1 Object.assign 模拟实现

// 第一步判断 Object 是否支持 assign2 
if (typeof Object.assign2 != 'function') {
  // 第二步:不存在assign2, 使用Object.definePerperty 创建并将函数绑定到Object上。
  Object.defineProperty(Object, 'assign2', {
    value: function (target) {
      'use strict' // 在严格模式下对不可写的属性进行修改时会报错。默认情况下静默失败。
      // 第三步:判断参数是否正确,target 不可以为空可以是 {}。
      if (target == null) {
        throw new TypeError('Cannot convert undefined or null to object')
      }
      // 第四步:使用Object() 转为对象,保存为 to,最后返回 to。将原始类型包装成对象。
      var to = Object(target)
      for (var index = 1; index < arguments.length; index++){
        var nextSource = arguments[index]
        if (nextSource != null) {
          // 第五步:for...in遍历出所有可枚举的自有属性。并赋值个新的变量
          for (var nextKey in nextSource) {
            // 可访问性:hasOwnProperty 判断对象 nextSoruce 中是否存在 nextKey 属性。
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              // 赋值给 对象 to。
              to[nextKey] = nextSource[nextKey]
            }
          }
        }
      }
      return to
    },
    // enumerable: false, // 不可枚举,原生Object属性是不可以枚举的。默认为 false。
    writable: true,
    configurable: true
  })
}

let a = {
  name: '1111',
  age: 19
}
let b = {
  name: 'owenli',
  book: {
    title: 'lalalal',
    price: 20
  }
}
let c = Object.assign2(a, b)
console.log(c)

console.log(a === c)

3.2 可枚举性

原生的Object中的属性是不可以枚举的。 Object.assign 是否可以被枚举。

Object.getOwnPropertyDescriptor(Object, 'assign')
// {value: ƒ, writable: true, enumerable: false, configurable: true} enumerable:false 说明是不可以被枚举。
Object.propertyIsEnumerable('assign')
// false

直接向 Object 上挂载属性是可以枚举的。

Object.a = function () {
  //...
}
Object.getOwnPropertyDescriptor(Object, 'a')
// {value: ƒ, writable: true, enumerable: true, configurable: true} 
// 直接挂载 a ,是可以枚举的。

所以要想实现 Object.assign 就需要使用 Object.defineProperty,并设置 writable: true, enumerable: false, configurable: true。默认都是 false。

Object.defineProperty(Object, 'b', {
	value: function() {
        console.log('b')
    }
})
Object.getOwnPropertyDescriptor(Object, 'b')
// {value: ƒ, writable: false, enumerable: false, configurable: false}

综上: Object.assign2 实现是需要设置 writable: true, configurable: true 。因为默认是enumerable 是 false。

3.3 参数判断

undefined == null // true。

在使用判断的时候直接用 target == null 。

3.4 原始类型包装成对象

var v1 = 'abc'
var v2 = true
var v3 = 10
var v4 = Symbol('foo')
// 原始类型会被包装,null 和 undefined 会被忽略。
var obj = Object.assign({}, v1, null, v2, undefined, v3, v4)

console.log(obj)

// {0: "a", 1: "b", 2: "c"}

v2, v3, v4 被忽略,原因是自身没有可枚举的属性。

通过 Object.keys() 和 Object.getOwnPropertyNames() 可以查看枚举属性和所有的属性。

// 通过 Object.keys() 测试,是否含有可枚举属性。
console.log(Object.keys(v1)) // ["0", "1", "2"]
console.log(Object.keys(v2)) // []
console.log(Object.keys(v3)) // []

// 获取所有属性 Object.getOwnPropertyNames(),无论是不是枚举类型。

console.log(Object.getOwnPropertyNames(v1)) // ["0", "1", "2", "length"]
console.log(Object.getOwnPropertyNames(v2)) // []

那没 Object.assign 是如何实现:

var a = 'abc'
var b = {
  v1: 'def',
  v2: true, 
  v3: 10,
  v4: Symbol('foot'),
  v5: null,
  v6: undefined
}
var objc = Object.assign(a, b)
console.log(objc) // k可以获取到 v2, v3, v4 .. 的值。

原因:因为 undefined , true 不是作为对象,而是作为对象 b 的属性值,对象 b 是可以枚举的。

console.log(Object.keys(b)) // 可枚举

这里其实又可以看出一个问题来,那就是目标对象是原始类型,会包装成对象,对应上面的代码就是目标对象 a 会被包装成 [String: ‘abc’],那模拟实现时应该如何处理呢?很简单,使用 Object(..) 就可以了。

另外,在严格模式下向不可修改的对象属性赋值会报错。默认情况静默失败。

3.5 存在性

如何在不访问对象属性的情况下判断属性是否存在。

var obj = {
  a: 1
}
// newObj 是关联到 obj 的对象。
var newObj = Object.create(obj)
newObj.b = 10

console.log(newObj)

// in 操作符会检查属性是否在对象及其 `[[Prototype]]`原型链中。
console.log('a' in newObj) // true
console.log('b' in newObj) // true
// 只检测是否在 newObj 中,不检查原型链。
console.log(newObj.hasOwnProperty('a')) // false
console.log(newObj.hasOwnProperty('b')) // true

两种方法的区别:

  1. in 操作符检查 对象 和 原型链。
  2. hasOwnProperty 值检查 对象,不检查原型链。

模拟实现 Object.assign 只需检测对象中属性,不需要检查原型链。

var obj1 = Object.create(null)
obj1.b = 2
// 问题: b 没有连接到prototype上
console.log(obj1.hasOwnProperty('b')) // false
console.log(Object.prototype.hasOwnProperty.call(obj1, 'b')) // true

链接: https://fly63.com/article/detial/5902

CSS定位之BFC背后的神奇原理

BFC已经是一个耳听熟闻的词语了,网上有许多关于 BFC 的文章,介绍了如何触发 BFC 以及 BFC 的一些用处(如清浮动,防止 margin 重叠等)。BFC直译为\"块级格式化上下文\"。它是一个独立的渲染区域,只有Block-level box参与

天天都在使用CSS,那么CSS的原理是什么呢?

作为前端,我们每天都在与CSS打交道,那么CSS的原理是什么呢?开篇,我们还是不厌其烦的回顾一下浏览器的渲染过程,学会使用永远都是最基本的标准,但是懂得原理,你才能触类旁通,超越自我。

JavaScript 中的函数式编程原理

做了一些研究,我发现了函数式编程概念,如不变性和纯函数。 这些概念使你能够构建无副作用的功能,而函数式编程的一些优点,也使得系统变得更加容易维护。我将通过 JavaScript 中的大量代码示例向您详细介绍函数式编程和一些重要概念。

Angular ZoneJS 原理

如果你阅读过关于Angular 2变化检测的资料,那么你很可能听说过zone。Zone是一个从Dart中引入的特性并被Angular 2内部用来判断是否应该触发变化检测

Vue.js响应式原理

updateComponent在更新渲染组件时,会访问1或多个数据模版插值,当访问数据时,将通过getter拦截器把componentUpdateWatcher作为订阅者添加到多个依赖中,每当其中一个数据有更新,将执行setter函数

new运算符的原理

一个继承自 Foo.prototype 的新对象被创建;使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数时,Foo 不带任何参数调用的情况

彻底弄懂HTTP缓存机制及原理

Http 缓存机制作为 web 性能优化的重要手段,对于从事 Web 开发的同学们来说,应该是知识体系库中的一个基础环节,同时对于有志成为前端架构师的同学来说是必备的知识技能。

https的基本原理

HTTPS = HTTP + TLS/SSL,简单理解 HTTPS 其实就是在 HTTP 上面加多了一层安全层。HTTP 可以是 Http2.0 也可以是 Http1.1,不过现在 Http2.0 是强制要求使用 Https 的。使用非对称密钥(即公钥私钥))和对称密钥)(即共享密钥)相结合

Node中的Cookie和Session

HTTP是无状态协议。例:打开一个域名的首页,进而打开该域名的其他页面,服务器无法识别访问者。即同一浏览器访问同一网站,每次访问都没有任何关系。Cookie的原理是

理解Promise原理

Promise 必须为以下三种状态之一:等待态(Pending)、执行态(Fulfilled)和拒绝态(Rejected)。一旦Promise 被 resolve 或 reject,不能再迁移至其他任何状态(即状态 immutable)。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!