Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Day371:使用 Symbol 函数都有哪些要注意的点? #1205

Open
Genzhen opened this issue Aug 23, 2021 · 0 comments
Open

Day371:使用 Symbol 函数都有哪些要注意的点? #1205

Genzhen opened this issue Aug 23, 2021 · 0 comments
Labels
JavaScript teach_tag

Comments

@Genzhen
Copy link
Collaborator

Genzhen commented Aug 23, 2021

每日一题会在下午四点在交流群集中讨论,五点小程序中更新答案
欢迎大家在下方发表自己的优质见解

二维码加载失败可点击 小程序二维码

扫描下方二维码,收藏关注,及时获取答案以及详细解析,同时可解锁800+道前端面试题。


Symbol

ES6 中引入了一种新的基础数据类型:Symbol,这是一种新的基础数据类型(primitive type)。

它的功能类似于一种标识唯一性的 ID。通常情况下,我们可以通过调用 Symbol()函数来创建一个 Symbol 实例:

let s1 = Symbol();

或者,你也可以在调用 Symbol()函数时传入一个可选的字符串参数,相当于给你创建的 Symbol 实例一个描述信息:

let s2 = Symbol("another symbol");

如果用当下比较流行的 TypeScript 的方式来描述这个 Symbol()函数的话,可以表示成:

/**
 *  @param {any} description 描述信息。可以是任何可以被转型成字符串的值,如:字符串、数字、对象、数组等
 */
 function Symbol(description:any):symbol;

由于 Symbol 是一种基础数据类型,所以当我们使用 typeof 去检查它的类型的时候,它会返回一个属于自己的类型 symbol,而不是什么 string、object 之类的:

typeof s1; // 'symbol'

另外,我们需要重点记住的一点是:每个 Symbol 实例都是唯一的。因此,当你比较两个 Symbol 实例的时候,将总会返回 false:

let s1 = Symbol();
let s2 = Symbol("another symbol");
let s3 = Symbol("another symbol");

s1 === s2; //false
s2 === s3; //false

1.1 一些应用场景

  • 场景 1:使用 Symbol 来作为对象属性名(key)

在这之前,我们通常定义或访问对象的属性时都是使用字符串,比如下面的代码:

let obj = {
  abc: 123,
  hello: "world",
};

obj["abc"]; // 123
obj["hello"]; // 'world'

而现在,Symbol 可同样用于对象属性的定义和访问:

const PROP_NAME = Symbol();
const PROP_AGE = Symbol();

let obj = {
  [PROP_NAME]: "yd",
};
obj[PROP_AGE] = 6;

obj[PROP_NAME]; // yd
obj[PROP_AGE]; // 6

随之而来的是另一个非常值得注意的问题:就是当使用了 Symbol 作为对象的属性 key 后,在对该对象进行 key 的枚举时,会有什么不同?在实际应用中,我们经常会需要使用Object.keys()或者for...in来枚举对象的属性名,那在这方面,Symbol 类型的 key 表现的会有什么不同之处呢?来看以下示例代码:

let obj = {
  [Symbol("name")]: "yd",
  age: 6,
  title: "symbol",
};
Object.keys(obj); // ["age","title"]
for (let p in obj) {
  console.log(p); // 分别输出 “age” 和 “title”
}
Object.getOwnPropertyNames(obj); // ["age","title"]

由上代码可知,Symbol 类型的 key 是不能通过Object.keys()或者for...in来枚举的,它未被包含在对象自身的属性名集合(property names)之中。所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用 Symbol 来定义。

也正因为这样一个特性,当使用 JSON.stringify()将对象转换成 JSON 字符串的时候,Symbol 属性也会被排除在输出内容之外:

JSON.stringify(obj); // {"age":6,"title":"symbol"}

我们可以利用这一特点来更好的设计我们的数据对象,让“对内操作”和“对外选择性输出”变得更加优雅。

然而,这样的话,我们就没办法获取以 Symbol 方式定义的对象属性了么?非也。还是会有一些专门针对 Symbol 的 API,比如:

// 使用Object的API
Object.getOwnPropertySymbol(obj); // [Symbol(name)]

// 使用新增的的反射API
Reflect.ownKeys(obj); // [Symbol(name),"age","title"]
  • 场景 2:使用 Symbol 代替常量

