• [ES6深度解析]7:符号(Symbols)


    第七种类型

    自从JavaScript在1997年首次标准化以来,已经有了六种类型。在ES6之前,JS程序中的每个值都属于这些类别之一:

    • Undefined
    • Null
    • Boolean
    • Number
    • String
    • Object
      每种类型都是一组值。前五个集合都是有限的。当然,只有两个布尔值,truefalse,而且它们不会产生新的值。有更多的Number和String值。该标准称,共有18,437,736,874,454,810,627个不同的数字(包括NaN,即非数字的缩写)。与可能的字符串的数量相比,这简直是九牛一毛。

    然而,Object值的集合是开放式的。每一件物品都是独一无二的、珍贵的雪花。每次打开Web页面时,都会创建大量新对象。

    ES6 Symbols是值,但不是字符串。他们不是对象。它们是新的东西:第七种类型的值。让我们来谈谈它们可能会派上用场的情况。

    一个简单的布尔值

    有时,将一些额外的数据存储在真正属于其他人的JavaScript对象上是非常方便的。例如,假设您正在编写一个JS库,它使用CSS转换使DOM元素在屏幕上快速移动。你已经注意到,尝试在单个div上同时应用多个CSS过渡是行不通的。它会导致丑陋的、不连续的“跳跃”。你认为可以修复这个问题,但首先你需要一种方法来确定给定元素是否已经在移动。

    这个问题该如何解决?

    一种方法是使用CSS APIs询问浏览器元素是否在移动。但这听起来有点过分了。你的库应该已经知道元素在移动;这是一开始让它移动的代码!你真正需要的是一种跟踪哪些元素在移动的方法。你可以保存一个包含所有移动元素的数组。每次调用库动画元素时,都可以搜索数组,查看该元素是否已经存在。但是如果数组很大,线性搜索会很慢。

    另一个办法是在元素上设置一个标志:

    if (element.isMoving) {
      smoothAnimations(element);
    }
    element.isMoving = true;
    

    这也存在一些潜在的问题。它们都与这样一个事实有关:你的代码并不是唯一使用DOM的代码。

    • 其他使用for-inObject.keys()的代码可能会在你创建的属性上出错。
    • 其他一些聪明的库作者可能首先想到了这种技术,因此你的库与现有库的交互会很糟糕。(属性名重复了,会有冲突)
    • 其他一些聪明的库作者可能会在未来想到它,而你的js库与那个未来的库进行糟糕的交互。
    • 标准委员会可能决定向所有元素添加.ismoving()方法。那么你真的傻眼了。

    当然,你可以通过选择一个非常乏味或愚蠢的字符串来解决最后三个问题:(避免重名)

    if (element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__) {
      smoothAnimations(element);
    }
    element.__$jorendorff_animation_library$PLEASE_DO_NOT_USE_THIS_PROPERTY$isMoving__ = true;
    

    这代码辣眼睛!

    你还可以使用加密技术为属性生成一个实际唯一的名称:

    // get 1024 Unicode characters of gibberish
    var isMoving = SecureRandom.generateName();
    
    ...
    
    if (element[isMoving]) {
      smoothAnimations(element);
    }
    element[isMoving] = true;
    

    Object[name]语法允许使用任意字符串作为属性名。所以这是可行的:命名冲突实际上是不可能的,你的代码看起来是正常的。但这将导致糟糕的调试体验。每当你在console.log()中添加一个带有该属性的元素时,将看到一个巨大的垃圾字符串。如果你需要不止一个这样的属性呢?你是如何让它们保持一致的?每次重新加载时,它们都会有不同的名称。

    为什么这么难?我们只需要一个布尔值!

    Symbols就是你要的答案

    Symbols是程序可以创建并用作属性键的值,而不会有名称冲突的风险

    var mySymbol = Symbol();
    

    调用Symbol()将创建一个新的符号,该符号的值不等于任何其他值。

    就像字符串或数字一样,可以使用符号作为属性键。因为它不等于任何字符串,所以这个符号键控属性保证不会与任何其他属性发生冲突。

    obj[mySymbol] = "ok!";  // mySymbol是不会重复的属性名
    console.log(obj[mySymbol]);  // ok!
    

    以下是在上面讨论的情况下如何使用符号:

    // create a unique symbol
    var isMoving = Symbol("isMoving");
    
    ...
    
    if (element[isMoving]) {
      smoothAnimations(element);
    }
    element[isMoving] = true;
    

    关于这段代码的几点注意事项:

    • Symbol("isMoving")中的字符串"isMoving"被称为描述。这对调试很有帮助。当你将symbol写入console.log()时,当使用.tostring()将其转换为字符串时,以及可能在错误消息中都会显示它。这就是描述的用途。
    • element[isMoving]被称为符号键控属性(symbol-keyed property)。它只是一个名称是符号而不是字符串的属性。除此之外,它在任何方面都是正常的性质。
    • 与数组元素一样,符号键控属性不能使用点语法访问,如obj.name。必须使用方括号访问它们。
    • 如果已经获得了符号键控属性,那么访问该符号键控属性是很简单的。上面的例子展示了如何获取和设置element[isMoving],我们还可以询问if (isMoving in element),甚至如果需要的话可以删除delete element[isMoving]
    • 另一方面,只要isMoving在作用域内,所有这些都是可能的。这使得Symbol成为一种弱封装机制:为自己创建一些Symbol的模块可以在任何它想要的对象上使用它们,而不必担心与其他代码创建的属性冲突

    因为符号键(symbol keys)是为了避免冲突而设计的,所以JavaScript最常见的对象检查特性就是简单地忽略符号键。例如,for-in循环只在对象的字符串键上循环。跳过符号键。Object.keys(obj)Object.getOwnPropertyNames(obj)做同样的事情。但是Symbol并不是完全私有的:可以使用新的APIObject. getownpropertysymbols(obj)来列出对象的符号键。另一个新的APIReflect.ownKeys(obj)同时返回字符串和符号键。

    但到底什么是符号Symbols呢?

    > typeof Symbol()
    "symbol"
    

    Symbols和其他东西不完全一样。

    它们一旦被创造就不可改变。你不能在它们上设置属性(如果你在严格模式下尝试,你会得到一个TypeError)。它们可以是属性名。这些都是类似String的性质。

    另一方面,每个Symbol都是独一无二的,不同于所有其他符号(甚至其他具有相同描述的符号),你可以轻松创建新的符号。这些都是类似Object的特性。

    ES6 Symbol类似于Lisp和Ruby等语言中更传统的符号,但并没有紧密地集成到语言中。在Lisp中,所有标识符都是Symbols。在JS中,标识符和大多数属性键仍然被认为是字符串。Symbols只是一个额外的选择。

    关于Symbol的一个快速警告:不像语言中的其他任何东西,它们不能自动转换为字符串。试图将符号与字符串进行转换将导致TypeError。

    > var sym = Symbol("<3");
    > "your symbol is " + sym
    // TypeError: can't convert symbol to string
    > `your symbol is ${sym}`
    // TypeError: can't convert symbol to string
    

    可以通过显式地将符号转换为字符串来避免这种情况,例如写入String(sym)sym.tostring()

    三组符号

    有三种方法可以获得一个Symbol:

    • 调用Symbol()。正如我们已经讨论过的,每次调用它都会返回一个新的唯一符号。
    • 调用Symbol.for(string)。这将访问一组称为符号注册表(symbol registry)的现有符号(symbol)。与Symbol()定义的唯一符号不同,符号注册表中的符号是共享的。如果你调用Symbol.for("cat") 30次,它每次都会返回相同的符号。当多个网页或同一网页中的多个模块需要共享一个符号时,注册表是有用的。
    • 使用由标准定义的符号,比如Symbol.iterator。一些符号是由标准本身定义的。每一个都有它自己的特殊目的。

    Symbol的应用

    Symbol.iterator

    我们已经看到了ES6使用符号来避免与现有代码冲突的一种方法。在关于迭代器的文章中,我们看到for (var item of myArray)的循环首先调用myArray[Symbol.iterator]()。这个方法可以被称为myArray.iterator(),但是symbol符号更利于代码向后兼容。

    让instanceof可扩展

    在ES6中,表达式 object instanceof constructor被指定为构造函数constructor的一个方法:constructor[Symbol.hasInstance](object)。这意味着它是可扩展的。

    消除新特性和旧代码之间的冲突

    某些ES6 Array方法仅仅出现在代码里就破坏了现有的网站。其他Web标准也有类似的问题:简单地在浏览器中添加新方法就会破坏现有的站点。然而,这种破坏主要是由所谓的动态作用域(dynamic scope)造成的,所以ES6引入了一种特殊的符号symbol.unscopables, Web标准可以使用它来防止某些方法卷入动态作用域。

    支持新的字符串匹配方式

    在ES5中,str.match(myObject)会试图把myObject转换为RegExp(正则表达式)。在ES6中,JS首先会检查myObject是否有一个myObject[Symbol.match](str)方法。现在JS库可以提供自定义字符串解析类,这些类可以在RegExp对象工作的所有地方工作。

    每一种用途都很小众。这些特性本身很难对我的日常代码产生重大影响。长远的观点更有趣。众所周知的符号是JavaScript在PHP和Python中__doubleUnderscores下划线的改进版本。该标准将来将使用它们向语言中添加新的钩子,而不会对现有代码造成风险。

    本文来自博客园,作者:Max力出奇迹,转载请注明原文链接:https://www.cnblogs.com/welody/p/15176871.html

    如果觉得文章不错,欢迎点击推荐

  • 相关阅读:
    基于.net EF6 MVC5+WEB Api 的Web系统框架总结(2)-业务项目搭建
    基于Html5 Plus + Vue + Mui 移动App开发(三)-文件操作(读取、保存、更新数据)
    基于Html5 Plus + Vue + Mui 移动App 开发(二)
    Mysql数据库(一)-------安装
    Sublime---破解+安装+安装插件
    Bayboy功能详解
    MySQL基础语句
    python变量类型&字符串的内建函数使用
    数据库基础知识笔试题(一)
    软件测试笔试题(二)
  • 原文地址:https://www.cnblogs.com/welody/p/15176871.html
Copyright © 2020-2023  润新知