详解JavaScript 为什么要有 Symbol 类型?


Posted in Javascript onApril 03, 2019

Symbols 是 ES6 引入了一个新的数据类型 ,它为 JS 带来了一些好处,尤其是对象属性时。 但是,它们能为我们做些字符串不能做的事情呢?

在深入探讨 Symbol 之前,让我们先看看一些 JavaScript 特性,许多开发人员可能不知道这些特性。

背景

js 中的数据类型总体来说分为两种,他们分别是:值类型 和 引用类型

值类型(基本类型):数值型(Number),字符类型(String),布尔值型(Boolean),null 和 underfined

引用类型(类):函数,对象,数组等

值类型理解:变量之间的互相赋值,是指开辟一块新的内存空间,将变量值赋给新变量保存到新开辟的内存里面;之后两个变量的值变动互不影响,例如:

var a = 10; //开辟一块内存空间保存变量a的值“10”;
var b = a; //给变量 b 开辟一块新的内存空间,将 a 的值 “10” 赋值一份保存到新的内存里;
//a 和 b 的值以后无论如何变化,都不会影响到对方的值;

一些语言,比如 C,有引用传递和值传递的概念。JavaScript 也有类似的概念,它是根据传递的数据类型推断的。如果将值传递给函数,则重新分配该值不会修改调用位置中的值。但是,如果你修改的是引用类型,那么修改后的值也将在调用它的地方被修改。

引用类型理解:变量之间的互相赋值,只是指针的交换,而并非将对象(普通对象,函数对象,数组对象)复制一份给新的变量,对象依然还是只有一个,只是多了一个指引~~;例如:

var a = { x: 1, y: 2 }; //需要开辟内存空间保存对象,变量 a 的值是一个地址,这个地址指向保存对象的空间;
var b = a; // 将a 的指引地址赋值给 b,而并非复制一给对象且新开一块内存空间来保存;
// 这个时候通过 a 来修改对象的属性,则通过 b 来查看属性时对象属性已经发生改变;

值类型(神秘的 NaN 值除外)将始终与具有相同值的另一个值类型的完全相等,如下:

const first = "abc" + "def";
const second = "ab" + "cd" + "ef";
console.log(first === second); // true

但是完全相同结构的引用类型是不相等的:

const obj1 = { name: "Intrinsic" };
const obj2 = { name: "Intrinsic" };
console.log(obj1 === obj2); // false
// 但是,它们的 .name 属性是基本类型:
console.log(obj1.name === obj2.name); // true

对象在 JavaScript 语言中扮演重要角色,它们的使用无处不在。对象通常用作键/值对的集合,然而,以这种方式使用它们有一个很大的限制: 在 symbol 出现之前,对象键只能是字符串,如果试图使用非字符串值作为对象的键,那么该值将被强制转换为字符串,如下:

const obj = {};
obj.foo = 'foo';
obj['bar'] = 'bar';
obj[2] = 2;
obj[{}] = 'someobj';
console.log(obj);
// { '2': 2, foo: 'foo', bar: 'bar',
   '[object Object]': 'someobj' }

Symbol 是什么

Symbol() 函数会返回 symbol 类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的 symbol 注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:“new Symbol()”。所以使用 Symbol 生成的值是不相等:

const s1 = Symbol();
const s2 = Symbol();
console.log(s1 === s2); // false

实例化 symbol 时,有一个可选的第一个参数,你可以选择为其提供字符串。 此值旨在用于调试代码,否则它不会真正影响symbol 本身。

const s1 = Symbol("debug");
const str = "debug";
const s2 = Symbol("xxyy");
console.log(s1 === str); // false
console.log(s1 === s2); // false
console.log(s1); // Symbol(debug)

symbol 作为对象属性
symbol 还有另一个重要的用途,它们可以用作对象中的键,如下:

const obj = {};
const sym = Symbol();
obj[sym] = "foo";
obj.bar = "bar";
console.log(obj); // { bar: 'bar' }
console.log(sym in obj); // true
console.log(obj[sym]); // foo
console.log(Object.keys(obj)); // ['bar']

乍一看,这看起来就像可以使用 symbol 在对象上创建私有属性,许多其他编程语言在其类中有自己的私有属性,私有属性遗漏一直被视为 JavaScript 的缺点。

