ES6入门九:Symbol元编程

  • 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()转换,来看下面这几个示例:

 1 var os1 = Symbol({
 2     name:"txtx"
 3 });//Symbol([object Object])
 4
 5 var os2 = Symbol({
 6     name:"text",
 7     toString:function(){
 8         return this.name;
 9     }
10 });//Symbol(text)

2.Symbol值得唯一性:

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

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

 1 var os5 = Symbol("text");
 2 var os6 = Symbol("text");
 3 var obj = {
 4     [os5]:"hello",
 5     [os6]:function(){
 6         return "hello";
 7     }
 8 }
 9 console.log(obj);//{Symbol(text): "hello", Symbol(text): ƒ}
10 // 字符串的属性名
11 var a = "text";
12 var b = "text";
13 var objStr = {
14     [a]:"hello",
15     [b]:function(){
16         return "hello";
17     }
18 }
19 console.log(objStr);//{text: ƒ}

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

1 if(os5){
2     console.log(os6);//Symbol(text)
3 }
4 console.log(Boolean(Symbol()));//true
5 console.log(!Symbol());//false
6 console.log(String(os5));//Symbol(text)
7 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值登记到全局)

 1 var osv1 = Symbol("value");
 2 var osv2 = Symbol.for("textValue");
 3 var obj = {};
 4 var osv3 = Symbol.for(obj);
 5 var osT1 = Symbol.keyFor(osv1);
 6 var osT2 = Symbol.keyFor(osv2);
 7 var osT3 = Symbol.keyFor(osv3);
 8
 9 console.log(osT1);//undefined
10 console.log(osT2);//textValue
11 console.log(osT3);//[object Object]

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

二、Symbol的应用场景

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

 1 let obj = {
 2     num:123,
 3     text:"Str"
 4 }
 5 console.log( obj["num"] );//123
 6 console.log( obj["text"] );//Str
 7
 8 const osNum = Symbol("num");
 9 const osText = Symbol("text");
10 let obS = {
11     [osNum]:123,
12     [osText]:"Str"
13 }
14 console.log( obS[osNum] );//123
15 console.log( obS[osText] );//Str

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

 1 for(var ele in obS){
 2     console.log(ele);//无输出
 3 }
 4 Object.keys(obj);//["num", "text"]
 5 Object.keys(obS);//[]
 6 Object.getOwnPropertyNames(obj);//["num", "text"]
 7 Object.getOwnPropertyNames(obS);//[]
 8 Object.getOwnPropertyDescriptor(obS,osNum)
 9 //{value: 123, writable: true, enumerable: true, configurable: true}
10 Object.getOwnPropertySymbols(obS);//[Symbol(num), Symbol(text)]

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

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

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

 1 const TYPE_AUDIO = Symbol();
 2 const TYPE_VIDEO = Symbol();
 3 const TYPE_IMAGE = Symbol();
 4
 5 function handleFileResource(resourece){
 6     switch(resourece.type){
 7         case TYPE_AUDIO:
 8             playAudio(resourece);
 9             break;
10         case TYPE_VIDEO:
11             playVideo(resourece);
12             break;
13         case TYPE_IMAGE:
14             playImage(resourece);
15             break;
16         default:
17             throw new Error(‘Unknown type of resource‘);
18     }
19 }

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

三、11个Symbol静态属性

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

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

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

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

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

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

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

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

