第一节
1.let、const
作用域:ES5中作用域有函数作用域、全局作用域、局部作用域;ES6中新增了块级作用域。
let和const中属于块级作用域。
//ES6中,默认使用的是【use strict】严格模式
function test(){
let a=1;
//let a=2; 1. 不可以对已经声明的变量重新声明
console.log(a);// 1
for(let i=0;i<3;i++){ //2. i的作用域只在当前的循环有效
console.log(i);// 0 1 2
}
console.log(i);//报错,i在当前作用域找不到
}
console.log(a);//报错
function test(){
const PI=3.1415926;//1. const通常用来声明静态变量
//PI=555; 2. 当const声明的变量是一个具体数值,不可以对其进行赋值修改
const PI={a:1}; //该出声明的是一个引用类型
PI.a=3;//可以对其修改
//因为PI指向的是{a:1}的地址,不能改变的是地址。所以可以对对象里面的值可以改变
}
第二节
1.解构赋值
什么是解构赋值?
左边的结构和右边的结构一一对应,进行赋值。
解构赋值的分类
数组、对象、字符串、布尔值、函数参数、数值等解构赋值
{
let a,b,rest;
[a,b]=[1,2];//数组解构赋值
console.log(a,b);//1 2
}
{
let a,b,c;
[a,b,c=3]=[1,2];//c的默认值是3
console.log(a,b,c)//1 2 3
}
{
//使用场景:1.特别适用于变量交换
let a1=1,b1=2;
[a1,b1]=[b1,a1];
console.log(a1,b1);// 2 1
}
{
//使用场景:2.获取函数返回的值
function f(){
return [1,2];
}
let a2,b2;
console.log(a2,b2);//1 2
}
{
//使用场景:3.选择性获取某个值
function f(){
return [1,2,3,4,5];
}
let a,b,c;
[a,,,b]=f();//逗号之间的便是省略的
console.log(a,b);//1 4
}
{
let a,b,rest;
[a,b,...rest]=[1,2,3,4,5,6];//不确实数值存入rest中
console.log(a,b,rest);//a=1 b=2 rest=[3,4,5,6]
}
{
let a,b;
({a,b}={a:1,b:2});//对象解构赋值
console.log(a,b);// 1 2
}
{
let o={p:2,q:true};
let {p,q}=o;
console.log(o)//2 true
}
{
let {a=10,b=5}={a:3};//对a进行了重新赋值 而 b没有
console.log(a,b);//3 5
}
//使用场景:
{
let metaDta={
title:'abc',
test:[{
title:'test',
desc:'description'
}]
}
let {title:esTitle,test:[{title:cnTitle}]}=metaData;
console.log(esTitle,cnTitle);//abc test
}
第三节
1.正则扩展
正则新增特性:
这节介绍与ES5的比较,主要有构造函数的变化、正则方法的扩展、u修饰符、y修饰符、s修饰符。
//构造函数的变化
{
//ES5 定义正则的两种方法
let regex=new RegExp('xyz','i');
let regex2=new RegExp(/xyz/i);
console.log(regex.test('xyz123'),regex2.test('xyz123'));
//ES6的写法
let regex3=new RegExp(/xyz/ig,'i');
console.log(regex3,flags);//i ,flags是新加的是获取第二个参数的。
// ES6中第一个参数允许是正则表达式的方式,第二个参数在去填写修饰符,后边的会覆盖前面的修饰符。
}
//y修饰符 g\y都是全局匹配
{
let s='bbb_bb_b';
let a1=/b+/g;//es5 g修饰符是从上一次匹配的位置继续寻找,不要求下一次匹配的字符是符合的,只要有符合的就可以。
let a2=/b+/y;//es6 y修饰符则要求,下一次不许要符合要求的可以成功
console.log('one',a1.exec(s),a2.exec(s));//结果一样
console.log(a1.sticky,a2.sticky);//检查是否开启y修饰符
}
//u修饰符
//1.字符串中有大于两个字节的字符,则要加u修饰符
//2.在es6中 ‘.’ 并不能匹配任何字符(前提必须小于两个字符长度)
{
//没加u修饰符的把'\uD83D\uDC2A'当做两个字母匹配,而加u修饰符的当做一个字母
console.log(/^\uD83D/.test('\uD83D\uDC2A'));//true
console.log(/^\uD83D/u.test('\uD83D\uDC2A'));//false
// /\u{61}/表示Unicode编码,不加u是无法识别的
console.log(/\u{61}/.test('a'));//false
console.log(/\u{61}/u.test('a'));//true
}
第四节
1.字符串扩展
字符串新增特性:
Unicode表示法、遍历接口、模板字符串、新增方法(10种)
{
//Unicode表示方法
console.log('a',`\u0061`);//a a
console.log('s',`\u{20BB7}`);//𠮷 unicode的表示法
}
{
let s='𠮷';
console.log(s.length);//2
console.log(s.charAt(0));//乱码
console.log(s0.charCodeAt(0).toString(16));//d842
console.log(s0.charCodeAt(1).toString(16));//dfb7
console.log(s.codePointAt(0).toString(16));//20bb7
console.log(s.codePointAt(1).toString(16));//dfb7
}
//区别:能不能处理大于两个字节的Unicode字符
{
console.log(String.fromCharCode("0x20bb7");//es5 乱码
console.log(String.fromCodePoint("0x20bb7");//es6 𠮷
}
//遍历接口
{
let str='\u{20bb7}abc';
for(let i=0;i<str.length;i++){
console.log('es5',str[i]);// 乱码 乱码 a b c
}
for(let code of str){
console.log('es6',code);//𠮷 a b c
}
}
//判断一个字符串是否包含某个字符,是否是某个字符开始的或结尾的
{
let str="string";
console.log(str.includes("r"));//true
console.log(str.startsWith("str"));//true
console.log(str.endsWith("ng"));//true
}
{
let str="abc";
console.log(str.repeat(2));//abcabc 重复n次
}
//模板字符串
{
let name="list";
let info="hello world";
let m=`i am ${name},${info}`;
console.log(m);// i am list,hello world
}
//es7的草案 补全功能
{
console.log('1',padStart(2,'0'));//01
console.log('1',padEnd(2,'0'));//10
}
//标签模板 1.
{
let user={
name:'list',
info:'hello world'
};
abc`i am ${user.name},${user.info}`;
function abc(s,v1,v2){
console.log(s,v1,v2);
}
}
第五节
1.数值扩展
数值处理新增特性
新增方法、方法调整
{
console.log(0b11110111);// 二进制的表示方法
console.log(0o767);//八进制的表示方法
}
{
//Number.isFinite();
console.log('15',Number.isFinite(15));//true
console.log('NaN',Number.isFinite(NaN));//false
console.log('1/0',Number.isFinite(1/0));//false
console.log('NaN',Number,isNaN(NaN));//true
console.log('0',Number.isNaN(0));//false
}
//判断一个数是否是整数
{
console.log('25',Number.isInteger(25));//true
console.log('25.0',Number.isInteger(25.0));//true
console.log('25.1',Number.isInteger(25.1));//false
console.log('25.1',Number.isInteger('25.1'));//false 必须是数值
}
//检测一个是否符合是一个数字
{
console.log(Number.MAX_SAFE_INTEGER,Number.MIN_SAFE_INTEGER);
console.log('10',Number.isSafeInteger(10));//true
console.log('a',Number.isSafeInteger('a'));//false
}
//取整
{
console.log(4.1,Math.trunc(4.1));//4
console.log(4.9,Math.trunc(4.9));//4
}
//检测一个数字是正数还是负数
{
console.log('-5',Math.sign(-5));//-1
console.log('0',Math.sign(0));//0
console.log('5',Math.sign(5));//1
console.log('50',Math.sign(50));//1
console.log('foo',Math.sign('foo'));//NaN
}
第六节
1.数组的扩展
数组新增特性
Array.from、Array.of、copyWithin、find\findIndex
fill、entries\keys\values、includes
//Array.of() 和 Array 构造函数之间的区别在于处理整数参数:Array.of(7) 创建一个具有单个元素 7 的数组,而 Array(7) 创建一个长度为7的空数组(注意:这是指一个有7个空位(empty)的数组,而不是由7个undefined组成的数组)。
{
let arr= Array.of(3,4,7,9,11);
console.log('arr=',arr);//arr=[3,4,7,9,11]
let empty=Array.of();
console.log('empty=',empty);//empty=[]
}
//转换成数组
{
console.log(Array.from([1,2,3],x => x * 2));//[2 4 6]
console.log(Array.from("foo"));//["f","o","o"]
}
//填充数组
{
console.log([1,'a',undefined].fill(7)]);//Array[7,7,7]
console.log(['a','b','c'].fill(7,1,3));//['a',7,7] fill(替换值,起始位置,替换的个数)
}
//遍历相关函数
{
for(let index of ['1','2','ds'].keys()){
console.log(index);// 0 1 2
}
//有兼容问题 需要使用babel插件来解决
for(let value of ['1','2','ds'].values()){
console.log(value);//1 2 ds
}
for(let [index,value] of ['1','2','ds'].entries()){
console.log(index,value);//0 1,1 2,3 ds
}
}
//copyWithin()
{
console.log([1,2,3,4,5].copyWithin(0,3,4));//[0,2,3,4,5]
}
//find/findIndex 查找
{
console.log([1,2,3,4,5,6].find(item => item > 3));//4
console.log([1,2,3,4,5,6].findIndex(item => item > 3));//3 索引
}
{
console.log([1,2,3,4,5,NaN].includes(5));//true
console.log([1,2,3,4,5,NaN].includes(NaN));//true
}
第七节
1.函数扩展
函数新增特性
参数默认值、rest参数(…变量名)、扩展运算符、箭头函数、this绑定(定义所在)、尾调用
//参数默认值 如果参数设置了默认值,则后面不可以再定义没有默认值的参数
{
function test(x,y='world'){
consoel.log("默认值",x,y);
}
test('hello');//默认值 hello world
test('hello','kill');//默认值 hello kill
//function test(x,y='world',z){ 这是错误的
// consoel.log("默认值",x,y);
//}
}
//作用域的问题
{
let x='test';
function test(x,y=x){
console.log(x,y);
}
test('kill');//kill kill
let x='test';
function test(x,y=x){
console.log(x,y);
}
test();//undefined undefined
let x='test';
function test(c,y=x){
console.log(x,y);
}
test('kill');//kill test
}
//rest参数 之后不能再有其他的参数【把离散的值转成数组】
{
function test(...arg){
for(let v of arg){
console.log(v);
}
}
test(1,2,3,4,'a');//1 2 3 4 a
}
//扩展运算符
{
console.log(...[1,2,4]);//1 2 4 把数组转成离散的值
console.log('a',...[1,2,4]);//a 1 2 4
}
//箭头函数
{
//函数名 = 函数参数 => 函数返回值
let arrow = v => v*2;
console.log(arrow(3));//6
//没有参数使用()代替
let arrow1 = () => 2;
console.log(arrow());//2
}
//尾调用 性能优化过程中不断调用其它函数 就使用尾调用
{
tail = x => x;
foo = y => tail(y);
console.log(foo(3));//3
}
第八节
1.对象扩展
函数新增特性
简洁表示法、属性表达式、扩展运算符、Object新增方法
//简洁表示法
{
let o=1;
let k=2;
let es5={
o:o,
k:k
};
let es6={
o,
k
};
console.log(es5,es6);//Object{o:1,k:2} Object{o:1,k:2}
let es5_method={
hello:function(){
console.log('hello');
}
};
let es6_method={
hello(){
console.log('hello');
}
};
console.log(es5_method.hello(),es6_method.hello());//hello
}
//属性表达式
{
let a='b';
let es5_obj={
a:'c'
};
let es6_obj={
[a]:'c'//[a]是一个表达式 相当于 b:'c'
}
console.log(es5_obj);//a:c
console.log(es6_obj);//b:c
}
//常用的API
{
//判断两个字符串是否相等
console.log('字符串',Object.is('abc','abc'),'abc'==='abc');//true true
//该两个数组虽然的值都是空,但是数组是一个引用类型 指向的地址都不一样,严格意义上它们两个是不相等的。is() 与 === 作用是一样的
console.log('数组',Object.is([],[]),[]===[]);//false false
//拷贝 为浅拷贝 只会拷贝自身的 引用的无法拷贝
console.log('拷贝',Object.assign({a:'a'},{b:'b'}));//{a:'a',b:'b'} 合并为一个对象
let test={k:123,p:456};
for(let [key,value] of Object.entries(test)){
console.log([key,value]);//["k",123] ["p",456]
}
}
//扩展运算符 存在兼容问题 babel插件也无法解决
{
let {a,b,...c}={a:'test',b:'kill',c:'ddd',d:'ccc'};
c={
c:'ddd',
d:'ccc'
}
}
第九节
1.Symbol的概念
用Symbol声明的变量,是独一无二的。
2.Symbol的作用
{
//声明
let a1=Symbol();
let a2=Symbol();
console.log(a1===a2);//false
let b1=Symbol.for('b1');//该作用是在作用域内寻找是否符合要求的变量,没有就新增一个,否则就是原来找到的那个值
let b2=Symbol.for('b1');
console.log(b1===b2);//true
let c=Symbol.for('abc');
let obj={
[c]:"123",
'abc':345,
'a':1253
};
console.log(obj);
//需要注意的是使用了Symbol定义的key 是拿不到的
for(let [key,value] of Object.entries(obj)){
console.log(key,value);//abc 345、a 1253
}
//获取Symbol定义的
Object.getOwnPropertySymbols(obj).forEach(function(item){
console.log(obj[item]);//123
});
//获取Symbol定义的及非Symbol定义的
Reflect.ownKeys(obj).forEach(function(item){
console.log(item,obj[item]);
})
}
第十节
数据结构
Set的用法、weakset的用法、map的用法、weakmap的用法
{
//set的用法
let list = new Set();
list.add(5);
list.add(7);
//查看长度
console.log(list.size);
let arr=[1,2,3,4,5];
let list1 = new Set(arr);
console.log(list1.size);
}
{
//特性:Set()定义的内容如果是重复的,是不会生效的 作用:去重
let list = new Set();
list.add(1);
list.add(2);
list.add(1);
console.log(list);//1 2
//注意:不会做数据类型转换
let arr = [1,2,3,1,5];
let list1 = new Set(arr);
console.log(list1);//{1,2,3,5}
//相关方法
let arr=['add','delete','clear','has'];
let list2=new Set(arr);
console.log(list2.has('add'));//true
console.log(list2.delete('add'),list2);//true {'delete','clear','has'}
list2.clear();//清空内容
}
{
//Set()的遍历
let arr=['add','delete','clear','has'];
let list=new Set(arr);
//可以发现keys与values获取的值都一样
for(let key of list.keys()){
console.log(key);// 'add','delete','clear','has'
}
for(let value of list.values()){
console.log(value);// 'add','delete','clear','has'
}
for(let [key,value] of list.entries()){
console.log(key,value);// add add,delete delete,clear clear,has has
}
list.forEach(item => console.log(item));// 'add','delete','clear','has'
}
{
//WeakSet() 与 Set()的区别:
//1.支持的数据类型不一样,weakset只能是对象
//2.weakset的对象都是弱引用,它不会检测这个对象有没有在其他地方用过,意味着不会跟垃圾回收机制挂钩。通俗来说,在weakset中添加了一个对象,该对象不是整个值拷贝过来,它是一个地址的引用,而且它也不会检测这个地址是不是被垃圾回收机制给回收掉了。
//3.没有size方法,clear 不能遍历
let weakList = new WeakSet();
let arg={};
weakList.add(arg);
weakList.add(2);//出错
console.log(weakList);//WeakSet [ {} ]
}
{
//map方法
let map = new Map();
let arr =['123'];
//添加数据 map的key值可以使任意数据类型
map.set(arr,456);
console.log(map,map.get(arr));//Map { (1) […] → 456 } 456
}
{
let map = new Map([['a',123],['b',456]]);
console.log(map);//Map { a → 123, b → 456 }
//map相关属性与方法
console.log(map.size);
console.log(map.delete('a'));
console.log(map.clear(),map);
}
{
//weakmap与map的区别和weakset与set的区别一样
let weakmap=new WeakMap();
let o={};
weakmap.set(o,123);
console.log(weakmap.get(o));//123
}
现有数据结构的差异
{
//map、set与array的区别
let map=new Map();
let array=[];
//增
map.set('t',1);
array.push({t:1});
//查
let map_exist=map.has('t');//true
let array_exist=array.find(item=>item.t);//找到返回对象
//改
map.set('t',2);
array.forEach(item=>item.t?item.t=2:'');
//删
map.delete('t');
let index=array.findIndex(item=>item.t);
array.splice(index,1);
}
{
//set与array的区别
let set=new Set();
let array=[];
//增
set.add({t:1});
array.push({t:1});
//查
let set_exist=set.has({t:1});//false 想要返回true 对象需保存在一个变量中
let array_exist=array.find(item=>item.t);
//改
set.forEach(item=>item.t?item.t=2:'');
array.forEach(item=>item.t?item.t=2:'');
//删
set.forEach(item=>item.t?set.delete(item):'');
let index=array.findIndex(item=>item.t);
array.splice(index,1);
}
{
//map、set与object的区别
let item={t:1};
let map=new Map();
let set=new Set();
let obj={};
//增
map.set('t',1);
set.add(item);
obj['t']=1;
console.log(obj,map,set);
//查
console.info({
map_exist:map.has('t'),
set_exist:has(item),
obj_exist:'t' in obj
})
//改
map.set('t',2);
item.t=2;
obj['t']=2;
console.log(obj,map,set);
//删
map.delete('t');
set.delete(item);
delete obj['t'];
}
//使用建议:优先使用map,如果对数据要求比较高,保证数据唯一性使用set,摈弃array object
第十一节
Proxy和Reflect
Proxy和 Reflect的概念:代理与反射
{
//proxy
let obj={
time:'2019-12-21',
name:'vvv',
_f:456
}
//有时候用户不可以直接操作obj 所以代理一个moniter给用户操作
let moniter=new Proxy(obj,{
//拦截对象属性的读取
get(target,key){
return target[key].replace('2019','2020');
},
//拦截对象设置属性
set(target,key,value){
//只能修改name属性
if(key==='name'){
return target[key]=value;
}else{
return target[key];
}
},
//拦截 key in objet操作
has(target,key){
//只能查到name属性
if(key==='name'){
return target[key]
}else{
return false;
}
},
//拦截delete
deleteProperty(tartget,key){
//只能删除包含’_‘的键值对
if(key.indexOf('_') > -1){
delete target[key];
return true;
}else{
return false;
}
},
//拦截object.keys()、object.getOwnPropertySymbols、object.getOwnProxyNames
ownKeys(target){
//time属性用户不可见
return Object.keys(target).filter(item=>item!='time');
}
});
console.log('get',moniter.time);
moniter.time='2020';//修改失败
moniter.name='ggg';//修改成功
console.log('has','name' in moniter,'time' in moniter);//true false
console.log('ownKeys',Object.keys(moniter)); //[ "name", "_f" ]
}
{
//Proxy拥有的方法 Reflect都有 写法也一样
let obj={
time:'2019-12-21',
name:'vvv',
_f:456
};
console.log(Reflect.get(obj,'time'));//'2019-12-21'
Reflect.set(obj,'name','asdasd');//修改成功
console.log(Reflect.has(obj,'name'));
}
{
//使用场景:数据校验 解耦
let validator = (target,validator) =>{
return new Proxy(target,{
//保存配置选项
_validator:validator,
set(target,key,value,proxy){
//判断key是否存在
if(target.hasOwnProperty(key)){
let va=this._validator[key];
//如果存在
if(!!va(value)){
return Reflect.set(target,key,value,proxy)
}else{
throw Error(`不能设置${key}到${value}`);
}
}else{
throw Error(`${key}不存在`)
}
}
})
}
//设置过滤选项
const personValidators={
name(val){
return typeof val === 'string';
},
age(val){
return typeof val === 'number' && val > 18;
}
}
class Person{
constructor(name,age){
this.name=name;
this.age=age;
return validator(this,personValidators)
}
}
const person=new Person('lilei',30);
console.info(person);//Object { name: "lilei", age: 30 }
person.name=48;//报错 不能设置
}
第十二节
类和对象
类的概念
基本语法、类的继承、静态方法、静态属性、getter、setter
{
//基本定义和生成实例
class Parent{
constructor(name='song'){
this.name=name;
}
}
//实例
let v_parent=new Parent('b');
console.log('构造函数和实例'v_parent);//{name:'b'}
}
{
//继承
class Parent{
constructor(name='song'){
this.name=name;
}
}
class Child extends Parent{
}
console.log('继承',new Child());//{name:'song'}
}
{
//继承传递参数
class Parent{
constructor(name='song'){
this.name=name;
}
}
class Child extends Parent{
constructor(name='child'){
//改变父类的默认值 使用super()方法
super(name);//如果super为空 就会使用父类的值
//添加自己的属性,需要定义在super后面
this.type='child';
}
}
console.log('继承传递参数',new Child());//{name:'child',type:'child'}
}
{
//getter setter
class Parent{
//构造函数
constructor(name='song'){
this.name=name;
}
//这是属性
get longName(){
return 'i am '+this.name;
}
set longName(value){
this.name=value;
}
}
let v=new Parent();
console.log('getter',v.longName);// i am song
v.longName='hello';
console.log('setter',v.longName);// i am hello
}
{
//类的静态方法
class Parent{
constructor(name='song'){
this.name=name;
}
//这是方法
static tell(){
console.log('tell');
}
}
//调用静态方法 必须使用类来调用
Parent.tell();//tell
}
{
//静态属性
class Parent{
constructor(name='song'){
this.name=name;
}
//这是方法
static tell(){
console.log('tell');
}
}
//设置属性
Parent.type='test';
console.log('静态属性',Parent.type);//test
}
第十三节
Promise
什么是异步:a与b同时进行
Promise的作用:解决异步操作问题
Promise的基本用法:
{
//es5中完成a执行完后执行b
var ajax=function(callback){
console.log('a');
setTimeout(function(){
callback && callback.call()
},1000);
};
ajax(function(){
console.log('b');//先执行a 一秒后执行b
})
}
{
//es6 resolve代表下一步要执行的内容,reject代表中断当前操作
let ajax = () =>{
console.log('a');
return new Promise((resolve,reject) => {
setTimeout(() =>{
resolve();
},1000);
})
};
ajax().then(() => {
console.log('b');
})
}
{
//如果在实际工作中,我要a执行完 再执行b 再执行n个函数,这显而易见es5写法很难做到。所以使用Promise
let ajax = () =>{
console.log('a');
return new Promise((resolve,reject) => {
setTimeout(() =>{
resolve();
},1000);
})
};
ajax()
.then(() => {
console.log('b');
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve();
},2000);
});
})
.then(() => {
console.log('c');
})
}
{
//在串行执行过程中,如何捕获过程中的错误
let ajax = num => {
console.log('a');
return new Promise((resolve,reject) => {
if(num>5){
resolve();
}else{
throw new Error(`出错了`);
}
})
}
ajax(6).then(() => {
console.log('正确');//输出这句
}).catch(err =>{
console.log(err);
})
ajax(3).then(() => {
console.log('正确');
}).catch(err =>{
console.log(err);//输出这句
})
}
{
//使用场景:所有图片加载完再添加到页面
function loadImg(src){
return new Promise((resolve,reject)=>{
let img=document.createElement('img');
img.src=src;
img.onload=function(){
resolve(img);
}
img.onerror=function(err){
reject(err);
}
})
}
function showImgs(imgs){
imgs.forEach(function(img){
document.body.appendChild(img);
})
}
Promise.all([
loadImg('1.jpg'),
loadImg('2.jpg'),
loadImg('3.jpg')
]).then(showImgs)
}
{
//使用场景:有三张图片,分别来自不同的网站,哪张先加载完成就显示在页面上,怎么实现?
function loadImg(src){
return new Promise((resolve,reject)=>{
let img=document.createElement('img');
img.src=src;
img.onload=function(){
resolve(img);
}
img.onerror=function(err){
reject(err);
}
})
}
function showImgs(img){
let p=document.createElement('p');
p.appendChild(img);
document.body.appendChild(p);
}
Promise.race([
loadImg('1.jpg'),
loadImg('2.jpg'),
loadImg('3.jpg')
]).then(showImgs)
}
第十四节
Iterator 和 for…of循环
{
//如何直接调用
let arr=['hello','world'];
let map=arr[Symbol.iterator]();
//done如果为false表明未执行完成,否则执行完成
console.log(map.next());//{ value: "hello", done: false }
console.log(map.next());//{ value: "world", done: false }
console.log(map.next());//{ value: undefined, done: true }
}
{
//如何自定义iterator
let obj={
start:[1,3,2],
end:[4,5,9],
[Symbol.iterator](){
let self=this;
let index=0;
let arr=self.start.concat(self.end);
let len=arr.length;
return {
next(){
if(index < len){
return{
value:arr[index++],
done:false
}
}else{
return{
value:arr[index++],
done:true
}
}
}
}
}
}
//验证
for(let key of obj){
console.log(key);//1 3 2 4 5 9
console.log(obj[Symbol.iterator]().next());
}
}
{
//for...of循环 是基于 iterator
let arr=['hello','world'];
for(let value of arr){
console.log(value);
}
}
第十五节
Generator
基本概念
generator就是异步问题的解决方案,相对于promise更高级一点。在执行过程中遇到yield 或 return 就停止执行。next函数是执行下一步。
{
//generator基本定义
let tell=function* (){
yield 'a';
yield 'b';
return 'c'
};
let k=tell();
console.log(k.next());//{ value: "a", done: false }
console.log(k.next());//{ value: "b", done: false }
console.log(k.next());//{ value: "c", done: true }
console.log(k.next());//{ value: undefined, done: true }
}
{
//经过以上的输出,会发现输出结果与iterator遍历器是一样的,所以generator也可以做作为iterator遍历器的返回值 写起来会比自定义iterator方便很多
let obj={};
obj[Symbol.iterator]=function* (){
yield 1;
yield 2;
yield 3;
}
for(let value of obj){
console.log(value);//1 2 3
}
}
{
//什么情况下generator有它最大的优势呢?其实就是一个状态机,比如说有a、b、c三种状态其描述一个事务,也就是说这个事务只存在这三种状态,使用generator来实现这种状态是特别合适的
//定义一个状态机
let state=function* (){
while(1){
yield 'A';
yield 'B';
yield 'C';
}
}
//调用---通过不断的调用next()会一直循环执行状态机里的内容
let status=state();
console.log(status.next());//{ value: "A", done: false }
console.log(status.next());//{ value: "B", done: false }
console.log(status.next());//{ value: "C", done: false }
console.log(status.next());//{ value: "A", done: false }
console.log(status.next());//{ value: "B", done: false }
}
{
//generator的语法糖 执行效果与上面的并无差别
//但是执行它需要babel的一个插件
let state=async function (){
while(1){
await 'A';
await 'B';
await 'C';
}
}
let status=state();
console.log(status.next());//{ value: "A", done: false }
console.log(status.next());//{ value: "B", done: false }
console.log(status.next());//{ value: "C", done: false }
console.log(status.next());//{ value: "A", done: false }
console.log(status.next());//{ value: "B", done: false }
}
{
//使用场景1:抽奖环节,当前用户还可以抽奖五次,如何做抽奖次数的限制?
let draw=function(count){
//抽奖逻辑
console.info(`剩余${count}次`);
}
let residue=function* (count){
while(count>0){
count--;
yield draw(count);
}
}
let start=residue(5);//这里由服务端传来数据
let btn=document.createElement('button');
btn.id='start';
btn.textConten='抽奖';
document.getElementById('start').addEventListenner('click',function(){
start.next();
},false)
}
{
//使用场景2:服务端的某个数据状态定期的变化,前端则需要定时的去取这个数据状态,因为http是无状态的连接,我们要怎么去实时的取这个状态呢?
//有两种方法:一是'长轮询',二是'websocket'。因为第二种方法兼容性问题,所以第一种使用较普遍
//长轮询
//模拟通信过程
let ajax=function* (){
yield new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve({code:0})
},2000);
})
}
//轮询过程
let pull=()=>{
let generator=ajax();
let step=generator.next();
step.value.then((d)=>{
if(d.code!=0){
setTimeout(()=>{
console.log('wait');
pull();
},1000);
}else{
console.log(d);
}
})
}
pull();
}
第十六节
Decorators 修饰器
基本概念:修饰器是一个函数,修改行为(扩展类的功能),修改类的行为(只能作用于类)
在下面的代码中,都是自己手动写的,在实际开发中,可以使用第三方库:core-decorators
{
//定义修饰器函数 需要安装babel-plugin-transform-decotators-legacy插件来支持运行
let readonly=(target,name,descriptor){
descriptor.writable=false;
return descriptor;
};
class Test{
//对类的内部进行操作
@readonly
time(){
return '2019-12-22'
}
}
let test=new Test();
//无法修改time属性 正是readonly修饰器起的作用
test.time=function(){
console.log('reset time');
}
console.log(test.time);
}
修饰器也可以对类的外部进行操作,但是只能写在类的前面
{
//定义修饰器函数
let typename=(target,name,descriptor){
target.myname='hello';
}
@typename
class Test{}
//因为上面定义的是一个空类,但是在定义修饰器的时候,设置了一个默认属性,所以可以直接诶调用该属性
console.log(Test.myname);
}
{
//使用场景:日志系统,传统写法都写在业务逻辑中。在一个网站需要做广告日志,现在使用修饰器来实现买点日志。
let log=(type)=>{
return (target,name,descriptor)=>{
let src_method=descriptor.value;
descriptor.value=(...arg)=>{
src_method.apply(target.arg);
console.log(`log ${type}`);//在实际开发中换成实际的代码
}
}
}
class AD{
@log('show')
show(){
console.log('ad is show')
}
@log('click')
click(){
console.log('ad is lick')
}
}
let ad=new AD();
ad.show();//ad is show => log show
ad.click();//ad is click => log click
}
//这样写的优势:可维护性好、复用性好
第十七节
模块化
import导入 export导出
//模块文件model.js
{
//官方写法 但是不推荐
export let A=123;
export let test=function(){
console.log('test');
}
export class Hello{
test(){
console.log('class')
}
}
//推荐写法
let A=123;
let test=function(){
console.log('test');
}
class Hello{
test(){
console.log('class')
}
}
//把需要的代码统一写完,最后再进行统一的导出,default代表导出的文件名可以由第三方引入者随意命名
export default{
A,
test,
Hello
}
}
//引用文件
{
//这样一个一个引用显得很笨重,如果需要引入文件有上百个,确实恒吃力 不推荐
import {A,test,Hello} from './model';
console.log(A,test,Hello);
//导入方法二:
import * as model from './model';
console.log(model.A,model.test,model.Hello);
//导入方法三:
import myname from './model';
console.log(myname.A,myname.test,myname.Hello);
}