• JS第七种数据类型:Symbol
  • Symbol的应用场景
  • 11个Symbol静态属性
  • Symbol元编程

一、JS第七种数据类型:Symbol

在ES6之前的JavaScript的基本数据类型有undefined、null、boolean、number、string、object,现在Symbol作为第七种基本数据类型。翻译symbol这个单词就是“符号,标志”的意思,顾名思义Symbol的应用场景也就离不开唯一性,想想“符号、标志”都是用来干嘛的?不就是用来标记特定事物的符号嘛,在程序中用来作为标记的不就是名称或者ID么,可能在这时候你会想到ES6提供的const常量声明字符,为什么有了const还需要Symbol呢?

注意:const常量的意思是值保持不变,而不是名称保持不变,也就说Symbol可以用来指代一个名称并保持不变的状态。

1.声明Symbol以及基本操作:

var os = Symbol("hello");

从上面示例中的Symbol变量成名中可以看到,Symbol不需要使用new关键字构造,所以Symbol不是对象,只有构造对象才使用new关键字。

typeof os ; //symbol

控制台打印Symbol类型值:

console.log(os);//Symbol(hello)

实际上在控制台打印Symbol类型值得时候Symbol()包裹得内容会发生toString()转换,来看下面这几个示例:

 var os1 = Symbol({
name:"txtx"
});//Symbol([object Object]) var os2 = Symbol({
name:"text",
toString:function(){
return this.name;
}
});//Symbol(text)

2.Symbol值得唯一性:

 var os3 = Symbol();
var os4 = Symbol();
console.log(os3 === os2);//false

基于Symbol实现属性名得唯一性:

 var os5 = Symbol("text");
var os6 = Symbol("text");
var obj = {
[os5]:"hello",
[os6]:function(){
return "hello";
}
}
console.log(obj);//{Symbol(text): "hello", Symbol(text): ƒ}
// 字符串的属性名
var a = "text";
var b = "text";
var objStr = {
[a]:"hello",
[b]:function(){
return "hello";
}
}
console.log(objStr);//{text: ƒ}

3.关于Symbol值的类型转换,Symbol的值不能进行运算,否在会报错,但可以进行boolean逻辑运算,能隐式或者显式的转换成Boolean值,也可以显式的转换成字符串和对象:

 if(os5){
console.log(os6);//Symbol(text)
}
console.log(Boolean(Symbol()));//true
console.log(!Symbol());//false
console.log(String(os5));//Symbol(text)
console.log(Object(os6));//Symbol {Symbol(text)}

4.关于Symbol()、Symbol.for()、Symbol.keyFor()的值与使用:

Symbol()的返回值是一个全新的不重复的值;

Symbol.for()指向同一个key的Symbol值;

Symbol.keyFor()返回Symbol.for()登记在全局的Symbol值的key。

Symbol()与Symbol.for()两种方式都可以生成一个新的Symbol,前者是每次都会生成一个完全不同的Symbol,后者生成的值会被登记到全局环境中提供搜索,使用Symbol.for()生成的话会先查询全局中是否登记过同一个键的Symbol,如果有登记就直接返回该Symbol值,如果没有新生成一个并且登记到全局。(注意:Symbol.for只能查询全局登记的Symbol,所以Symbol.for()使用Symbol()使用相同的key也是不能查询到,而是Symbol.for自己生成一个全新的Symbol值登记到全局)

 var osv1 = Symbol("value");
var osv2 = Symbol.for("textValue");
var obj = {};
var osv3 = Symbol.for(obj);
var osT1 = Symbol.keyFor(osv1);
var osT2 = Symbol.keyFor(osv2);
var osT3 = Symbol.keyFor(osv3); console.log(osT1);//undefined
console.log(osT2);//textValue
console.log(osT3);//[object Object]

通过示例可以看到,Symbol.keyFor()只能返回登记在全局的Symbol值的key,并且返回值为字符串,所以对象类型的key默认返回[object Object]。

二、Symbol的应用场景

场景一:使用Symbol值作为对象属性名

 let obj = {
num:123,
text:"Str"
}
console.log( obj["num"] );//
console.log( obj["text"] );//Str const osNum = Symbol("num");
const osText = Symbol("text");
let obS = {
[osNum]:123,
[osText]:"Str"
}
console.log( obS[osNum] );//
console.log( obS[osText] );//Str

