【H2O2|全栈】JS进阶知识(八)ES6(4)

news2024/11/24 7:03:50

目录

前言

开篇语

准备工作

浅拷贝和深拷贝

浅拷贝

概念

常见方法

弊端

案例

深拷贝

概念

常见方法

弊端

逐层拷贝

原型

构造函数

概念

 形式

成员

弊端

显式原型和隐式原型

概念

形式

constructor

概念

形式

原型链

概念

形式

结束语


前言

开篇语

本系列博客主要分享JavaScript的进阶语法知识,本期为第八期,依然围绕ES6的语法进行展开。

本期内容为:拷贝和原型。

与基础部分的语法相比,ES6的语法进行了一些更加严谨的约束和优化,因此,在之后使用原生JS时,我们应该尽量使用ES6的语法进行代码编写。

准备工作

软件:【参考版本】Visual Studio Code

插件(扩展包):Open in browser, Live Preview, Live Server, Tencent Cloud AI Code Assistant, htmltagwrap

提示:在不熟练的阶段建议关闭AI助手

浏览器版本:Chrome

系统版本: Win10/11/其他非Windows版本

浅拷贝和深拷贝

浅拷贝

概念

对于基本数据类型,它们存储于栈内存中,浅拷贝直接就是把这份数据复制一份,依然存储在栈内存中。

而对于引用数据类型,在栈内存中存储的只是指向堆内存中数据的地址。浅拷贝只能复制这个地址,而不能真正复制里面的数据。

简而言之,浅拷贝只能实现复制一层

常见方法

ES6提供的浅拷贝的方式为Object.assign(),JQuery也为我们提供了$.clone()方法。 

弊端

由于浅拷贝只能复制引用类型数据的地址,所以复制之后的“所谓的新变量”实际上和原来的变量还是指向了堆内存中相同的数据

那么,当我们修改“新变量”中的数据时,原来的变量下存储的数据同样被改变了。

案例

来看下面的例子——

let obj1 = {name: 'aa', age: 18}

let obj2 = obj1;

obj2.age = 20;

console.log(obj1)

此时输出结果为:

很显然,我们的目的明明只是更改obj2的数据,最后却将obj1的数据更改了。 

深拷贝

概念

对于引用类型的数据,我们需要创建一个新的空对象,对数据内部的每一项都进行拷贝,并添加到空对象中,以此达到真正的意义上的拷贝。

有时,我们对象中的数据也可能是引用类型的,乃至在这些数据中,还有多层的引用类型数据嵌套。我们的深拷贝,应该不断深入至数据项的每一层,确保每一层都进行拷贝

简单来说,深拷贝就是多层拷贝,逐层进行拷贝。

常见方法

我们知道,JSON的stringify()方法可以按照对象原来的格式(结构)进行复制,而parse()方法又可以将它们转换回对象。

JSON.parse(JSON.stringify(obj))

在两次转换的过程中,parse()实际上返回了一个新的对象,由此,我们就实现了引用数据类型的深拷贝。

弊端

然而,使用JSON转化的方式实现深拷贝,会带来如下的问题——

  1. 对象属性的enumerable: false会失效
  2. Date数据类型会被转化为字符串
  3. RegExp,Error数据类型会被转换成空对象 {}
  4. undefined,function,symbol的属性会丢失
  5. NaN,-Infinity,Infinity将会被转化为null

这就会导致我们复制的新对象和原对象的数据可能会出现差异,造成拷贝失败的后果。

逐层拷贝

对此,我们处理上述问题的思路是不一次性全处理,而是分开应对各类情况。 

由于我们的数据项中可能依然有引用数据类型或多层的结构,所以我们要不断地向深层进行搜索,而递归方法就能很好的解决多层搜索的问题,即在遇到某些引用类型的数据时,重复搜索-拷贝的步骤。

假设我们的递归方法的名称为deep(ori),传入的参数ori为待拷贝变量,方法内部返回拷贝后的结果的变量res。

首先,判断这个变量是否为引用数据类型,即使用typeof运算符得到的结果为Object,如果不是,则为基本数据类型,直接使用赋值运算符=复制即可。

if (typeof ori == 'object') {

} else {
    res = ori;
}

然后,利用构造器constructor判断数据是否为RegExp,Date和null中的一种,如是则依然直接使用=复制。

这三种情况下,数据不会是多层的结构,不需要再进入数据内部拷贝深层。

