拷贝、原型原型链

news2024/11/24 5:53:08

浅拷贝

将原对象或原数组的引用直接赋给新对象,新数组

新对象只是对原对象的一个引用,而不复制对象本身。新旧对象还是共享同一块内存

如果属性是一个基本数据类型,拷贝的就是基本数据类型的值

如果属性是引用类型,拷贝的是内存地址。存在数据“共享”,一个对象改变了这个地址指向的属性值会影响到另一个对象的属性值

浅拷贝的实现方式

Object.assign()

ES6 中 Object 内置对象的方法,该方法可以用于 js 对象的合并等用途,其中一个用途就是可以进行浅拷贝

语法:Object.assign(target, ...sources)
target:拷贝的目标对象
sources:拷贝的来源对象,可以是多个来源

使用 Object.assign() 的注意点:

  • 不会拷贝对象的继承属性
  • 不会拷贝对象的不可枚举属性
  • 可以拷贝 Symbol 类型的属性 

扩展运算符

利用扩展运算符,可以在构造对象时完成浅拷贝的功能

语法:let cloneObj = { ...obj }

扩展运算符和 Object.assign() 有同样的缺陷,也就是实现的功能相差不大

但如果属性都是基本数据类型,使用扩展运算符进行浅拷贝会更方便 

数组 concat()

使用 concat 方法连接一个含有引用值的数组时,需要注意修改原数组中的元素属性,因为它会影响拷贝后连接的数组

concat 方法只适用于数组的浅拷贝,使用场景比较局限

数组 slice()

slice 方法也只适用于数组的浅拷贝,使用场景比较局限

语法:arr.slice(begin, end)
begin 开始截取的元素下标
end 结束截取的元素下标(不包括)

slice 方法会返回一个新的数组对象,不会改变原数组

总结

1. Object.assign()
2. ...  扩展运算符
3. Array.prototype.concat()
4. Array.prototype.slice()

浅拷贝的限制在于只能拷贝一层属性,如果存在嵌套属性,浅拷贝就差点意思

此时就要用到深拷贝,解决多层对象嵌套问题,彻底实现拷贝

手写浅拷贝

根据浅拷贝的定义,如果要手动封装一个浅拷贝方法,大致思路如下:

判断源数据类型,如果是基本类型,直接返回源数据;如果是引用类型,for...in 遍历源数据内部属性/元素(浅拷不会拷贝不可枚举属性),判断是否是源数据自由属性/元素(浅拷贝不会拷贝继承属性),内部属性/元素为基本类型,直接赋值;内部属性/元素为引用类型,复制地址,数据共享

代码如下:

const shallowClone = (target) => {
  if (typeof target === 'object' && target !== null) {
    const cloneTarget = target instanceof Array ? [] : {}
    for (let item in object) {
      if (target.hasOwnProperty(item)) {
        cloneTarget[item] = target[item]
      }
    }
    return cloneTarget
  } else {
    return target
  }
}

let obj = { a: 1, b: 2, c: { cc: 3 } }
let cloneObj = shallowClone(obj)
cloneObj.b = 3
// 此时 obj 中的 b 值仍为 2
cloneObj.c.cc = 4
// 此时 obj 中的 cc 值也变为 4,证明共享了一片内存,浅拷贝
console.log(obj, cloneObj)

可实现普通对象、数组对象、函数对象和基本数据类型的浅拷贝 

深拷贝

浅拷贝只是创建了一个新的对象,复制了源对象的基本类型的值;对于引用数据,只复制了地址,拷贝出来的数据和源数据共用同一个内存空间,只是多了个指向该空间的引用

深拷贝和浅拷贝的最大不同之处在于:深拷贝对于引用数据类型,会在堆内存中完全开辟一个新的内存空间,并将源对象完全复制过来存放

这两个对象是相互独立,互不影响的,彻底实现了内存上的分离

总的来说,深拷贝的原理可以总结为:

将源对象从内存中完整地拷贝出来一份给目标对象,并在堆内存中开辟一个全新的空间存放新对象,且新对象的修改不会改变源对象,二者实现真正的分离

深拷贝的实现方式

JSON.stringify

是目前开发过程中最简单的实现深拷贝的方法,其实就是将一个对象序列化为 JSON 字符串,并将对象里面的内容转换成字符串,最后再用 JSON.parse 方法将 JSON 字符串生成一个新的对象

let arr = [1, 2, { a: 3 }]
let cloneArr = JSON.parse(JSON.stringify(arr))
cloneArr[2].a = 4
// 此时 arr 中第三个元素对象的属性 a 的值仍为 3
console.log(arr, cloneArr)

