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

目录

前言

开篇语

准备工作

浅拷贝和深拷贝

浅拷贝

概念

常见方法

弊端

案例

深拷贝

概念

常见方法

弊端

逐层拷贝

原型

构造函数

概念

 形式

成员

弊端

显式原型和隐式原型

概念

形式

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.rhkb.cn/news/477303.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

订单日记为“惠采科技”提供全方位的进销存管理支持

感谢温州惠采科技有限责任公司选择使用订单日记! 温州惠采科技有限责任公司,成立于2024年,位于浙江省温州市,是一家以从事销售电气辅材为主的企业。 在业务不断壮大的过程中,想使用一种既能提升运营效率又能节省成本…

【Isaac Sim】配置 Nucleus 本地服务器

Omniverse 提供了本地(局域)服务器 Nucleus,可以将资产上传到该服务器,Nucleus 能够高效地存储和管理大量三维模型和其他资产,确保用户可以轻松访问这些资源。它还支持多用户环境下的实时协作,使得不同地理…

递归-迭代

24. 两两交换链表中的节点 Leetcode 24 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 递归解法 // 注意:cpp …

小蒋聊技术:大数据驱动决策——技术落地与业务深度融合

时间:2024年 10月 23日 作者:小蒋聊技术 邮箱:wei_wei10163.com 音频: 喜马拉雅 一.数据决策,真的是企业的“未来”吗? 大家好,欢迎来到“小蒋聊技术”!今天,我们继续聊一个让企业关…

无插件直播流媒体音视频播放器EasyPlayer.js播放器的g711系列的音频,听起来为什么都是杂音

在数字化时代,流媒体播放器已成为信息传播和娱乐消遣的重要工具。随着技术的进步,流媒体播放器的核心技术和发展趋势不断演变,以满足用户对于无缝播放、低延迟和高画质的需求。 EasyPlayer播放器属于一款高效、精炼、稳定且免费的流媒体播放…

UVM 验证方法学之interface学习系列文章(七)高级 《bind 操作》(4)级联

在 SystemVerilog 中,bind 操作符用于将一个模块或接口实例绑定到另一个模块或接口的层次结构中。这在很多情况下非常有用,尤其是当你需要在不修改原始模块代码的情况下,添加或替换某些组件时。bind 操作符常用于仿真和测试平台中,以便灵活地组织测试环境。 前面的文章,我…

Vue3+SpringBoot3+Sa-Token+Redis+mysql8通用权限系统

sa-token支持分布式token 前后端代码,地球号: bright12389

Ansys Zemax Optical Studio 中的近视眼及矫正

近视,通常称为近视眼,是一种眼睛屈光不正,导致远处物体模糊,而近处物体清晰。这是一种常见的视力问题,通常发生在眼球过长或角膜(眼睛前部清晰的部分)过于弯曲时。因此,进入眼睛的光…

利用FileZilla搭建ftp服务器

一 利用windows自带的ftp服务搭建服务器,要复杂一些,好处是无需借用外部软件。 也有一些好的工具,如FileZilla的Server版,构建过程简单,好用。 下面看看。 二 安装FileZilla Server 当前下载版本是0.9.43&#xf…

2022 年中高职组“网络安全”赛项-海南省省竞赛任务书-1-B模块B-1-Windows操作系统渗透测试

前言 本章节我将带领大家一起重新模拟操作一次Windows渗透测试模块,并加固的流程。 任务概览 环境部署 我的实验复现环境: 服务器Windows server 2008 R2 攻击机Kali Linux 场景操作系统Windows 7 额外还有台交换机支持: 这里我使用的是…

【滑动窗口】变种题目:leetcode76:最小覆盖子串

前言 滑动窗口是算法的数组部分中非常重要的一个内容,关于滑动窗口的题目,我已经发布过相关的变种题目文章,链接如下,欢迎访问: 【滑动窗口】相关题目分析讲解:leetcode209,leetcode904 如果你不了解什么是滑动窗口&a…

蚁群算法(Ant Colony Optimization, ACO)

简介 蚁群算法(Ant Colony Optimization, ACO)是一种基于自然启发的优化算法,由意大利学者马可多里戈(Marco Dorigo)在1992年首次提出。它受自然界中蚂蚁觅食行为的启发,用于解决离散优化问题。 在自然界…

1-测试go-redis缓存数据

1-测试go-redis缓存数据 1.go-redis缓存数据测试效果 a.测试页面 测试页面:--这里使用 Postman 来做测试 http://127.0.0.1:8000/article/getone/3 http://127.0.0.1:8000/article/getone/4 http://127.0.0.1:8000/article/getone/5b.测试效果 查看终端&#xf…

计算机毕业设计SparkStreaming+Kafka图书推荐系统 豆瓣图书数据分析可视化大屏 豆瓣图书爬虫 知识图谱 图书大数据 大数据毕业设计 机器学习

温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…

字符串的常用函数

目录 一、引入 二、13个字符串的常用函数 总结 一、引入 在C语言中,字符串被视为字符数组的序列,以空字符\0结尾。这个空字符不是数字0,而是一个特殊的控制字符,用于标记字符串的结束。例如,声明char name[7] {R,…

丹摩|重返丹摩(下)

目录 四.模型构建与训练 1.模型选择 (1). 机器学习模型 (2). 深度学习模型 (3). AutoML 功能 2.参数配置 (1). 模型参数 (2). 数据划分 (3). 超参数优化 3.模型训练与评估 (1). 训练模型 (2). 查看训练结果 (3). 模型评估 五.模型部署与应用 1.模型部署 (1). 直…

浪潮信息自动驾驶框架AutoDRRT 2.0,赋能高阶自动驾驶

随着自动驾驶技术的迅猛进步,BEVTransformer的感知模式为高阶自动驾驶带来了前所未有的精度、泛化能力和多模态融合效果,已成为众多顶尖汽车制造商的首选方案。然而,当前自动驾驶方案中的大模型算法参数规模剧增,对算力、数据IO及…

【电源专题】BUCK电源SW电压的平均值为什么等于输出电压?

在Buck电源测试过程中,我们会去测试SW开关节点的波形。那么从SW波形中我们能看出什么呢? 首先查看SW波形一般会看SW频率,通过SW波形的频率知道目前芯片的运行状态是什么。比如PSM还是PWM模式。 此外,还会看SW波形的占空比,通过占空比我们可以知道目前输出的状态是怎么样的…

微信分账系统供应链分润微信支付 (亲测源码)

搭建环境:nginxphp7.2mysql5.7 1.上传源码到网站根目录并解压 2.导入数据库文件到数据库 3.修改数据库链接文件/.env 4.设置运行目录为/public 5.伪静态设置成tp 6.后台地址:域名/zh9025.php 源码下载:https://download.csdn.net/down…

HTB:Buff[WriteUP]

目录 连接至HTB服务器并启动靶机 信息搜集 使用rustscan对靶机TCP端口进行开放扫描 使用nmap对靶机开放的端口进行脚本、服务扫描 使用curl分别访问靶机的两个端口 使用浏览器访问靶机8080端口页面 漏洞利用 使用searchsploit搜索该WebAPP 通过python2利用该EXP成功ge…