✨JavaScript 第十二章(Symbol使用场景)

在JavaScript中,Symbol是ES6引入的一种新的原始数据类型,它代表了一个独一无二的值。这种独特性使得Symbol在某些特定场景下非常有用,尤其是当你需要创建一个不能与其他属性冲突的对象属性名时。

让我们来探讨一下Symbol的几个典型使用场景

一、对象属性的唯一性

对象的属性名通常是字符串。如果不慎使用了与现有属性同名的字符串作为属性名,就会发生属性值被覆盖的问题。这种情况在大型项目或多人协作时尤其常见,因为不同的人可能会无意中使用相同的属性名。下面通过错误示例和正确示例来详细讲解这一知识点。

错误示例
假设我们有一个用户对象,我们想要为这个用户对象添加一个ID属性。如果我们使用字符串作为属性名,就可能不小心覆盖了已有的属性。

let user = {name: 'Alice',id: 'USER_1' // 初始的用户ID
};// 另一段代码尝试为用户添加一个新的ID
user['id'] = 123; // 这里发生了属性覆盖console.log(user['id']); // 输出:123
// 原来的用户ID 'USER_1' 被新值123覆盖了

在这个错误示例中,我们看到,由于不小心使用了相同的属性名’id’,原来的字符串ID被新的数字ID覆盖了。这种错误可能会导致数据丢失,而且在复杂的代码库中可能很难追踪。

正确示例
为了避免这种属性名冲突,我们可以使用Symbol来创建一个唯一的属性名。

let id = Symbol('id'); // 创建一个Symbol作为属性名let user = {name: 'Alice',[id]: 'USER_1' // 使用Symbol作为属性键
};// 即使另一段代码尝试使用相同的描述符来创建Symbol
let anotherId = Symbol('id'); // 创建另一个Symbol,即使描述符相同,也是唯一的user[anotherId] = 123; // 这不会覆盖之前的id属性console.log(user[id]); // 输出:'USER_1',原来的ID保持不变
console.log(user[anotherId]); // 输出:123,这是新添加的ID

在这个正确示例中,我们首先创建了一个Symbol,它是唯一的,即使另一个Symbol使用了相同的描述符(这里是’id’),它们也是不同的。因此,当我们使用这些Symbol作为属性名时,它们不会相互冲突,每个Symbol都会对应一个独立的属性。

这样,即使代码库非常庞大,或者多个开发者在同一个对象上工作,使用Symbol作为属性键也能确保每个属性都是唯一的,从而避免了属性名冲突的问题。这是Symbol在JavaScript编程中非常有价值的一个特性,它有助于提高代码的健壮性和可维护性。

二、表示对象的私有属性

对象的属性默认是公开的,这意味着它们可以在对象外部被访问和修改。这在某些情况下可能不是我们想要的,特别是当我们试图隐藏某些内部状态或实现细节时。下面通过错误示例和正确示例来详细讲解如何使用Symbol来保护私有属性。

错误示例
假设我们有一个表示人的对象,我们想要将年龄属性作为私有属性来处理。

let person = {name: 'Bob',age: 30 // 假设我们想让age属性是私有的
};// 尝试打印所有属性
for (let property in person) {console.log(property); // 输出:name, age
}console.log(Object.keys(person)); // 输出:['name', 'age']
// age属性并不私有,可以被外部访问和枚举

在这个错误示例中,我们将age属性作为一个普通的字符串键添加到了person对象中。结果是,age属性可以被外部代码轻易地访问和枚举,这违背了我们的初衷,即希望age属性是私有的。

正确示例
为了确保age属性是私有的,我们可以使用Symbol来定义这个属性,因为Symbol属性不会被常规的枚举方法如for...in循环或Object.keys()所包含。

let age = Symbol('age');let person = {name: 'Bob',[age]: 30 // 使用Symbol作为属性键
};// 尝试打印所有属性
for (let property in person) {console.log(property); // 只输出:name
}console.log(Object.keys(person)); // 只输出:['name']
console.log(person[age]); // 输出:30,可以通过Symbol直接访问

在这个正确示例中,我们创建了一个Symbolage,并将其作为属性键用于person对象。这样,age属性就不会出现在对象属性的枚举中,从而实现了属性的私有化。尽管如此,我们仍然可以通过直接使用Symbol键来访问和修改age属性。

使用Symbol作为属性键的方式,可以帮助我们在不改变现有对象结构的情况下,实现属性的私有化。这对于封装对象的内部状态、保护对象的完整性以及防止外部代码意外干扰对象的内部逻辑非常有用。这种方法在需要对对象的访问进行严格控制的库和框架中尤其常见。

三、扩展第三方对象