看一下下面的代码是不是非常熟悉

const TYPE_AUDIO = "AUDIO";
const TYPE_VIDEO = "VIDEO";
const TYPE_IMAGE = "IMAGE";

function handleFileResource(resource) {
  switch (resource.type) {
    case TYPE_AUDIO:
      playAudio(resource);
      break;
    case TYPE_VIDEO:
      playVideo(resource);
      break;
    case TYPE_IMAGE:
      previewImage(resource);
      break;
    default:
      throw new Error("Unknown type of resource");
  }
}

如上面的代码中那样,我们经常定义一组常量来代表一种业务逻辑下的几个不同类型,我们通常希望这几个常量之间是唯一的关系,为了保证这一点,我们需要为常量赋一个唯一的值(比如这里的'AUDIO'、'VIDEO'、 'IMAGE'),常量少的时候还算好,但是常量一多,你可能还得花点脑子好好为他们取个好点的名字。

现在有了 Symbol,我们大可不必这么麻烦了:

const TYPE_AUDIO = Symbol();
const TYPE_VIDEO = Symbol();
const TYPE_IMAGE = Symbol();

这样定义,直接就保证了三个常量的值是唯一的了!是不是挺方便的呢。

  • 场景 3:使用 Symbol 定义类的私有属性/方法

我们知道在 JavaScript 中,是没有如 Java 等面向对象语言的访问控制关键字 private 的,类上所有定义的属性或方法都是可公开访问的。因此这对我们进行 API 的设计时造成了一些困扰。

而有了 Symbol 以及模块化机制,类的私有属性和方法才变成可能。例如:

在文件 a.js 中

const PASSWORD = Symbol();

class Login {
  constructor(userName, password) {
    this.userName = userName;
    this[PASSWORD] = password;
  }
  checkPassword(pwd) {
    return this[PASSWORD] === pwd;
  }
}
export default Login;

在文件 b.js 中

import Login from "./a";

const login = new Login("admin", "123456");

login.checkPassword("admin"); // true

login.PASSWORD; // oh!no!
login[PASSWORD]; // oh!no!
login["PASSWORD"]; // oh!no!

由于 Symbol 常量 PASSWORD 被定义在 a.js 所在的模块中,外面的模块获取不到这个 Symbol,也不可能再创建一个一模一样的 Symbol 出来(因为 Symbol 是唯一的),因此这个 PASSWORD 的 Symbol 只能被限制在 a.js 内部使用,所以使用它来定义的类属性是没有办法被模块外访问到的,达到了一个私有化的效果。

1.2 注册和获取全局 Symbol

通常情况下,我们在一个浏览器窗口中(window),使用 Symbol()函数来定义 Symbol 实例就足够了。

但是,如果你的应用涉及到多个 window(最典型的就是页面中使用了<iframe>),并需要这些 window 中使用的某些 Symbol 是同一个,那就不能使用Symbol()函数了,因为用它在不同 window 中创建的 Symbol 实例总是唯一的,而我们需要的是在所有这些 window 环境下保持一个共享的 Symbol。这种情况下,我们就需要使用另一个 API 来创建或获取 Symbol,那就是Symbol.for(),它可以注册或获取一个 window 间全局的 Symbol 实例:

let gs1 = Symbol.for("global_symbol_1"); // 注册一个全局Symbol
let gs2 = Symbol.for("global_symbol_2"); // 获取全局Symbol

gs1 === gs2; // true

这样一个 Symbol 不光在单个 window 中是唯一的,在多个相关 window 间也是唯一的了。

1.3 一些需要特别注意的点

  1. Symbol 函数前不能使用 new 命令,否则会报错。
  2. Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
  3. Symbol 作为属性名,该属性不会出现在 for...infor...of 循环中,也不会被 Object.keys()Object.getOwnPropertyNames()JSON.stringify() 返回。
  4. Object.getOwnPropertySymbols 方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
  5. Symbol.for 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。
  6. Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key。
@Genzhen Genzhen added the JavaScript teach_tag label Aug 23, 2021
@Genzhen Genzhen changed the title 使用 Symbol 函数都有哪些要注意的点? Day371:使用 Symbol 函数都有哪些要注意的点? Aug 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
JavaScript teach_tag
Projects
None yet
Development

No branches or pull requests

1 participant