ECMAScript(ES): 也是脚本语言,通常被看成是JavaScript的标准化规范,实际上JavaScript是ES的扩展语言,ES提供了基本的语法,比如怎么去定义变量,怎么进行条件判断等。JavaScript则实现了这些语法,并做了相应的扩展,比如在浏览器环境中操作BOM/DOM,在node环境中操作文件等操作。
浏览器环境:JavaScript = ES + Web APIs(BOM+DOM) node环境:JavaScript = ES + Node APIs(fs + net + etc.)
ECMAScript和JavaScript的关系: 1996年11月,JavaScript的创造者–Netscape公司,决定将JavaScript提交给国际标准化组织ECMA,希望这门语言能够成为国际标准。 次年,ECMA发布262号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为ECMAScript,这个版本就是1.0版。该标准一开始就是针对JavaScript语言制定的,但是没有称其为JavaScript, 有两个方面的原因。 一是商标,JavaScript本身已被Netscape注册为商标。 二是想体现这门语言的制定者是ECMA,而不是Netscape,这样有利于保证这门语言的开发性和中立性。
ECMAScript2015(ES2015/ES6): 1.较之前版本的变化 解决原有语法上的问题或不足(let,const提供的块级作用域); 对原有语法进行增强(解构展开,模板字符串等); 全新的对象,全新的方法,全新的功能(Promise,proxy等); 全新的数据类型和数据结构(Symbol,Set,Map); 2.let/const与块级作用域 作用域,即某个成员起作用的范围。在全局作用域和函数作用域的基础之上,ES2015又新增了块级作用域。
块,就是代码中一对花括号内的范围,如if语句和for循环语句中的花括号都是块,在没有块级作用域之前,在块里边定义的变量(使用var声明的变量),在外边也能访问到。使用let/const声明的变量在块外边是无法访问到的。
const在let基础上多了一个只读的特性,即变量一旦声明就不可改变(指向内存地址不可改变),所以const声明变量时必须赋值。
如果声明的变量是一个引用,引用中的属性改变不会改变变量的指向地址,是有效的。
1 2 3 const obj = {}obj.name = 'lagou' obj = {}
最佳实践:不用var,主用const,配合let
3.数组和对象的解构 解构赋值是对赋值运算符的扩展。它是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
数组解构 基本 1 let [a, b, c] = [1 , 2 , 3 ];
可嵌套 1 2 3 4 let [a, [[b], c]] = [1 , [[2 ], 3 ]];
可忽略 1 2 3 let [a, , b] = [1 , 2 , 3 ];
不完全解构 剩余运算符 1 2 3 let [a, ...b] = [1 , 2 , 3 ];
字符串 1 2 3 4 5 6 let [a, b, c, d, e] = 'hello' ;
解构默认值 1 2 3 4 let [a = 2 ] = [undefined ]; let [a = 3 , b = a] = []; let [a = 3 , b = a] = [1 ]; let [a = 3 , b = a] = [1 , 2 ];
对象解构 基本 1 2 3 4 5 6 let { foo, bar } = { foo : 'aaa' , bar : 'bbb' }; let { baz : foo } = { baz : 'ddd' };
可嵌套可忽略 1 2 3 4 5 6 7 let obj = {p : ['hello' , {y : 'world' }] };let {p : [x, { y }] } = obj;let obj = {p : ['hello' , {y : 'world' }] };let {p : [x, { }] } = obj;
不完全解构 1 2 3 4 let obj = {p : [{y : 'world' }] };let {p : [{ y }, x ] } = obj;
剩余运算符 1 2 3 4 let {a, b, ...rest} = {a : 10 , b : 20 , c : 30 , d : 40 };
解构默认值 1 2 3 4 let {a = 10 , b = 5 } = {a : 3 };let {a : aa = 10 , b : bb = 5 } = {a : 3 };
4.模板字符串 模板字符串相当于加强版的字符串,用反引号`,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。(模板字符串中的换行和空格都是会被保留的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let string1 = `Hey, can you stop angry now?` ;console .log(string1);let name = "Mike" ;let age = 27 ;let info = `My Name is ${name} ,I am ${age+1 } years old next year.` console .log(info);function f ( ) { return "have fun!" ; } let string2= `Game start,${f()} ` ;console .log(string2);
标签模板—-是一个函数的调用,其中调用的参数是模板字符串。
1 2 3 alert`Hello world!` ; alert('Hello world!' );
5.字符串的扩展方法(startsWith,endsWith,includes) 6.参数默认值 1 2 3 4 5 6 7 function myFunction (x, y = 10 ) { return x + y; } myFunction(0 , 2 ) myFunction(5 );
7.剩余参数 JS函数内部有个arguments对象,可以拿到全部实参,ES6给我们带来了一个新的对象,可以拿到除开始参数外的参数,即剩余参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function func (a, ...rest ) { console .log(a) console .log(rest) } func(1 ) func(1 , 2 , 3 , 4 ) function func (a, ...rest, b ) { } function func (a, b, ...rest ) {} func.length
arguments和剩余参数的区别
arguments是一个伪数组(Array-like) 剩余参数是一个真正数组(Array),具有Array.prototype上的所有方法 arguments上有callee,callee上有caller 8.数组展开 其实参照上一条,可以看出“…”符号的作用是将数组展开。
1 2 3 const arr = [1 , 2 , 3 ]console .log(...arr)
9.箭头函数 极大地减少代码量,简化代码且更易读;
1 2 3 4 5 6 7 function a (n1, n2 ) { return n1+n2 } let a = (n1, n2 ) => n1 + n2
箭头函数内部的this是词法作用域,由上下文确定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const person = { name : 'Tom' , say : () => { console .log(this .name) }, say_ : function ( ) { setTimeout (()=> { console .log(this .name) }) } } person.say() person.say_()
10.Object.assign Object.assign方法用来将源对象(source)的所有可枚举属性,复制到目标对象(target)。它至少需要两个对象作为参数,第一个参数是目标对象,后面的参数都是源对象。只要有一个参数不是对象,就会抛出TypeError错误。
如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 var target = { a : 1 };var source1 = { b : 2 };var source2 = { c : 3 };Object .assign(target, source1, source2);target var obj = {a : 1 }; Object .assign(obj) === obj typeof Object .assign(2 ) Object .assign(undefined ) Object .assign(null ) Object .assign({b : 'c' }, Object .defineProperty({}, 'invisible' , { enumerable : false , value : 'hello' }) ) Object .assign({b : 'c' }, Object .defineProperty({}, 'invisible' , { enumerable : true , value : 'hello' }) ) var target = { a : { b : 'c' , d : 'e' } }var source = { a : { b : 'hello' } }Object .assign(target, source)var obj1 = {a : {b : 1 }}; var obj2 = Object .assign({}, obj1); obj1.a.b = 2 ; obj2.a.b
11.Proxy(大部分摘自:http://blog.poetries.top/2018/12/21/es6-proxy/) proxy在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作,必须通过这层拦截。
1 var proxy = new Proxy (target, handler);
new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var target = { name : 'poetries' }; var logHandler = { get : function (target, key ) { console .log(`${key} 被读取` ); return target[key]; }, set : function (target, key, value ) { console .log(`${key} 被设置为 ${value} ` ); target[key] = value; } } var targetWithLog = new Proxy (target, logHandler); targetWithLog.name; targetWithLog.name = 'others' ; console .log(target.name);
targetWithLog 读取属性的值时,实际上执行的是 logHandler.get :在控制台输出信息,并且读取被代理对象 target 的属性。 在 targetWithLog 设置属性值时,实际上执行的是 logHandler.set :在控制台输出信息,并且设置被代理对象 target 的属性的值1 2 3 4 5 6 7 8 9 10 var proxy = new Proxy ({}, { get : function (target, property ) { return 35 ; } }); proxy.time proxy.name proxy.title
Proxy 实例也可以作为其他对象的原型对象
1 2 3 4 5 6 7 8 var proxy = new Proxy ({}, { get : function (target, property ) { return 35 ; } }); let obj = Object .create(proxy);obj.time
proxy对象是obj对象的原型,obj对象本身并没有time属性,所以根据原型链,会在proxy对象上读取该属性,导致被拦截。
对于代理模式Proxy的作用主要体现在三个方面:
拦截和监视外部对对象的访问 降低函数或类的复杂度 在复杂操作前对操作进行校验或对所需资源进行管理 Proxy所能代理的范围–handler
实际上handler本身就是ES6所新设计的一个对象.它的作用就是用来 自定义代理对象的各种可代理操作 。它本身一共有13中方法,每种方法都可以代理一种操作.其13种方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 handler.getPrototypeOf() handler.setPrototypeOf() handler.isExtensible() handler.preventExtensions() handler.getOwnPropertyDescriptor() handler.defineProperty() handler.has() handler.get() handler.set() handler.deleteProperty() handler.ownKeys() handler.apply() handler.construct()
Proxy场景 实现私有变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 var target = { name : 'poetries' , _age : 22 } var logHandler = { get : function (target,key ) { if (key.startsWith('_' )){ console .log('私有变量age不能被访问' ) return false } return target[key]; }, set : function (target, key, value ) { if (key.startsWith('_' )){ console .log('私有变量age不能被修改' ) return false } target[key] = value; } } var targetWithLog = new Proxy (target, logHandler); targetWithLog.name; targetWithLog.name = 'others' ;
在下面的代码中,我们声明了一个私有的apiKey,便于api这个对象内部的方法调用,但不希望从外部也能够访问api._apiKey
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var api = { _apiKey : '123abc456def' , getUsers : function ( ) {}, getUser : function (userId ) {}, setUser : function (userId, config ) {} }; console .log("An apiKey we want to keep private" , api._apiKey);var apiKey = api._apiKey; api._apiKey = '987654321' ;
很显然,约定俗成是没有束缚力的。使用 ES6 Proxy 我们就可以实现真实的私有变量了,下面针对不同的读取方式演示两个不同的私有化方法。第一种方法是使用 set / get 拦截读写请求并返回 undefined:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 let api = { _apiKey : '123abc456def' , getUsers : function ( ) { }, getUser : function (userId ) { }, setUser : function (userId, config ) { } }; const RESTRICTED = ['_apiKey' ];api = new Proxy (api, { get (target, key, proxy ) { if (RESTRICTED.indexOf(key) > -1 ) { throw Error (`${key} is restricted. Please see api documentation for further info.` ); } return Reflect .get(target, key, proxy); }, set (target, key, value, proxy ) { if (RESTRICTED.indexOf(key) > -1 ) { throw Error (`${key} is restricted. Please see api documentation for further info.` ); } return Reflect .get(target, key, value, proxy); } }); console .log(api._apiKey);api._apiKey = '987654321' ;
第二种方法是使用has拦截in操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var api = { _apiKey : '123abc456def' , getUsers : function ( ) { }, getUser : function (userId ) { }, setUser : function (userId, config ) { } }; const RESTRICTED = ['_apiKey' ];api = new Proxy (api, { has (target, key ) { return (RESTRICTED.indexOf(key) > -1 ) ? false : Reflect .has(target, key); } }); console .log("_apiKey" in api);for (var key in api) { if (api.hasOwnProperty(key) && key === "_apiKey" ) { console .log("This will never be logged because the proxy obscures _apiKey..." ) } }
抽离校验模块
让我们从一个简单的类型校验开始做起,这个示例演示了如何使用Proxy保障数据类型的准确性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let numericDataStore = { count : 0 , amount : 1234 , total : 14 }; numericDataStore = new Proxy (numericDataStore, { set (target, key, value, proxy ) { if (typeof value !== 'number' ) { throw Error ("Properties in numericDataStore can only be numbers" ); } return Reflect .set(target, key, value, proxy); } }); numericDataStore.count = "foo" ; numericDataStore.count = 333 ;
如果要直接为对象的所有属性开发一个校验器可能很快就会让代码结构变得臃肿,使用Proxy则可以将校验器从核心逻辑分离出来自成一体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 function createValidator (target, validator ) { return new Proxy (target, { _validator : validator, set (target, key, value, proxy ) { if (target.hasOwnProperty(key)) { let validator = this ._validator[key]; if (!!validator(value)) { return Reflect .set(target, key, value, proxy); } else { throw Error (`Cannot set ${key} to ${value} . Invalid.` ); } } else { throw Error (`${key} is not a valid property` ) } } }); } const personValidators = { name (val ) { return typeof val === 'string' ; }, age (val ) { return typeof age === 'number' && age > 18 ; } } class Person { constructor (name, age ) { this .name = name; this .age = age; return createValidator(this , personValidators); } } const bill = new Person('Bill' , 25 );bill.name = 0 ; bill.age = 'Bill' ; bill.age = 15 ;
通过校验器和主逻辑的分离,你可以无限扩展 personValidators 校验器的内容,而不会对相关的类或函数造成直接破坏。更复杂一点,我们还可以使用 Proxy 模拟类型检查,检查函数是否接收了类型和数量都正确的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 let obj = { pickyMethodOne : function (obj, str, num ) { }, pickyMethodTwo : function (num, obj ) { } }; const argTypes = { pickyMethodOne : ["object" , "string" , "number" ], pickyMethodTwo : ["number" , "object" ] }; objProxy = new Proxy (obj, { get : function (target, key, proxy ) { var value = target[key]; return function (...args ) { var checkArgs = argChecker(key, args, argTypes[key]); return Reflect .apply(value, target, args); }; } }); function argChecker (name, args, checkers ) { for (var idx = 0 ; idx < args.length; idx++) { var arg = args[idx]; var type = checkers[idx]; if (!arg || typeof arg !== type) { console .warn(`You are incorrectly implementing the signature of ${name} . Check param ${idx + 1 } ` ); } } } objProxy.pickyMethodOne(1 ,1 ,'' ); objProxy.pickyMethodTwo("wopdopadoo" , {}); objProxy.pickyMethodOne({}, "a little string" , 123 ); objProxy.pickyMethodTwo(123 , {});
访问日志
对于那些调用频繁、运行缓慢或占用执行环境资源较多的属性或接口,开发者会希望记录它们的使用情况或性能表现,这个时候就可以使用Proxy充当中间件的角色,轻而易举实现日志功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 let api = { _apiKey : '123abc456def' , getUsers : function ( ) { }, getUser : function (userId ) { }, setUser : function (userId, config ) { } }; function logMethodAsync (timestamp, method ) { setTimeout (function ( ) { console .log(`${timestamp} - Logging ${method} request asynchronously.` ); }, 0 ) } api = new Proxy (api, { get : function (target, key, proxy ) { var value = target[key]; return function (...arguments ) { logMethodAsync(new Date (), key); return Reflect .apply(value, target, arguments ); }; } }); api.getUsers();
预警和拦截
假设你不想让其他开发者删除 noDelete 属性,还想让调用 oldMethod 的开发者了解到这个方法已经被废弃了,或者告诉开发者不要修改 doNotChange 属性,那么就可以使用 Proxy 来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 let dataStore = { noDelete : 1235 , oldMethod : function ( ) { }, doNotChange : "tried and true" }; const NODELETE = ['noDelete' ]; const NOCHANGE = ['doNotChange' ];const DEPRECATED = ['oldMethod' ]; dataStore = new Proxy (dataStore, { set (target, key, value, proxy ) { if (NOCHANGE.includes(key)) { throw Error (`Error! ${key} is immutable.` ); } return Reflect .set(target, key, value, proxy); }, deleteProperty (target, key ) { if (NODELETE.includes(key)) { throw Error (`Error! ${key} cannot be deleted.` ); } return Reflect .deleteProperty(target, key); }, get (target, key, proxy ) { if (DEPRECATED.includes(key)) { console .warn(`Warning! ${key} is deprecated.` ); } var val = target[key]; return typeof val === 'function' ? function (...args ) { Reflect .apply(target[key], target, args); } : val; } }); dataStore.doNotChange = "foo" ; delete dataStore.noDelete; dataStore.oldMethod();
过滤操作
某些操作会非常占用资源,比如传输大文件,这个时候如果文件已经在分块发送了,就不需要在对新的请求作出响应(非绝对),这个时候就可以使用 Proxy 对当请求进行特征检测,并根据特征过滤出哪些是不需要响应的,哪些是需要响应的。下面的代码简单演示了过滤特征的方式,并不是完整代码,相信大家会理解其中的妙处
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 let obj = { getGiantFile : function (fileId ) { } }; obj = new Proxy (obj, { get (target, key, proxy ) { return function (...args ) { const id = args[0 ]; let isEnroute = checkEnroute(id); let isDownloading = checkStatus(id); let cached = getCached(id); if (isEnroute || isDownloading) { return false ; } if (cached) { return cached; } return Reflect .apply(target[key], target, args); } } });
中断代理
Proxy支持随时取消对target的代理,这一操作常用于完全封闭对数据或接口的访问。在下面的示例中,我们使用了Proxy.revocable方法创建了可撤销代理的代理对象:
1 2 3 4 5 6 7 let target = {};let handler = {};let {proxy,revoke} = Proxy .revocable(target, handler);proxy.foo = 123 ; proxy.foo revoke(); proxy.foo
Proxy和Object.defineProperty()的区别 Object.defineProperty() 的问题主要有三个:
不能监听数组的变化 必须遍历对象的每个属性 必须深层遍历嵌套的对象 Proxy: 针对对象:针对整个对象,而不是对象的某个属性 支持数组:不需要对数组的方法进行重载,省去了众多 hack 嵌套支持:get 里面递归调用 Proxy 并返回优势:Proxy 的第二个参数可以有 13 种拦截方法,比 Object.defineProperty() 要更加丰富,Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个已有的老方法。 劣势:Proxy 的兼容性不如 Object.defineProperty() (caniuse 的数据表明,QQ 浏览器和百度浏览器并不支持 Proxy,这对国内移动开发来说估计无法接受,但两者都支持 Object.defineProperty()),不能使用 polyfill 来处理兼容性
12.Reflect Reflect是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与proxy handlers的方法相同。Reflect不是一个函数对象,因此它是不可构造的(不能使用new进行调用,所有属性和方法都是静态的)。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Reflect .apply(target, thisArgument, argumentsList)Reflect .construct(target, argumentsList[, newTarget])Reflect .defineProperty(target, propertyKey, attributes)Reflect .deleteProperty(target, propertyKey)Reflect .get(target, propertyKey[, receiver])Reflect .getOwnPropertyDescriptor(target, propertyKey)Reflect .getPrototypeOf(target)Reflect .has(target, propertyKey)Reflect .isExtensible(target)Reflect .ownKeys(target)Reflect .preventExtensions(target)Reflect .set(target, propertyKey, value[, receiver])Reflect .setPrototypeOf(target, prototype)
更多 13.Promise 在promise这种编程概念出现之前,回调函数是异步编程的主要思想,但是当一个业务依赖另一个业务结果时,就出现了回调嵌套,当回调嵌套越来越多,就形成了回调地狱,代码冗杂,不易读,不易维护,之后便出现了promise的编程概念,直到被ES规范实现成为标准API。
顾名思义,promise就是承诺,这里就是Promise对象对使用者的一个承诺,即当你使用promise去处理一个业务时,它能承诺给你对应的结果,不管是成功还是失败,都是可预见的。且promise实现了链式调用和promise.all等方法,彻底解决了回调地狱的问题,代码层次清晰,更易读,更易维护。
此文不再对Promise做更多详细介绍,具体可参考以下链接:
使用 promises —— MDN Promise —— MDN Promise — 廖雪峰 JavaScript Promise:去而复返 —— 司徒正美 (上面的原文)JavaScript Promise:简介 —— Web Fundamentals 1 分钟读完《10 分钟学会 JavaScript 的 Async/Await》 —— justjavac JavaScript Promise 迷你书(中文版) JavaScript 进阶之路——认识和使用 Promise,重构你的 Js 代码 —— 博客园 熟悉掌握Promise后,可自己尝试实现Promise的一些基本功能,以下给出一些思路和待实现功能点,不详细之处可自行实现:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
14.class类 MDN中对class的定义是,class 声明创建一个基于原型继承的具有给定名称的新类。
简单点,你就理解为,class定义后,可以new出一个实例来,它其实是一个让JavaScript看上去更像面向对象的语法糖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 let p = new Person('SeaChan' )p.say();
class中的静态方法(static);不使用new就可通过类名调用的方法,如果其中返回实例,就可不用new直接创建一个实例对象,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Person { constructor (name ) { this .name = name } say ( ) { console .log(`my name is ${this .name} ` ); } static create (name ) { return new Person(name) } } let p = Person.create('SeaChan' )p.say();
类的继承(extends);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Person { constructor (name ) { this .name = name } say ( ) { console .log(`my name is ${this .name} ` ); } } class Kids extends Person { constructor (name, age ) { super (name); this .age = age } hello ( ) { super .say(); console .log(`hello, I am ${this .age} years old!` ); } } let kid = new Kids('SeaChan' , 17 )kid.hello()
15.Set和Map Set和Map是ES6新增的数据类型;
Set和数组很像,它是一系列数据的集合,可以按照插入的顺序迭代它的元素。Set中的元素只会出现一次,即Set中的元素是唯一的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 let set = new Set ()set.add(1 ).add(3 ).add(2 ).add(4 ).add(2 ) console .log(set);set.forEach(val => console .log(val)) for (let val of set) { console .log(val) } console .log(set.size); console .log(set.has(5 )); console .log(set.delete(2 )); console .log(set); set.clear() console .log(set); const arr = [1 , 2 , 3 , 1 , 2 , 6 , 4 ]const res = new Set (arr)console .log(res) Array .from(res) [...res]
Map是一种能根据原始插入顺序(即:可迭代)记录键值对的数据类型。其中任何值(对象或者原始值)都可以作为一个键或者一个值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 let map = new Map ()map.set('SeaChan' , '99' ) map.set('Tom' , '90' ) let key1 = {a : 123 }map.set(key1, 'hahahaha' ) console .log(map);map.forEach((val, key ) => { console .log(key, val) })
16.Symbol Symbol是ES6新增的基本数据类型,每个从Symbol()返回的symbol值都是唯一的,使用它的目的是为对象创建一个唯一属性的标识符,保证对象中的属性不会被修改或者被覆盖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const sym1 = Symbol ()const sym2 = Symbol ('foo' )const sym3 = Symbol ('foo' )sym2 === sym3 let obj = { key0 : '000' , [sym1]: '123' , [sym2]: '456' } for (const objKey in obj) { console .log(objKey); } console .log(Object .keys(obj)); console .log(Object .getOwnPropertySymbols(obj)) const s1 = Symbol .for('foo' )const s2 = Symbol .for('foo' )console .log(s1 === s2); console .log(Symbol .keyFor(s1)); console .log(Symbol .keyFor(s2)); JSON .stringify(obj)
17.for…of循环 因为for…in对象遍历存在一些一定的局限性,引入了全新的for…of循环,作为遍历所有数据结构的统一方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 const arr = [1 ,2 ,3 ,4 ]for (const item of arr) { if (item > 2 ) { break } } const s = new Set (['s1' , 's2' , 's3' ])for (const item of s) { console .log(item); } const m = new Map ()m.set(Symbol .for('m1' ), 'm1' ) m.set(Symbol .for('m2' ), 'm2' ) for (const [val, key] of m) { console .log(key, val); } const obj = { a : 1 , b : 2 } for (const item of obj) { console .log(item); }
以上看到Object通过for…of遍历报错,提示obj不可迭代,说明要满足for…of循环必须满足iterator条件,为了给数据提供这种条件,ES2015提供了iterator接口,实现该接口就是for…of遍历的基础。来看一下数组上的iterator:
1 2 3 4 5 6 const arr = [1 ,2 ,3 ]let ire = arr[Symbol .iterator]()ire.next() ire.next() ire.next() ire.next()
可迭代接口实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const obj = { store : [1 , 2 , 3 , 4 ], [Symbol .iterator]: function ( ) { let index = 0 const _this = this return { next : function ( ) { return { value : _this.store[index], done : index++ >= _this.store.length } } } } } for (const item of obj) { console .log(item); }
迭代器模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 const todos = { life : ['吃饭' , '睡觉' , '打豆豆' ], learn : ['语文' , '数学' , '英语' ], work : ['编程' , '洗衣服' ], [Symbol .iterator]: function ( ) { let index = 0 const allData = [...this.life, ...this.learn, ...this.work] return { next : function ( ) { return { value : allData[index], done : index++ >= allData.length } } } } } for (const item of todos) { console .log(item); }
18.生成器(Generator) 由生成器函数函数返回,并且满足可迭代接口。
避免异步编程中回调嵌套太深,提供更好的异步编程解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function * fn ( ) { console .log('SeaChan' ); } let f = fn()console .log(f); f.next() f.next()
完整的生成器配合yield使用,实质效果就是,调用一下生成器对象的next方法,就执行一部分代码,到下一个yield停止,next直到所有的yield执行完后结果变为{value: undefined, done: true}不再改变。
1 2 3 4 5 6 7 8 9 10 11 function * fn ( ) { yield 1 yield 2 yield 3 } const g = fn()g.next() g.next() g.next() g.next()
Generator应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function *numberMaker ( ) { let i = 0 while (true ) { yield i++ } } let numMaker = numberMaker()console .log(numMaker.next().value); console .log(numMaker.next().value); console .log(numMaker.next().value); ...... const todos = { life : ['吃饭' , '睡觉' , '打豆豆' ], learn : ['语文' , '数学' , '英语' ], work : ['编程' , '洗衣服' ], [Symbol .iterator]: function * ( ) { const allData = [...this.life, ...this.learn, ...this.work] for (const item of allData) { yield item } } } for (const item of todos) { console .log(item); }
19.ES2016 新增特性 1 2 3 4 5 6 7 8 9 10 11 const arr = [1 ,'seachan' , NaN ]arr.includes(1 ) arr.includes('seachan' ) arr.includes(NaN ) arr.includes(0 )
20.ES2017 新增特性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 const obj = { a : 1 , b : 2 } Object .values(obj) for (const [key,val] of Object .entries(obj)) { console .log(key, val); } new Map (Object .entries(obj))const p1 = { firstName : 'Sea' , lastName : 'Chan' , get fullName () { return this .firstName + ' ' + this .lastName } } console .log(p1.fullName) const p2 = Object .assign({}, p1)p2.lastName = 'Li' console .log(p2.fullName) let des = Object .getOwnPropertyDescriptors(p1)const p3 = Object .defineProperties({}, des)p3.lastName = 'Li' console .log(p3.fullName); const str = 'string' str.padStart(10 , '+' ) str.padEnd(10 , '@' ) function fn (param1, param2, ) { }
大前端,冲鸭!