if (ori.constructor == RegExp || ori.constructor == Date || !ori) {
    res = ori
}

然后,判断数据是否为Array类型,这种类型的数据可以存储元素,所以需要对其进行遍历。

而Array的每一项,依然可能为Array或对象等可以存储元素的结构,因此还需要对遍历的每一项item再进行一次deep(item)(递归)。

else if (ori.constructor == Array) {
    res = []
    ori.forEach(item => res.push(deep(item)))
}

最后,剩下的情况就是对象类型,使用类似处理数组的方式处理即可。

else {
    res = {}
    for (let key in ori) {
        res[key] = deep(ori[key])
    }
}

最后,逐层拷贝的完整代码如下——

  function deep(ori) {
    let res;
    if (typeof ori == 'object') {
      if (ori.constructor == RegExp || ori.constructor == Date || !ori) {
        res = ori
      } else if (ori.constructor == Array) {
        res = []
        ori.forEach(item => res.push(deep(item)))
      } else {
        res = {}
        for (let key in ori) {
          res[key] = deep(ori[key])
        }
      }
    } else {
      res = ori
    }
    return res
  }

原型

构造函数

概念

构造函数是我们用来创建实例化对象的方法,它将传入的参数赋值给this中对应的属性。它的this指向为创建出来的实例化对象。

 形式

比方说,我们想要用构造函数Per来创建人对象,那么我们可以这样来写Per()方法——

  function Per(name, age, sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    this.getMsg = function () {
      console.log(`姓名:${this.name};年龄:${this.age};性别:${this.sex}。`);
    }
  }

成员

构造函数的成员是指构造函数内部的属性,分为实例成员静态成员两种。

实例成员是构造函数内通过this创建的成员,这类成员的特点是可以被实例化,也就是可以使用下面的形式去访问——

实例.成员

静态成员是构造函数外通过构造函数名创建的成员,这类成员的特点是不可以被实例访问,但是只能使用下面的形式去访问——

构造函数名.成员

弊端

我们知道,方法也是可以作为构造函数的实例成员的。

比如,我们创建两个人对象——

let aa = Per('aa', 22, 'man');
let bb = Per('bb', 20, 'man');

对于它们的getMsg()成员,使用==进行比较,显然是不同的。

console.log(aa.getMsg == bb.getMsg); // false

因此,这种方式存在一个问题,就是我们每创建一个实例化对象调用一次方法,就需要开辟一块新的栈空间来完成一件重复的事,造成内存浪费的问题。

所以,我们需要使用一块公用的空间,来存储同一个构造函数创建出来的公用方法

如果使用静态成员方法,由于它不能被实例对象访问,所以方法的this将会失效

显式原型和隐式原型

概念

原型实际上就是一块空间,用来存储一些构造函数和实例化对象的公用方法与属性。

当我们使用到这些公用的内容时,就可以利用原型来访问,由此达到节省空间的目的。

对于构造函数,使用prototype来获取原型,这类原型就是显式原型

对于实例化对象,使用__proto__来获取原型(注意两边都有两个_哈),这类原型就是隐式原型

对于构造函数和它实例化的对象,它们的显式原型和隐式原型相同

形式

将上面的getMsg()方法添加到原型中,就像下面这样——

Per.prototype.getMsg = function () {
    console.log(`姓名:${this.name};年龄:${this.age};性别:${this.sex}。`);
}

那么,对于Per构造函数创建出来的所有实例化对象,都可以调用原型的getMsg()方法。

对于下面的两种方式调用getMsg(),实际上访问的是内存中的相同位置——

console.log(Per.prototype === aa.__proto__); // true

constructor

概念

constructor,中文释义为构造函数,用来从原型指回原来的构造函数

我们可以在原型中声明多个方法,然后利用constructor把它们一次性交给原来的构造函数。

形式

使用下面的三个方法,将Per实例化对象的name,age,sex属性分别输出出来——

  Per.prototype = {
    constructor: Per,
    getName() {
      console.log(this.name);
    },
    getAge() {
      console.log(this.age);
    },
    getSex() {
      console.log(this.sex);
    }
  }

原型链

概念

原型链,实质上就是链式查找方法。

如果在调用方法时,自身没有方法,则程序会在原型链上查找方法。

一条完整的原型链为实例=>构造函数原型=>Object=>null(报错),Object是所有构造函数、对象的原型,又叫做基类

