模板字符串
1.模板字符串: 只要拼接字符串 都用模板字符串代替+
a.整个字符串包裹在一对儿反引号 ` ` 中
b.反引号``中支持换行
c.反引号中需要动态生成的内容必须放在${}里
d. ${}里:
1). 可以放一切有返回值的合法的变量或js表达式。
2). 不能放程序结构(分支和循环)以及没有返回值的js表达式
//示例:
var price=12.5;
var count=5;
console.log(`单价:${price.toFixed(2)}, 数量:${count},
总计:${(price*count).toFixed(2)}`);
let
旧JS中:var 声明的变量会被声明提前破坏了程序执行顺序,没有块级作用域,导致代码块{}内变量,会影响{}外的程序
说明:代码块 除了函数和对象以为的{},都称为代码块,旧JS中拦不住内部变量影响外部程序
//例子:
var t=0;//用于累加每个函数执行耗费的总时间
function fn1(){
console.log(`执行fn1,耗时0.3s`)
t+=0.3
}
function fn2(){
//var t; //if里的var t被提前到这里
//因为fn2中已经有了局部变量t,所以fun2中所有对t的操作,从此与全局t再无关系!
console.log(`执行fun2,耗时0.8s`)
t+=0.8; //不再加到全局t,而是加到局部t
//函数调用后局部变量t释放,所以原本正常的程序,少了0.8s。
if(false){
var t=new Date();
console.log(`当前时间:${t.toLocaleString()}`)
}
}
fn1()
fn2()
console.log(`共耗时${t}s`)
解决:let代替var声明变量
优点:
(1). 用let声明的变量不会被声明提前
(2). let让代码块也变成一级作用域——保证块内的变量不会超出块的范围,影响块外的代码。
原理: 其实let会自动添加匿名函数自调,让let的变量变成局部变量
let的特点:
(1)相同代码块中只有let的变量受控不会超出块的范围。其它如果有var声明的变量,照样超出块的范围,影响外部
(2). 即使在全局let的变量,也不会保存在window中
(3). 在相同范围内,不能重复声明一个变量
(4). 禁止在let之前提前使用该变量
//示例:
var a=10;
let b=10;
console.log(window.a) //10
console.log(window.b) //undefined
console.log(b) //10
//匿名函数自调
(function(){
var b=10;
console.log(b);//10
})()
箭头函数
使用:
(1)去掉function ,在()和{}之间加=>
(2)如果()只有一个形参变量,可省略()
(3)如果{}函数体中只有一句话,可以省略{},如果仅剩这句话还是return 必须去掉return
箭头函数特征:
(1)我们需要回调函数内的this与回调函数外部的this保持一致!但是,几乎所有回调函数中的this都默认值window。
var james={ //new Object()不是一个作用域
sname:"JAMES",
friend:["davis","kuzma","Schroder"," Harrell"],
intr:function(){ //intr的函数作用域
this.friend.forEach(function(elem){ //回调函数的函数作用域 //this指向lilei.friend
console.log(`${this.sname}传球给${elem}`) //this指向window
})
}
}
james.intr() //结果undefined 传球给xxx
解决: 只要将回调函数改为箭头函数,就能轻松让回调函数中的this->回调函数外的this
原理: 箭头函数有一个特征: 箭头函数内的this可与外部的this保持一致 仅限于this 至于箭头函数内的其它局部变量,依然只能在箭头函数内部使用
(2)所有function都能改成箭头函数吗?
不希望内外this保持一致时,就不应该用箭头函数简化 :比如: 对象中的方法,就不应该用箭头函数简化
var james={
sname:"JAMES",
friend:["davis","kuzma","Schroder"," Harrell"],
intr:()=>{
this.friend.forEach((elem)=>{ //this指向window,undefined.forEach()报错
console.log(`${this.sname}传球给${elem}`)
})
}
}
james.intr()
解决:ES6为了去掉对象中方法的function,提供了一个专门的简写:var james={ intr(){...} }
好处:intr()简写不改变this指向
箭头函数与普通函数的差别:
(1). 箭头函数中的this与外部的this保持一致。但是普通函数的this与外部的this是无关的
(2). 箭头函数不能作为构造函数
(3). 箭头函数中不允许使用arguments()
for of
问题:
(1). for循环既可以遍历索引数组,又可以遍历类数组对象,但是不够简化
(2). forEach虽然可以简化,但是只能用于遍历索引数组,无法遍历类数组对象。
解决:
(1)ES6提供了一个for of 循环,统一了for和forEach的优缺点
何时:
(1)只要遍历数字下标的索引数组和类数组对象,都可以用for of来代替for或者forEach
如何:
for(var 变量 of 索引数组或类数组对象){
//of会依次取出每个元素的值
//将当前元素值保存在of前的变量中
}
for of的局限:
(1). 遍历过程中,只能获得元素值,无法获得下标位置
(2). 只能从头到尾顺序遍历,无法调整遍历的顺序。
(3). 无法调整遍历的步调
参数增强
调用函数时,即使没有传入实参值,形参变量也能有一个默认值使用;
希望即使没有传入实参值,形参变量也能有一个默认值使用时;
如何使用:
function 函数名(形参, ..., 形参=默认值){
//调用时,带有=默认值的形参,即使没有传入实参值,也有默认值可用
//如果调用时用户传入了自定义的实参值,则形参使用传入的自定义实参值——默认值就成了备用!
}
//示例:
function intrSelf(str="主人很懒,什么也没有写"){
console.log(`自我介绍:${str}`)
}
intrSelf("you can you up");
intrSelf();
问题: 单靠默认值无法解决多个形参不确定有没有值的情况
//示例:
function order(zhushi="奥尔良烤腿堡",xiaochi="土豆泥",yinliao="咖啡"){
console.log(`您本次点的餐是:
主食:${zhushi},
小吃:${xiaochi},
饮料:${yinliao}
`)
}
//第一个每个点
order("香辣鸡腿堡","薯条","可乐");
//第二个就点默认套餐
order();
//第三个只想换套餐里的饮料
// order("雪碧");//错误
// order(,,"雪碧");//错误
// order("","","雪碧");//错误
剩余参数
ES6中,箭头函数中无法使用arguments对象.那么,如果遇到参数个数不确定的情况.
今后只要在ES6中,遇到不确定实参值个数的情况时,我们都要用剩余参数的语法来代替arguments
如何:
function 函数名(...数组名){
//...表示收集的意思,在函数被调用时,收集所有传入函数的实参值,保存到一个数组中。
//数组名就是...后的数组名
}
//例子:
var add=(...arr)=>{
console.log(`arr:${arr}`);
var sum=0;
for(var n of arr){
sum+=n
}
return sum;
}
console.log(add(1,2,3));//6
console.log(add(1,2,3,4,5));//15
优点:
a. ...剩余参数语法支持ES6的箭头函数
b. ...剩余参数语法获得的是一个纯正的数组,可随意使用数组家的函数
c. ...剩余参数语法还可和其它形参配合使用,只获得剩余的参数。不必获得所有实参值。
//示例:
function jisuan(ename, ...arr){
console.log(`ename:${ename}`);
console.log(`arr:${arr}`);
console.log(`${ename}的总工资是:${
arr.reduce((box,elem)=>box+elem,0)
}`)
}
jisuan("Li Lei",10000,1000,2000);
jisuan("Han Meimei",3000,4000,5000,1000,2000);
打散数组
问题: 函数需要多个实参值,但是多个实参值却是放在数组中给的,出现了不一致
解决:
a. 不好的解决: apply
apply的本职工作是先替换this,再打散数组 如果一个需求与this无关,仅希望打散数组时,使用apply()时,第一个实参值对象写什么就很尴尬 不能不写!但是随便也就行!
b. 好的解决: ES6的打散数组操作
如何:
调用函数时:
函数名(...数组)
总结:...定义时收集,调用时打散
//示例:
console.log(Math.max(2,7,5,1));
var arr=[2,7,5,1];
console.log(Math.max(arr));// NaN 不支持
console.log(
Math.max.apply(Math, arr)//不好
Math.max(...arr) //打散
)
...常用语法
a.克隆一个对象: var obj2={...obj1} //打散旧对象中每个属性,放入新对象中
b.克隆一个数组: var arr2=[...arr2]//打散旧数组为多个元素值,再放入新数组
c.合并两个数组: var arr3=[...arr1,...arr2]; //先将旧数组arr1和arr2分别打散为多个元素值 //然后再讲所以元素值放入新数组中
d.合并两个对象: var obj3={ ... obj1, ...obj2 } //先将旧对象打散为多个属性,然后将多个属性值放入新对象中保存
//示例:
var lilei={
sname:"Li Lei",
sage:11
}
var lilei2={...lilei};
console.log(lilei2);
console.log(lilei==lilei2);//false
var arr=[1,2,3];
var arr2=[...arr];
console.log(arr2);
console.log(arr==arr2);//false
var arr1=[1,2,3];
var arr2=[5,6,7];
var arr3=[...arr1,4,...arr2,8];
console.log(arr3);
var obj1={a:0,x:1,y:2};
var obj2={a:4,m:5,n:6};
var obj3={
...obj1,
z:3,
...obj2,
o:7
}
console.log(obj3);
解构
数组解构:从一个巨大的数组中提取出个别成员单独使用;
如何: [变量1, 变量2, ...]=数组;
原理:
a. 先将=左边装扮为一个数组的样子。其中,每个元素位置上都放一个准备接受实际数组元素值的变量
b. 执行时: =右边的数组会将对应位置的元素值,自动赋值给=左边对应位置的变量中!
结果:
变量1=数组[0];
变量2=数组[1];
示例: 从数组中只解构出月和日
var arr=[2020,9,4,15,43,37];
// 0 1 2 3 4 5
//只想用年、月、日
// var [y,m,d]=arr;
// // 0 1 2
// console.log(y,m,d);
//只想用月、日
var [ ,m,d]=arr;
// 0 1 2
console.log(m,d);//9,4
对象解构:从一个巨大的对象中仅提权出个别想要的成员单独使用
如何: {属性名1: 变量1, 属性名2:变量名2 , ... }=对象 // 配对儿 : 接属性值
原理:
a. 先将=左边装扮成一个对象的结构。但是=左边写的属性名必须在=右边的对象中有包含才行
b. 执行时,=右边的对象,会将相同属性名的属性值赋值给=左边相同属性名的变量
结果:
变量1=对象.属性名1
变量2=对象.属性名2
问题: 原对象中的属性名或方法名起的已经很好了 我们其实是没必要改名的
解决: 通常我们从原对象中解构出属性和方法时,都不会轻易修改原属性名和方法名
问题: 被迫要把属性名和变量名,相同的名字写两遍
解决: 其实,ES6中,如果一个属性名和:后的变量名相同 则只需要写一个即可
var { 属性名1, 属性名2, ... } = 对象
一个名字两用
既配对
又变量名
示例: 使用对象解构从对象中提取出个别属性和方法使用
var user={
uname:"dingding",
sex:1,
email:"dingding@163.cn",
login(){
console.log(`登录...`)
},
logout(){
console.log(`注销...`)
},
changePwd(){
console.log(`修改密码...`)
}
}
//只想用用户名和注销
// 配对 变量名 配对 变量名
// var {uname:uname, logout:logout}=user;
var { uname, logout } = user;
// 配对
// 变量
console.log(`用户名:${uname}`);
logout();
参数解构:
问题: 多个实参值不确定有没有,而且还要求每个实参值与形参对应!
解决: 参数解构
什么是: 以对象解构方式来接收和传递函数的实参值
如何:
a. 定义函数: 要把所有形参变量都装扮在一个对象结构中
function 函数名({
// 配对儿 接值
属性名1: 形参1=默认值,
属性名2: 形参2=默认值,
... : ...
}){
}
b. 调用函数时,所有实参值必须放在一个对象结构中整体传给函数作为参数
函数名({
// 配对儿
属性名1:实参值1,
属性名2:实参值2,
... : ...
})
(5). 结果: 在函数内部
a. 形参1 接住了实参值1
b. 形参2 接住了实参值2
(6). 问题: 多数情况下属性名和形参变量名都是一样的!就要写2遍
(7). 解决: 其实,在定义函数时,形参对象结构中,可以只写一个属性名:
但是,一个属性名2用: 既配对,又接收实参值
(8). 示例: 定义一个订餐函数,可接收任意个实参值
//示例:
function order({
zhushi="奥尔良烤腿堡",
xiaochi="土豆泥",
yinliao="咖啡"
}){
console.log(`您本次点的餐是:
主食:${zhushi},
小吃:${xiaochi},
饮料:${yinliao}
`)
}
//第一个挨个点:
order({
zhushi:"香辣鸡腿堡",
xiaochi:"薯条",
yinliao:"可乐"
});
//第二个默认:
order({})
//第三个只要换饮料
order({
yinliao:"雪碧"
})
//第四个想换饮料和小吃
order({
yinliao:"豆浆",
xiaochi:"菠萝派"
})
class
问题:
(1). 箭头函数不能用作构造函数,不能用new来调用
(2). 旧js中,明明构造函数和原型对象同属一家人,但是却分开写
解决:
class
什么是: 专门集中保存一个类型的构造函数和所有原型对象方法的程序结构
如何:
(1). 用class{}包裹以前的构造函数和原型对象方法
(2). 构造函数名提升为class名,所有构造函数更名为constructor——构造函数内容保持不变
(3). 所有直接包含在class内的方法,默认就保存在原型对象中。不用加"类型名.prototype"前缀 而且"=function"也可省略
本质:底层依然是构造函数、原型对象、继承机制。只不过表面的格式换了
//示例:
class student{ //class包裹构造函数和原型对象
// 构造函数
constructor(sname,sage){ //构造函数名字提升到class名字,所有构造函数改为constructor
this.sname=sname;
this.sage=sage;
}
// 原型对象
intr(){ //包含在class内的方法,默认保存在原型对象中
console.log(`I'm${this.sname},${this.sage}`)
}
}
var lilei=new student("lilei",11);
lilei.intr()
问题:多个class之间,拥有部分相同的属性结构和方法定义,但是,我们又不能把相同的属性结构和方法定义写2遍
解决: 两种class间的继承
如何:
两种class间的继承: 2大步:
(1). 额外创建一个公共的父类型class,集中保存两个子类型class相同部分的属性结构和功能。子类型中就不再需要重复保存这些属性和方法了!
问题: 现在的子类型是残缺不全的!
(2). 让子类型class继承公共的父类型class: 2步:
a. 使用extends让子类型class继承父类型class
class 子类型 extends 父类型{
...
}
说明: extends有点儿像setPrototypeOf()
问题: 虽然子类型的子对象可以使用爷爷类型的方法,但是,因为子类型的构造函数缺少必要的语句,所以,子类型的 子对象内部依然缺少属性
b. 在子类型构造函数中借用父类型的构造函数来帮子类型为孩子添加必要的属性。
class 子类型 extends 父类型{
constructor(){
super(实参值,...) //super是extends附赠的关键词,
//专门指代父类型的构造函数
//所以,调用super就相当于调用父类型构造函数了
... ...
}
}
//示例:
class Flyer{ //公共的父类型class ,集中保存两个子类型class向同功能
constructor(x,y){
this.x=x;
this.y=y;
}
fly(){
console.log(`飞到x:${this.x}, y:${this.y}位置`)
}
}
// 继承
class Plane extends Flyer{ //extends 让子类型class继承父类型class
constructor(x,y,score){
//借用父类型构造函数帮忙给子对象添加必要属性
super(x,y);
this.score=score;
}
getScore(){
console.log(`击落一架敌机,得${this.score}分`)
}
}
var p=new Plane(10,50,5);
p.fly();
p.getScore();
console.log(p);
promise 异步任务的顺序执行
1.问题:多个异步任务,如何保证必须顺序执行
2.错误:单纯调用三个异步任务函数
3.原因: 异步任务都是在主程序之外执行,主程序中其它代码不会等待异步任务执行完才开始的。而是只要异步任务一开始,后续代码也会立刻执行 与异步任务同时执行
4.旧的解决:回调函数来解决
(1). 为前一项异步任务添加一个回调函数参数。用于提前保存下一项要执行的任务。
(2). 在前一项异步任务内,最后一句执行的语句之后,自动调用回调函数参数。
(3). 调用时,在调用前一项任务时,就通过回调函数的方式,将下一项任务包在一个匿名函数中,提前传给前一项任务的回调函数参数。
(4). 结果:
a. 当前一项任务执行时,后一项任务仅仅暂时保存在回调函数参数中,暂不执行!
b. 只有当前一项任务最后一句话执行完,自动调用回调函数参数时,才会执行回调函数参数提前保存的下一项任务。
5. 问题: 回调地狱: 由于反复使用回调函数的语法,形成的深层的嵌套代码结构
//示例:回调函数解决异步任务顺序执行(不好)
function james(callback){
console.log(`james 起跑..`);
setTimeout(()=>{
console.log(`james到达终点`)
callback()
},3000)
};
function davis(callback){
console.log(`davis起跑..`);
setTimeout(()=>{
console.log(`davis到达终点`)
callback()
},2000)
};
function kuzma(callback){
console.log(`kuzna起跑..`);
setTimeout(()=>{
console.log(`kuzna到达终点`);
callback()
},4000)
}
//如果想异步函数顺序执行,旧的方法给每个函数加上回调函数,但是这种写法会形成回调地狱
james(function(){
davis(function(){
kuzma()
})
})
用promise解决异步函数的顺序执行问题:
1.专门保证多个异步任务顺序执行,且不会产生回调地狱问题的新技术
2.何时: 今后只要多个异步任务必须顺序执行时,都首选promise
3.如何:
1.将每个异步任务用new Promise包裹,变成一个格子间
function 前一项任务(){
return new Promise(){
function(door){
//原来的异步函数代码
//异步函数代码结束后:door()
}}}
2.调用时:前一项任务().then(后一项任务)
4.原理:1). 当调用前一项任务时:
i. 创建一个new Promise格子间对象
ii. 自动开始执行new Promise中的异步任务代码
iii. 前一项任务将创建好的new Promise格子间对象返回到函数外
2). 因为前一项任务返回的是一个new Promise格子间对象,所以可以用.then()继续连接下一项任务。
i. .then()中下一项任务一定不要加()。因为()是立刻调用的意思,但是我们不希望下一项任务立刻调用。
ii. 当前一项任务内执行完之后,自动调用开门的函数door()时,程序会自动执行.then()中的下一项任务
//示例:
function james(){ //前一项任务
return new Promise(
function(door){ //原来异步任务的代码
console.log(`james起跑..`);
setTimeout(()=>{
console.log('james到达终点..')
door() //原异步任务结束后,传递给下一个要执行的异步任务
},2000)
}
)
}
function davis(){
console.log(`davis起跑..`);
setTimeout(()=>{
console.log('davis到达终点..')
door() //原异步任务结束后,传递给下一个要执行的异步任务
},4000)
}
james().then(davis)
前一项任务可以向下一项任务传递参数: 2步
(1). 前一项任务在使用door()开门时,可以传入一个实参值door(实参值)
(2). 放在then中的下一项任务,必须提前准备好一个形参变量,准备接上一个任务开门时传下来的实参值
//示例:
function james(baton){ //前一项任务
return new Promise(
function(door){ //原来异步任务的代码
var baton="接力棒" //要传递的参数
console.log(`james拿着${baton}起跑..`);
setTimeout(()=>{
console.log('james到达终点..')
door(baton) //原异步任务结束后,传递给下一个要执行的异步任务
},4000)
}
)
}
function davis(baton){ //接入要传递的参数
return new Promise(
function(door){
console.log(`davis拿着${baton}起跑..`);
setTimeout(()=>{
console.log('davis到达终点..')
door(baton)
},2000)
}
)
}
function kuzma(baton){
return new Promise(
function(door){
console.log(`kuzma拿着${baton}起跑..`);
setTimeout(()=>{
console.log('kuzmas到达终点..')
door()
},1000)
}
)
}
james().then(davis).then(kuzma)
</script>