Symbol
符号是一个原始数据类型、和Number、String、Boolean不同,它没有字面量语法。
普通符号创建
const syb = Symbol();
const syb1 = Symbol("对符号的描述");
没有字面量语法的创建方式,只能通过Symbol("描述")
方法创建。
不能把Symbol当作构造函数调用,new Symbol()
会报错。
可以传入一个String参数,用于描述这个符号实例。不是String类型会转换成String。
特点
-
没有字面量。
-
typeof Symbol()
返回"symbol"。 -
符号实例是唯一的、不可变的。
const syb = Symbol(); const syb1 = Symbol(); syb !== syb1
-
符号可以作为对象的属性名存在,称为符号属性。
-
开发者可以通过精心的设计,让这些属性无法通过常规方式被外界访问(私有属性)。利用闭包,将符号实例作为对象的属性名,无法访问符号实例,自然无法通过对象的属性名访问属性了。说明:
[]
为计算属性名,里面可以放表达式。var person = (function() { const name = Symbol("私有属性_名字"); return { [name]: "Herb", getName() { return this[name]; } } }())
-
符号属性是不能枚举的,因此在
for-in
循环、Object.keys
方法都无法读取到符号属性。 -
Object.getOwnPropertyNames
尽管可以得到自身所有无法枚举的属性名,但是仍然无法读取到符号属性名。 -
Object.getOwnPropertyDescriptor
获取某个对象的某个属性和符号的属性描述符对象(该属性必须直接属于该对象)。 -
ES6新增
Object.getOwnPropertySymbols
方法,可以读取符号属性名,返回一个数组。
-
-
符号无法被隐式转换,因此不能被用于数学运算、字符串拼接或其他隐式转换的场景,但符号可以显式的转换为字符串,通过 String 转型函数进行转换即可(实际上是调用toString()方法)。
var syb = Symbol("符号"); var sybStr = String(syb); String(syb) == syb.toString()
-
如果想使用符号包装对象,可以借用Object()函数
var sybObj = Object(Symbol("符号对象")); typeof sybObj == "object" sybObj.constructor // ƒ Symbol() { [native code] } sybObj instanceof Symbol // true
共享符号
-
for
通过
Symbol.for("符号描述")
创建共享符号,可以在不同对象中用相同的符号作为属性名。创建后,会把符号存储在全局符号注册表中,以符号描述作为键,符号实例作为名。
调用
Symbol.for("查找")
时,会先查看注册表中有没有这个符号描述("查找"),如果没有就创建一个Symbol("查找")
,如果有的话,就根据键返回名。let foo = Symbol.for("foo"); let foo1 = Symbol.for("foo"); foo == foo1 let bar = Symbol("bar"); let bar1 = Symbol.for("bar"); bar !== bar1 let unDes = Symbol.for(); // Symbol(undefined)
内部实现大概类似如此:
const symbolFor = (() => { const global = {}; return des => { if(!global[des]) { global[des] = Symbol(des); } return global[des]; } })();
利用立即执行函数保存global变量,形成全局符号注册表。
作为参数传给Symbol.for()的任何值(除了符号实例)都会被转换为字符串。如果是符号实例就会抛出TypeError,应该是经过了特殊处理。
-
keyFor
Symbol.keyFor()
来查询全局注册表,接受一个符号实例,返回全局符号注册表中对应的键(符号描述),如果不是全局符号,就返回undefined。如果参数不是符号,则抛出TypeError。
知名符号
ES6引入了一批常用内置符号,用于暴露语言内部行为。
-
Symbol.hasInstance
这个符号作为一个属性表示一个方法,该方法决定一个构造器对象是否认可一个对象是它的实例。
这个属性定义在Function原型上,所有函数和类上都可以调用。
在ES6中,instanceof操作符会使用Symbol.hasInstance函数来确定关系。
class Person{ constructor(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } }
var p = new Person("Herb", 18, "男"); var isPersonInstance = p instanceof Person; // true
p在Person的原型链上,p的隐式原型
__proto__
就是Person的原型。p.__proto__ == Person.prototype
结果为true。isPersonInstance = Person[Symbol.hasInstance](p); // true
两者方式是等价的。
Person instanceof Function // true Person[Symbol.hasInstance] === Function.prototype[Symbol.hasInstance] // true
Symbol.hasInstance方法定义在Function的原型上,因为Person构造函数是由Function创建的,所以Person继承了Function。
在Person及其原型链上寻找Symbol.hasInstance方法,由于没有在Person上重写,最后找到了Function.prototype。
let propertyObj = Object.getOwnPropertyDescriptor(Function.prototype, Symbol.hasInstance); // writable: false
查看其属性描述符发现,Symbol.hasInstance不能被改写。
所以不能直接用:
Person[Symbol.hasInstance] = function() { console.log("重写Function.prototype的方法"); return false; }
会在原型链上查找有无Symbol.hasInstance属性,最后发现Function原型上有,修改后发现修改失败。
所以可以直接在Person类中写一个静态方法。
var obj = {}; Object.prototype.name = "herb"; Object.defineProperty(Object.prototype, "name", { writable: false }); obj.name = "ice"; obj.name // "herb" obj // {}
属性是无法修改的。也不会出现在obj上。
var obj = {} Object.prototype.name = "herb"; obj.name = "ice"; obj.name // "ice" obj // { name: "ice" }
没有设置属性是不可写的时候,是能添加到对象上的。
class Person{ constructor(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } // 在Person类的prototype上定义了Symbol.hasInstance方法,无意义 [Symbol.hasInstance]() { console.log("Person改写Function原型上的方法"); return false; } // 在类上重写了函数,是类的静态方法 static [Symbol.hasInstance]() { console.log("Person改写Function原型上的方法"); return false; } }
p instanceof Person Person[Symbol.hasInstance](p) // Person改写Function原型上的方法 // false
使用类的静态方法书写形式,可以重写。
Object.defineProperty(Person, Symbol.hasInstance, { value: function() { console.log("defineProperty重写") return false; } }) // defineProperty重写 // false
也可以定义一个对象的属性,也就是类的属性(静态)。
知名符号可以让我们更多的参与到js语言的内部实现上来。
-
Symbol.toPrimitive
这个符号作为属性表示一个方法,该方法将对象转换为相应的原始值。
通过在一个实例的Symbol.toPrimitive属性上定义一个函数,这个函数可以改变默认行为。
class Team{ constructor(teamName, teamNum) { this.teamName = teamName; this.teamNum = teamNum; this[Symbol.toPrimitive] = function(type){ switch(type) { case "number": return this.teamNum; case "default": case "string": return this.teamName; } } } }
const team = new Team("特种部队", 4); Number(team) // 4 +team // 4 team + 1 // "特种部队1" team + "nb" // "特种部队nb" team * 5 // 20 String(team) // "特种部队"
进行数学运算时,type为number。
出现加号和字符串时,type为default。
调用String()转型函数时,type为string。
这里还要再研究一下。
把这个属性放在实例中,就会重复创建多次。也可以放在原型链上。
class Team{ constructor(teamName, teamNum) { this.teamName = teamName; this.teamNum = teamNum; } [Symbol.toPrimitive](type) { switch(type) { case "number": return this.teamNum; case "default": case "string": return this.teamName; } } }
不过这样会对Team.prototype产生影响。
String(Team.prototype) // "undefined"
-
Symbol.toStringTag
这个符号作为一个属性表示一个字符串,用于创建对象的默认字符串描述。
由内置方法Object.prototype.toString()使用。
通过toString()方法获取对象标识时,会检索由Symbol.toStringTag指定的实例标识符,默认为"Object"。
class Bar { constructor() { this[Symbol.toStringTag] = "Bar"; } // [Symbol.toStringTag] = "Bar" } var bar = new Bar(); bar.toString(); // "[object Bar]"
写成实例属性或类的静态属性都可以。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!