Symbol原理

用来生成一个独一无二的值

Symbol是ES6规范中的一种原生数据类型,也就是说所有支持ES6规范的JavaScript都应该实现Symbol这一基本数据类型

那么对于V8而言,就应该在底层实现Symbol对象

src/builtins/builtins-symbol.cc 17

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ES #sec-symbol-constructor
BUILTIN(SymbolConstructor) {
HandleScope scope(isolate);
if (!args.new_target()->IsUndefined(isolate)) { // [[Construct]]
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kNotConstructor,
isolate->factory()->Symbol_string()));
}
// [[Call]]
Handle<Symbol> result = isolate->factory()->NewSymbol();
Handle<Object> description = args.atOrUndefined(isolate, 1);
if (!description->IsUndefined(isolate)) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, description,
Object::ToString(isolate, description));
result->set_description(String::cast(*description));
}
return *result;
}

我们可以看到这个最终的函数返回结果是 result,

result = isolate -> factory() -> NewSymbol();

src/torque/earley-parser.h 437

1
2
3
4
5
6
Symbol* NewSymbol(std::initializer_list<Rule> rules = {}) {
auto symbol = std::make_unique<Symbol>(rules);
Symbol* result = symbol.get();
generated_symbols_.push_back(std::move(symbol));
return result;
}

可以看出Symbol 是从 make_unique 函数中创建的

参考 https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
namespace detail {
template<class>
constexpr bool is_unbounded_array_v = false;
template<class T>
constexpr bool is_unbounded_array_v<T[]> = true;

template<class>
constexpr bool is_bounded_array_v = false;
template<class T, std::size_t N>
constexpr bool is_bounded_array_v<T[N]> = true;
} // namespace detail

template<class T, class... Args>
std::enable_if_t<!std::is_array<T>::value, std::unique_ptr<T>>
make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template<class T>
std::enable_if_t<detail::is_unbounded_array_v<T>, std::unique_ptr<T>>
make_unique(std::size_t n)
{
return std::unique_ptr<T>(new std::remove_extent_t<T>[n]());
}

template<class T, class... Args>
std::enable_if_t<detail::is_bounded_array_v<T>> make_unique(Args&&...) = delete;

可以看出

make_unique 是从unique_ptr中派生出来的

参考 https://en.cppreference.com/w/cpp/memory/unique_ptr

std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope.

也就是说unique_ptr是一个对象.

看一下Symbol在pollyfill中的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
function Symbol(description) {
if (!(this instanceof Symbol)) return new Symbol(description, secret);
if (this instanceof Symbol && arguments[1] !== secret) throw TypeError();

var descString = description === undefined ? undefined : String(description);

set_internal(this, '[[SymbolData]]', unique(128));
set_internal(this, '[[Description]]', descString);

symbolMap[this] = this;
return this;
}

可以看出,就是一个构造函数, 只是这个函数在使用的时候有点特殊,return 了一个Symbol自身的实例,也就是一个对象.

问题一: 那么一个对象,是不能在对象中作为key来使用的

1
2
3
4
5
6
7
Object.defineProperty(Symbol.prototype, 'toString', {
value: function toString() {
var s = strict(this);
var desc = s['[[Description]]'];
return 'Symbol(' + (desc === undefined ? '' : desc) + s['[[SymbolData]]'] + ')';
},
configurable: true, writeable: true, enumerable: false });

在一下返回值,'Symbol(' + (desc === undefined ? '' : desc) + s['[[SymbolData]]'] + ')';
这个只是一个以Symbol字符串description开头的字符串, 按道理是应该没有后面的s['[[SymbolData]]'],为了保持唯一性就加上了这个,唯一标示.

问题二, Object.getPropertyNames 是不是会打印出 Symbol,答案 是 ,所以需要改写 getPropertyNames

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
define(
Object, 'getOwnPropertyNames',
function getOwnPropertyNames(o) {
if (Object.prototype.toString.call(o) === '[object Window]') {
// Workaround for cross-realm calling by IE itself.
// https://github.com/inexorabletash/polyfill/issues/96
try {
return $getOwnPropertyNames(o).filter(isStringKey);
} catch (_) {
return $window_names.slice();
}
}
return $getOwnPropertyNames(o).filter(isStringKey);
}, !nativeSymbols);

// 19.1.2.8 Object.getOwnPropertySymbols ( O )
define(
Object, 'getOwnPropertySymbols',
function getOwnPropertySymbols(o) {
return $getOwnPropertyNames(o).filter(symbolForKey).map(symbolForKey);
}, !nativeSymbols);

问题三 JSON在序列化的时候,不能序列化Symbol,但是目前而言,它只是一个String,也就是说会被序列化.

没有办法, JSON.stringify 后 会变成 一个随机字符串

问题四 typeof symbol 怎么被改写

没有办法, typeof 是一个不能被改写的操作, 如此依赖 typeof symbol === ‘object’

Symbol 不是能是一个构造函数,它没有 prototype,

同时 symbol实例是没有__proto__属性的,因此 instanceof 不能被修改