看示简单的应用场景,其实这里隐藏了好几个值得注意的问题,使用Symbol值作为对象属性名不能被for...in和for...of正常枚举,不能使用Object.keys()、Object.getOwnPropertyNames()正常获取属性名,但是Object.getOwnPropertyDescriptor()获取的对象属性描述符中enumerable(枚举描述符)的值为true,也就说从对象描述符所表示的来看Symbol值作为对象属性名是可以被枚举的,但是需要一个特定的枚举方法来实现:Object.getOwnPropertySymbols()

 for(var ele in obS){
console.log(ele);//无输出
}
Object.keys(obj);//["num", "text"]
Object.keys(obS);//[]
Object.getOwnPropertyNames(obj);//["num", "text"]
Object.getOwnPropertyNames(obS);//[]
Object.getOwnPropertyDescriptor(obS,osNum)
//{value: 123, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertySymbols(obS);//[Symbol(num), Symbol(text)]

由枚举的区别可以看到普通枚举方法不能枚举Symbol值作为名称的属性,这也就是说可以通过这样的特性来定义“对内操作的属性”,可以非常便捷的区分对象的“对内操作”与“对外操作”;并且这一特性被延伸到了JSON数据格式的转换中,看示例:

 JSON.stringify(obj);//"{"num":123,"text":"Str"}"
JSON.stringify(obS);//"{}"

场景二:使用Symbol()替代常量值

 const TYPE_AUDIO = Symbol();
const TYPE_VIDEO = Symbol();
const TYPE_IMAGE = Symbol(); function handleFileResource(resourece){
switch(resourece.type){
case TYPE_AUDIO:
playAudio(resourece);
break;
case TYPE_VIDEO:
playVideo(resourece);
break;
case TYPE_IMAGE:
playImage(resourece);
break;
default:
throw new Error('Unknown type of resource');
}
}

场景三:模块的Singleton模式(nodejs部分补充)

三、11个Symbol静态属性

1.Symbol.hasInstance:实现instanceof关键字底层计算逻辑。

//语法:判断a是否是b的构造实例
a instanceof b;

实际上instanceof的底层逻辑是b调用Symbol.hasInstance内部方法来实现的,看下面这个ES5语法示例:

 function a(){
this.name = "a";
this[Symbol.hasInstance] = function(foo){
return foo instanceof Array;
}
}
var obj = new a();
console.log([1,2,3] instanceof obj);//true

再来看一下ES6语法的一个示例:

 class Even{
static name = "Even";
static [Symbol.hasInstance](obj){
console.log("instanceof调用了“" + this.name + "”上的Symbol.hasInstance方法,是由“" + obj + "”启动的命令。");
}
} 1 instanceof Even;//instanceof调用了“Even”上的Symbol.hasInstance方法,是由“1”启动的命令。

注:Symbol.hasInstance只适合Object类型,不能自定义Function类型上的instanceof指令。

2.Symbol.isConcatSoreadable:该属性等于一个布尔值,表示对象使用Array.prototype.concat()时是否可以展开。

 let arr1 = ['c','d'];
console.log( ['a','b'].concat(arr1,'e') ); //["a", "b", "c", "d", "e"]
console.log( arr1[Symbol.isConcatSpreadable] ); //undefined
arr1[Symbol.isConcatSpreadable] = false;
console.log( ['a','b'].concat(arr1,'e') ); //["a", "b", Array(2), "e"]
arr1[Symbol.isConcatSpreadable] = true;
console.log( ['a','b'].concat(arr1,'e') ); //["a", "b", "c", "d", "e"]
arr1[Symbol.isConcatSpreadable] = undefined;
console.log( ['a','b'].concat(arr1,'e') ); //["a", "b", "c", "d", "e"]

3.Symbol.species:属性指向当前对象的构造函数。创建实例时会默认调用这个方法。

 class MyArray extends Array{
static get [Symbol.species]() {return Array}
}
let a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);
console.log(a);
console.log(a instanceof MyArray); //true
console.log(a instanceof Array); //true console.log(mapped instanceof MyArray); //false
console.log(mapped instanceof Array); //true

4.Symbol.match:当String对象调用match方法时,实际是让匹配的正则表达式或者字符串调用Symbol.match这个标识指向的函数:

 var a = String('abc');
a[Symbol.match] = function(){
console.log("match");
}
console.log(a.match(/b/)); //["b", index: 1, input: "abc", groups: undefined] class myMatcher extends String{
static get [Symbol.species]() {return String}
// constructor(val){
// this.value = val;
// }
[Symbol.match](str){
return this.indexOf(str);
}
}
var b = new myMatcher("abc");
console.log(b);//abc
console.log(a.match(b));//
console.log('b'.match(b));//

