Bootstrap

【深入理解ES6】Symbol

创建Symbol

新增基础类型 Symbol,用于解决 私有属性 问题

let firstName = Symbol()
let person = {}

person[firstName] = 'Nicholas'
console.log(person["firstName"])

注意, Symbol是原始值,不能 new Symbol()构建实例,只能是new Object(【Symbol 值】)

 Symbol 接受可选参数,用于描述该  Symbol 

内部存储在 [[Description]] 属性中,所以只有 Symbol 的toString 方法能读取

而 console.log 隐式调用 toString 方法

let firstName = Symbol('first name')
let person = {}

person[firstName] = 'abc'

console.log(person)

Symbol辨识

通过 typeof,返回值为 symbol

let firstName = Symbol('first name')
console.log(typeof firstName)

Symbol的使用方法

  • 对象中括号引用
  • 对象字面量
  • Object.defineProperty
let firstName = Symbol('first name')
let person_1 = {}

person_1[firstName] = 'person111'

let person_2 = {
  [firstName]: 123
}

Object.defineProperty(person_2, firstName, { writable: false })

let lastName = Symbol('last name')

Object.defineProperties(person, {
  [lastName]: {
    value: 111,
    writable: false
  }
})

Symbol的共享体系

Symbol.for() 会在 Symbol全局注册表 中查找

  • 存在,则返回对应的 Symbol 值
  • 不存在,则新创建一个 Symbol 存入注册表
let uid = Symbol.for("uid")
let object = {}

object[uid] = 1234

console.log(object) // { [Symbol(uid)]: 1234 }

let uid2 = Symbol.for('uid')

console.log(uid === uid2) // true
console.log(uid2, object[uid2]) // Symbol(uid) 1234

Symbol.keyFor 在 Symbol全局注册表 中检索 Symbol 相关的键值

let uid = Symbol.for("uid")
console.log(Symbol.keyFor(uid))

let uid2 = Symbol.for("uid")
console.log(Symbol.keyFor(uid2))

let uid3 = Symbol('uid')
console.log(Symbol.keyFor(uid3))

Symbol 的类型转换

  • 其他类型的值不存在逻辑等价
  • 尤其Number 和 String ,会报错
  • String()对象实例化,内部调用了Symbol.toString()
let uid = Symbol.for('uid'),
  desc = String(uid) // uid.toString()

console.log(desc) // 'Symbol(uid)'

Symbol 属性检索

ES5中

Object.keys() 可枚举属性

Object.getOwnPropertyNames() 对象的所有属性

以上一律不支持对象中 Symbol 属性

ES6新增 Object.getOwnPropertySymbols 返回对象中的所有 Symbol 属性

let uid = Symbol('uid')
let password = Symbol('password')

let obj = {
  a: 123,
  b: 'asd',
  [uid]: '12',
  [password]: 123,
}

// [ 'a', 'b' ]
console.log(Object.getOwnPropertyNames(obj)) 

// [ Symbol(uid), Symbol(password) ]
console.log(Object.getOwnPropertySymbols(obj)) 



通过 well-know Symbol 暴露内部操作

Symbol.hasInstance

用于确定对象是否为函数的实例(即 obj instanceof Object)

function Test() {}

Object.defineProperty(Test, Symbol.hasInstance, {
  value: function (num) {
    return (num instanceof Number) && (num > 0 && num < 100)
  }
})

let test = new Number(1)
let test_0 = new Number(0)

console.log(test instanceof Test)
console.log(test_0 instanceof Test)

let a = new Number('123') // 123 === a flase

let b = Number('123')     // 123 === b true

a instanceof Number       // true

b instanceof Number       // false

 Symbol.isConcatSpreadable

用于配置某对象作为Array.prototype.concat()方法的参数时是否展开其数组元素。

标识对象是否具有 length 和 数字键

let a = {
  2: '123',
  3: 'awe',
  'test': 'test',
  length: 5,
  [Symbol.isConcatSpreadable]: true
}

let arr = ['0', '1', '2']

let newArr = arr.concat(a)

console.log(newArr)
// [ '0', '1', '2', <2 empty items>, '123', 'awe', <1 empty item> ]

