ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript での Symbol 型の用途は何ですか?

JavaScript での Symbol 型の用途は何ですか?

不言
不言転載
2019-03-19 11:19:447432ブラウズ

この記事では、JavaScript での Symbol 型の用途について説明します。一定の参考値があるので、困っている友人は参考にしていただければ幸いです。

Symbols は ES6 で導入された新しいデータ型で、特にオブジェクト プロパティに関して JS にいくつかの利点をもたらします。しかし、文字列にはできず、文字列には何ができるのでしょうか?

シンボルについて説明する前に、多くの開発者が気づいていない可能性がある JavaScript の機能をいくつか見てみましょう。

背景

jsのデータ型は大きく分けて値型と参照型の2種類に分かれます。

値型(基本型):数値型(数値型) )、文字型 (String)、ブール型 (Boolean)、null およびアンダーファイン参照型 (クラス): 関数、オブジェクト、配列など。

値の型の理解: 変数間の相互代入、冒頭を参照新しいメモリ空間を作成し、変数値を新しい変数に割り当て、新しく開かれたメモリに保存します。その後の 2 つの変数の値の変更は相互に影響しません。たとえば:

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

C などの一部の言語には、参照渡しと値渡しの概念があります。 JavaScript にも同様の概念があり、渡されたデータのタイプに基づいて推論されます。値が関数に渡された場合、値を再割り当てしても呼び出し場所の値は変更されません。ただし、参照型を変更すると、変更された値も呼び出された場所で変更されます。

参照型の理解: 変数間の相互代入は、オブジェクト (通常のオブジェクト、関数オブジェクト、配列オブジェクト) を新しい変数にコピーするのではなく、単なるポインターの交換です。まだオブジェクトは 1 つだけですが、ガイドがもう 1 つだけあります~~; 例:

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' }

シンボルとは

シンボル() この関数はシンボル タイプを返します。このタイプの値には静的プロパティと静的メソッドがあります。その静的プロパティはいくつかの組み込みメンバー オブジェクトを公開します。その静的メソッドはグローバル シンボル登録を公開し、組み込みオブジェクト クラスに似ていますが、構文 "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)

シンボルをオブジェクト属性として使用する

シンボルにはもう 1 つの重要な用途があり、次のようにオブジェクトのキーとして使用できます。

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 の欠点と考えられてきました。

残念ながら、オブジェクトと対話するコードは、キーがシンボルであるオブジェクトのプロパティに引き続きアクセスできます。これは、呼び出しコードがまだシンボル自体にアクセスできない場合でも可能です。たとえば、Reflect.ownKeys() メソッドは、文字列や記号を含む、オブジェクト上のすべてのキーのリストを取得できます。

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 以降で利用可能です。

プロパティ名の競合の防止

シンボルは、オブジェクトにプライベート プロパティを提供する JavaScript の恩恵を直接受けられない可能性があります。ただし、それらは別の理由で有益です。これらは、異なるライブラリが名前の競合の危険を冒さずにオブジェクトにプロパティを追加したい場合に役立ちます。

Symbol JavaScript オブジェクトにプライベート プロパティを提供するのは少し難しいですが、Symbol には別の利点があります。それは、異なるライブラリがオブジェクトにプロパティを追加するときに名前が競合するリスクを回避できることです。

2 つの異なるライブラリがオブジェクトに基本データを追加したい状況を考えてみましょう。おそらく、両方ともオブジェクトに何らかの識別子を設定したいと考えているでしょう。単純に id をキーとして使用すると、複数のライブラリが同じキーを使用する大きな危険があります。

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

シンボルを使用すると、各ライブラリはインスタンス化時に必要なシンボルを生成できます。次に、生成された 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 实例中使用。但这是一个可怕的兔子洞。

本篇文章到这里就已经全部结束了,更多其他精彩内容可以关注PHP中文网的JavaScript教程视频栏目!

以上がJavaScript での Symbol 型の用途は何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。