当我们需要扩展或添加新属性到第三方对象时,如果不慎使用了可能与第三方属性名相冲突的字符串键,就有可能导致未来的维护问题。这是因为第三方对象可能在未来的版本中添加了同名的属性,或者与其他第三方库添加的属性发生冲突。使用Symbol可以有效避免这类问题。让我们通过错误示例和正确示例来详细了解这个知识点。

错误示例
假设我们正在使用一个第三方库提供的对象,并且想要在这个对象上添加自定义的属性或方法。

let thirdPartyObject = {name: 'thirdPartyName'
};// 我们尝试添加一个新的属性
thirdPartyObject['myProperty'] = 'myValue';// 如果第三方库在未来的更新中添加了'myProperty'属性,我们的值就会被覆盖
// 或者如果另一个库也向这个对象添加了'myProperty'属性,就会发生冲突

在这个错误示例中,我们直接使用了字符串键’myProperty’来添加属性。这种做法存在风险,因为如果第三方库在更新中添加了同名的属性,或者其他库也使用了同样的属性名,就会发生属性冲突。

正确示例
为了安全地扩展第三方对象而不引入潜在的冲突,我们可以使用Symbol来创建唯一的属性键。

let thirdPartyObject = {name: 'thirdPartyName'
};// 创建一个Symbol
let myProperty = Symbol('myProperty');// 使用Symbol作为属性键来添加属性
thirdPartyObject[myProperty] = 'myValue';// 这样就算第三方库更新或其他库使用了'myProperty'作为属性名,也不会影响到我们添加的属性

在这个正确示例中,我们创建了一个SymbolmyProperty,并将其作为属性键添加到第三方对象中。由于每个Symbol都是唯一的,即使其他代码使用了相同的描述符来创建Symbol,它们也不会与我们的Symbol冲突。因此,我们可以确保我们的属性不会被第三方库或其他库的更新所影响。

使用Symbol作为属性键的方法,特别适合在不控制第三方对象源代码的情况下进行安全的扩展。这种做法可以保护我们的自定义属性和方法,确保它们在未来的更新中不会被覆盖或冲突,从而提高代码的稳定性和可维护性。

四、实现Well-Known Symbols

迭代器协议定义了一种标准的方法来产生一系列值,而不必创建一个临时的数组。任何对象,只要它实现了一个遵循迭代器协议的[Symbol.iterator]()方法,就可以被迭代。下面将详细解释错误的用法和正确的用法。

错误的用法示例

let collection = {items: [1, 2, 3],next: function() {// 这个函数并不遵循迭代器协议// 它只是一个普通的函数,而不是一个迭代器工厂函数}
};// 由于collection没有实现[Symbol.iterator],以下代码会报错
for (let item of collection) {console.log(item); // TypeError: collection is not iterable
}

在这个错误的示例中,collection对象定义了一个next方法,但这并不符合迭代器协议。next方法应该是迭代器对象的一部分,而不是直接挂载在可迭代对象上。此外,没有实现[Symbol.iterator]方法,因此collection对象不是可迭代的。

正确的用法示例

let collection = {items: [1, 2, 3],[Symbol.iterator]() {let i = 0;let items = this.items;return {next() {let done = i >= items.length;let value = !done ? items[i++] : undefined;return { value, done };}};}
};for (let item of collection) {console.log(item); // 依次输出:1, 2, 3
}

在这个正确的示例中,collection对象实现了[Symbol.iterator]()方法。这个方法返回一个迭代器对象,该对象包含一个next()方法。next()方法在被调用时返回一个对象,该对象包含两个属性:valuedonevalue是当前的值,done是一个布尔值,表示迭代是否结束。这样,collection对象就遵循了迭代器协议,可以使用for...of循环进行迭代。

这种正确的实现方式允许任何对象自定义它的迭代行为,非常适合创建自定义的数据结构。通过实现[Symbol.iterator],你可以控制对象如何响应迭代,这在处理复杂的数据结构时非常有用。而且,这种方式与JavaScript的内置类型(如数组和字符串)提供的迭代行为保持一致,使得自定义对象可以无缝地集成到使用迭代的JavaScript代码中。

五、模块内部状态的封装

封装是一个重要的概念,它意味着模块的内部状态和实现细节应该对外部隐藏,只暴露必要的接口。这有助于减少模块间的耦合,提高代码的可维护性和可重用性。下面我们将对比错误和正确的代码实践,以及如何使用Symbol来正确地封装模块内部状态。

错误的代码实践

// module.js
let module = {counter: 0, // 这是一个公开的属性,可以被外部访问和修改increment() {this.counter++;},getCount() {return this.counter;}
};export default module;// main.js
import module from './module.js';module.counter = -999; // 外部代码可以直接修改内部状态
module.increment();
console.log(module.getCount()); // -998,内部状态被破坏

在这个错误的实践中,counter属性是公开的,这意味着任何导入该模块的代码都可以直接访问和修改counter。这违反了封装原则,因为模块的内部状态应该是私有的,不应该被外部直接控制。

正确的代码实践

// module.js
let _counter = Symbol('counter'); // 使用Symbol创建一个独一无二的属性键let module = {[_counter]: 0, // 使用Symbol作为属性键,这个状态是私有的increment() {this[_counter]++;},getCount() {return this[_counter];}
};export default module;// main.js
import module from './module.js';// module._counter = -999; // 这行代码无法执行,因为外部代码不知道_symbol的值
module.increment();
console.log(module.getCount()); // 1,内部状态被正确地维护

