First of all, ​​symbol​​​ is a built-in primitive type. And it's guaranteed to be unique. Symbols are often used to add unique property keys to an object won't collide with keys that any other code might add to the object. And the properties under the Symbol key are hidden from any mechanisms other code will typically use to access the object(like ​​for...in​​​, ​​for...of​​​, ​​Object.keys​​​, ​​Object.getOwnPropertyNames​​​ and ​​JSON.stringify​​​ etc.), but can be accessed by ​​Object.getOwnPropertySymbols​​​ and ​​Reflect.ownKeys​​​ (which will return both ​​string​​​ and ​​symbol​​ keys).

13 types of built-in Symbols

There are 13 types of built-in symbols to be listed as Vue3 goes.

const isSymbol = (val: unknown): val is symbol => typeof val === 'symbol'

const buildInSymbols = new Set(
Object.getOwnPropertyNames(Symbol)
.map(key => Symbol[key])
.filter(isSymbol)
)

​Symbol.hasInstance​

A method determining if a constructor object recognizes an object as its instance.

class Foo {
// static method
static [Symbol.hasInstance](value) {
return value === 'hi'
}

// instance method
[Symbol.hasInstance](value) {
return value instanceof Array
}
}

// is conceptually equal to `Foo[Symbol.hasInstance]('hi')`, and return `true`
'hi' instanceof Foo
// is conceptually equal to `(new Foo())[Symbol.hasInstance]([1,2,3])`, and return `true`
[1,2,3] instanceof new Foo()

​Symbol.isConcatSpreadable​

A Boolean value indicating if an object should be flattened to its array elements. The default value of ​​Symbol.isConcatSpreadable​​​ property of ​​Array​​​ is ​​true​​​, even if the ​​Array.prototype[Symbol.isConcatSpreadable]​​​ returns ​​undefined​​​, while the default value of the Array like object like ​​{length: 2, 0: 'element1', 1: 'element2'}​​​ is ​​false​​​ returning ​​undefined​​ also.

let foo = {length: 4, 0: 'e1', 1: 'e2', 'not an array element': 'e3'}
foo[Symbol.isConcatSpreadable] = true

// result is `['hi', 'e1', 'e2', undefined, undefined]`
let result = ['hi'].concat(foo)
console.log(result.length) // 5

​Symbol.iterator​

A method returning the default iterator for an object. Iterator offers an unified interface for different data structures to access their elements, and can be iterated over by ​​for...of​​​ construct. The so-called iterable object is that with ​​Symbol.iterator​​ property.

And the built-in iterable object are, ​​Array​​​, ​​String​​​, ​​Set​​​, ​​Map​​​, ​​generator object​​​, ​​TypedArray​​​, some of array-like objects(e.g. ​​arguments​​​, ​​NodeList​​​, ​​HTMLCollection​​ etc.) and so forth.

The interface of an Iterator is like:

interface Iterator<T> {
/**
* get the next element
*/
next(): {
done: boolean // indicate whether iteration finishes or not
value: T // element itself
}
}

And here's a good example for this:

// returning object with next method
class List<T> {
// instance method
[Symbol.iterator]() {
let index = 0
// iterator duck type instance;)
return {
next: () => ({
done: index + 1 >= this.xs.length
value: this.xs[index++]
})
}
}

constructor(private xs: T[]){}
}

// by generator function
class List<T> {
// instance method
*[Symbol.iterator]() {
for (let i = 0; i < this.xs.length; i++) {
yield this.xs[i]
}
}

constructor(private xs: T[]){}
}

​Symbol.asyncIterator​

It might be a good segue into ​​Symbol.asyncIterator​​ which is the asynchronous version of ​​Symbol.iterator​​ that we mention before.

And it's a method that returns the default AsyncIterator for an object, only used by the brand new loop statement ​​for await...of​​, I'll illustrate in an example:

// iterating over async iterables
class List<T> {
// instance method
[Symbol.asyncIterator]() {
let index = 0
// async iterator duck type instance whose `next` method returning Promise instance, and which will be fulfilled with value returning from the sync version of `next` method.
// I'm so sorry for the above boring, verbose description :)
return {
next: () => {
if (index + 1 >= this.xs.length) {
return Promise.resolve({done: true, value: this.xs[index++]})
}
else {
return Promise.resolve({done: false, value: this.xs[index++]})
}
}
}
}

constructor(private xs: T[]){}
}

// iterating over async generator
class List<T> {
// instance method
async *[Symbol.iterator]() {
for (let i = 0; i < this.xs.length; i++) {
yield this.xs[i]
}

// we are able to return an fulfilled Promise instance directly also.
yield Promise.resolve('hi')
}

constructor(private xs: T[]){}
}

// show time;)
List<String> names = new List<String>(['john', 'mary'])
for await(const name of names) {
console.log(name)
}

​Symbol.match​

A method that matches a string, also used to determine if an object may be used as a regular expression. Used as input by ​​String.prototype.match()​​.

Let's have an example:

class LeftBracket {
private value = '<'

[Symbol.match](str: string) {
const idx = str.indexOf(this.value)
if (idx === -1) {
return null
}
else {
let m = [this.value]
m.index = idx
m.input = str
m.groups = undefined
}
}
}

// usage
let str = '<div>'
str.match(new LeftBracket()) // ['<', index: 0, input: '<div>', groups: undefined]
// equal to the following invocation
(new LeftBracket())[Symbol.match](str) // ['<', index: 0, input: '<div>', groups: undefined]

By the way, there are two points should be notice for ​​String.prototype.match​​ invocation.

  1. Calling​​String.prototype.match​​​ with a non-​​RegExp​​​ input, it's implicitly coerced into a​​RegExp​​​ by using​​new RegExp()​​.
  2. If the​​RegExp​​​ input is with​​/g​​​ flag, the returning will contain the full matches with no​​index​​​,​​input​​​ or​​groups​​​ properties as that without​​/g​​ flag.
