obj: object是什么意思_为什么用Symbols?

Symbols是最新的JavaScript 原始数据类型,在用作对象属性时特别有用。 但是,它跟string用法上有什么不一样?

在讨论Symbols之前,先来回顾一下许多开发人员可能都不知道的JavaScript功能。

背景

JavaScript中基本上有两种数据类型。 第一种类型是原始数据,第二种类型是引用数据。 原始数据包括简单的数据类型,例如Number,Booleans,Strings,Undefined和null(注意:即使typeof null ==='object',null仍然是原始数据)。

原始数据类型不可改变。 但是赋了原始数据类型的变量可以被重新赋值。 例如,

x = 1; x ++;

你已经重新赋值变量x。 但是,你没有改变1的原始数据。

某些语言(如C)具有传递引用和传值的概念。 JavaScript也有这个概念,它是根据传递的数据类型推断出来的。 如果您将原始数据类型传递给函数,函数调用并不会修改这个原始数据类型,但是,如果传进去的不是原始数据类型,那么调用函数的时候就有可能修改这个值了。

例子:

function primitiveMutator(val) {
  val = val + 1;
}
let x = 1;
primitiveMutator(x);
console.log(x); // 1
function objectMutator(val) {
  val.prop = val.prop + 1;
}
let obj = { prop: 1 };
objectMutator(obj);
console.log(obj.prop); // 2

原始数据类型的变量(神秘的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
// 但是它们的属性是原始数据类型
console.log(obj1.name === obj2.name); // true

对象在JavJavaScript中的重要角色:Object,通常用来表示键/值对的集合。 但是,这种方式会有一个很大的限制:在Symbols出来之前,对象的键只能是String。如果我们尝试使用非字符串值作为对象的键,则该值将被强制转换为String。

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

另:稍微跑一下题,Map数据结构允许键不是String的情况下存储键/值结构的数据。

什么是Symbol?

现在我们知道什么是原始数据类型了,终于可以讨论什么是Symbol了。Symbol是不能被重新创建的原始数据类型。用上文举例就是,Symbol类似于对象,每个新建的实例都完全不相等,但是Symbol是原始数据类型,因为它的值不能改变:

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

实例化Symbol的时候,可以传入一个String类型的参数。 此值旨在用于调试代码,并不会真正影响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有一个很重要的用途就是用作对象的属性,先上例子:

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()能够获取对象上所有键的列表,包括字String和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

防止属性名冲突

Symbol可能无法直接为对象添加私有属性。 但是,它可以在不同库希望向相同对象添加属性时避免名称冲突的风险。

有时候两个不同的库都想要将某种元数据添加到相同的对象。 可能是通过简单地使用两个字符串id作为键某种标识符。

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

每个库可以在实例化时生成其所需的Symbol。 然后,只要遇到Object时,就可以检查Object上的Symbol,并设置为Object。

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

你可能会好奇,这个例子里,为什么每个库在实例化时都不能简单地生成随机字符串或使用特殊命名空间的字符串?

const library1property = uuid(); // 随机字符串
function lib1tag(obj) {
  obj[library1property] = 42;
}
const library2property = 'LIB2-NAMESPACE-id'; // 特殊命名空间
function lib2tag(obj) {
  obj[library2property] = 369;
}

这种方法实际上与Symbol非常相似,除非两个库选择使用相同的属性名称,否则不存在重叠的风险。

这种唯一名称的属性名的方法仍有一个缺点:它的键非常容易找到,尤其是当代码运行以迭代键或以其他方式序列化对象时。参考示例:

const library2property = 'LIB2-NAMESPACE-id'; // 命名空间
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属性。

现在可以通过使用Object.defineProperty()轻松纠正库对象字符串污染JSON输出的问题:

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


通过将其可枚举描述符设置为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。隐藏的字符串属性和符号都隐藏在序列化程序中。可以使用Reflect.ownKeys()方法提取这两个属性,因此实际上不是私有的。假设我们对属性名称的字符串版本使用某种命名空间/随机值,那么我们就可以排除多个库同时修改同一键的风险。

但是,由于字符串是不可变的,并且Symbol始终保证是唯一的,因此仍有可能生成一个可能的字符串组合并产生冲突。这意味着Symbol确实提供了我们无法从字符串中获得的好处。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值