let obj = { a: 1, b: 2, c: { cc: 3 } }
let cloneObj = JSON.parse(JSON.stringify(obj))
cloneObj.b = 22
cloneObj.c.cc = 33
// 修改 cloneObj 的属性值,不会影响 obj 的属性值
console.log(obj, cloneObj)

使用 JSON.stringify 实现深拷贝需要注意:

  • 拷贝对象的属性值中如果有函数、undefined、Symbol 这几种类型,经过 JSON.stringify 序列化之后的字符串中这个键值对会消失
  • 拷贝的 Date 类型数据会变成字符串
  • 无法拷贝不可枚举的属性
  • 无法拷贝对象的原型链
  • 拷贝 RegExp 引用类型会变成空对象
  • 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的结果会变成 null
  • 无法拷贝对象的循环应用,即对象成环 obj[key] = obj
function Obj() {
  this.funFun = function () {}
  this.obj = { a: 1 }
  this.arr = [1, 2, 3]
  this.und = undefined
  this.reg = /123/
  this.date = new Date()
  this.NaN = NaN
  this.infinity = Infinity
  this.sym = Symbol(1)
}

let caseObj = new Obj()
Object.defineProperty(caseObj, 'innumerable', {
  enumerable: false,
  value: 'innumerable',
})
console.log('实例对象:', caseObj)

let cloneObj = JSON.parse(JSON.stringify(caseObj))
console.log('克隆对象:', cloneObj)

 

使用 JSON.stringify 方法实现深拷贝对象,虽然还有很多无法实现的功能,但这种方法足以满足日常的开发需求,并且是最简单和快捷的

如果需求比较严格,就需要对复杂一些的属性值进行单独处理

基础版(可适用于大部分情况)

思路:封装 deepClone 函数。判断参数是否是引用类型,不是直接返回;是则通过 for...in 遍历传入参数的属性。如果属性值是引用类型则再次递归调用该函数;如果属性值是基本数据类型则直接复制

const deepClone = (obj) => {
  if (typeof obj === 'object' && obj !== null) {
    let cloneObj = Array.isArray(obj) ? [] : {}
    for (let item in obj) {
      if (typeof obj[item] === 'object' && obj[item] !== null) {
        cloneObj[item] = deepClone(obj[item])
      } else {
        cloneObj[item] = obj[item]
      }
    }
    return cloneObj
  } else {
    return obj
  }
}

let caseObj = new Obj()
Object.defineProperty(caseObj, 'innumerable', {
  enumerable: false,
  value: 'innumerable',
})
console.log('实例对象:', caseObj)

let cloneObj = deepClone(caseObj)
console.log('克隆对象:', cloneObj)

虽然利用递归可以实现深拷贝,但和 JSON.stringify 一样, 仍然有一些问题存在:

  • 不能复制不可枚举属性和 Symbol 类型属性
  • 无法拷贝属性描述符和原型链
  • 只针对普通对象和数组对象做递归复制,而对于 Date、RegExp、Error、Function 这样的引用类型并不能正确拷贝
  • 对象属性里面成环,循环引用的情况无法解决

基础版写法简单,可以应对大部分情况,但仍有缺陷

改进版

针对上面所说的问题,先讲思路:

  • 当参数为 Date、RegExp 类型,则直接生成一个新的实例返回
  • 利用 Object.getOwnPropertyDescriptors 方法可以获得对象的所有属性,以及对应的特性,顺便结合 Object.create 方法创建一个新对象,并继承传入原对象的原型链
  • 遍历对象的不可枚举属性和 Symbol 类型属性,可以用 Reflect.ownKeys 方法
  • 利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱引用类型,可以有效防止内存泄漏,作为检测循环引用很有帮助。如果存在循环,则引用直接返回 WeakMap 存储的值

代码如下:

// 判断是否是复杂引用类型数据
const isComplexDataType = (obj) =>
  (typeof obj === 'object' || typeof obj === 'function') && obj !== null
const deepClone = function (obj, hash = new WeakMap()) {
  // 日期对象和正则对象直接通过构造函数返回
  if (obj.constructor === Date) return new Date(obj)
  if (obj.constructor === RegExp) return new RegExp(obj)
  // 循环引用使用 weakMap 解决
  if (hash.has(obj)) return hash.get(obj)

  // 获取所有属性的描述符
  let allDesc = Object.getOwnPropertyDescriptors(obj)
  // 继承原型链,遍历传入参数所有属性的特性
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
  hash.set(obj, cloneObj)
  for (const key of Reflect.ownKeys(obj)) {
    cloneObj[key] = isComplexDataType(obj[key])
      ? deepClone(obj[key], hash)
      : obj[key]
  }
  return cloneObj
}
let obj = {
  num: 0,
  str: '',
  boolean: true,
  unf: undefined,
  nul: null,
  obj: { name: '我是一个对象', id: 1 },
  arr: [0, 1, 2],
  func: function () {
    console.log('我是一个函数')
  },
  date: new Date(0),
  reg: new RegExp('/我是一个正则/ig'),
  [Symbol('1')]: 1,
}
Object.defineProperty(obj, 'innumerable', {
  enumerable: false,
  value: '不可枚举属性',
})
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
// 设置 loop 成循环引用属性
obj.loop = obj
let cloneObj = deepClone(obj)
obj.arr.push(4)
console.log('实例对象:', obj)
console.log('克隆对象:', cloneObj)