不幸的是,与该对象交互的代码仍然可以访问其键为 symbol 的属性。 在调用代码尚不能访问 symbol 本身的情况下,这甚至是可能的。 例如,Reflect.ownKeys() 方法能够获取对象上所有键的列表,包括字符串和 symbol :

function tryToAddPrivate(o) {
  o[Symbol("Pseudo Private")] = 42;
}
const obj = { prop: "hello" };
tryToAddPrivate(obj);
console.log(Reflect.ownKeys(obj));
// [ 'prop', Symbol(Pseudo Private) ]
console.log(obj[Reflect.ownKeys(obj)[1]]); // 42

注意:目前正在做一些工作来处理在 JavaScript 中向类添加私有属性的问题。这个特性的名称被称为私有字段,虽然这不会使所有对象受益,但会使类实例的对象受益。私有字段从 Chrome 74 开始可用。

代码部署后可能存在的 BUG 没法实时知道,事后为了解决这些 BUG,花了大量的时间进行 log 调试,这边顺便给大家推荐一个好用的 BUG 监控工具 Fundebug。

防止属性名称冲突

符号可能不会直接受益于 JavaScript 为对象提供私有属性。然而,他们是有益的另一个原因。当不同的库希望向对象添加属性而不存在名称冲突的风险时,它们非常有用。

Symbol 为 JavaScrit 对象提供私有属性还有点困难,但 Symbol 还有别外一个好处,就是避免当不同的库向对象添加属性存在命名冲突的风险。

考虑这样一种情况:两个不同的库想要向一个对象添加基本数据,可能它们都想在对象上设置某种标识符。通过简单地使用 id 作为键,这样存在一个巨大的风险,就是多个库将使用相同的键。

function lib1tag(obj) {
  obj.id = 42;
}
function lib2tag(obj) {
  obj.id = 369;
}

通过使用 Symbol,每个库可以在实例化时生成所需的 Symbol。然后用生成 Symbol 的值做为对象的属性:

const library1property = Symbol("lib1");
function lib1tag(obj) {
  obj[library1property] = 42;
}
const library2property = Symbol("lib2");
function lib2tag(obj) {
  obj[library2property] = 369;
}

出于这个原因,Symbol 似乎确实有利于 JavaScript。

但是,你可能会问,为什么每个库在实例化时不能简单地生成随机字符串或使用命名空间?

const library1property = uuid(); // random approach
function lib1tag(obj) {
  obj[library1property] = 42;
}
const library2property = "LIB2-NAMESPACE-id"; // namespaced approach
function lib2tag(obj) {
  obj[library2property] = 369;
}

这种方法是没错的,这种方法实际上与 Symbol 的方法非常相似,除非两个库选择使用相同的属性名,否则不会有冲突的风险。
在这一点上,聪明的读者会指出,这两种方法并不完全相同。我们使用唯一名称的属性名仍然有一个缺点:它们的键非常容易找到,特别是当运行代码来迭代键或序列化对象时。考虑下面的例子:

const library2property = "LIB2-NAMESPACE-id"; // namespaced
function lib2tag(obj) {
  obj[library2property] = 369;
}
const user = {
  name: "Thomas Hunter II",
  age: 32
};
lib2tag(user);
JSON.stringify(user);
// '{"name":"Thomas Hunter II","age":32,"LIB2-NAMESPACE-id":369}'

如果我们为对象的属性名使用了 Symbol,那么 JSON 输出将不包含它的值。这是为什么呢? 虽然 JavaScript 获得了对 Symbol 的支持,但这并不意味着 JSON 规范已经改变! JSON 只允许字符串作为键,JavaScript 不会尝试在最终 JSON 有效负载中表示 Symbol 属性。

const library2property = "f468c902-26ed-4b2e-81d6-5775ae7eec5d"; // namespaced approach
function lib2tag(obj) {
  Object.defineProperty(obj, library2property, {
    enumerable: false,
    value: 369
  });
}
const user = {
  name: "Thomas Hunter II",
  age: 32
};
lib2tag(user);
console.log(user); // {name: "Thomas Hunter II", age: 32, f468c902-26ed-4b2e-81d6-5775ae7eec5d: 369}
console.log(JSON.stringify(user)); // {"name":"Thomas Hunter II","age":32}
console.log(user[library2property]); // 369

