字符串
函数
判断语句
对象
数组
字符串、数字对象
循环
类和继承
类的定义和实例化
// 定义类
class Car { type = '汽车' //知道的属性hasTire = trueconstructor(price, owner) {//构造函数;不知道的属性,需要参数传进来this.price = pricethis.owner = owner}showInfo(){//类的方法;类里面定义的函数(包括constructor)可以叫类的方法,都不需要function关键字声明console.log(`车型 :${this.type} - 车主 : ${this.owner} - 售价 : ${this.price} `)}
} // 创建实例对象
var myCar1 = new Car(300000, '白月黑羽')
var myCar2 = new Car(350000, '白月黑羽')console.log(myCar1.type, myCar1.hasTire,myCar1.price,myCar1.owner)
console.log(myCar2.type, myCar2.hasTire,myCar2.price,myCar2.owner)myCar1.showInfo()
myCar2.showInfo()
instanceof关键字
用于判断前面的对象是否为后面对象的一个具体实例
extend关键字
类的继承,🔺js中子类会自动拥有父类的一切属性和方法
super关键字
当子类的 初始化代码 有一部分和父类相同,这时就可以用super(注意是初始化代码)直接调用父类的constructor代码
class Car { type = '汽车'hasTire = trueconstructor(price, owner) {this.price = pricethis.owner = owner}showInfo(){console.log(`车型 :${this.type} - 车主 : ${this.owner} - 售价 : ${this.price} `)}
}class ElectricCar extends Car {static type = '电动车'static hasBattery = truestatic hasEngine = falseconstructor(price, owner, batteryCap) {// 调用父类的构造函数super(price, owner)this.batteryCap = batteryCap}}
当然super不仅可以调用父类的构造函数方法,也可以调用父类的其他方法
class Car { type = '汽车'hasTire = trueconstructor(price, owner) {this.price = pricethis.owner = owner}showInfo(){console.log(`车型 :${this.type} - 车主 : ${this.owner} - 售价 : ${this.price} `)}
}class ElectricCar extends Car {type = '电动车'hasBattery = truehasEngine = falseconstructor(price, owner, batteryCap) {// 调用父类的构造函数super(price, owner)this.batteryCap = batteryCap}showInfo(){super.showInfo()console.log(`电池容量 :${this.batteryCap}`)}}var myCar1 = new ElectricCar(300000, '白月黑羽', 200)
myCar1.showInfo()
错误捕获
抛出错误 —— 关键字throw
关键字throw后需要跟一个错误对象
var miles,fmiles,km
miles = prompt('请输入英里数:')
fmiles = parseFloat(miles)
if(isNaN(fmiles))//这里是直接使用Error构造函数创建了一个新对象;Error构造函数的参数会作为error对象的message属性,用来描述此处具体的错误信息,比如原因。执行完throw抛出异常的代码后,后续的代码不会再执行throw new Error('输入的必须是数字')
km = fmiles * 1.6
console.log(`${miles} 英里等于 ${km}公里`)
throw 抛出的不一定非得是Error类型的对象,也可以是TypeError,Error类型的子对象 ,也可以是自定义的对象
if (isNaN(fmiles))throw {code : 401,info : '输入的必须是数字'}
捕获错误 —— try ... catch ...
在编码时,预料到了某些代码在运行时可能会出现某些错误,如果这些错误不至于必须结束程序,就可以使用try ... catch ...
正常情况下:
var stockTable = {sh : {华远地产 : '600743',中天科技 : '600522',},sz : {深南股份 : '002417',中兴通讯 : '000063',},
}
while(true){ let input = prompt('请输入股市代码-股票进行查询:')let [stock, corp] = input.split('-')let code = stockTable[stock][corp]console.log(`${stock} ${corp} 股票代码是 ${code}`)
}
捕获错误:
while (true){ let input = prompt('请输入股市代码-股票进行查询:')try{if(input==='exit') breaklet [stock, corp] = input.split('-')let code = stockTable[stock][corp]console.log(`${stock} ${corp} 股票代码是 ${code}`)} catch (e){//一旦try里的代码执行错误,就会捕获这个错误,并执行catch里的代码;catch引导的代码段就是对错误 的一种处理;这里的e是错误对象console.log(e.name,e.message) console.error('请输入有效的股市代码') }
}
Case1.当预估某个代码段中可能出现好几种类型的错误,可以使用 instanceof 判定错误类型,并进行相应的处理。
myCar1 instanceof Car
=> true
判断前面对象是否为后面对象的一个具体实例
while (true){ let input = prompt('请输入股市代码-股票进行查询:')try{if(input==='exit') breaklet [stock, corp] = input.split('-')let code = stockTable[stock][corp]console.log(`${stock} ${corp} 股票代码是 ${code}`)} catch (e){if (e instanceof TypeError) {console.error('请输入有效的股市代码')} else if (e instanceof RangeError) {console.error(e)}// 未知类型错误,继续抛出else {console.log('未知类型错误')throw e //在catch代码块中,如果发现当前代码没法处理这个异常,可以使用throw继续往上抛出}}
}
Case2.嵌套捕获
当内层代码抛出异常,优先会被内层的catch 捕获, 不会执行到外层的 catch 代码块中;当内层catch代码发现不适合处理,又 throw 抛出, 才会被外层的 catch 捕获进行处理。
try{var inNum = 401try {if (inNum === 401)throw {code : 401,info : '输入的是401!'}else throw {code : 500,info : '输入的是其它!'}} catch (e) {if (e.code === 401) console.log('401 错误处理')else {console.log('未知类型错误')throw e}}
}
catch (e) {console.log('处理内层代码不适合处理的错误')
}
Case3.在函数调用里面产生错误
优先使用当前函数里面捕获处理错误的 try catch ;如果没有捕获到(比如没有 try catch),就会把错误对象抛到 调用该函数的外层代码 处, 查看是否有相应的 try catch
function throwError(inNum) {if (inNum === 401)throw {code : 401,info : '输入的是401!'}else throw {code : 500,info : '输入的是其它!'}
}try {//这行代码是被try catch保护处理的,异常会被捕获,所以函数里面抛出的异常,在函数外面的catch被捕获处理了throwError(401); // 500
}
catch (e) {if (e.code === 401) {console.log('401 错误处理')} else {console.log('未知类型错误')throw e}
}
console.log('后续代码执行')
捕获错误 —— try ... catch ... finally
不管try里面有无错误抛出,都要执行finally代码块里的代码
特别是在没有catch的时候有用:
try {throw {code : 401,info : '输入的必须是数字'}
}
finally {console.log('不管有无错误,都要做的处理')
}
console.log('后续代码')
3个点操作符
剩余参数:前有3个点的参数,称之为剩余参数
场景:实现一个printAge,它的参数是一些学生的姓名;这个函数需要打印这些输入的学生的年龄
常规做法:定义一个数组
var studentInfo = {'张飞' : 18,'赵云' : 17,'许褚' : 16,'典韦' : 18,'关羽' : 19,
}function printAge(students){for (let student of students) {console.log(`学生:${student} , 年龄 ${studentInfo[student]}`);}
}printAge(['张飞', '典韦', '关羽'])
printAge(['赵云'])
使用可变参数:
var studentInfo = {'张飞' : 18,'赵云' : 17,'许褚' : 16,'典韦' : 18,'关羽' : 19,
}function printAge(...students){for (let student of students) {console.log(`学生:${student} , 年龄 ${studentInfo[student]}`);}
}printAge('张飞', '典韦', '关羽')//执行函数时,js解释器创建一个数组赋值给这个students,里面存放了 '张飞', '典韦', '关羽' 三个字符串对象作为元素
printAge('赵云')
var studentInfo = {'张飞' : 18,'赵云' : 17,'许褚' : 16,'典韦' : 18,'关羽' : 19, }function printAge(...students){console.log(students)for (let student of students) {console.log(`学生:${student} , 年龄 ${studentInfo[student]}`);} }printAge('张飞', '典韦', '关羽')
运行结果
[ '张飞', '典韦', '关羽' ] 学生:张飞 , 年龄 18 学生:典韦 , 年龄 18 学生:关羽 , 年龄 19
注意:剩余参数往往不单独出现在函数参数中,通常和其他参数一起出现
var studentInfo = {'张飞' : 18,'赵云' : 17,'许褚' : 16,'典韦' : 18,'关羽' : 19,
}function printAge(prefix, ...students){console.log(students)for (let student of students) {console.log(`${prefix} 学生:${student} , 年龄 ${studentInfo[student]}`);}
}printAge('-->', '张飞', '典韦', '关羽')//prefix固定参数,优先匹配,剩下的都是剩余参数的
printAge('==>', '张飞', '典韦', '关羽')
应用:
调用函数时
var onebatch = ['张飞', '典韦', '关羽']
printAge(...onebatch)//等价于printAge (onebatch[0], onebatch[1], onebatch[2])
构建新数组时
var batch1 = ['张飞', '典韦', '关羽']
var batch2 = [...batch1, '赵云', '马超']
console.log(batch2)
构建新对象
var studentInfo1 = {'张飞' : 18,'赵云' : 17,'许褚' : 16,'典韦' : 18,'关羽' : 19,
}var studentInfo2 = {...studentInfo1,'黄忠' : 78,'张辽' : 39,
}
注意一点:
1.在使用多次数据展开时,就比如在实现多个Object 的合并时,其中重复的数据会被后面的Object里面的值替换
var studentInfoAll = {...studentInfo1,...studentInfo2
}
2.赋值1:studentInfoAll_clone1 只是对象的新变量名, 和 studentInfo 对应的是同一个数据对象;赋值2:studentInfoAll_clone2 对应的则是一个新的对象,里面是studentInfo里面的内容。
var studentInfo = {'黄忠' : 78,'张辽' : 39
}// 赋值1
var studentInfoAll_clone1 = studentInfo// 赋值2
var studentInfoAll_clone2 = { ...studentInfo}
回调函数
同步&异步
同步:一起走,等完成后再继续
import time
print('这里是等待2s前的代码')
time.sleep(2)//等待2s;一直在这儿等着
print('这里是等待2s后的代码')
异步:不搁这儿等着,继续往后执行,到2s后立即执行 taskAfter2Second
function taskAfter2Second(){console.log("这里是等待2s的后续代码");
}
setTimeout( taskAfter2Second , //要执行要回调函数的函数名2000 //2000毫秒,2s
)console.log("这里是等待2s的后续代码");
何为回调函数
先定义,等事情完成,回头再调用
回调函数中的this指代问题
首先前面说过:通过哪个对象调用了这个函数,函数里的this对应的就是这个对象
情景1:这里的this对应的就是car1
class Car { constructor(price, owner) {this.price = pricethis.owner = owner}showInfo(){console.log(`车辆信息如下:`)console.log(`车主 : ${this.owner} - 售价 : ${this.price} `)}}car1 = new Car(230000,'白月黑羽')
car1.showInfo()
情景2:这里的this对应的就是obj1
这里相当于给car1.showInfo又起了个anotherShowInfo
class Car { constructor(price, owner) {this.price = pricethis.owner = owner}showInfo(){console.log(`车辆信息如下:`)console.log(`车主 : ${this.owner} - 售价 : ${this.price} `)}}car1 = new Car(230000,'白月黑羽')let obj1 = {price: '3333',owner: '张三',anotherShowInfo : car1.showInfo
}obj1.anotherShowInfo()
情景3:这里的this对应的就是obj2;但输出的是undefinded,undefinded,因为obj2没有price、owner这俩属性
class Car { constructor(price, owner) {this.price = pricethis.owner = owner}showInfo(){console.log(`车辆信息如下:`)console.log(`车主 : ${this.owner} - 售价 : ${this.price} `)}}car1 = new Car(230000,'白月黑羽')let obj2 = {anotherShowInfo : car1.showInfo
}obj2.anotherShowInfo()
🔺情景4:这里的this对应的就是window;输出undefinded,undefinded
setTimeout这个函数是由js引擎实现的,由于它内部的回调函数function,调用时没有xxx.这样的前缀,js中调用函数没有前缀就等于是通过全局对象window调用。
而window没有price、owner这两个属性,所以都是undefinded。
class Car { constructor(price, owner) {//构造函数this.price = pricethis.owner = owner}showInfo_Delay1Sec(){//this.owner 要是在这里this对应的就是car1//1s后通过回调函数打印this.owner和this.pricesetTimeout(function(){//console.log(this === window)console.log(`车辆信息如下:`)console.log(`车主 : ${this.owner} - 售价 : ${this.price} `)},1000)}}car1 = new Car(230000,'白月黑羽')car1.showInfo_Delay1Sec()
如何解决回调函数中this对应的window而不是car1这样的问题呢
法一:设一个中间变量,保存this到其他变量比如self中
class Car { constructor(price, owner) {//构造函数this.price = pricethis.owner = owner}showInfo_Delay1Sec(){let self = thissetTimeout(function(){console.log(`车辆信息如下:`)console.log(`车主 : ${self.owner} - 售价 : ${self.price} `)},1000)}}car1 = new Car(230000,'白月黑羽')
car1.showInfo_Delay1Sec()
法二:使用箭头函数
箭头函数中的this具有特殊性:即它对应的是 包含该箭头函数的函数 的执行环境
简单来说就是看 :=> 这个箭头函数出现在哪个函数定义里面
注意这里不是setTimeout,而是showInfo_Delay1Sec;它只是作为setTimeout这个函数的参数。
this关键字在方法中,那它对应的就是那个方法所属的对象
class Car { constructor(price, owner) {//构造函数this.price = pricethis.owner = owner}showInfo_Delay1Sec(){setTimeout(()=>{console.log(`车辆信息如下:`)console.log(`车主 : ${this.owner} - 售价 : ${this.price} `)},1000)}}car1 = new Car(230000,'白月黑羽')
car1.showInfo_Delay1Sec()
若箭头函数没有包含函数,那么里面的this对应的就是全局对象,在浏览器中就是window
var price = 1000
var owner = '白月黑羽'setTimeout(()=>{ console.log(`车主 : ${this.owner} - 售价 : ${this.price} `)},1000
)
匿名函数
由于前面taskAfter2Second只被用1次,起名较为麻烦,所以引入匿名函数
上面代码可改为:
setTimeout( //直接定义函数,不用起名function(){console.log("这里是等待2s的后续代码");}2000 //2000毫秒,2s
)console.log("这里是等待2s的后续代码");
let add100 = function(a) { return a+100 ;}
箭头函数
eg. (a) => {return a+100 ;}
相当于把function换成了=>
注意两点:1.当箭头函数只有一个参数时,()也可省 a => {return a+100 ;}
2.当箭头函数体内只有一行代码且返回一个值时, {}、return均可省 a => a+100
通过结合前面的匿名函数和箭头函数对下面的代码进行简化
const array1 = [1,4,9,16];
function square(x) {return x**2}
const map1 = array1.map(square);
console.log(map1);
const array1 = [1,4,9,16];
console.log(array1.map(x => x**2));
本文参考自:Javascript 简介 - 白月黑羽 (byhy.net)