1.块级作用域let
var 关键字定义变量有两个作用域 全局作用域 和 局部作用域
let 关键字定义变量有三个作用域 全局作用域, 局部作用域 和 块级作用域
全局变量: 在全局范围内定义的变量
局部变量: 在函数内部定义的变量
块级变量: 在语法块中定义的变量 比如循环,判断语法块
let和var的两点主要区别:
// 1, var 在同一作用域可以重复定义同一个变量, let不行
// 2, var 没有块级作用域, 在循环中定义的变量都是全局的, 会相互覆盖, let在循环中定义的变量都是独立的,互不影响
2.const 常量
使用const定义的是常量, 它只能/必须初始化一次, 初始化之后不可修改
const定义值类型数据, 绝对不能改
const定义引用类型数据, 数据内容(对象/数组中的数据)可以改, 引用类型本身(内存地址)不能改
3.字符串模板
如果要把字符串和变量拼接到一起, 有两种写法
var year = 2020, month = 4, day = 20;
// 1, ES5字符串拼接
console.log(year + "年" + month + '月' + day + "日")
// 2, es6模板字符串
console.log(`${year}年${month}月${day}日`)
// 由于模板字符串支持换行, 所以可以用来渲染标签字符串
var htmlStr = `
<ul>
<li>${year}年</li>
<li>${month}月</li>
<li>${day}日</li>
</ul>
console.log(htmlStr)
4.箭头函数
普通的function函数不能转化为箭头函数
只有匿名函数可以转化为箭头函数
在node环境下, this默认指向空对象
setTimeout会把this指向修改为Timeout对象
箭头函数会保留this的上下文指向, 使this指向和setTimeout外部相同
箭头函数的简化
var add = (count)=>{
// return count ++
return ++ count
}
// 如果箭头函数只有一个参数, 可以省略小括号
var add = count => { return ++ count; }
// 如果箭头函数的函数体中只有一句return返回, return和{}可同时省略
var add = count => ++count;
1, 在事件函数或计时器或异步回调函数中可以保留this上下文指向
2, 箭头函数在参数和返回值处,满足条件时,都可以简化
3, 在对象中有一个简化的函数写法
箭头函数的简化
var add = (count)=>{
// return count ++
return ++ count
}
5.数组对象的结构与赋值
// 数组的解构
var array = [1,2,3,4]
// 一般,使用数组中的数据要使用索引取值
// 数组解构允许我们直接使用变量读取数组中的数据
let [count1, count2, count3, count4] = array
// 注意: 由于数组有顺序,在解构时,一般前边变量个数和数组中数据个数要一一对应
// 可以使用 ... 语法解构数组中的一部分数据(靠后的好几条数据)
let [num1, ...num2] = array
// 数组的赋值
var array2 = ["a", "b", "c"]
// 需求: 把array2拼接到array中
// 方法1: 循环array2把array2的每一条数据加入array中
// array2.forEach(item=>{
// array.push(item)
// })
// console.log(array)
// 方法2: 使用concat数组拼接API
// array = array.concat(array2)
// console.log(array)
// 方法3: 使用 ... 数组赋值
array.push(...array2)
// 数组也可以如下拼接
array = [...array, ...array2, 5,6,7]
// 对象的解构
var student = {
name: "张三",
age: 12,
sex: "男",
phone: "110"
}
console.log(student)
// 如果要获取一个对象中的某个字段, 直接用对象打点调用即可
console.log(student.phone)
// 也可以使用对象解构写法获取某个字段值
var { age, sex } = student;
// 注意: 由于对象中的数据没有顺序, 所有对象解构无需把所有字段都写上,可以按需定义变量读取对象中的某一个或几个字段即可, 但必须保证变量名和字段名相同
// 把student这个对象中的数据拼接到people对象中
var people = {
height: 120,
weight: 180,
...student
}
console.log(people)
// 字符串解构, 和数组解构类似
var string = "ABC"
var [a,b,c] = string;
// 总结:
// 数组解构: var [count1,count2,...count3] = array
// 数组赋值1: array.push(...array2)
// 数组赋值2: array = [...array, ...array2, 5,6,7]
// 对象解构: var { age } = student
// 对象赋值: {...student, height:120}
6.参数默认值
function add1(a,b){
console.log(a + b)
}
add1() // NaN : not a number
add1(3) // NaN : not a number
add1(2,3) // 5
// js中调用函数时如果不给指定参数,也可以调用函数, 但可能造成函数内部错误或得不到指定结果
// ES5中函数默认值的写法
function add2(a,b){
a = a || 0; // 设置a的默认值, 如果a有值则保留a的值,如果a没值则取0
b = b || 0;
console.log(a + b)
}
add2() // 0
add2(3) // 3
add2(2,3) // 5
// ES6中函数默认值的写法 (或者叫做: 可选参数)
function add3(a=0,b=0){
console.log(a + b)
}
add3() // 0
add3(3) // 3
add3(2,3) // 5
// 对于特殊的函数,它的参数个数是不固定的, 怎么写?
function add4(){
// arguments是一个函数中隐藏字段,对象类型,里边是所有参数
// console.log(arguments)
let sum = 0;
for(let i = 0; i < arguments.length; i++){
sum += arguments[i]
}
console.log(sum)
}
add4(1)
add4(1,2)
add4(1,2,3)
add4(1,2,3,4)
// 也可以用es6中的扩展参数写法
function add5(...array){
// 扩展参数array是一个数组,可接收传入函数的所有参数
// console.log(array)
let sum = 0;
array.forEach(item=>{
sum += item
})
console.log(sum)
}
add5(1)
add5(1,2)
add5(1,2,3)
add5(1,2,3,4)
// 总结: ES6参数的写法有1, 可选参数(a=0) 2, 扩展参数(...array)
7.数组和对象的遍历
// 数组遍历
var array = [1,2,3,4]
// 1, 使用for循环遍历
for(let i = 0; i < array.length; i++){
console.log(array[i])
}
// 2, 使用数组函数forEach遍历
array.forEach(item=>{
console.log(item)
})
// 3, 使用forof遍历
for (const item of array) {
console.log(item)
}
// 4, forof也可以遍历字符串
for (const item of 'abcd') {
console.log(item)
}
// forof无法遍历对象, 可以用forin遍历对象
let obj = {name: 'zhangsan', age:20, sex:"男"}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
console.log(key, value)
}
}
8.对象的字面量创建
// js中对象有两种创建方式
// 1, 使用new关键字配合构造函数创建
let date = new Date() ;
// let promise = new Promise();
// 2, 使用{}字面量创建
var student = {
name: "张三"
}
// ES6中针对字面量创建方式实现了简化写法, 如下
let name = "张三";
let age = 20;
var zhangsan = {
"name": name,
"age": age,
"sex": "男"
}
// 对象中属性名,在 文件中定义, 可以省略属性名上的引号,如下
zhangsan = {
name: name,
age: age,
sex: "男"
}
// ES6中规定,如果对象中属性名和对应的变量名相同,则可以简化
zhangsan = {
name,
age,
sex: "男"
}
let sex = "男"
// 注意区分对象的简化写法 和 数组的结构
zhangsan = { name, age, sex }
console.log(zhangsan)
zhangsan = [ name, age, sex ]
console.log(zhangsan)
9.类的创建与继承
// 首先复习, ES5中构造函数的创建和继承, 还有原型的设置和继承
// 1, 构造函数的创建
function People(name){
this.name = name;
}
// 3, 在原型中设置对象共有的数据和函数
People.prototype.eat = function(){
console.log(this.name + "不好好听课,在想中午吃什么?")
}
// 构造函数中不建议写函数,建议把函数定义到原型中, 因为原型中的数据不会在对象创建时在对象中备份,但对象都可以调用原型的数据,
// 原型的优点: 避免了内存中的数据重复和内存浪费,提高有性能,
// 原型的缺点: 子构造函数继承父构造函数时,原型的数据不会继承, 需要进行原型的继承
// var array = []
// for(var i = 0; i < 100; i ++){
// array.push(new People(`前男友${i}号`))
// }
// console.log(array[88].eat())
// 2, 构造函数继承, 再创建一个构造函数Student继承于People
function Student(name, stuID){
// 构造函数继承原理: 在子构造函数中调用父构造函数
// 注意: 父构造函数中的this应该指向子构造函数的this
People.call(this, name);
this.stuID = stuID;
}
// 4, 在构造函数继承之后, 需要继承原型, 子构造函数原型继承父构造函数原型, 就形成原型链
Student.prototype = Object.create(People.prototype)
var zhangsan = new Student("张三", 12)
console.log(zhangsan.name, zhangsan.stuID)
zhangsan.eat()
// 总结: js高级语法, 原型和原型链 包括以下4点
// 1, 构造函数的创建
// 2, 构造函数的继承
// 3, 原型的设置
// 4, 原型的继承, 原型继承后形成原型链
// ES6中类的构建与继承
// 1, 类的创建: 通过class关键字创建类
class People1 {
// constructor 是类的构造器,类似于构造函数
constructor(name){
this.name = name
}
eat(){
console.log(this.name + "该吃饭了")
}
}
var xiaoming = new People1("小明")
console.log(xiaoming.name)
xiaoming.eat()
// 2, 类的继承, 使用extends关键字继承父类
class Stutent1 extends People1{
constructor(name, stuID){
// super指的是父类People1的构造器,无需考虑this
super(name)
this.stuID = stuID
}
}
var lisi = new Stutent1("李四", 20)
console.log(lisi.name, lisi.stuID)
lisi.eat()
// 总结: ES6的类构建和继承时,无需考虑原型
10.promise
// 1, Peomise 语法结构 ,
// 一个Promise对象在创建时,会调用一个回调函数, 回调函数有两个参数resolve和reject
// resolve是一个回调函数, 当这个promise对象的状态变为成功状态时调用
// reject 是一个回调函数, 当这个promise对象的状态变为失败状态时调用
// 一个promise对象共有三种状态, 分别是 等待padding 成功resolve 失败reject
// promise默认都是padiing状态, 当异步任务结束会调用resolve或reject这两个函数的其中一个, 把状态改为成功或失败状态, 状态改变之后不能再次更改
let promise = new Promise(function(resolve, reject){
console.log("创建了promise对象")
// Promise对象创建时会直接执行这个回调, 在这里开始一个异步任务, 用计时器模拟
setTimeout(() => {
if(Math.random() > 0.5){
resolve("promise这是请求成功的响应数据")
}else{
reject("promise这是请求失败的错误信息")
}
}, 1000);
})
// 通过promise对象调用then方法拿到成功或失败的结果, then函数的参数有两个分别是成功状态的回调函数和失败状态的回调函数
promise.then(res=>{
console.log(res)
}, err=>{
console.log(err)
})
// 2, peomise内部实现原理, 接下来我们自己实现Promise这个构造函数和then方法
function MyPromise(callback){
// 初始化promise状态.默认为等待状态
this.status = "padding"
// resolve和reject中this指向不是当前promise对象,所以注意指向
let self = this
// console.log("等待", self, this)
// 成功的回调函数
function resolve(data){
// console.log("成功", self, this)
self.status = "resolve" // 在成功回调中把状态改为成功状态
self.value = data ; // 用value属性记录成功时的数据
// 调用then函数中成功的回调, 把数据传过去
self.resFun(data)
}
// 失败的回调函数
function reject(err){
// console.log("失败",self, this)
self.status = "reject" // 在失败回调中把状态改为失败状态
self.value = err; // 用value属性记录失败时的错误信息
// 调用then函数中失败的回调, 把错误信息传过去
self.rejFun(err)
}
// 在构造函数中直接调用回调函数这个参数
callback(resolve, reject)
}
// 把then方法设置到MyPromise的原型对象中
MyPromise.prototype.then = function(res, rej=()=>{}){
if(this.status == "resolve"){
// 如果当前状态是成功状态,则调用成功回调, 把成功数据传入参数
res(this.value)
}
else if(this.status == "reject"){
// 如果当前状态是失败状态,则调用失败回调, 把失败信息传入参数
rej(this.value)
}
else{
// 如果当前状态是等待状态, 成功失败的回调都不能调用, 所以此时把成功或失败的回调记录下来, 等到状态改变时再调用, 使用promise对象的属性来记录
this.resFun = res; // 使用resFun属性记录成功回调
this.rejFun = rej; // 使用rejFun属性记录失败回调
}
}
// 创建我自己的peomise对象
var myPromise = new MyPromise(function(resolve, reject){
console.log("我自己的promise创建了")
setTimeout(() => {
if(Math.random() > 0.5){
// console.log(3333, this)
resolve("mypromise这是请求成功的响应数据")
}else{
reject("mypromise这是请求失败的错误信息")
}
}, 1000);
})
myPromise.then(data=>{
console.log(data)
},err=>{
console.log(err)
})
// 以上代码仅模拟实现了Promise底层原理中的构造函数和then函数的实现, 并不支持链式调用
// 3, promise的用法
let fs = require("fs")
// 第一个需求: 使用fs模块异步读取一个文件,在异步任务回调函数之外如何拿到读取的结果
// 思路1: 使用全局变量记录数据
var info = null
fs.readFile("./1, es6简介.txt", function(err, data){
// console.log(data.toString())
info = data
})
// 在这里拿不到fs读取的数据
console.log(info) // null
// 思路2: 使用函数的返回值也拿不到数据
function get(){
fs.readFile("./1, es6简介.txt", function(err, data){
console.log(1)
return data
})
}
var data = get()
console.log(2,data) // undefined
// 思路3: 使用回调函数获取异步任务结果
function myGet(callback){
fs.readFile("./1, es6简介.txt", function(err, data){
callback(data)
})
}
myGet(res=>{
console.log(res)
})
// 总结: 以上三种思路, 前两种均拿不到数据, 第三种可以拿到数据, 事实上第三种写法就是简化版的Promise, 所以primise用于在异步函数回调之外,拿到异步指向的结果数据
// 第二个需求: 按顺序读取data目录下的a,b,c,d四个文件,把四句诗拼到一起
// 写法1, 使用同步函数读取
var data1 = fs.readFileSync("./data/a.txt").toString();
var data2 = fs.readFileSync("./data/b.txt").toString();
var data3 = fs.readFileSync("./data/c.txt").toString();
var data4 = fs.readFileSync("./data/d.txt").toString();
console.log(data1 + data2 + data3 + data4)
// 弊端: 同步读取会造成线程阻塞, 会造成界面卡顿
// 写法2, 使用异步函数读取
fs.readFile("./data/a.txt", (err,data1)=>{
fs.readFile("./data/b.txt", (err,data2)=>{
fs.readFile("./data/c.txt", (err,data3)=>{
fs.readFile("./data/d.txt", (err,data4)=>{
console.log(data1 + data2 + data3 + data4)
})
})
})
})
// 弊端: 会造成多层嵌套, 结构复杂
// 写法3, 使用promise解决多异步任务多层嵌套问题
new Promise(function(resolve){
fs.readFile("./data/a.txt", (err,data1)=>{
resolve(data1)
})
}).then(data1=>{
return new Promise(function(resolve){
fs.readFile("./data/b.txt", (err,data2)=>{
resolve(data1 + data2)
})
})
}).then(data12=>{
return new Promise(function(resolve){
fs.readFile("./data/c.txt", (err,data3)=>{
resolve(data12 + data3)
})
})
}).then(data123=>{
return new Promise(function(resolve){
fs.readFile("./data/d.txt", (err,data4)=>{
resolve(data123 + data4)
})
})
}).then(data1234=>{
console.log(data1234)
})
// 弊端: 以上4个异步任务是按照时间顺序执行的, 上一个执行完才执行下一个, 效率太低
// 例如, 四个异步任务各自耗费的时间是3ms, 6ms, 4ms, 2ms, 花费的总时间是 15ms
// 写法4: 使用promise合并解决多异步任务并发执行问题, 提高执行效率
var p1 = new Promise(function(resovle){
fs.readFile("./data/a.txt", (err,data1)=>{ resovle(data1) })
})
var p2 = new Promise(function(resovle){
fs.readFile("./data/b.txt", (err,data2)=>{ resovle(data2) })
})
var p3 = new Promise(function(resovle){
fs.readFile("./data/c.txt", (err,data3)=>{ resovle(data3) })
})
var p4 = new Promise(function(resovle){
fs.readFile("./data/d.txt", (err,data4)=>{ resovle(data4) })
})
// 以上四个promise对象创建后, 四个异步任务立即开始执行, 也就是并发执行
// 使用Promise类方法all把多个promise对象合并成一个,只有当四个promise的状态都改成成功状态时,合并的promise才会变成成功状态, then函数回调参数是数组,数据对应合并时的顺序
Promise.all([p1,p2,p3,p4]).then(([data1,data2,data3,data4])=>{
console.log(data1+data2+data3+data4)
})
// 优点: 既解决了多异步任务嵌套问题,有解决了多异步任务并发执行的顺序问题
// 如: 四个异步任务各自耗费的时间是3ms, 6ms, 4ms, 2ms, 花费的总时间是 6ms
// Promise.all() // 所有任务结束才结束
// Promise.race() // 只要有一个任务结束,立即结束