5.Symbol.replace:当字符串对象调用replace方法时,实际上是由该方法传入的参数调用Symbol.replace方法来实现的:

 class myReplace extends String{
static get [Symbol.species]() {return String}
[Symbol.replace](){
console.log(this);
}
}
"abc def abc def".replace(new myReplace("abc"),'ccc'); //myReplace {"abc"}

6.Symbol.search:当字符串对象调用search方法时,实际上是由该方法传入的参数调用Symbol.search方法来实现的:

 var str = "abcmnifjabcfsfk";
String.prototype[Symbol.search] = function(string){
console.log("返回" + this + "在当前字符串中的第一个匹配位置");
return string.indexOf(this);
}
console.log(str.search("abc"));

7.Symbol.split:当字符串调用split方法时,实际上是由该方法传入的参数调用Symbol.split方法来实现,下面实现的是当参数为字符串时的方法,split方法参数为正则表达式时调用的是正则表达式对象原型上的Symbol.split方法(下面的示例暂时没有实现):

 String.prototype[Symbol.split] = function(str){
var arr = [];
var len = typeof this.toString() === "string" ? this.length : 0;
var index = str.indexOf(this);
if(index === -1 || index === 0){
arr.push(str);
}else{
arr.push(str.substr(0,index));
str = str.slice(index + len,str.length)
arr = arr.concat(this[Symbol.split](str));
}
return arr;
}
var str = "How are you doing today?";
console.log( str.split(" ") );

8.Symbol.iterator:迭代器,在Array,Set,Map,Nodelist对象原型上都有这个迭代函数,在这篇博客不详细介绍迭代器,下面演示一个具备迭代特性的对象手动添加迭代器,让这个对象可以被循环迭代(关于迭代器会在下一篇博客中详细的解析):

 var obj = {
0:"a",
1:"b",
2:"c",
length:3,
[Symbol.iterator]:function(){
let curIndex = 0;
let next = () => {
return {
value:this[curIndex],
done:this.length == curIndex++,
}
}
return {
next
}
}
} for(let p of obj){
console.log(p); //输出a b c (如果不再obj上添加Symbol.iterator迭代方法,会报错:obj is not iterable)
}

9.Symbol.toPrimitive:该方法是当对象被转换为原始值类型时被触发,方法会接收到一个参数,这个参数根据执行的原始值转换类型分别为string、number、boolean三个字符串,手动实现代码如下:

 Object.prototype[Symbol.toPrimitive] = function(hint){
console.log(hint);
switch (hint){
case 'number':
return this.valueOf();
case 'boolean':
return this.valueOf();
case 'string':
return this.toString() === '[object String]' ? this.valueOf() : this.toString();
default:
throw new Error();
} }

10.Symbol.toStringTag:这个属性可以说是用来配置对象调用toString方法时的返回值:

 var obj = {[Symbol.toStringTag]:"对象"};
console.log(obj.toString());//[object 对象]

需要注意的是这种配置必须写在类的大括号中“{}”,否在会报错。

11.Symbol.unscopables:用来指定对象属性不被with扩展到作用于上:

 var obj = {
name:"他乡踏雪",
age:3,
stature:666,
[Symbol.unscopables]:{
name:true
}
}
with(obj){
console.log(name);//这里打印一个空值
console.log(age);//
console.log(stature);//
}

四、Symbol元编程

元编程可以简单理解为针对程序本身编程,用来操作目标程序本身的行为特性的编程。比如查看对象a是否是在对象b的原型链上,这种元编程通常也被叫做内省introspection;还有宏也是元编程的典型,代码在编译时修改自身;用for...in循环枚举对象的键,或者检查一个对象是否是某个“类构造器”的实例,也都是常见的元编程例子。

元编程关注以下一个或者几个点:代码查看自身、代码修改自身、代码修改默认语言特性,以此来影响其他代码。

(以后补充)