如果在基类上依然没有找到我们需要的方法,则程序会向我们报错,提示没有该方法。

形式

最长的原型链如下——

实例化对象.prototype.__proto__.__proto__

在该位置仍然找不到目标方法时报错。

整个原型链的各个部分的转化关系示意图如下——

结束语

本期内容到此结束。关于本系列的其他博客,可以查看我的JS进阶专栏。

在全栈领域,博主也只不过是一个普通的萌新而已。本系列的博客主要是记录一下自己学习的一些经历,然后把自己领悟到的一些东西总结一下,分享给大家。

文章全篇的操作过程都是笔者亲自操作完成的,一些定义性的文字加入了笔者自己的很多理解在里面,所以仅供参考。如果有说的不对的地方,还请谅解。

==期待与你在下一期博客中再次相遇==

——临期的【H2O2】

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

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

相关文章

RabbitMQ3:Java客户端快速入门

欢迎来到“雪碧聊技术”CSDN博客! 在这里,您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者,还是具有一定经验的开发者,相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导,我将…

zotero7 插件使用

zotero style 1、下载地址 Zotero 插件商店 | Zotero 中文社区 2、配置 在工具插件里 3、配置 style 进入高级→设置编辑器 查找 easy 设置完即可显示, 注1:easyscholar的密钥要自行申请注册,注册地址:easySchol…

uniapp+vue3+ts H5端使用Quill富文本插件以及解决上传图片反显的问题

uniappvue3ts H5端使用Quill富文本插件以及解决上传图片反显的问题 1.在项目中安装Quill npm i quill1.3.72.需要显示富文本的页面完整代码 <template><view><div ref"quillEditor" style"height: 65vh"></div></view> &…

关于pip install 包 时出现This is an issue with the package mentioned above,not pip的问题

关于This is an issue with the package mentioned above,not pip 今天在用pip下载gensim包的时候&#xff0c;出现了上图中的问题&#xff0c;提示信息是&#xff1a;This is an issue with the package mentioned above,not pip 那说明是包的问题&#xff0c;而不是在使用 pi…

rust中解决DPI-1047: Cannot locate a 64-bit Oracle Client library问题

我们在使用rust-oracle crate连接oracle进行测试的过程中&#xff0c;会发现无法连接oracle&#xff0c;测试运行过程中抛出“DPI-1047: Cannot locate a 64-bit Oracle Client library”错误。该问题是由于rust-oracle需要用到oracle的动态连接库&#xff0c;我们通过安装orac…

软件测试面试之常规问题

1.描述一下测试过程 类似题目:测试的生命周期 思路:这是一个“范围”很大的题目&#xff0c;而且回答时间一般在3分钟之内&#xff0c;不可能非常详细的描述整个过程&#xff0c;因此答题的思路要从整体结构入手&#xff0c;不要过细。为了保证答案的准确性&#xff0c;可以引…

D77【 python 接口自动化学习】- python基础之HTTP

day77 postman接口请求 学习日期&#xff1a;20241123 学习目标&#xff1a;http 定义及实战&#xfe63;&#xfe63;postman接口请求 学习笔记&#xff1a; get请求 post请求 总结 get请求用于查询数据post请求用于添加数据

在 Swift 中实现字符串分割问题:以字典中的单词构造句子

文章目录 前言摘要描述题解答案题解代码题解代码分析示例测试及结果时间复杂度空间复杂度总结 前言 本题由于没有合适答案为以往遗留问题&#xff0c;最近有时间将以往遗留问题一一完善。 LeetCode - #140 单词拆分 II 不积跬步&#xff0c;无以至千里&#xff1b;不积小流&…

【从零开始的LeetCode-算法】3297. 统计重新排列后包含另一个字符串的子字符串数目 I

给你两个字符串 word1 和 word2 。 如果一个字符串 x 重新排列后&#xff0c;word2 是重排字符串的 前缀&#xff0c;那么我们称字符串 x 是 合法的 。 请你返回 word1 中 合法 子字符串的数目。 示例 1&#xff1a; 输入&#xff1a;word1 "bcca", word2 "…

.NET9 - 新功能体验(二)

书接上回&#xff0c;我们继续来聊聊.NET9和C#13带来的新变化。 01、新的泛型约束 allows ref struct 这是在 C# 13 中&#xff0c;引入的一项新的泛型约束功能&#xff0c;允许对泛型类型参数应用 ref struct 约束。 可能这样说不够直观&#xff0c;简单来说就是Span、ReadO…