match && replace && search && split

  • String.match(reg) 字符串是否匹配正则表达式

  • String.replace(reg, replacement) 字符串匹配正则表达式部分内容替换

  • String.search(reg) 字符串匹配正则表达式索引位置的字符

  • String.split(reg) 字符串以正则表达式符合分割

// 等价于 /^.{10}$/
let hasLengthOf10 = {
  [Symbol.match]: function (value) {
    return value.length === 10 ? [value] : null
  },
  [Symbol.replace]: function (value, replacement) {
    return value.length === 10 ? replacement : value
  },
  [Symbol.search]: function (value) {
    return value.length === 10 ? 0 : -1
  },
  [Symbol.split]: function (value) {
    return value.length === 10 ? [,] : [value]
  }
}

const message_1 = "Hello world", // 11 字符
  message_2 = "Hello John" // 10 字符

const match_1 = message_1.match(hasLengthOf10),
  match_2 = message_2.match(hasLengthOf10)

print("match:", match_1, match_2)

const replace_1 = message_1.replace(hasLengthOf10, '1'),
  replace_2 = message_2.replace(hasLengthOf10, '1')

print("replace: ", replace_1, replace_2)

const search_1 = message_1.replace(hasLengthOf10),
  search_2 = message_2.replace(hasLengthOf10)

print("search: ", search_1, search_2)

const split_1 = message_1.split(hasLengthOf10),
  split_2 = message_2.split(hasLengthOf10)

print("split: ", split_1, split_1)

function print(str, ...arg) {
  console.log(str)
  arg.forEach((item, index) => {
    console.log(index, item)
  })
  console.log('-'.repeat(10))
}

Symbol.toPrimitive

会将对象转换为对应的原始值

  • 数字模式
    • 首先用 valueOf() 方法,如果结果为原始值,则返回
    • 其次才是 toString() 方法,如果结果为原始值,则返回
    • 报错
  • 字符串模式
    • 首先用 toString() 方法,如果结果为原始值,则返回
    • 其次才是  toString() 方法,如果结果为原始值,则返回
    • 报错
  • Default模式
    • 多数按照Number模式
    • Date对象则采用 string 模式
function Template(degrees) {
  this.degrees = degrees
}

Template.prototype[Symbol.toPrimitive] = function (hint) {
  switch (hint) {
    case 'string':
      return this.degrees + '\u00b0';
    case 'number':
      return this.degrees;
    case 'default':
      return this.degrees + ' degrees'
  }
}

const freezing = new Template(32)

console.log(freezing + '!') // "32 degrees!"
console.log(freezing / 2) // 16
console.log(String(freezing)) // "32°"



Symbol.toStringTag

更改 Object.prototype.toString.call 的返回

function Person(name) {
  this.name = name
}

Person.prototype[Symbol.toStringTag] = "Person"

let person = new Person('wgf')

console.log(Object.prototype.toString.call(person)) // [object Person]

ES5 之前JSON是由 Douglas Crockfound 的 json2.js 提供

在出现ES默认全局JSON对象后,代码中需要判断 JSON 的来源

function supportsNativeJSON() {
  return JSON !== 'undefined' && 
    Object.prototype.toString.call(JSON) === '[object JSON]'
}

Symbol.unscropable

由于 ES6 语法向下兼容,解决 with 语句绑定问题

with语句

 等价于调用了两次 colors.push()

 但是 ES6 数组引入新的方法 values,会导致上述代码歧义

 (原意应为:调用 colors 数组外部变量)

var values = [1, 2, 3],
  colors = ['red', 'yellow', 'blue'],
  color = 'black'

with (colors) {
  push(color)
  push(...values)
}

console.log(colors)

默认内置

通过手动设置该属性,with 语句将不再创建这些方法的绑定,从而支持老代码的运行

Array.prototype[Symbol.unscopables] = Object.assign(Object.create(null), {
  copyWithin: true,
  entries: true,
  fill: true,
  find: true,
  findIndex: true,
  keys: true,
  values: true
})

注意:除非使用 with 语句且正在修改代码库中的已有的代码,否则不要为自己的对象定义 Symbol.unscopable

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;