总结

1. JSON.parse(JSON.stringify())
2. 递归操作
3. cloneDeep
4. Jquery.extend()

原型原型链

原型

每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。这个对象就是通过调用构造函数创建的对象的原型

使用原型对象的好处:在原型上定义的属性和方法可以被对象实例共享

无论如何,只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype 属性(指向原型对象)

默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数

在自定义构造函数时,原型对象默认只会获得 constructor 属性,其他的所有方法都继承自 Object。每次调用构造函数创建一个新实例,这个实例的内部 [[Prototype]] 指针就会被赋值为构造函数的原型

脚本中没有访问这个 [[Prototype]] 特性的标准方式,但 Firefox、Safari 和 Chrome 会在每个对象上暴露 _proto_ 属性,通过这个属性可以访问对象的原型

在其他实现中,这个特性被完全隐藏

关键在于理解这一点:实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有

Person 构造函数、Person 的实例对象和 Person 现有的两个实例对象之间的关系

Person.prototype 指向原型对象,而 Person.prototype.constructor 指回 Person 构造函数

原型对象包含 constructor 属性和其他后来添加的属性

Person 的两个实例对象 person1 和 person2 都只有一个内部属性 [[Prototype]] 指回 Person.prototype,且两者都与构造函数没有直接联系

虽然现在这两个实例没有添加更多的属性和方法,但它们可以调用 Person.prototype 所拥有的属性和方法。此处涉及到了对象属性查找机制

虽然不是所有的实现都对外暴露了 [[prototype]],但可以使用 isPrototypeOf() 方法确定两个对象之间的这种关系

本质上,isPrototypeOf 方法会传入参数的 [[Prototype]] 

ian

 

 

 

闭包

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/432349.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Oracle的学习心得和知识总结(二十)|Oracle数据库Real Application Testing之DBMS_SQLTUNE包技术详解

目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《Oracle Database SQL Language Reference》 2、参考书籍:《PostgreSQL中文手册》 3、EDB Postgres Advanced Server User Gui…

SRv6项目实践(二):基本的P4框架