1 let arr1 = [‘c‘,‘d‘];
2 console.log( [‘a‘,‘b‘].concat(arr1,‘e‘) ); //["a", "b", "c", "d", "e"]
3 console.log( arr1[Symbol.isConcatSpreadable] ); //undefined
4 arr1[Symbol.isConcatSpreadable] = false;
5 console.log( [‘a‘,‘b‘].concat(arr1,‘e‘) ); //["a", "b", Array(2), "e"]
6 arr1[Symbol.isConcatSpreadable] = true;
7 console.log( [‘a‘,‘b‘].concat(arr1,‘e‘) ); //["a", "b", "c", "d", "e"]
8 arr1[Symbol.isConcatSpreadable] = undefined;
9 console.log( [‘a‘,‘b‘].concat(arr1,‘e‘) ); //["a", "b", "c", "d", "e"]

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

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

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

 1 var a = String(‘abc‘);
 2 a[Symbol.match] = function(){
 3     console.log("match");
 4 }
 5 console.log(a.match(/b/)); //["b", index: 1, input: "abc", groups: undefined]
 6
 7 class myMatcher extends String{
 8     static get [Symbol.species]() {return String}
 9     // constructor(val){
10     //     this.value = val;
11     // }
12     [Symbol.match](str){
13         return this.indexOf(str);
14     }
15 }
16 var b = new myMatcher("abc");
17 console.log(b);//abc
18 console.log(a.match(b));//0
19 console.log(‘b‘.match(b));//1

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

1 class myReplace extends String{
2     static get [Symbol.species]() {return String}
3     [Symbol.replace](){
4         console.log(this);
5     }
6 }
7 "abc def abc def".replace(new myReplace("abc"),‘ccc‘); //myReplace {"abc"}

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

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

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

 1 String.prototype[Symbol.split] = function(str){
 2     var arr = [];
 3     var len = typeof this.toString()  === "string" ? this.length : 0;
 4     var index = str.indexOf(this);
 5     if(index === -1 || index === 0){
 6         arr.push(str);
 7     }else{
 8         arr.push(str.substr(0,index));
 9         str = str.slice(index + len,str.length)
10         arr = arr.concat(this[Symbol.split](str));
11     }
12     return arr;
13 }
14 var str = "How are you doing today?";
15 console.log( str.split(" ") );

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

 1 var obj = {
 2     0:"a",
 3     1:"b",
 4     2:"c",
 5     length:3,
 6     [Symbol.iterator]:function(){
 7         let curIndex = 0;
 8         let next = () => {
 9             return {
10                 value:this[curIndex],
11                 done:this.length == curIndex++,
12             }
13         }
14         return {
15             next
16         }
17     }
18 }
19
20 for(let p of obj){
21     console.log(p); //输出a b c (如果不再obj上添加Symbol.iterator迭代方法,会报错:obj is not iterable)
22 }

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

 1 Object.prototype[Symbol.toPrimitive] = function(hint){
 2     console.log(hint);
 3     switch (hint){
 4         case ‘number‘:
 5             return this.valueOf();
 6         case ‘boolean‘:
 7             return this.valueOf();
 8         case ‘string‘:
 9             return this.toString() === ‘[object String]‘ ? this.valueOf() : this.toString();
10         default:
11             throw new Error();
12     }
13
14 }

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

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

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

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

 1 var obj = {
 2     name:"他乡踏雪",
 3     age:3,
 4     stature:666,
 5     [Symbol.unscopables]:{
 6         name:true
 7     }
 8 }
 9 with(obj){
10     console.log(name);//这里打印一个空值
11     console.log(age);//3
12     console.log(stature);//666
13 }

四、Symbol元编程

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

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

(以后补充)

原文地址:https://www.cnblogs.com/ZheOneAndOnly/p/11411968.html

时间: 2024-10-03 10:48:21

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

ES6入门之Symbol

ES5对象属性名都是字符串容易造成属性名的冲突. eg:var a = { name: 'lucy'}; a.name = 'lili';这样就会重写属性 ES6引入了一种新的原始数据类型Symbol,表示独一无二的值. 重新复习下新知识:基本数据类型有6种:Undefined.Null.布尔值(Boolean).字符串(String).数值(Number).对象(Object). 这里新添加了一种:Symbol 注意,Symbol函数前不能使用new命令,否则会报错.这是因为生成的Symbol

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

