ECMAScript和javascript的关系:前者是后者的规范,后者是前者的一种实现
1 模板字符串:
重点:
`${data.id},吴明娜` 得到模板字符串和普通字符串的拼接 模板字符串的注入,通过${}取值注入
2 箭头函数
什么是箭头函数?箭头函数的结构?如何将原来的函数改写成箭头函数
箭头函数的注意事项
(1)单个参数,单个参数可以省略括弧,但是没有参数或者多个参数不能省略
//单个参数可以省略圆括弧 const add = x =>{ return x } // 无参数或多个参数不能省略圆括号 const add = () => { return 1 + 1; }; const add = (x, y) => { return x + y; };
(2)单行函数体
//单行函数体可以同时省略 {} 和 return const add = (x,y) => x+y; // 多行函数体不能再化简了 const add = (x, y) => { const sum = x + y; return sum; };
(3) 单行对象
// 3.单行对象 const add = (x, y) => { return { value: x + y }; }; const add = (x, y) => ({ value: x + y }); // 如果箭头函数返回单行对象,可以在 {} 外面加上 (),让浏览器不再认为那是函数体的花括号 const add = (x, y) => [x, y]; //返回单行数组是不会出现简写单行对象的问题的,依旧按照化简单行函数的原则进行化简即可。
非箭头函数的this指向
(1)全局作用域于的this指向(简单) 指向的是window
// 1.全局作用域中的 this 指向 console.log(this); // window
(2)一般非箭头函数的this指向
// 2.一般函数(非箭头函数)中的 this 指向 // 'use strict'; function add() { console.log(this); } // 只有在函数调用的时候 this 指向才确定,不调用的时候,不知道指向谁 // this 指向和函数在哪儿调用没关系,只和谁在调用有关 // 没有具体调用对象的话,this 指向 undefined,在非严格模式下,转向 window // 严格模式('use strict')就指向 undefined add(); // undefined->window(非严格模式下,浏览器把他从undefined转化为window) const calc = { add: add }; calc.add(); // calc const adder = calc.add; adder(); // undefined->window(非严格模式下)
(2)箭头函数的this指向
// 1.箭头函数中的 this 指向 // 箭头函数没有自己的 this const calc = { add: () => { console.log(this); } }; calc.add(); // window //首先在箭头函数作用域中寻找this,由于箭头函数没有自己的 this,因此向外寻找,外面就是全局作用域 window // 2.练习 // 'use strict'; const calc = { add: function () { // this const adder = () => { console.log(this); }; adder(); } }; calc.add(); // calc const addFn = calc.add; addFn(); // 非严格模式,undefined->window
不适用箭头函数的场景
//(1)不能用箭头函数书写构造函数 // (2) 需要 this 指向调用对象的时候,主要用于给dom绑定事件,因为箭头函数没有自己的this document.onclick = function () { console.log(this); }; document.addEventListener( 'click', () => { console.log(this); //window }, false ); // 3.需要使用 arguments 的时候,但是箭头函数中没有 arguments function add() { console.log(arguments); } add(1, 2,3,4,5); const add = () => console.log(arguments); add();
3 解构赋值:数据解析出来=>赋值
数组:
数组的解构是:解析出右边变量的数据,赋值给相同索引值的常量/变量,不取的数据可以用逗号跳过
数组的默认值:只有当数组的成员严格===undefined的时候,默认值才能生效
// const [a,,c] = [1,2,3]; // const [a,b] = []; // a = undefined, b=undefined // const [a =2,b] = [1]; // a = 1, b=undefined // const [a=1,b=2]=[]; // a = 1, b=2 const [a=1,b=2]=[3,null]; // a = 1, b=null,没有严格等于undefined console.log(b);
数组的默认值如果是一个表达式,那么该默认表达式就是惰性求值的,用到的时候会执行,用不到就不执行
const func = ()=>{ console.log(func); return 2 }; // const [x=func()] = [1]; // x=1,不执行func // const [x=func()] = [1]; //x=2,执行func console.log(x);
类数组:
(1) arguments
function func(){ const [a,b]=arguments; console.log(a,b); }; func(); //undefined undefined func(1,2) //a=1,b=2
(2) NodeList
console.log(document.querySelectorAll('p')); //NodeList(2) [p, p] const [p1,p2] = document.querySelectorAll('p'); console.log(p1,p2); //<p></p> <p></p>
(3)函数参数
const arr =[1,1]; const add = ([x=0,y=0]=[]) => { return x+y; } //这里一定要是[x=0,y=0]=[]才能满足不传参数时,严格等于undefined console.log(add()); //0 console.log(add(arr)); //2
(4)交换变量
let x = 1; let y = 2; [y,x] = [x,y]; //这就是数组解构赋值的原理,先把右边的数据解构出来,然后根据索引值赋值给变量 console.log(x,y); //2,1
对象:
对象解构赋值看两点:
(1) 结构匹配 {}={}
(2)相同的属性名完成对应的属性值赋值
const{username:username1,age:age2} = {username:'alex',age:12}; console.log(username1,age2); //这就是找到相同的属性名,然后把对应的属性值赋值到username1,age2,这样打印结果是alex 12
默认值:
(1)首先也是严格等于=== undefined时,默认值才能有效
这里注意默认值是书写方式是: username='alex'不是username:'alex',书写上的语法
const Info = ({username='alex',age=12}={})=>{ console.log(username,age); }; Info() //alex 12 Info({username:'wumingna',age:12}); //wumingna 12
(2)和数组一样,默认值如果是表达式的话,是惰性求值的
(3)当将一个已经声明的变量用于对象的解构赋值时,要加上圆括弧,数组不用在意这个
let x =2; ({x} = {x:1}); console.log(x);
(4)对象可以取到继承的属性,根据原型链查找
其他类型数据
字符串:
字符串的解构赋值既可以参照数组也可以按照对象,都是根据index赋值的
// 数组 const [a,b,,,e] = 'hello'; console.log(a,b,e); //h,e,o // 对象 const {0:a,1:b,3:c} = 'hello' console.log(a,b,c); //h,e,l
数值和布尔值
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。
let {toString: s} = 123; s === Number.prototype.toString // true let {toString: s} = true; s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有toString
属性,因此变量s
都能取到值。
undefined和null
他们是无法转化为对象的,所以他们是不能进行结构赋值的,会报错
4 对象字面量增强
对象字面量是什么
是对象的一种写法,通常我们创建一个Object类型的实例的方法有两个
(1)实例化构造函数
var person=new Object(); person.name="Mina"; person.age=20;
(2)使用对象字面量的形式
const person = { name:'Mina' age:20 }
对象字面量有几种简洁表示法:
(1)对象的属性名和变量或者常量一样的时候,可以只写属性名
const name = 'wuva'; const person = { neme }
(2)对象方法的简介表示,省略冒号和function
const person = { speak(){ console.log('speak') } }
方括号语法增强
方括号语法的用法:
const prop = 'age'; const person = {}; person.prop = 18; //此时属性名是prop person[prop] = 18; //会先对方括号中的prop的求值,此时属性名是age // 方括号语法可以写在对象字面中 const person2 = { [prop] : 16 }; console.log(person2); // 会先对方括号中的prop的求值,此时属性名是age {age: 16}
方括号里面可以放什么
(1)${}
(2)值或者表达式
方括号和点语法的区别
点语法是方括号的一种特殊形式,
当属性名是合法字符串的时候,可以使用点语法,其他情况使用方括号语法,因此更推荐使用方括号语法
5 函数参数的默认值
(1) 函数参数默认值的生效条件是不传参或者明确传递undefined作为参数
(2) 默认参数是表达式的时候是惰性求值的
(3)小技巧:函数默认参数的设置从参数列表的右边开始设置
假如从参数列表的左边设置:
const mul = (x=1,y) => x*y; console.log(mul(undefined,2)); //麻烦
6 剩余参数
剩余参数的本质:剩余参数永远都是一个数组,即使没有值,也是一个空数组
注意事项:
(1)箭头函数的参数即使只有一个剩余参数,也不能省略圆括弧
const add = (...args) => {};
(2)使用剩余参数代替argument获取实际参数
const add = function () { console.log(arguments); }; const add = (...args) => { console.log(args); }; add(1, 2);
(3)剩余参数只能是最后一个参数,之后不能再有其他参数,否则会报错
const add = (x, y, ...args) => { console.log(args); };
7 展开运算符
认识展开运算符
...[2,3,4] => 3,2,1 // 1.认识展开运算符 // [3, 1, 2]; ==》怎么找到数组的最大值和最小值 // Math.min console.log(Math.min([3, 1, 2])); // math.min不能接收数组,接收的是参数列表的形式,如下面一行 console.log(Math.min(3, 1, 2)); // [3, 1, 2]->3, 1, 2 // 2.数组展开运算符的基本用法 console.log(Math.min(...[3, 1, 2])); // 相当于 console.log(Math.min(3, 1, 2));
区分剩余参数和展开运算符
根本区别:
剩余参数: 3,2,1 --> [3,2,1]
展开运算符: [3,2,1] -->3,2,1
数组的展开运算符的应用
(1)复制数组(地址不相同)
// const a = [1, 2]; // // const b = a; // // a[0] = 3; // // console.log(b); //这样指向的同一个地址,修改a,b也会变 const c = [...a]; // const c = [1, 2]; a[0] = 3; console.log(a); console.log(c);
(2)合并数组
const a = [1, 2]; const b = [3]; const c = [4, 5]; console.log([...a, ...b, ...c]); console.log([...b, ...a, ...c]); console.log([1, ...b, 2, ...a, ...c, 3]);
(3)字符串/类数组 转为数组,这样就能使用数组的方法了
//字符串 console.log([...'alex']) // ["a","l","e","x"] //类数组 // (1) arguments function func() { console.log([...arguments]); } func(1, 2); //(2) NodeList console.log([...document.querySelectorAll('p')].push);
对象的展开运算符
(1)对象不能直接展开,要在{}中展开,将属性罗列出来放到{}中 构成一个新的对象
const apple = { color: '红色', shape: '球形', taste: '甜' }; console.log({...apple}); //{color: '红色', shape: '球形', taste: '甜'} // console.log(...apple); //报错 // console.log([...apple]); //报错
(2)合并对象,中间用逗号隔开,合并的对象具有所有属性,相同的属性后者覆盖前者
const apple = { color: '红色', shape: '球形', taste: '甜' }; const pen = { color: '黑色', shape: '圆柱形', use: '写字' }; console.log({ ...apple, ...pen }); //{color: '黑色', shape: '圆柱形', taste: '甜', use: '写字'}
注意事项
(1)如果展开一个空对象,则没有任何效果
(2)展开的不是一个对象的时候,会自动将其转化为对象,再将其属性罗列出来,找不到属性时候就是空对象
(3)展开的是字符串的时候,会自动将字符串转换为一个类数组对象,返回的不是一个空对象
(4)对于对象的展开,不会展开对象中的对象
对象展开运算符的应用
(1)复制对象(地址不同)
// const a = { x: 1, y: 2 }; // // const b = a; //这样其实是地址相同 const c = { ...a }; //正确的复制操作 // console.log(c, c === a);
(2)默认参数和实际参数
const logUser = userParam => { const defaultParam = { username: 'ZhangSan', age: 0, sex: 'male' }; const param = { ...defaultParam, ...userParam }; // const param = { ...defaultParam, ...undefined }; console.log(param.username); // const { username, age, sex } = { ...defaultParam, ...userParam }; // console.log(username, age, sex); }; logUser();
7 Set和Map
Set
认识Set
Set是一个无序且没有重复值的数据结构,Set是没有下标标识每个值的,因此不能像数组一样通过下标值访问Set成员
const s = new Set([1,2,3,3,4]) console.log(s)
小知识点:Set内部NaN被视为相等的元素只会加入一个,但是其实NaN !== NaN, 但是两个空对象视为两个不同的元素
Set的方法和属性
属性:Set.size:返回 Set 实例的成员总数。
操作方法:
-
Set.add(value) :添加某个值,返回 Set 结构本身。
-
Set.delete(value) :删除某个值,返回一个布尔值,表示删除是否成功。
-
Set.has(value) :返回一个布尔值,表示该值是否为 Set 的成员。
-
Set.clear() :清除所有成员,没有返回值。
遍历方法: Set的遍历顺序就是成员的添加顺序
Set 结构的实例有四个遍历方法,可以用于遍历成员。
-
Set.keys() :返回键名的遍历器
-
Set.values() :返回键值的遍历器
-
Set.entries() :返回键值对的遍历器
-
Set.forEach() :使用回调函数遍历每个成员
let s = new Set([1,2,3]); s.forEach(function(value,key,set){ console.log(value); console.log(this); //#document },document); // 该函数的参数与数组的 forEach 一致,依次为键值、键名、集合本身(上例省略了该参数),doucument所在参数位置是修改函数的this指向,不写就是默认window
keys
方法、 values
方法、 entries
方法返回的都是遍历器对象
。由于 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys
方法和 values
方法的是相等的,都是成员的值。这意味着,可以省略values
方法,直接用 for...of
循环遍历 Set
let set = new Set(['red', 'green', 'blue']); for (let x of set) { console.log(x); }
Set构造函数的参数
1.数组
2.字符串、argument、NodeList、Set等具有 iterable 接口的其他数据结构。
Set的注意事项
1.判断重复的方式
// Set 对重复值的判断基本遵循严格相等(===) // 但是对于 NaN 的判断与 === 不同,Set 中 NaN 等于 NaN // 两个空对象是不严格相等的 const s = new Set([1, 2, 1]); const s = new Set([NaN, 2, NaN,{},{}]); // {NaN,2,{},{}} console.log(NaN === NaN); //false NaN其实是不等于NaN的 console.log({} === {}); //false
-
什么时候用Set
(1)数组或者字符串去重
(2)只需要遍历,不需要通过下标去访问
(3)为了使用Set的属性和方法时
Set去重(最常用)
1 数组去重
// 方法1: new_s1 = [...new Set(s)]; //方法2: new_s2 = Array.from(new Set(s)) ; //Array.from 方法可以将 Set 结构转为数组 console.log(new_s2);
2.字符串去重
Set->数组->字符串
const s = new Set('abbagn'); console.log([...s].join('')); //abgn
Map
认识Map
map也是一种键值对的数据结构,由于对象的键只能采用字符串,这给他带来了很大的限制,为了能够让各种类型的值都可以当做键,由此提出来Map数据结构。
Map的属性和方法
属性:
Map.size :返回Map结构的成员总数 ,对象没有size属性
操作方法:
(1)Map.set(key,value): 添加新成员,如果key已经存在,后者覆盖前者,set 方法返回的是当前的 Map 对象,因此可以采用链式写法。
let map = new Map() .set(1, 'a') .set(2, 'b') .set(3, 'c');
(2)Map.get(key) :通过键获取某个成员。如果找不到的话,返回undefined
(3)Map.has(key) : 返回一个布尔值,表示某个键是否在当前 Map 对象之中
(4)Map.delete(key): 删除某个键,返回 true 。如果删除失败,返回 false ,不报错
(5)Map.clear(): 一键清除
遍历方法:
-
Map.keys() :返回键名的遍历器。
-
Map.values() :返回键值的遍历器。
-
Map.entries() :返回所有成员的遍历器。
-
Map.forEach() :遍历 Map 的所有成员。
m.forEach(function(value,key,m){ console.log(value,key); console.log(this); //doucument },document)
Map构造函数的参数
<script> // 1.数组 // 只能传二维数组,而且必须体现出键和值 console.log( new Map([ ['name', 'alex'], //第一个键值对 ['age', 18] ]) ); // 2.Set、Map 等 // Set // Set 中也必须体现出键和值,一般还是二维数组的形式 // const s = new Set([ // ['name', 'alex'], // ['age', 18] // ]); // console.log(new Map(s)); // console.log(s); // Map // 复制了一个新的 Map const m1 = new Map([ ['name', 'alex'], ['age', 18] ]); console.log(m1); const m2 = new Map(m1); console.log(m2, m2 === m1); // false </script>
Map的注意事项
-
判断键名是否相等,遵循严格相等,例外的就是NaN也是等于NaN
-
什么时候使用Map:
如果只是需要key->value的结构或者需要字符串以外的值作为键,使用Map更合适,因为Map相对对象具有更多的方法
与其他数据结构的互相转换
(1) Map转数组
const myMap = new Map().set(true, 7).set({foo: 3}, ['abc']); [...myMap] // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
(2) 数组转Map
new Map([[true, 7], [{foo: 3}, ['abc']}])
(3) Map 转为对象
function mapToObj(m){ let obj = Object.create(null); m.forEach(function(key,value){ obj[key] = value; }); return obj; } const myMap = new Map().set('yes', true).set('no', false); console.log(mapToObj(myMap)); //{true: 'yes', false: 'no'}
(4)对象转Map,可以通过Object.entries()函数 或者如上写一个转换函数
let obj = {"a":1, "b":2}; let map = new Map(Object.entries(obj));
8 遍历器和For...of...
认识遍历器
1.遍历器(迭代器) Iterator
2.寻找 Iterator
console.log([1, 2][Symbol.iterator]()); const it = [1, 2][Symbol.iterator](); console.log(it); // Array Iterator {}
3.使用 Iterator
const it = [1, 2][Symbol.iterator](); //返回一个遍历器的对象 console.log(it.next()); // {value: 1, done: false} console.log(it.next()); // {value: 2, done: false} 此时遍历并没有结束,因为此时done为false,还要接着遍历 console.log(it.next()); // {value: undefined, done: true} ,此时done为true,遍历完成 console.log(it.next()); // {value: undefined, done: true} // it:可遍历对象(可迭代对象) // Symbol.iterator:可遍历对象的生成方法
4.什么是 Iterator,主要是下面这个过程被称为iterator
// 4.什么是 Iterator,主要是下面这个过程被称为iterator // Symbol.iterator(可遍历对象的生成方法) -> it(可遍历对象) -> it.next() -> it.next() -> ...(直到 done 为 true)
遍历器的意义
-
遍历数组、 Set 、Map等:for of, forEach
遍历对象:for in 循环
例子:
//for...of 循环只会遍历出那些 done 为 false 时,对应的 value 值 const arr = [1, 2, 3]; for (const item of arr) { console.log(item); } //for...of的本质 const it = arr[Symbol.iterator](); let next = it.next(); console.log(next); while (!next.done) { console.log(next.value); next = it.next(); console.log(next); } // 2.与 break、continue 一起使用 const arr = [1, 2, 3]; for (const item of arr) { if (item === 2) { // break; continue; } console.log(item); } // 3.在 for...of 中取得数组的索引 const arr = [1, 2, 3]; // keys() 得到的是索引的可遍历对象,可以遍历出索引值 console.log(arr.keys()); //得到一个可遍历对象 for (const key of arr.keys()) { console.log(key); //0 1 2 } //values() 得到的是值的可遍历对象,可以遍历出值 for (const value of arr.values()) { console.log(value); } //和下面的方法结果一样,这种方式其实没有必要 for (const value of arr) { console.log(value); } // entries() 得到的是索引+值组成的数组的可遍历对象 for (const entries of arr.entries()) { console.log(entries); // [0,1] [1,2] [2,3] } // 结合解构赋值,将索引和值结构出来[index,value] for (const [index, value] of arr.entries()) { console.log(index, value); } //对象的遍历 let es6 = { edition: 6, committee: "TC39", standard: "ECMA-262" }; for (let e in es6) { console.log(e); }
2.如何更方便的使用 Iterator
Iterator 遍历器是一个统一的遍历方式,无论是数组还是对象,都可以使用iterator遍历,但是将Symbol.iterator->it->next()这种机制很麻烦,所以我们一般不会直接使用 Iterator 去遍历,通常也不会用,但是我们需要了解iterator的运行机制即可,开发人员利用这套遍历的流程封装好了,即for..of
原生可遍历和非原生可遍历
for ……of……只能循环原生可遍历数据
何为原生可遍历数据:只要有Symblo.iterator()方法且这个方法可以生成可遍历的对象就是原生可遍历
原生可遍历:数组、字符串、类数组、Set、Map
非原生可遍历:
(1) 一般的对象,对象的遍历一般使用 for...in,也可以手动添加 Symbol.iterator 方法变成可遍历对象; (2) 有 length 和索引属性的对象,可以额外添加一个方法,因为比较简单,这里给出一个小例子
//原生可遍历的例子 for (const item of [1, 2, 3]) { console.log(item); } for (const item of 'hi') { console.log(item); } for (const item of new Set([1, 2])) { console.log(item); } for (const elem of document.querySelectorAll('p')) { console.log(elem); elem.style.color = 'red'; } // 一般的对象手动添加Symbol.iterator 方法 const person = { sex: 'male', age: 18 }; person[Symbol.iterator] = () => { let index = 0; return { next() { index++; if (index === 1) { return { value: person.age, //手动可随意指定 done: false }; } else if (index === 2) { return { value: person.sex, done: false }; } else { return { done: true }; } } }; }; // 有 length 和索引属性的对象 const obj = { '0': 'alex', '1': 'male', length: 2 }; obj[Symbol.iterator] = Array.prototype[Symbol.iterator]; for (const item of obj) { console.log(item); }
使用了 Iterator 的场合
<script> // 原生可遍历的 // Array 数组 // String 字符串 // Set // Map // 函数的 arguments 对象 // NodeList 对象 // for...of // 1.数组的展开运算符 console.log(...[1, 2, 3]); console.log(1, 2, 3); console.log(...'str'); console.log(...new Set([1, 2, 3])); // 2.数组的解构赋值 const [a, b] = [1, 2]; const [a, b] = [...[1, 2]]; const [a, b] = 'hi'; const [a, b] = [...'hi']; const [a, b] = [...new Set([3, 4])]; console.log(a, b); // 3.Set 和 Map 的构造函数 new Set(iterator) new Map(iterator) </script>
9 Promise
认识Promise
Promise是异步操作的一种解决方案
异步和同步的简单理解:
document.addEventListener( 'click', () => { console.log('这里是异步的'); }, false ); console.log('这里是同步的'); // 只有点击事件发生时,click才会发生,但是“这里是同步的”的输出是不用其他操作的,是同步的
什么时候使用 Promise
Promise一般用来解决层层嵌套的回调函数,回调地狱(callback hell)
<script> // Promise 一般用来解决层层嵌套的回调函数(回调地狱 callback hell)的问题 // 编写运动函数move const move = (el, { x = 0, y = 0 } = {}, end = () => {}) => { el.style.transform = `translate3d(${x}px, ${y}px, 0)`; el.addEventListener( 'transitionend', () => { // console.log('end'); end(); }, false ); }; const boxEl = document.getElementById('box'); //层层嵌套回调函数,这样很复杂,一旦修改一个值,其他的都要修改,回调地狱 callback hell document.addEventListener( 'click', () => { move(boxEl, { x: 150 }, () => { move(boxEl, { x: 150, y: 150 }, () => { move(boxEl, { y: 150 }, () => { // console.log('object'); move(boxEl, { x: 0, y: 0 }); }); }); }); }, false ); </script>
Promise的基本用法
(1)实例化一个Promise对象
new Promise(function(resolve,reject)=>{ })
(2)Promise的状态
Promise有三种状态,一开始是pending(未完成),
(1)执行了resolve, 变成了fulfilled(resolved)已成功的状态
(2)执行了reject, 变成了rejected已失败的状态
Promise的状态一旦改变,就不会再变化
(3)初识Then方法
then方法有两个回调函数,第一个是pedding->fulfilled的时候执行,第二个是pedding->rejected的时候执行,其中第二个函数是可选的,不一定要提供。
then执行后的返回值:
then方法执行后返回一个新的Promise对象,在then的回调函数中,return后面的东西会自动会包装成一个成功状态的Promise对象,没有return 返回值的时候,就相当于return undefined
const p = new Promise((resolve,reject)=>{ resolve({'name':'Mina'}); reject(new Error('reason')) }).then( (data)=>{ return data; // 自动包装成一个成功状态的Promise对象,也就是相当于 // return new Promise((resolve,reject)=>{ // resolve(data); // }) }, (err) =>{ console.log('error',err); } ).then((data)=>{ console.log(data); // {name:'Mina'} }) // 想改变默认,从padding->reject return new Promise((resolve,reject) => { reject(); })
(4)resolve和reject的参数
resolve和reject函数的参数会被传递到then方法对应的函数中的参数
const p = new Promise((resolve,reject)=>{ resolve({'name':'Mina'}); reject(new Error('reason')) }).then( (data)=>{ console.log('success',data); //success {name: 'Mina'} }, (err) =>{ console.log('error',err); } ) // 其中resolve中的参数{name: 'Mina'}是then中的data, reject中的new Error('reason')是then中的error // 要记得Promise 的状态一旦变化,就不会再改变了,只能从等待到成功或者从等待到失败,不能从成功到失败或者失败到成功
(5)catch()方法
开发环境中,使用最多的还是使用成功态,catch 专门用来处理 rejected 状态, catch 本质上是 then 的特例 因此,可以用then专门处理成功,catch处理失败
(6)finally()方法
finally()
方法本质是then()的一个特例,用于指定不管Promise
对象最后状态如何,都会执行的操作
promise .then(result => {···}) .catch(error => {···}) .finally(() => {···});
上面代码中,不管 promise 最后的状态,在执行完 then 或 catch 指定的回调函数以后,都会执行 finally 方法指定的回调函数。
(7)all()方法
Promise.all()
方法用于将多个Promise
实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
上面代码中, Promise.all() 方法接受一个数组作为参数, p1 、 p2 、 p3 都是 Promise 实例,如果不是,就会先调用下面讲到的 Promise.resolve 方法,将参数转为 Promise 实例,再进一步处理。另外, Promise.all() 方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p 的状态由 p1 、 p2 、 p3 决定,分成两种情况。
(1)只有 p1 、 p2 、 p3 的状态都变成 fulfilled , p 的状态才会变成 fulfilled ,此时 p1 、 p2 、 p3 的返回值组成一个数组,传递给 p 的回调函数。
(2)只要 p1 、 p2 、 p3 之中有一个被 rejected , p 的状态就变成 rejected ,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数。
//例子 const delay = ms => { return new Promise(resolve => { setTimeout(resolve, ms); }); }; const p1 = delay(1000).then(() => { console.log('p1 完成了'); // 成功打印出 return 'p1'; }); const p2 = delay(2000).then(() => { console.log('p2 完成了'); return Promise.reject('reason'); }); // Promise.all() 的状态变化与所有传入的 Promise 实例对象状态有关 // 所有状态都变成 resolved,最终的状态才会变成 resolved // 只要有一个变成 rejected,最终的状态就变成 rejected const p = Promise.all([p1, p2]); p.then( data => { console.log(data); }, err => { console.log(err); } ); // 如果是成功,依次执行p1和p2,然后执行第一个函数, // 如果p1成功,p2失败,首先打印p1完成了,然后因为状态是rejected,会立马执行第二个函数,打印出“reason”,然后执行p2,打印出p2执行了 </script>
10 Class
认识class
<script> // 1.认识 Class // 人类:类 // 具体的人:实例、对象 // 类可以看做是对象的模板,用一个类可以创建出许多不同的对象 // 2.Class 的基本用法 // 类名一般大写 // class Person {} √ // class Person() {} × // class Person {}; × // function func() {} class Person { // 实例化时执行构造方法,所以必须有构造方法constructor,但可以不写出来,浏览器会自动加上,推荐写上 constructor(name, age) { // console.log('实例化时执行构造方法'); // this 代表实例对象,上面定义的是实例属性/方法 this.name = name; this.age = age; // 一般在构造方法中定义属性,方法不在构造方法中定义 // this.speak = () => {}; } // speak:function(){} //不是这样的写法,这是对象的写法, // 各实例共享的方法,没有逗号和分号,区别对象 speak() { console.log('speak'); } } // // Person(); const zs = new Person('ZS', 18); const ls = new Person('LS', 28); console.log(zs.name); // ZS console.log(zs.age); // 18 zs.speak(); // speak console.log(ls.name); // LS console.log(ls.age); // 18 console.log(zs.speak === ls.speak); //true // 3.Class 与构造函数 class Person1 { constructor(name, age) { this.name = name; this.age = age; // this.speak = () => {}; } speak() { console.log('speak'); } run(){} } // 构造函数的写法 function Person2(name, age) { this.name = name; this.age = age; // this.speak = () => {}; } Person.prototype.speak = function () {console.log('speak'); }; Person.prototype.run = function () {}; console.log(typeof Person1); // function,因此class的本质也是一个构造函数 // console.log(Person.prototype.speak); </script>
Class的两种定义形式
<script> // 1.声明形式 重点,最常用 class Person1 { constructor() {} speak() {} } new Person1(); // 2.表达式形式 了解 // function Person2(){} // const Person = function () {}; 构造函数的表达式形式 const Person2 = class { constructor() { console.log('constructor'); } speak() {} }; new Person2(); // constructor //立即执行匿名函数 // (function () { // console.log('func'); // })(); // func() // 立即执行的匿名类,不要忘记写new new (class { constructor() { console.log('constructor'); } })(); </script>
实例属性、静态方法、静态属性
<script> // 1.实例属性 // 方法就是值为函数的特殊属性 class Person1 { age = 0; sex = 'male'; getSex = function () { return this.sex; }; constructor(name, sex) { this.name = name; // this.age = 18; this.sex = sex; } speak() { this.age = 18; } } const p = new Person('Alex'); console.log(p.name); console.log(p.age); // 2.静态方法 // 类的方法 class Person { constructor(name) { this.name = name; } speak(){ console.log("speak"); console.log(this); //谁调用指向那个实例化对象 } static speak() { console.log('人类可以说话'); // this 指向类本身 console.log(this); } } //静态方法的另一种方式,但是不推荐,不太符合类的封装的概念 // Person.speak = function () { // console.log('人类可以说话'); // console.log(this); // }; const p = new Person('Alex'); p.speak(); //对象的方法,打印出speak Person.speak(); //类的方法,打印出人类可以说话 // 3.静态属性 // 类的属性 class Person { constructor(name) { this.name = name; } // 不要这么写,目前只是提案,有兼容性问题 // static version = '1.0'; //推荐写法,写成方法,返回一个静态的值 static getVersion() { return '1.0'; } } // 写到类的外面,但是违反了封装的原则 Person.version = '1.0'; const p = new Person('Alex'); console.log(p.name); // console.log(Person.version); console.log(Person.getVersion()); </script>
私有属性和方法
ES6没有私有属性和方法,通常是模拟私有属性和方法
<script> // 2.模拟私有属性和方法,约束力弱 // 2.1._ 开头表示私有 class Person { constructor(name) { this._name = name; // 私有属性 } _speak() { console.log('speak'); } //私有方法 getName() { return this._name; } } const p = new Person('Alex'); // console.log(p.name); p.name = 'zd'; console.log(p.getName()); // 2.2.将私有属性和方法移出类,约束力强 (function () { let name = ''; class Person { constructor(username) { // this.name = name; name = username; } speak() { console.log('speak'); } getName() { return name; } } window.Person = Person; })(); (function () { const p = new Person('Alex'); console.log(p.name); console.log(p.getName()); })(); </script>
extend方法
<script> // 1.子类继承父类 class Person { constructor(name, sex) { this.name = name; this.sex = sex; this.say = function () { console.log('say'); }; } speak() { console.log('speak'); } static speak() { console.log('static speak'); } } Person.version = '1.0'; class Programmer extends Person { constructor(name, sex) { super(name, sex); // 必须在构造函数中调用父类 } } const zs = new Programmer('zs', '男'); console.log(zs.name); console.log(zs.sex); zs.say(); zs.speak(); Programmer.speak(); console.log(Programmer.version); // 全部都继承了 // 2.改写继承的属性或方法 class Programmer extends Person { constructor(name, sex, feature) { // this.feature = feature; × // this 操作不能放在 super 前面 super(name, sex); this.feature = feature; } hi() { console.log('hi'); } // 同名覆盖 speak() { console.log('Programmer speak'); } static speak() { console.log('Programmer static speak'); } } Programmer.version = '2.0'; const zs = new Programmer('zs', '男', '秃头'); console.log(zs.name); console.log(zs.sex); console.log(zs.feature); zs.say(); zs.speak(); zs.hi(); Programmer.speak(); console.log(Programmer.version); </script>
super方法
有时候不想完全覆盖父类的方法,子类重写之后还是想调用父类的同名方法,该怎么办呢?
<script> // 1.作为函数调用 // 代表父类的构造方法,只能用在子类的构造方法中,用在其他地方就会报错 // super 虽然代表了父类的构造方法,但是内部的 this 指向子类的实例 class Person { constructor(name) { this.name = name; console.log(this); } } class Programmer extends Person { constructor(name, sex) { super(name, sex); } hi() { super(); // × } } new Person(); new Programmer(); // 2.作为对象使用 // 2.1.在构造方法中使用或一般方法中使用 // super 代表父类的原型对象 Person.prototype // 所以定义在父类实例上的方法或属性,是无法通过 super 调用的 // 通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例 class Person { constructor(name) { this.name = name; console.log(this); } speak() { console.log('speak'); // console.log(this); } static speak() { console.log('Person speak'); console.log(this); } } class Programmer extends Person { constructor(name, sex) { super(name, sex); console.log(super.name); // undefined,因为name是定义在实例对象上的属性,不是Person.prototype上的 super.speak(); // "speak" } //在一般方法上的使用 hi() { super(); // × } speak() { // 先调用父类的同名方法,在写自己的逻辑 super.speak(); console.log('Programmer speak'); } // 2.2.在静态方法中使用 // 指向父类,而不是父类的原型对象 // 通过 super 调用父类的方法时,方法内部的 this 指向当前的子类,而不是子类的实例 static speak() { super.speak(); console.log('Programmer speak'); //static是类的方法不是原型的方法,所以此时指向的是类,这样就可以重写静态方法 } } // new Person(); // new Programmer(); Programmer.speak(); //'Programmer speak' // 3.注意事项 // 使用 super 的时候,必须显式指定是作为函数还是作为对象使用,否则会报错,也就是说直接写super()的时候,是会报错的 // super.函数名这样就作为对象 // super()这样就作为函数 class Person { constructor(name) { this.name = name; } speak() { console.log('speak'); } } class Programmer extends Person { constructor(name, sex) { super(name, sex); // console.log(super); × console.log(super()); // 函数 console.log(super.speak); //对象 } } </script>