ES6学习笔记
ES6
参考阮一峰的博客https://es6.ruanyifeng.com/#R...
Babel 转码器
将ES6转为ES5.
let命令
let声明变量只在它所在的代码块有效。
for循环就非常适合let的使用。
但是:
for (let i = 0;
i < 10;
i++) {
}
console.log(i);
// ReferenceError: i is not defined
同一个作用域不能使用let重复声明同一个变量。
不存在变量提升
let放在它被调用之前。
暂时性死区
只要块级作用域内存在let命令,它所声明的变量绑定“bind”这个区域就不再受外部影响。
var tmp = 123;
if (true) {
tmp = 'abc';
// ReferenceError
let tmp;
}
全局变量TMP但是块级作用域内let有声明了一个局部变量tmp,导致后绑定了这个作用域,在let声明变量之前,tmp会报错。
不容易发现的死区
function bar(x = y, y = 2) {
return [x, y];
}
bar();
x=y并没有声明,就错了。
var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined
不允许重复声明
let不允许在同一个作用域内,重复声明一个变量。
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
ES6 Symbol ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
ES6 数据类型除了 Number 、 String 、 Boolean 、 Object、 null 和 undefined ,还新增了 Symbol 。
Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。
由于每一个 Symbol 的值都是不相等的,所以 Symbol 作为对象的属性名,可以保证属性不重名。
绑定key与value值不同。
、
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for...in 、 for...of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
扩展运算符
两种运算符
1.扩展运算符 扩展运算符用3个点表示(...)用于将一个数组或者类数组对象转化为逗号分隔符的值序列。
拆解数组和字符串。
const array = [1,2,3,4];
console.log(...array);
const str = 'string';
console.log(...str);
拓展运算符代替apply()函数 获取数组最大值时,使用apply()函数
let arr = [1,4,6,8,2];
console.log(Math.max.apply(null,arr))
拓展运算符
let arr = [1,4,6,8,2];
console.log(Math.max(...arr));
拓展运算符代替contact()函数合并数组
console.log([...arr1,...arr2]);
2.rest运算符
rest运算符同样使用...表示,用于将逗号分隔的值序列转化成数组。
结构组合的使用
let arr = ['one','two','three','four'];
let[arg1,...arg2] = arr;
console.log(arg1);
//oneconsole.log(arg2)//[two,three,four]
rest运算符代替arguments处理函数参数
function foo(...args){for(let arg of args){console.log(arg);
}}foo('one','two','three','four');
模板字符串
$('#result').append(` There are ${basket.count} items in your basket, ${basket.onSale} are on sale!`);
let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}`// "1 + 2 = 3"`${x} + ${y * 2} = ${x + y * 2}`// "1 + 4 = 5"let obj = {x: 1, y: 2};
`${obj.x + obj.y}`// "3"
includes(), startsWith(), endsWith()
三个函数都是返回布尔值,表示是否找到了参数字符串
padStart(), padEnd() padStart()用于1头部补全,padEnd()用于尾部补全。
console.log('x'.padStart(5,'ax'));
//axaxx console.log('x'.padEnd(5,'ba'));
//xbaba
字符提示格
console.log('12'.padStart(10, 'YYYY-MM-DD'));
// "YYYY-MM-12"console.log('09-12'.padStart(10, 'YYYY-MM-DD'));
trimStart(),trimEnd() 消除头部或者尾部的空格
replaceAll()
字符串代替所有的指定数值
console.log('aabbcc'.replaceAll('b', '_'));
// 'aa__cc'
数值新增的方法
Number.isInteger() 判断一个数值是否为整数,boolean值。
25.0和25都是整数,返回true。
Math.trunc() 用于去除一个数的小数部分,返回整数。
对于非数值,它会将非数值转化成数值。
对于空值和无法取证的数值,就返回NaN.
Math.sign() 它来判断一个数到底是正数还是负数还是零。对于非数值,会将其先转换为数值。
正数返回+1;
负数返回-1;
参数为0返回0;
参数-0,返回-0;
其他值返回NaN;
函数参数的默认值
ES6允许函数的参数设置为默认值,直接写在参数定义的后面。
function Point(x=0,y=0){this.x=x;
this.y=y;
}const p=new Point();
console.log(p);
解构赋值默认值结合使用
function foo({x, y = 5}) { console.log(x, y);
}foo({}) // undefined 5foo({x: 1}) // 1 5foo({x: 1, y: 2}) // 1 2foo() // TypeError: Cannot read property 'x' of undefined
函数的length属性
指定了默认值以后,函数的length属性,将返回没有默认指定默认值的参数个数,指定了默认值以后,length属性将失真。
(function (a) {}).length // 1(function (a = 5) {}).length // 0(function (a, b, c = 5) {}).length // 2
(function (a, b, c = 5) {}).length // 2
本来应该是3个参数,长度为3,但是有一个定义了默认值,所以要减去一个。
作用域 一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域context,等初始化结束,这个作用域就会消失,没有设置参数默认值时就不会出现。
var x=1;
function f(x,y=x){console.log(y);
}f(2);
参数 y 的默认值等于变量 x 。调用函数 f 时,参数形成一个单独的作用域。在这个作用域里面,默认值变量 x 指向第一个参数 x ,而不是全局变量 x ,所以输出是 2 。
let x = 1;
function f(y = x) { let x = 2;
console.log(y);
}f() // 1
箭头函数
ES6可以让箭头=>定义函数
var f = () => 5;
// 等同于var f = function () {return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于var sum = function(num1, num2) { return num1 + num2;
}
如果箭头函数代码部分多于一条语句,就要使用{}把他们括起来。
并且使用return语句返回。
var sum = (num1, num2) => { return num1 + num2;
}
因为大括号被解释为代码块,如果箭头函数直接返回一个对象,必修在对象外面加上括号,否则会报错。
// 报错let getTempItem = id => { id: id, name: "Temp" };
// 不报错let getTempItem = id => ({ id: id, name: "Temp" });
箭头函数可以与变量解构结合使用。
const full = ({ first, last }) => first + ' ' + last;
// 等同于function full(person) { return person.first + ' ' + person.last;
}
箭头函数的使用注意点
1.箭头函数没有自己的this对象
2.不可以构造函数,也就是不能使用函数使用new()
3.不可以使用arguments对象,该对象在函数体内不存在,如果必须要用可以用rest参数代替。
4.不可以使用yield命令,箭头函数不能用作Generator函数。
不使用场合 箭头函数从动态变成静态。
globalThis.s = 21;
const obj = { s: 42, m: () => console.log(this.s)};
obj.m() // 21
需要动态this的时候,也不应该使用箭头函数。
Array.from()
Array.from 方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
let arrayLike={'0':'a','1':'b','2':'c',length:3}let arr2=Array.from(arrayLike);
console.log(arr2)
Array.of()
用于一组值转化为数组
用于弥补Array()的不足,因为参数个数的不同,会导致Array(有
差异。只有参数个数不少于2个时,Array()才会返回由参数组成的新数组。
Array.of()总是返回参数值组成的数组,如果没有参数,就返回一个空数组。
function ArrayOf(){ return [].slice.call(arguments);
}
数组实例的fill()
fill方法使用给定值,填充一个数组
['a', 'b', 'c'].fill(7)// [7, 7, 7]
数组实例的flat()
使用 Array.prototype.flat() 用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。
当使用一次flat时只能拉平一个2维数组,如果要拉平3维数组时,就到再使用一次了。
对象的拓展
const name = 'cao teacher';
const age = 18;
const obj={name,age};
//等同于const obj = {name:'caoteacher',age:18
定义obj对象时,变量名name作为了对象的属性名,它的值作为了属性值,一次只需要写一个name就可以表示{name:name}的含义。
除了属性可以简写,函数也可以简写,即省略关键字function。
属性遍历
一共有5种方法实现对象属性的变量。
1.for...in
2.Object.getOwnPropertyNames(obj)
3.Object.getOwnPropertySymbols(obj)
4.Object.keys(obj)
5.Reflect.ownKeys(obj)
Object.is()
和ES5很像(==)和(===)
和严格比较运算符(===)行为基本一致
Object.assign()
用于对象的合并,将源对象的所有可枚举属性,复制到目标对象(target)
浅拷贝 Object.assign() 方法实行的是浅拷贝
Symbol
保证每个属性名的名字都是独一无二。
s 就是一个独一无二的值。 typeof 运算符的结果,表明变量 s 是 Symbol 数据类型,而不是字符串之类的其他类型。
这个symbol函数不能使用new命令。
如果symbol参数是一个对象,那么就会调用tostring方法,将其转化为字符串,然后才生成一个symbol值。
Symbol 函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的 Symbol 函数的返回值是不相等的。
作为属性名的symbol 因为每个symbol值都是不相同的,对于对象的属性名,就能够保证不会出现同名的属性,在多个模板够成的情况下,能够防止某一个键被不小心改写或者覆盖。
let mySymbol=Symbol();
let a={};
a[mySymbol]='hello';
console.log(a);
let a={[mySymbol]:'hello'}console.log(a);
let a={};
Object.defineProperty(a,mySymbol,{value:'hello'});
console.log(a) ;
代码通过方括号结构和 Object.defineProperty ,将对象的属性名指定为一个 Symbol 值。注意,Symbol 值作为对象属性名时,不能用点运算符。
使用symbol值定义属性时,symbol值必须放在[]方括号里面。
使用Symbol最大的好处是,在switch语句时,也可以保证他的工作,Symbol作为属性名时,该属性还是公开属性,不是私有属性。
const COLOR_RED=Symbol();
const COLOR_GREEN=Symbol();
function getComplement(color){switch (color){case COLOR_GREEN:return COLOR_GREEN;
case COLOR_RED:return COLOR_RED;
}}
实例:消除魔术字符串 在代码中多次出现,与代码形成强耦合的某一个具体字符串或者数值,为了代码的健壮性,尽量消除魔术字符串,改为清晰的变量。
function getArea(shape,options){let area=0;
switch(shape){case 'tra'://魔术字符串area=.5*options.width*options.height;
break;
}return area;
}getArea('tra',{switch:100,height:100})//魔术字符串
tra就是一个魔术字符串,他多次出现,与代码形成了强耦合,不利于代码的维护,我们可以把他写成为一个变量。
function getArea(shape, options) { let area = 0;
switch (shape) {case shapeType.triangle:area = .5 * options.width * options.height;
break;
} return area;
}getArea(shapeType.triangle, { width: 100, height: 100 })
shapeType.triangle等于哪个值并不重要,只需要确保他不会和其他shapeType重名就好,所以特别适合symbol值。
属性的遍历
Symbool作为属性名时,该属性不会出现在for...in for...of循环中,也不会被object.keys(),object.getOwnPropertyNames()
JSON.stringify()返回
但是他也不是私有属性,他有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有Symbol属性名,该方法返回一个数组,成员是当前对象所有作用域属性名的symbol值。
const obj={};
let a=Symbol('a');
let b=Symbol('b');
obj[a]='hello';
obj[b]='world';
const objectSymbols=Object.getOwnPropertySymbols(obj);
console.log(objectSymbols);
const obj = {};
const foo = Symbol('foo');
obj[foo] = 'bar';
for (let i in obj) { console.log(i);
// 无输出}console.log(Object.getOwnPropertyNames(obj) ) // []console.log(Object.getOwnPropertySymbols(obj)) ;
//[Symbol(foo)]
可以看到,使用for...in循环和Object.getOwnPropertyNames并没有获取到symbol的键名,需要使用Object.getOwnPropertySymbols方法。
另一个是 Reflect.ownKeys() 方法
可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let obj = {[Symbol('my_key')]: 1,enum: 2,nonEnum: 3};
console.log( Reflect.ownKeys(obj));
//["enum", "nonEnum", Symbol(my_key)]
用于symbol值作为键名,不会被常规方法遍历得到,我们可以利用这个特性,为对象定义一些非私有,但又只用于内部的方法
let size = Symbol('size');
class Collection { constructor() {this[size] = 0;
} add(item) {this[this[size]] = item;
this[size]++;
} static sizeOf(instance) {return instance[size];
}}let x = new Collection();
Collection.sizeOf(x) // 0x.add('foo');
Collection.sizeOf(x) // 1Object.keys(x) // ['0']Object.getOwnPropertyNames(x) // ['0']Object.getOwnPropertySymbols(x) // [Symbol(size)
set和Map的数据结构
set()的用法
ES6提供了数据结构set,他类似于数组,但是成员是唯一的,没有重复的值。类数组
const s=new Set();
[1,2,2,3,3,4,4,5,4,4,6].forEach(x=>s.add(x));
for(let ins){console.log(i);
}console.log(s)
通过add()方法向set结构中加入成员,结果表明set结果不会添加重复的值,set函数可以接受一个数组,作为参数,用来初始化。
const set = new Set([1, 2, 3, 4, 4]);
[...set]// [1, 2, 3, 4]// 例二const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5// 例三const set = new Set(document.querySelectorAll('div'));
set.size // 56//类似属const set = new Set();
document.querySelectorAll('div').forEach(div => set.add(div));
set.size // 56
set()实例的属性和方法 set有以下属性
Set.prototype.constructor :构造函数,默认就是 Set 函数。
Set.prototype.size :返回 Set 实例的成员总数。
四个操作方法
Set.prototype.add(value) :添加某个值,返回 Set 结构本身。
Set.prototype.delete(value) :删除某个值,返回一个布尔值,表示删除是否成功。
Set.prototype.has(value) :返回一个布尔值,表示该值是否为 Set 的成员。
Set.prototype.clear() :清除所有成员,没有返回值
Array.from()方法可以将set结构转化为数组
const items=new Set([7,2,3,4,4,5,4,4,5]);
const array=Array.from(items);
console.log(array);
与set()提结合供数组去重方法。
set()的常用方法
单一数组的去重,用于set成员具有唯一性,因此可以使用Set来进行数组的去重。
let arr = [1,2,2,3,3,3,4,4,5,5,6,6];
console.log(new Set(arr));
多个数组的合并去重
Set可以用于单一数组的合并去重,也可以用做多个数组的合并去重。
let arr1=[1,2,3,3,4,4,5];
let arr2=[1,1,2,3,3,4,4,5,5,8];
let set1=new Set([...arr1,...arr2]);
console.log(set1);
Set与数组的转化
Set()与数组都有便利的数据处理函数,但是两者的相互转换也很简单,我们可以选择对两者进行转化,并调用对应的函数。
Array.from 方法可以将 Set 结构转为数组
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
set的遍历 forEach()函数的第一个参数表示的是Set中的每个元素,第二个参数表示的元素的索引,第三个就是他的全值
- keys():返回键名的遍历器。
- values():返回键值的遍历器。
- entries():返回键值对的遍历器
上述函数或者对象都是遍历对象iterator,通过for 循环可以获得每一项都值。
let set = new Set(['red','blue','yellow']);
for(let item of set.keys()){console.log(item);
}for(let item of set.values()){console.log(item);
}for(let item of set.entries()){console.log(item);
}
Map的用法 ES6新增加了一种数据结构Map,与传统的对象字面量类似,他的本质还是一种键值对的组合,对于非字符串的值会强制性转化为字符串,而Map的键却可以由各种类型的值组成。
Map
对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。一个Map对象在迭代时会根据对象中元素的插入顺序来进行 — 一个
for...of
循环在每次迭代后会返回一个形式为[key,value]的数组const data = https://www.it610.com/article/{};
const element = document.getElementById('myDiv');
data[element] = 'metadata';
data['[object HTMLDivElement]'] // "metadata"
以上代码只是将DOM节点作为对象data的值,但是由于对象只接受字符串作为键名,所以有以element被自动转化为字符串。
const m=new Map();
const o={p:'hello world'};
m.set(o,'content');
console.log(m.get(o)) ;
console.log(m.has(o)) ;
console.log(m.delete(o)) ;
console.log(m.has(o)) ;
展示了如何向Map添加成员,作为构造函数,map也可以接受一个数组作为参数。
Map构造函数接受数组作为参数,实际上是执行以下的算法。
一个Map对象在迭代时会根据对象中元素的插入顺序来进行 — 一个
for...of
循环在每次迭代后会返回一个形式为[key,value]的数组。const items = [['name', '张三'],['title', 'Author']];
const map = new Map();
items.forEach(([key, value]) => map.set(key, value));
不仅仅是数组,任何具有lterator的接口,且每一个成员都是一个双元素数组的数据结构都可以当作Map的构造函数的参数。
const set = new Set([['foo', 1],['bar', 2]]);
const m1 = new Map(set);
m1.get('foo') // 1const m2 = new Map([['baz', 3]]);
const m3 = new Map(m2);
m3.get('baz') // 3
对一个键进行多次赋值,后者就要会覆盖前者的值。
const map = new Map();
map.set(1, 'aaa').set(1, 'bbb');
map.get(1) //bbb
如果读取一个未知的键,则返回undefined。
new Map().get('asfddfsasadf')// undefine
set和get()方法一样,表面是针对同一个键,但是实际上是两个不同的数组实例。内存地址也一样。
const map = new Map();
const k1 = ['a'];
const k2 = ['a'];
map.set(k1, 111).set(k2, 222);
map.get(k1) // 111map.get(k2) /
map.size size 属性返回 Map 结构的成员总数
Map.prototype.set(key, value) set 方法设置键名 key 对应的键值为 value ,然后返回整个 Map 结构。如果 key 已经有值,则键值会被更新,否则就新生成该键。
Map.prototype.get(key) get 方法读取 key 对应的键值,如果找不到 key ,返回 undefined 。
Map.prototype.has(key) Map.prototype.delete(key) Map.prototype.clear() 遍历方法 提供三个遍历器生成函数和一个遍历方法。
Map的遍历顺序就是插入顺序。
const map = new Map([['F', 'no'],['T','yes'],]);
for (let key of map.keys()) { console.log(key);
}for (let value of map.values()) { console.log(value);
}// "no"// "yes"for (let [key, value] of map.entries()) { console.log(key, value);
}for (let [key, value] of map) { console.log(key, value);
}// "F" "no"// "T" "yes"
map[Symbol.iterator] === map.entries
Map转化为数组结构的,快速方法是使用扩展运算符(...)
const map = new Map([[1, 'one'],[2, 'two'],[3, 'three'],]);
[...map.keys()]// [1, 2, 3][...map.values()]// ['one', 'two', 'three'][...map.entries()]// [[1,'one'], [2, 'two'], [3, 'three']][...map]// [[1,'one'], [2, 'two'], [3, 'three']]
结合数组的map方法,filter方法,可以实现Map的遍历和过滤
const map0 = new Map().set(1, 'a').set(2, 'b').set(3, 'c');
const map1 = new Map([...map0].filter(([k, v]) => k < 3));
// 产生 Map 结构 {1 => 'a', 2 => 'b'}const map2 = new Map([...map0].map(([k, v]) => [k * 2, '_' + v]) );
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
forEach方法还可以接受第二个参数,用来绑定this
Map转化为数组 前面我们试过用...转化
数组转化为Map 将数组传入Map构造函数,就可以转化为Map
new Map([[true, 7],[{foo: 3}, ['abc']]])
Map转化为对象 如果所有Map的键值是字符串,那么他可以完好无损的转化为对象
function strMapToObject(strMap){let obj=Object.create(null);
for(let [k,v]of strMap){obj[k]=v;
}return obj;
}const myMap=new Map().set('yes',true).set('no',false);
console.log(strMapToObject(myMap)) ;
如果有非字符穿的键名,那么这个键值会被转化成字符串。
对象转化为Map 对象转化为Map可以通过object.entries()
MAP转化为JSON
JSON.stringify
JSON 转为 Map
JSON.parse
Proxy
代理器
主要用于改变对象的默认访问行为,实际表现是在访问对象之前增加一层拦截,任何对象的访问行为都会通过这层拦截,在拦截中,我们可以增加自定义行为。
const proxy = new Proxy(target,handler);
构造函数,接受2个参数,一个目标对象target,另一个配置对象是handler,用来定义拦截行为。
const person={name:'cao',age:18,sex:'woman'};
let handler={get:function(target,prop,receiver){console.log('你访问了person属性');
return target[prop];
}}const p=new Proxy(person,handler);
console.log(p.name);
console.log(p.age);
你访问了person属性es6.js:481 caoes6.js:476 你访问了person属性es6.js:482 18
必须通过代理实例访问
配置对象不能为空对象
Proxy实例函数以及其基本使用
- get(target,propKey,receiver)
- set(target,propKey,value,receiver)
- has(target.propKey)
- ........
let person={name:'cao'}const proxy=new Proxy(person,{get:function (target,propKey){if(propKey in target){return target[propKey];
}else{throw new ReferenceError(`访问的属性${propKey}不存在`);
}}});
console.log(proxy.name);
console.log(proxy.age)
读取读索引的值
通过proxy()增加索引的查找速度
target[target.length+index];
禁止访问私有属性 私有属性会以_开头,因为我们并不想访问到他的属性,同样可以设置Proxy.get()函数;来实现
const person = {name:'cao teacher',_pwd:'123456'}const proxy = new Proxy(person,{get:function(target,prop){if(prop.indexOf('_') ===0){throw new ReferenceError('不能直接访问私有属性');
}else{return target[prop];
}}});
console.log(proxy.name);
console.log(proxy._pwd)
实现真正的私有
通过proxy处理下滑线来真正实现私有
不能访问,修改,不能遍历私有值,遍历出来的属性中也不会包含私有属性。
get:function(target,prop){if(prop[0] === '_'){return undefined;
增加日志记录
针对那些缓慢或者资源密集型的接口,我们可以先使用Proxy进行拦截,通过get()函数拦截调用的函数名,如何用apply()函数进行函数调用。
const apis = {_apiKey:'12ab34cd56ef',getAllUsers:function(){console.log('这是查询全部用户的函数');
},getUserById:function(userId){console.log('这是根据用户ID查询用户的函数');
},saveUser:function(user){console.log('这是保存用户的函数');
}};
//记录日志的方法function recordLog(){console.log('这是记录日志的函数');
}const proxy = new Proxy(apis,{get:function(target,prop){const value = https://www.it610.com/article/target[prop];
return function(...args){//此处调用记录日志的函数recordLog();
//调用真实的函数return value.apply(null,args);
} }});
proxy.getAllUsers();
Reflect
Reflect 对象与Proxy对象一样,也是 ES6为了操作对象而提供的新 API。
名为Reftect 的全局对象,上面挂载了对象的某些特殊函数,这些函数可以通过类似于 Reflect.apply()这种形式来调用,所有在Reflecl对象上的函数要么可以在Object原型链中找到,要么可以通过命令式操作符实现,
Reflect静态函数 Reflect.apply(target, thisArgument, argumentsList)
和 Function.prototype.apply()功能类似。
Reflect.construct(target, argumentsList[, newTarget])
..............
Reflect.has(obj, name) Reflect.has 方法对应 name in obj 里面的 in 运算符。
Reflect.apply(func, thisArg, args)
类似于 Function.prototype.apply.call(func, thisArg, args) ,用于绑定this 对象后执行给定函数。
使用Proxy实现观察者模式 实现自动观察数据对象
一旦有变化函数就会自动执行
const person = observable({ name: '张三', age: 20});
function print() { console.log(`${person.name}, ${person.age}`)}observe(print);
person.name = '李四'
Promise
处理Ajax请求代码
以前因为耦合太严重,执行多个异步请求,每一个请求又需要依赖上一个请求的结果,按照回调函数。
//第一个请求$.ajax({url:'url1',success:function(){//第二个请求$.ajax({url:'url2',success:function(){//第三个请求$.ajax({url:'url3',success:function(){//第四个请求$.ajax({url:'url4',success:function(){//成功的回调}})}})}}) }})
因为行为产生的异步请求,导致代码嵌套太深,引发“回调地狱”
导致问题:
1.代码臃肿,可读性差
2.耦合度高,可维护性差,难以复用。
3.回调函数都是匿名函数,不方便调试。
为了解决此问题,我们使用promise
promise中有三种状态,即Pending,fulfill和reject
promise执行成功,pending状态改变为fulfilled状态,promise执行失败时,pending变成rejected状态。
promise对象本身就是一个构造函数,可以通过new操作符生成promise实例。
const promise = new Promise((resolve,reject)=>{//异步处理请求if(/异步请求标识/){resolve();
}else{reject();
}})
promise执行过程中,在接受函数时处理异步请求,然后判断异步请求的结果,如果返回true,则表示异步请求成功,调用resolve()函数,一旦执行,就从pending变成fulling,如果是false,则表示异步请求失败,调用reject()函数,reject函数一旦执行,promise就会从pending变为reject。
let promise=new Promise(function(resolve,reject){console.log('Promise');
resolve();
});
promise.then(function(){console.log('resolve');
});
console.log('hello');
打印结果如下:
Promisees6.js:610 helloes6.js:608 resolve
先执行hello再执行resolve
如果把resolve()改成reject,你们promise.then会返回undefined,只会输出Promise, hello
也就是所有同步代码执行完毕后,会执行then()函数,输出resolve.
function ajaxGetPromise(url){const promise = new Promise(function(resolve,reject){const handler = function(){if(this.readyState !== 4){return;
}//当状态码为200时,表示请求成功,执行resolve()函数if(this.status === 200){//将请求的响应体作为参数,传递给resolve()函数resolve(this.response);
}else{//当状态码不为200时,表示请求失败,reject()函数reject(new Error(this.statusText));
}}//原生ajax操作const client = new XMLHttpRequest();
client.open("GET",url);
client.onreadystatechange = handler;
client.responseType="json";
client.setReqestHeader("Accept","application/json");
client.send();
});
return promise;
then()和catch()
处理成功或者失败的异步处理
then()表示在promise实例状态改变时执行的回调函数。
他有2个参数,第一个在promise执行成功后,函数参数通过resolve()函数传递的参数,第二个参数是可选的,表示promise在执行失败后,执行的回调函数。
then()函数返回的是一个新的promise实例,因此可以使用链式调用then()函数,在上一轮then()函数内部return值会作为新一轮的then()函数接受的参数值。
const promise=new Promise((resolve,reject)=>{resolve(1);
});
promise.then((result)=>{console.log(result);
return 2;
}).then((result)=>{console.log(result);
return 3;
}).then((result)=>{console.log(result);
return 4;
}).then((result)=>{console.log(result);
catch()函数 catch()函数与then()函数成对存在,then()函数在promise执行成功后回调,而catch()函数是promise执行失败后回调。
const promise=new Promise((resolve,reject)=>{try{throw new Error('err11111');
}catch(err){reject(err);
}});
promise.catch((err)=>{console.log(err);
})
只要promise执行过程中出现了异常,就会自动抛出,并触发reject(err),而不要我们去使用try...catch,在catch()函数中手动调用reject()函数。
也可以直接改写为如下所示:
const promise=new Promise((resolve,reject)=>{ throw new Error('err1111');
});
promise.catch((err)=>{console.log(err);
})
promise.race() 利用多个Promise实例,包装成为一个新的实例。
const p = Promise.race([p1, p2, p3]);
只要p1,p2,p3中有一个实例率先改变状态,p就会跟着改变,那个率先改变promise实例的返回值,就传递给p的回调函数。
如果没有在指定时间获得结果,就将promise的状态变为reject,否则就是resolve。
const p=Promise.race([fetch(''),new Promise(function (resolve,reject){setTimeout(() => {reject(new Error('request timeout'))}, 5000);
})]);
p.then(console.log);
p.catch(console.error);
如果在指定时间没有获得结果,就会将promise的状态变成reject。
promise.reject() Promise.reject(reason) 方法也会返回一个新的Promise 实例,该实例的状态为 rejected 。
const p=Promise.reject('出错了');
p.then(null,function(s){console.log(s);
});
生成一个promise对象实例,状态为rejected,回调函数会立即执行。
lterator和for循环
集合的概念,主要是数组和对象object,ES6又添加的Map和Set。
他就是一种遍历起器,一种接口,为不同的数据结构提供了统一的访问机制,任何数据只要部署了lterator接口,他就可以完成遍历操作。
作用:
1.为各种数据提供统一,简便的访问接口
2.使得数据结构的成员能够按照某种次序排列
3.ES6创造了一种新的遍历命令,for...of循环。lterator接口主要提供for...of消费。
遍历过程如下:
1.创建一个指针,指向当前数据结构的起始位置。
2.第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
3.第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
4.不断调用指针的next方法,直到他指向数据结构的结束位子。
默认的iterator接口 为了所有的数据结构,提供了一种统一的访问机制,即for...of循环,当使用for...of循环遍历某种数据结构时,该循环就会自动去寻找lterator接口。
Symbol.iterator属性他本身就是一个函数,当前数据结构默认的遍历器生成函数,执行这个函数就会返回一个遍历器。类型为Symbol的特殊值,所以要放在方括号里面。
const obj={[Symbol.iterator]:function(){return {next:function(){return {value:1,done:true}}}}}
原生iterator接口的数据结构有6种
1.Array
2.Map
3.Set
4.String
5.函数的arguments对象
6.nodeList对象
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
console.log(iter.next())// { value: 'a', done: false }console.log(iter.next())// { value: 'b', done: false }console.log(iter.next())// { value: 'c', done: false }console.log(iter.next())// { value: undefined, done: true }
使用[Symbol.iterator]遍历循环['a', 'b', 'c']
for...of循环 只要部署了Symbol.iterator属性,就被视作为iterator接口,就可以用for...of循环他的成员。
数组 原生数组具备了iterator接口,for...of循环本质上就是调用这个接口产生的遍历器。
const arr=['red','green','blue'];
const obj={};
obj[Symbol.iterator]=arr[Symbol.iterator].bind(arr);
for(let v of obj){console.log(v);
}
输出结果:red
es6.js:680 green
es6.js:680 blue
Symbol.iterator 属性,结果 obj 的 for...of 循环,产生了与 arr 完全一样的结果。
set和map结构 他们也具有iterator接口,可以直接使用for...of循环
let map=new Map().set('a',1).set('b',2);
for (let pair of map){console.log(pair);
}for(let [key,value]of map){console.log(key +':'+value);
}
类似数组的对象 包括DOM NodeList对象,arguments对象。
let str="hello";
for(let s of str){console.log(s);
}function printArgs(){for (let x of arguments){console.log(x);
}}printArgs('a','b');
Generator函数
它是ES6提供的一种异步编程解决的方案,语法与传统函数不同。
可以把他当成一个状态机。里面封装了多个内部函数状态。
执行Genertaor函数会返回一个遍历对象,除了状态机,他还是一个遍历对象生成函数,返回遍历器对象,可以依次遍历Generator函数中的每一个状态。
function关键字与函数名之间有一个*号,函数内部使用yield表达式。
function* helloGenerator(){yield 'hello';
yield 'world';
return 'ending';
}var hw=helloGenerator();
console.log(hw);
Generator函数调用后,该函数并不执行,返回的也不是函数运行的结果,而是一个指向内部状态的指针对象。
下一步调用遍历器对象中的next()方法,使得指针移向下一个状态,每次调用next方法,内部指针就从函数头部或者上一次停下来的地方开始执行,一直遇到下一个yield表达式(或者为return)Generator函数是分段执行的,yield表达式是暂停执行的标记。next方法可以恢复执行。
yield yield表达式后面的表达式,只有调用next方法,指针指向该语句时才会执行。
如果Generator函数没有使用yield表达式,这时候就变成了一个单纯的暂缓执行函数。
function*f(){console.log('xxxxx')}var generator=f();
setTimeout(() => {generator.next()},5000);
没有yield返回时,变成了一个暂缓执行函数。
异步操作同步化表达 异步操作放在yield表达式下面,要等到next方法再执行,Generator函数的一个实际意义就是来处理异步操作,改写回调函数
先把JSON反序列化。再请求。
function*main(){var result=yield request("http:");
var resp=JSON.parse(result);
console.log(resp.value);
}function request(url){makeAjaxCall(url,function(response){it.next(response);
});
}var it =main();
it.next();
通过 Generator 函数部署 Ajax 操作,可以用同步的方式表达。
class
引入class类作为对象的模板,通过class关键字,可以定义类。
class Point { constructor(x, y) {this.x = x;
this.y = y;
} toString() {return '(' + this.x + ', ' + this.y + ')';
}}
里面的constructor()是构造方法,而this关键字就是代表实例对象。
constructor()方法是类的默认方法,通过new生成对象实例的时,自动调用该方法,一个类中必须有constructor()方法。如果没有,那就会被默认添加。
必须要使用new。
如果忘记加上new就会报错。
【ES6学习笔记】实例中的非显示调用在其本身,否则都是定义在class上。
推荐阅读
- EffectiveObjective-C2.0|EffectiveObjective-C2.0 笔记 - 第二部分
- 由浅入深理解AOP
- 继续努力,自主学习家庭Day135(20181015)
- python学习之|python学习之 实现QQ自动发送消息
- Android中的AES加密-下
- (二)ES6第一节变量(let|(二)ES6第一节变量(let,const)
- 六步搭建ES6语法环境
- 一起来学习C语言的字符串转换函数
- 定制一套英文学习方案
- 漫画初学者如何学习漫画背景的透视画法(这篇教程请收藏好了!)