Git错误:gnutls_handshake() failed: The TLS connection was non-properly terminated

最终我通过这个博客解决了问题&#xff1a;解决Git错误&#xff1a;gnutls_handshake() failed: The TLS connection was non-properly terminated 解决方案 1. 检查网络连接 首先&#xff0c;确保你的网络连接是稳定的。尝试访问其他HTTPS网站或服务&#xff0c;以排除网络问…

【C++知识总结】c++第一篇,简单了解一下命名空间是什么

一、C的由来 C语言是一种结构化和模块化的编程语言&#xff0c;它对于处理较小规模的程序非常适用。然而&#xff0c;当面临需要高度抽象和建模的复杂问题&#xff0c;以及规模较大的程序时&#xff0c;C语言就显得不那么合适了。为了应对这种挑战&#xff0c;并在解决软件危机…

Sickos1.1 详细靶机思路 实操笔记

Sickos1.1 详细靶机思路 实操笔记 免责声明 本博客提供的所有信息仅供学习和研究目的&#xff0c;旨在提高读者的网络安全意识和技术能力。请在合法合规的前提下使用本文中提供的任何技术、方法或工具。如果您选择使用本博客中的任何信息进行非法活动&#xff0c;您将独自承担…

【大数据学习 | Spark-Core】Spark提交及运行流程

spark的集群运行结构 我们要选择第一种使用方式 命令组成结构 spark-submit [选项] jar包 参数 standalone集群能够使用的选项。 --master MASTER_URL #集群地址 --class class_name #jar包中的类 --executor-memory MEM #executor的内存 --executor-cores NUM # executor的…

【CSP CCF记录】201903-2第16次认证 二十四点

题目 样例1输入 10 934x3 54x5x5 7-9-98 5x6/5x4 3579 1x19-9 1x9-5/9 8/56x9 6x7-3x6 6x44/5 样例1输出 Yes No No Yes Yes No No No Yes Yes 样例1解释 思路 参考&#xff1a;CCF小白刷题之路---201903-2 二十四点&#xff08;C/C 100分&#xff09;_ccf认证小白-CSDN博客 …

多目标粒子群优化(Multi-Objective Particle Swarm Optimization, MOPSO)算法

概述 多目标粒子群优化&#xff08;MOPSO&#xff09; 是粒子群优化&#xff08;PSO&#xff09;的一种扩展&#xff0c;用于解决具有多个目标函数的优化问题。MOPSO的目标是找到一组非支配解&#xff08;Pareto最优解&#xff09;&#xff0c;这些解在不同目标之间达到平衡。…

tomcat 后台部署 war 包 getshell

1. tomcat 后台部署 war 包 getshell 首先进入该漏洞的文件目录 使用docker启动靶场环境 查看端口的开放情况 访问靶场&#xff1a;192.168.187.135:8080 访问靶机地址 http://192.168.187.135:8080/manager/html Tomcat 默认页面登录管理就在 manager/html 下&#xff0c…

4.6 JMeter HTTP信息头管理器

欢迎大家订阅【软件测试】 专栏&#xff0c;开启你的软件测试学习之旅&#xff01; 文章目录 前言1 HTTP信息头管理器的位置2 常见的HTTP请求头3 添加 HTTP 信息头管理器4 应用场景 前言 在 JMeter 中&#xff0c;HTTP信息头管理器&#xff08;HTTP Header Manager&#xff09…

NVR管理平台EasyNVR多品牌NVR管理工具的流媒体视频融合与汇聚管理方案

随着信息技术的飞速发展&#xff0c;视频监控已经成为现代社会安全管理和业务运营不可或缺的一部分。无论是智慧城市、智能交通、还是大型企业、校园安防&#xff0c;视频监控系统的应用都日益广泛。NVR管理平台EasyNVR&#xff0c;作为功能强大的流媒体服务器软件&#xff0c;…

【大数据学习 | Spark-Core】Spark的改变分区的算子

当分区由多变少时&#xff0c;不需要shuffle&#xff0c;也就是父RDD与子RDD之间是窄依赖。 当分区由少变多时&#xff0c;是需要shuffle的。 但极端情况下&#xff08;1000个分区变成1个分区)&#xff0c;这时如果将shuffle设置为false&#xff0c;父子RDD是窄依赖关系&…