1.数据包头的定义 在实现SRv6之前,有很多的工作需要做,首先先阅读一下p4的代码总体框架,数据包的包头格式一共有如下这些,我们需要把他们的协议逐一完善 struct parsed_headers_t {cpu_out_header_t cpu_out;cpu_in_header_t cpu_in;ethern…

PostgreSQL环境搭建和主备构建

目录 1 Windows 上安装 PostgreSQL2 docker安装PostgreSQL2.1 检索当前镜像2.2. 拉取当前镜像2.3 创建挂载文件夹2.4 启动镜像2.5 查看日志2.7 查看进程2.8 使用连接 3 postgresql主从主备搭建3.1 安装好网络源(主1.11、从1.12)3.2 安装postgresql&#…

(数字图像处理MATLAB+Python)第五章图像增强-第二节:基于直方图修正的图像增强

文章目录 一:灰度直方图(1)定义(2)程序(3)性质 二:直方图修正法理论三:直方图均衡化(1)直方图均衡化变换函数T(r)的求解(2&#xff09…

设计模式-创建型模式之简单工厂模式( Simple Factory Pattern )

1.创建型模式简介创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整…

HCIP之MPLS中的LDP协议

LDP协议 LDP协议 --- 标签分发协议 MPLS控制层面需要完成的工作主要就是分配标签和传递标签。分配标签的前提是本地路由表中得先存在标签,传递标签的前提也是得先具备路由基础。所以,LDP想要正常工作,则需要IGP作为基础。 LDP协议主要需要完…

信号处理流程

1.降噪处理 我们在录制音频数据的同时,大量噪声都会掺杂进来,不同环境和情境下产生的噪声也不尽相同,噪声信号中的无规则波纹信息影响了声学信号所固有的声学特性,使得待分析的声音信号质量下降,并且噪声对声音识别系统…

02-数据库连接池+lombok工具

数据库连接池 概念: 数据库连接池是个容器,负责分配、管理数据库连接(Connection) 它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个 释放空闲时间超过最大空闲时间的连接,来避免因为没有释…

【硬件外设使用】——UART

【硬件外设使用】——UART UART基本概念UART通信协议UART使用方法pyb.uartmachine.uart UART 可用的传感器 UART基本概念 UART全称为Universal Asynchronous Receiver/Transmitter,是通过异步(Asynchronous)方式传输数据的一个串行通信协议。…

C6678开发概述与Sys/bios基本使用

C6678开发概述 参考开发环境标记及术语创建sys/bios自定义平台运行第一个sys/bios程序Clock模块使用Demo 参考 TMS320C6678 Multicore Fixed and Floating-Point Digital Signal Processor Datasheet TMS320C66x DSP CorePac User Guide 官方手册 创龙6678开发教程 开发环境 …

使用 ChatGPT 改善 Android 开发效率的 7 个案例~

翻译 修改自 https://proandroiddev.com/chatgpt-for-android-developers-1c3c1ecc6440,原作者:Rafa Araujo ChatGPT 是由 OpenAI 公司创造的自然语言处理工具,对那些想要提高技能的软件开发人员来说,它绝对是不容错过的重要利器…

日撸 Java 三百行day32

文章目录 说明day32 图的连通性检测1.思路1.1矩阵表示1.2.矩阵相乘1.3结合矩阵运算思考图的连通性。 2.代码 说明 闵老师的文章链接: 日撸 Java 三百行(总述)_minfanphd的博客-CSDN博客 自己也把手敲的代码放在了github上维护:ht…

Linux下安装navicat

1.在https://www.navicat.com.cn/download/navicat-premium下载navicat安装包 2.在终端执行命令 给navicat16-premium-cs.AppImage赋予可执行的权限 chmod x navicat16-premium-cs.AppImage 启动Navicat16 ./navicat16-premium-cs.AppImage 3.点击连接——mysql——输入连…

java单机秒杀扛1万并发方案和代码

我们先来看普通的加锁加事务秒杀性能, 说明: 1.这里的秒杀业务执行一次耗时100毫秒 2.电脑配置16g内存 4核8线程 cpu i7 7代,数据库连接池max20 RequestMapping("/purchase2")public ResultJson purchase2( Long productId){int userId new Random().nextInt(10…

2 常见模块库(2)

2.5 复用器与分路器模块 Mux是一种用于将多个信号组合成一个信号的模块。Mux模块的名称来源于多路复用器(Multiplexer)。 使用Mux可以将多个输入信号组合成一个向量或矩阵,以便在模型中传递和处理。Mux模块可以接受任意数量的输入信号&#x…

Visio Studio 2017利用Qt插件开发Qt应用的安装方法

Visio Studio 2017利用Qt插件开发Qt应用的安装方法 1 安装Visio Studio 20172 安装QT3 在Visio Studio 2017中安装Qt插件 本教程介绍如何利用Visio Studio 2017,开发Qt.5.14.2的Qt应用 1 安装Visio Studio 2017 链接:https://pan.baidu.com/s/1t9j1fFj3…

Linux --- 简介、安装

一、Linux简介 1.1、主流操作系统 不同领域的主流操作系统,主要分为以下这么几类: 桌面操作系统、服务器操作系统、移动设备操作 系统、嵌入式操作系统。接下来,这几个领域中,代表性的操作系统是那些? 1、桌面操作系统 2、服务…

2023年农牧行业数字化:7大CRM软件、5大场景盘点

目录 一、5大业务场景能力,解密农牧行业持续增长秘籍 1、营销获客 2、客户管理 3、商机管理 4、生态“互联”能力 5、业财一体化 二、农牧行业企业CRM选型指南 1、SaaS模式或私有部署 2、是否具有行业成功“经验” 3、可扩展性 4、以营销为主题的体系建设…

【MySQL】基础介绍及表操作

目录 1.MySQL是什么? 2.为什么要学习数据库呢? 内存和硬盘的区别 3.数据库基本操作 1.创建数据库 2.使用数据库 3.删除数据库(慎用) 4.查看警告信息 5.查询当前数据可服务使用的编码集 7.表操作 1.创建一个学生成绩表 2…

Spring Cloud微服务网关Zuul过滤链和整合OAuth2+JWT入门实战

一、Spring Cloud Zuul 过滤链 1.1 工作原理 Zuul的核心逻辑是由一系列的Filter来实现的,他们能够在进行HTTP请求或者相应的时候执行相关操作。Zuul Filter的主要特性有一下几点: Filter的类型:Filter的类型决定了它在Filter链中的执行顺序…