通过将 enumerable 属性设置为 false 而“隐藏”的字符串键的行为非常类似于 Symbol 键。它们通过 Object.keys() 遍历也看不到,但可以通过 Reflect.ownKeys() 显示,如下的示例所示:

const obj = {};
obj[Symbol()] = 1;
Object.defineProperty(obj, "foo", {
  enumberable: false,
  value: 2
});
console.log(Object.keys(obj)); // []
console.log(Reflect.ownKeys(obj)); // [ 'foo', Symbol() ]
console.log(JSON.stringify(obj)); // {}

在这点上,我们几乎重新创建了 Symbol。隐藏的字符串属性和 Symbol 都对序列化器隐藏。这两个属性都可以使用Reflect.ownKeys()方法读取,因此它们实际上不是私有的。假设我们为属性名的字符串版本使用某种名称空间/随机值,那么我们就消除了多个库意外发生名称冲突的风险。
但是,仍然有一个微小的区别。由于字符串是不可变的,而且 Symbol 总是保证惟一的,所以仍然有可能生成字符串组合会产生冲突。从数学上讲,这意味着 Symbol 确实提供了我们无法从字符串中得到的好处。

在 Node.js 中,检查对象时(例如使用 console.log() ),如果遇到名为 inspect 的对象上的方法,将调用该函数,并将打印内容。可以想象,这种行为并不是每个人都期望的,通常命名为 inspect 的方法经常与用户创建的对象发生冲突。
现在 Symbol 可用来实现这个功能,并且可以在 equire(‘util').inspect.custom 中使用。inspect 方法在 Node.js v10 中被废弃,在 v1 1 中完全被忽略, 现在没有人会偶然改变检查的行为。
模拟私有属性

这里有一个有趣的方法,我们可以用来模拟对象上的私有属性。这种方法将利用另一个 JavaScript 特性: proxy(代理)。代理本质上封装了一个对象,并允许我们对与该对象的各种操作进行干预。
代理提供了许多方法来拦截在对象上执行的操作。我们可以使用代理来说明我们的对象上可用的属性,在这种情况下,我们将制作一个隐藏我们两个已知隐藏属性的代理,一个是字符串 _favColor,另一个是分配给 favBook 的 S ymbol :

let proxy;

{
  const favBook = Symbol("fav book");

  const obj = {
    name: "Thomas Hunter II",
    age: 32,
    _favColor: "blue",
    [favBook]: "Metro 2033",
    [Symbol("visible")]: "foo"
  };

  const handler = {
    ownKeys: target => {
      const reportedKeys = [];
      const actualKeys = Reflect.ownKeys(target);

      for (const key of actualKeys) {
        if (key === favBook || key === "_favColor") {
          continue;
        }
        reportedKeys.push(key);
      }

      return reportedKeys;
    }
  };

  proxy = new Proxy(obj, handler);
}

console.log(Object.keys(proxy)); // [ 'name', 'age' ]
console.log(Reflect.ownKeys(proxy)); // [ 'name', 'age', Symbol(visible) ]
console.log(Object.getOwnPropertyNames(proxy)); // [ 'name', 'age' ]
console.log(Object.getOwnPropertySymbols(proxy)); // [Symbol(visible)]
console.log(proxy._favColor); // 'blue'

使用 _favColor 字符串很简单:只需阅读库的源代码即可。 另外,通过蛮力找到动态键(例如前面的 uuid 示例)。但是,如果没有对 Symbol 的直接引用,任何人都不能 从proxy 对象访问'Metro 2033'值。

Node.js 警告:Node.js 中有一个功能会破坏代理的隐私。 JavaScript 语 言本身不存在此功能,并且不适用于其他情况,例 如 Web 浏览器。 它允许在给定代理时获得对底层对象的访问权。 以下是使用此功能打破上述私有属性示例的示例:

const [originalObject] = process.binding("util").getProxyDetails(proxy);
const allKeys = Reflect.ownKeys(originalObject);
console.log(allKeys[3]); // Symbol(fav book)

现在,我们需要修改全局 Reflect 对象,或者修改 util 流程绑定,以防止它们在特定的 Node.js 实例中使用。

以上所述是小编给大家介绍的JavaScript 为什么要有 Symbol 类型详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
javascript事件问题
Sep 05 Javascript
JS打印gridview实现原理及代码
Feb 05 Javascript
javascript中with()方法的语法格式及使用
Aug 04 Javascript
JavaScript中实现Map的示例代码
Sep 09 Javascript
轻松学习jQuery插件EasyUI EasyUI创建CRUD应用
Nov 30 Javascript
一次$.getJSON不执行的简单记录
Jul 19 Javascript
vue项目中做编辑功能传递数据时遇到问题的解决方法
Dec 19 Javascript
js中小数向上取整数,向下取整数,四舍五入取整数的实现(必看篇)
Feb 13 Javascript
vue项目打包部署到服务器的方法示例
Aug 27 Javascript
webpack4与babel配合使es6代码可运行于低版本浏览器的方法
Oct 12 Javascript
怎样使你的 JavaScript 代码简单易读(推荐)
Apr 16 Javascript
Vue数据绑定简析小结
May 07 Javascript
es6 filter() 数组过滤方法总结
Apr 03 #Javascript
基于Vue 实现一个中规中矩loading组件
Apr 03 #Javascript
javascript实现小型区块链功能
Apr 03 #Javascript
vue插槽slot的理解和使用方法
Apr 03 #Javascript
react写一个select组件的实现代码
Apr 03 #Javascript
vue框架下部署上线后刷新报404问题的解决方案(推荐)
Apr 03 #Javascript
JavaScript变速动画函数封装添加任意多个属性
Apr 03 #Javascript
You might like
php生成图形验证码几种方法小结
2013/08/15 PHP
BOOM vs RR BO5 第一场 2.14
2021/03/10 DOTA
javascript之水平横向滚动歌词同步的应用
2007/05/07 Javascript
CheckBoxList多选样式jquery、C#获取选择项
2013/09/06 Javascript
sencha touch 模仿tabpanel导航栏TabBar的实例代码
2013/10/24 Javascript
JavaScript实现找质数代码分享
2015/03/24 Javascript
简介JavaScript中fixed()方法的使用
2015/06/08 Javascript
分享有关jQuery中animate、slide、fade等动画的连续触发、滞后反复执行的bug
2016/01/10 Javascript
详解Angular的内置过滤器和自定义过滤器【推荐】
2016/12/26 Javascript
bootstrap switch开关组件使用方法详解
2017/08/22 Javascript
浅谈js中的this问题
2017/08/31 Javascript
Vuex 在Vue 组件中获得Vuex 状态state的方法
2018/08/27 Javascript
vue集成百度UEditor富文本编辑器使用教程
2018/09/21 Javascript
JavaScript变速动画函数封装添加任意多个属性
2019/04/03 Javascript
js实现飞机大战游戏
2020/08/26 Javascript
js实现贪吃蛇游戏(简易版)
2020/09/29 Javascript
vue使用exif获取图片经纬度的示例代码
2020/12/11 Vue.js
[03:31]DOTA2英雄基础教程 大地之灵
2013/12/17 DOTA
Python中遇到的小问题及解决方法汇总
2017/01/11 Python
Python常用内置模块之xml模块(详解)
2017/05/23 Python
基于Python3.7.1无法导入Numpy的解决方式
2020/03/09 Python
解决Jupyter Notebook开始菜单栏Anaconda下消失的问题
2020/04/13 Python
Python图像阈值化处理及算法比对实例解析
2020/06/19 Python
华丽的手绘陶瓷:MacKenzie-Childs
2017/02/04 全球购物
美国真皮手袋品牌:GiGi New York
2017/03/10 全球购物
服装销售人员求职自我评价
2013/09/26 职场文书
大学生实习思想汇报
2014/01/12 职场文书
庆国庆国旗下讲话稿2014
2014/09/21 职场文书
2014年重阳节老干部座谈会上的讲话稿
2014/09/25 职场文书
县委班子四风对照检查材料思想汇报
2014/09/29 职场文书
2015年光棍节活动总结
2015/03/24 职场文书
涨价通知怎么写
2015/04/23 职场文书
史上最牛辞职信
2015/05/13 职场文书
幼儿园教师教学反思
2016/03/02 职场文书
导游词之徐州云龙湖
2019/11/19 职场文书
详解GaussDB for MySQL性能优化
2021/05/18 MySQL