在这个正确的实践中,我们使用了Symbol来创建一个私有的属性键_counter。由于Symbol的值是唯一的,并且在模块外部无法访问(除非模块显式地导出它),因此_counter属性实际上是私有的。外部代码无法直接访问或修改_counter,它只能通过模块暴露的incrementgetCount方法来间接地与内部状态交互。

通过这种方式,我们确保了模块的内部状态不会被外部代码意外地修改,从而保持了模块的封装性和完整性。这是一种更安全和更可靠的编程实践,特别是在大型项目和团队协作的环境中。

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

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

相关文章

『OpenCV-Python鼠标画笔』

OpenCV-Python教程链接: https://opencv-python-tutorials.readthedocs.io/ 示例一:图片上双击的位置绘制一个圆圈 首先创建一个鼠标事件回调函数,鼠标事件发生时就会被执行。鼠标事件可以是鼠标上的任何动作,比如左键按下&#x…

论述Python中列表、元组、字典和集合的概念

Python列表是用于存储任意数目、任意类型的数据集合,包含多个元素的有序连续的内存空间,是内置可变序列,或者说可以任意修改。在Python中,列表以方括号([ ])形式编写。 Python元组与Python列表类似&#x…

蓝桥杯官网填空题(01串的熵)

问题描述 答案提交 这是一道结果填空的题, 你只需要算出结果后提交即可。本题的结果为一 个整数, 在提交答案时只填写这个整数, 填写多余的内容将无法得分。 import java.util.*;public class Main {public static void main(String[] args) {for(double zero1;zero<2333…

[docker] Docker资源管理

一、docker资源控制 Docker通过Cgroup 来控制容器使用的资源配额&#xff0c;包括CPU、内存、磁盘三大方面&#xff0c;基本覆盖了常见的资源配额和使用量控制。Caroup 是ControlGroups的缩写&#xff0c;是Linux 内核提供的一种可以限制、记录、隔离进程组所使用的物理资源(如…

运行Navicat转储的数据库SQL文件失败

报错&#xff1a;1067 - Invalid default value for ‘publish_date’ 单独拎出来该建表语句执行&#xff0c;报错一样&#xff0c;都是默认值出错 查看该字段的设计语句 publish_date timestamp NOT NULL DEFAULT 0000-00-00 00:00:00 COMMENT 发布时间, 发现该字段的默认值…

MyBatis详解(2)-- mybatis配置文件

MyBatis详解&#xff08;2&#xff09; mybatis配置文件 mybatis配置文件 1.构建SqlSessionFactory的依据。 2.MyBatis最为核心的内容&#xff0c;对MyBatis的使用影响很大。 3.配置文件的层次顺序不能颠倒&#xff0c;一旦颠倒会出现异常。 < c o n f i g u r a t i o n…

CC工具箱使用指南:【属性读取】

一、简介 假设有1个用地图层和1个区块图层&#xff08;里面包括1-6号地块&#xff09;&#xff0c;想要通过空间关系判断图地图层里的图斑分别位于区块图层的哪个地块内。 有1种简单的方法是空间连接&#xff0c;但是空间连接有1个很大的问题。 当用地图斑不完全位于某个地块…

Figma怎么设置中文,Figma有中文版吗?

不是很多人不想用 Figma&#xff0c;真是因为纯英文界面而头疼。这就是为什么有人会到处搜索 Figma 如何设置中文这样的问题。 然后我们直接快刀斩乱麻&#xff0c;Figma 没有中文版&#xff0c;但是我们还有其他的方法&#xff1a;例如&#xff0c; Figma 添加一个插件来解决…

在Windows 10/11中如何添加打印机?这里提供详细步骤

本文介绍如何将打印机添加到Windows10/11。有线设备和无线设备的过程不同。 在Windows 10中添加打印机 将网络打印机添加到Windows 10 网络打印机通过本地网络连接,如蓝牙或Wi-Fi。连接到打印机之前,请打开打印机并将其连接到网络。 注意:你可能需要管理员的许可才能安装…

grid布局,flex布局实现类似响应式布局的效果

一. grid布局 实现代码 <!DOCTYPE html> <html lang"en"><head><style>.box {display: grid;grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); /*自动填充&#xff0c;最小宽度300px*/justify-content: space-between;gap:…

yml配置文件怎么引用pom.xml中的属性

目录 前言配置测试 前言 配置文件中的一些参数有时要用到pom文件中的属性&#xff0c;做到pom文件变配置文件中也跟着变&#xff0c;那如何才能做到呢&#xff0c;下面咱们来一起探讨学习。 配置 1.首先要在pom.xml中做如下配置&#xff0c;让maven渲染src/main/resources下配…

推荐系统算法 协同过滤算法详解(二)皮尔森相关系数

目录 前言 协同过滤算法(简称CF) 皮尔森(pearson)相关系数公式 算法介绍 算法示例1&#xff1a; 算法示例2 前言 理解吧同胞们&#xff0c;实在是没办发把wps公式复制到文章上&#xff0c;只能截图了&#xff0c;我服了&#xff01;&#xff01;&#xff01; 协同过滤算法…

“License“(美式拼写)/ “Licence“(英式拼写)基本含义相同 许可证或执照,通常用于表示某人获得的特定权限或资格。

“License” 和 “licence” 是两个拼写不同但含义相近的单词&#xff0c;它们在用法上存在一些区别&#xff0c;主要取决于所在的英语体系。 “License”&#xff08;美式拼写&#xff09;/ “Licence”&#xff08;英式拼写&#xff09;&#xff1a; 这两个词在美式英语和英式…

【进口控制器替代】基于Zynq-7020 FPGA的NI 8槽CompactRIO控制器

667 MHz双核CPU&#xff0c;512 MB DRAM&#xff0c;1 GB存储容量&#xff0c;Zynq-7020 FPGA&#xff0c;更宽工作温度范围&#xff0c;8槽CompactRIO控制器 cRIO-9068是一款坚固耐用的无风扇嵌入式控制器&#xff0c;可用于高级控制和监测应用。这款软件设计控制器搭载FPGA、…

c++的第一天

思维导图 输入一个字符串&#xff0c;计算大小写&#xff0c;数字&#xff0c;空格和其他字符 #include <array> #include <string>using namespace std;int main() {string str;cout << "请输入一串包含大写小写数字和空格的字符串:" ;getline(c…

flask_apscheduler源码分析

前言 遵循flask框架的标准的库&#xff0c;都称为flask扩展&#xff0c;flask_apscheduler是对apscheduler的扩展&#xff0c;也称为flask的扩展&#xff0c;最近使用flask_apscheduler遇到了一个job死亡的bug。现象&#xff1a;job平时是正常启动的&#xff0c;突然某个时刻全…

MODNet 剪枝再思考: 优化计算量的实验历程分享

目录 1 写在前面 2 模型分析 3 遇到问题 4 探索实验一 4.1 第一部分 4.2 第二部分 Error 1 Error 2 4.3 实验结果 ①参数量与计算量 ②模型大小 ③推理时延 5 探索实验二 5.1 LR Branch 5.2 HR Branch 5.2.1 初步分析 5.2.2 第一部分 enc2x 5.2.3 第二部分 en…

量化交易科普

量化 语言和逻辑层面&#xff0c;用量词指定一个谓词的有效性的广度的构造 一些、很多、所有 量化交易 针对可交易的投资商品&#xff0c;理性地运用逻辑分析和归纳统计判断市场趋势 量化策略 常用的股票量化指标 相关平台 财经网站&#xff1a;新浪、雅虎、东方财富等 证券公…

php正则电话号

400电话的正则/^400[0-9]{7}/ 800电话正则/^800[0-9]{7}/ 手机号码正则/^1(3|4|7|5|8)([0-9]{9})/ 座机号码正则^0[0-9]{2,3}-[0-9]{8} 要匹配其中之一就在中间以|隔开 (400正则)|(800正则)|(手机号)|(座机号)| $phone preg_replace("/\s| /","",$p…

百度网盘网页无法调起客户端 目前未解决 2024年1月出现BUG

最近一周突然发现百度网盘网页无法调起客户端&#xff0c;多方式排查还是没找出问题所在&#xff0c;怀疑是客户端更新了什么东西导致bug。先记录下各种排查法和复现情况。 具体问题是&#xff1a; *任意网盘链接&#xff0c;都无法唤醒客户端。 *客户端在退出状态网页点击下…