1.前言 标题虽然是为了解释有了 IP 地址,为什么还要用 MAC 地址,但是本文的重点在于理解为什么要有 IP 这样的东西.本文对读者的定位是知道 MAC 地址是什么,IP 地址是什么. (本文同步发布于:http://www.52im.net/thread-2067-1-1.html) 2.关于作者 翟志军,个人博客地址:https://showme.codes/,Github:https://github.com/zacker330.感谢作者的原创分享. 作者的另一篇<即时通讯安全篇(七)

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

0.导言 最近从coffee切换到js,代码量一下子变大了不少,也多了些许陌生感.为了在JS代码中,更合理的使用ES6的新特性,特在此对ES6的特性做一个简单的总览. 1.模块(Module) --Chrome测试不可用 在ES6中,有class的概念,不过这只是语法糖,并没有解决模块化问题.Module功能则是为了解决模块化问题而提出的. 我们可以使用如下方式定义模块: 11_lib.js文件内容 // 导出属性和方法 export var PI = 3.1415926; export fun

[WebGL入门]九,顶点缓存的基础

注:文章译自http://wgld.org/,原作者杉本雅広(doxas),文章中如果有我的额外说明,我会加上[lufy:],另外,鄙人webgl研究还不够深入,一些专业词语,如果翻译有误,欢迎大家指正. 局部坐标 使用WebGL可以绘制各种各样的3D模型,而且,还可以绘制点和线,决定绘制什么肯定需要顶点.没有顶点的话,也就没有多边形了,因为没有办法进行点和线的绘制了.所以,WebGL的编程中一定要处理顶点情报.而且,顶点中有必须要包含的情报,那就是顶点的位置坐标.既然顶点的位置坐标是必须的,那

ES6深入学习记录(三)编程风格

今天学习阮一峰ES6编程风格,其中探讨了如何将ES6的新语法,运用到编码实践之中,与传统的JavaScript语法结合在一起,写出合理的.易于阅读和维护的代码. 1.块级作用域 (1)let 取代 var ES6 提出了两个新的声明变量的命令: let 和 const.其中,let 完全可以取代 var,因为两者语义相同,而且 let 没有副作用. 上面代码如果用 var 替代 let,实际上就声明了两个全局变量,这显然不是本意.变量应该只在其声明的代码块内有效,var 命令做不到这一点. va

使用 JavaScript 进行元编程(网站打不了)

Felix Woo 世界因我存在 首页 ThinkPage 新闻 天气 glickr 相册 留言板 友情链接 2007-8 8 使用 JavaScript 进行元编程 发表于: 16:50 | 分类: 开发技术 | 评论: 2 | 人气: 279 | 转自:http://benchwang.spaces.live.com/blog/cns!1621B5CAD6EB680B!149.entry Adam McCrea 写了篇使用 JavaScript 进行元编程的文章: Metaprogammin

ES6入门之let和const命令

前言 大家好,我是一只流浪的kk,当你看到这边博客的时候,说明你已经进入了ES6学习的领域了,从本篇博客开始,我将会将自己学习到ES6的相关知识进行整理,方便大家参考和学习,那么我将带你进入第一节的内容学习let和const命令,本篇博客从三个方面进行全方位解析. let命令 首先我们需要学习的是let命令的使用,这个使用非常简单,它其实也是声明变量的一种方式,和var相比我把它的特点总结了如下四点,一起来看看吧! (1):基本用法 <!DOCTYPE html> <html> &

ES6入门十二:Module(模块化)

webpack4打包配置babel7转码ES6 Module语法与API的使用 import() Module加载实现原理 Commonjs规范的模块与ES6模块的差异 ES6模块与Nodejs模块相互加载 模块循环加载 一.webpack4打包配置babel7转码ES6 1.webpack.config.js 在webpack中配置babel转码其实就是在我之前的webpack配置解析基础上添加babel的加载器,babel的具体转码配置在之前也有一篇详细的博客做了解析: webpack安装与

Julia元编程初探——以简单符号求导为例

Julia语言具有强大的元编程机制,本文用Julia实现<SICP>中文第二版中第 99 页中的实例:符号求导,体验一下Julia元编程. 运行结果如下: julia> include("deriv.jl") # 加载代码multiplicand (generic function with 1 method) julia> deriv(:(x + 3), :x) # 求表达式 x + 3 关于 x 的导数1 julia> deriv(:(x * y),