ES6入门九:Symbol元编程的更多相关文章

  1. ES6入门之Symbol

    ES5对象属性名都是字符串容易造成属性名的冲突. eg:var a = { name: 'lucy'}; a.name = 'lili';这样就会重写属性 ES6引入了一种新的原始数据类型Symbol ...

  2. 流畅的python第十九章元编程学习记录

    在 Python 中,数据的属性和处理数据的方法统称属性(attribute).其实,方法只是可调用的属性.除了这二者之外,我们还可以创建特性(property),在不改变类接口的前提下,使用存取方法 ...

  3. ES6入门十:iterator迭代器

    迭代模式 ES6迭代器标准化接口 迭代循环 自定义迭代器 迭代器消耗 一.迭代模式 迭代模式中,通常有一个包含某种数据集合的对象.该数据可能存在一个复杂数据结构内部,而要提供一种简单的方法能够访问数据 ...

  4. ES6中的元编程-Proxy & Reflect

    前言 ES6已经出来好久了,但是工作中比较常用的只有let const声明,通过箭头函数改this指向,使用promise + async 解决异步编程,还有些数据类型方法...所以单独写一篇文章学习 ...

  5. 网络编程懒人入门(九):通俗讲解,有了IP地址,为何还要用MAC地址?

    1.前言 标题虽然是为了解释有了 IP 地址,为什么还要用 MAC 地址,但是本文的重点在于理解为什么要有 IP 这样的东西.本文对读者的定位是知道 MAC 地址是什么,IP 地址是什么. (本文同步 ...

  6. cesium编程入门(九)实体 Entity

    cesium编程入门(九)实体 Entity 在cesium编程入门(五)绘制形状提到过添加实体的方法,这一节聊一聊实体相关的一些内容: 先来看 Entity 的各个属性 id 唯一标志,如果没设置, ...

  7. ES6入门笔记

    ES6入门笔记 02 Let&Const.md 增加了块级作用域. 常量 避免了变量提升 03 变量的解构赋值.md var [a, b, c] = [1, 2, 3]; var [[a,d] ...

  8. ES6入门系列三(特性总览下)

    0.导言 最近从coffee切换到js,代码量一下子变大了不少,也多了些许陌生感.为了在JS代码中,更合理的使用ES6的新特性,特在此对ES6的特性做一个简单的总览. 1.模块(Module) --C ...

  9. es6入门总结

    let和const命令 let命令 循环体的let变量只对花括号作用域可见,花括号外不可见 循环体的语句部分是一个父作用域,而循环体内部是一个单独的子作用域 let声明的变量不存在变量提升,未声明的使 ...

随机推荐

  1. Dynatrace

    1.概述 过去,企业的IT部门在测量系统性能时,一般重点测量为最终用户提供服务的硬件组件的利用率,如CPU利用率以及通过网络传输的字节数.虽然这种方法也提供了一些宝贵的信息,但却忽视了最重要的因素-- ...

  2. MySQL 慢查询日志介绍

    转: MySQL 慢查询日志介绍 2018年08月23日 08:47:40 曾梦想仗剑走天涯XX 阅读数 1104   一. 慢查询介绍 MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记 ...

  3. ubuntu Tensorflow object detection API 开发环境搭建

    https://blog.csdn.net/dy_guox/article/details/79111949 luo@luo-All-Series:~$ luo@luo-All-Series:~$ s ...

  4. Spring事务管理2----编程式事务管理

    编程式事务管理 通过使用将Spring框架提供的TransactionTemplate模板注入到业务层来进行事务管理,这样对业务层原来的代码修改过多.不利于项目的后期维护. 以下是声明式事务管理的具体 ...

  5. Python简单遍历字典及删除元素的方法

    Python简单遍历字典及删除元素的方法 这篇文章主要介绍了Python简单遍历字典及删除元素的方法,结合实例形式分析了Python遍历字典删除元素的操作方法与相关注意事项,需要的朋友可以参考下 具体 ...

  6. 使用PostMan测试WebService接口

    使用PostMan测试WebService接口 参考资料: 通过XML请求WebServer  https://blog.csdn.net/qq_33933408/article/details/53 ...

  7. laravel5.5的定时任务详解(demo)

    原文地址:https://blog.csdn.net/LJFPHP/article/details/80417552

  8. ffmpeg 使用 gdb 调试相关技巧

    本文说明了,在ffmpeg二次开发或调用库的过程,如何借助于ffmpeg源码进行调试. 注:ffmpeg版本是4.0. 1. 编写代码 编写将pcm数据转换为mp2的代码 pcm_to_mp2.c # ...

  9. ES 数据类型

    官网数据类型网址 有价值的参考博客 本文 Elasticsearch 版本为 7.2 1. 核心数据类型 (1)字符串类型: text, keyword (2)数字类型:long, integer, ...

  10. 记一次 vmware ESXI 升级

    旧服务器的esxi版本为 60(6765062),计划安装成为最新版 的为ESXI 60  (14513180),中间波折遇坑多次,现记录如下: 一.开启ESXI的SSH 访问权限(可以通过按F2进入 ...