let re = /t(e)st/,
str = 'test1test2'

str.match(re) // ['test', 'e', index: 0, input: 'test1test2', groups: undefined]

re = /t(e)st/g
str.match(re) // ['test', 'test']

​Symbol.matchAll​

A method that returns an iterator, that yields all matches of the regular expression against a string, including capturing groups. Used by ​​String.prototype.matchAll()​​.

And each match is an ​​Array​​ with extra properties ​​index​​ and ​​input​​. And the matched text is as the first item of the match array, and then one item for the parenthetical capture group of the matched text.

const RE = {
re: /t(e)st/g,
*[Symbol.matchAll](str: string) {
let m
while (m = re.exec(str)) {
yield m
}
}
}

[...'test1test2'.matchAll(RE)] // [['test', 'e', index: 0, input: 'test1test2', groups: undefined], ['test', 'e', index: 5, input: 'test1test2', groups: undefined]]

For ​​String.prototype.matchAll​​​, please take attention that passing an input without ​​/g​​​ flag will hit into an ​​TypeError​​.

​Symbol.replace​

A method that expects two parameters and replaces matched substrings of a string. Used by ​​String.prototype.replace(searchValue, replaceValue)​​​ as the ​​searchValue​​.

const foo = {
searchValue: 'test',
[Symbol.replace](value, replaceValue) {
return value.replace(this.searchValue, replaceValue)
}
}

'test11'.replace(foo) // "11"

Back to the ​​String.prototype.replace​​​, the first argument ​​pattern​​​ could be either a string or RegExp, and the second one ​​replacement​​ could be a string or a function to be called for each match. The arguments to the replacement function are as follows:

  • ​match​​​, the matched substring(Corresponds to​​$&​​)
  • ​p1,p2,...​​, the nth string found by a parenthesized capture group including named capture groups.
  • ​offset​​, the offset of the matched substring within the whole string being examined.
  • ​string​​, the whole string to be examined.
  • ​groups​​, the named capturing groups object of which keys are the names of capturing group and the values are the matched portions.

​Symbol.search​

A method that returns the index within a string that matches the regular expression. Used by ​​String.prototype.search()​​.

An innovation of ​​String.prototype.indexOf​​ which accepts a ​​RegExp​​ input.

​Symbol.split​

A method that splits a string at the indices that match a regular expression. Used by ​​String.prototype.split()​

​Symbol.toPrimitive​

A method converting an object to a primitive object.

class User {
[Symbol.toPrimitive](hint: 'string' | 'number' | 'default') {
if (hint === 'number') {
return 123
}
else if (hint === 'string') {
return 'hi'
}

return true
}
}

let user = new User()
console.log(+user) // 123
console.log(`${user}`) // "hi"
console.log(user + '') // "true"

​Symbol.toStringTag​

A string value used for the default description of an object. Used by ​​Object.prototype.toString()​​.

class User{}
let user = new User()
Object.prototype.toString.call(user) // [object Object]

class Account{
get [Symbol.toStringTag]() {
return 'Account'
}
}
let account = new Account()
Object.prototype.toString.call(account) // [object Account]

​Symbol.species​

It's a function-valued property that the construct function that is used to create derived objects.

For example:

class MyArray extend Array {}
const a = new MyArray(1,2,3)
const b = a.map(x=>x)

b instanceof MyArray // true
b instanceof Array // true

class MyArray extend Array {
static get [Symbol.species]() {
return Array
}
}
const a = new MyArray(1,2,3)
const b = a.map(x=>x)

b instanceof MyArray // false
b instanceof Array // true


class T2 extends Promise {
static get [Symbol.species]() {
return Promise
}
}

let t2 = new T2(r => r()).then(v=>v)
t2 instanceof T2 // true

​Symbol.unscopables​

An object value of whose own and inherited property names are excluded from the ​​with​​ environment bindings of the associated object.

There is no other better manner to make sense than through an example:

const foo = {
name: 'hi',
age: 1,
get [Symbol.unscopeables]() {
return {
name: true
}
}
}

let age = 2
let name = 'john'
with(foo) {
console.log(age) // 1, reference to the age of foo
console.log(name) // "john", refer to the name outside current scope
}

Define your own ​​symbol​​ instance

  • Create our own unique local Symbol value with code​​Symbol(key?: string)​
  • Note that​​Symbol('hi')​​​ won't coerce the string​​hi​​ into a Symbol, it creates a brand new Symbol value each time instead.
  • We can get the key by accessing​​description​​ property in ES2019.
  • Create or get the shared global Symbols by calling​​Symbol.for(key: string)​
  • return the global Symbol with the given value of​​key​​ if it can be found in th global Symbol registry. Otherwise, the new one is created, added to the global Symbol registry under the given key, and returned.
  • Return the key of a global symbol instance​​Symbol.keyFor(instance: symbol)​
  • Note that, calling​​Symbol.keyFor​​​ with a local symbol instance will return​​undefined​​.

properties with Symbol key will not exist in ​​for...in​​​, nor ​​for...of​

Digression - ​​Object.keys​​​ and ​​Object.getOwnPropertyNames​

​Object.keys​​​ will returns the enumerable own property names of the passing argument, while ​​Object.getOwnPropertyNames​​ returns all own property names no matter it's enumerable or not.

And the same point of both is that, all property name they return are ​​string​​​. The ​​Symbol​​​ ones should be got by ​​Object.getOwnPropertySymbols​​​ of which the type of return value is ​​Symbol[]​​.


欢迎添加我的公众号一起深入探讨技术手艺人的那些事!

Yet Another Intro for Symbol_ide