004.从零开始复习前端--JavaScript
004.从零开始复习前端–JavaScript
JavaScript内容非常多,但是常用的就是一些字符串和数组的方法已经Dom和BOM的事件
只有多用多看,没有速通的方法
复习时可以选择性的看一些书籍
- JavaScript半知半解
- JavaScript基础教程
- JavaScript权威指南
- JavaScript高级程序设计
- ES6标准入门
- 你不知道的JavaScript
- 等等
常规内容
1. 基础部分
变量:用来存放数据,保存的数据可以修改
常量:用来存放数据,保存的数据不可修改
变量和常量的本质,无论是变量还是常量,其本身都是数据,也需要在内存中占用内存空间,保存在内存的栈结构分区中
ECMAScript标准定义了8种数据类型
1. 7种原始数据类型:String,Numbr,Boolean,Undefined,null,Symbol,BigInt- Object
- 引用数据类型
Object,Function,Array,Date,RegExp
typeof 运算符(判断数据的类型)
- 作用:用来获取当前变量中存储的数据的类型
- typeof的返回值有多少个
- String,Number,Boolean,Undefined,Object,Function
- 特殊的有两个:null和array的结果都是Object
instanceOf 运算符(原生JS判断实例的类型)
- 作用:用来判断当前实例对象是不是某种数据类型
基本数据类型和引用数据类型在内存中,内存空间是如何存储数据的
- 除 Object 以外的所有类型都是不可变的(值本身无法被改变),JavaScript 中字符串是不可变的(译注:如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为“原始值”。
基本数据类型的值在栈空间中存储,如果修改了数据,则是把原来的值直接干掉,重新存放新的值
引用数据类型的对象在堆空间中存储,该空间的地址在栈空间中存储,如果修改栈空间存储的地址,则指向发生变化,也叫引用发生了变化,此时是在堆空间中重新指向了一个新的内存空间(存储了一个新的对象)
基本类型之间的值如何传递? 传递的是数值
引用类型之间的值如何传递? 传递的是引用(地址)

内存:用于暂时存放CPU中的运算数据以及与硬盘等外部存储器交换的数据
- 计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来,内存的运行也决定了计算机的稳定运行。
内存通电后产生的存储空间(临时的)
产生和死亡:内存条(集成电路板)—>通电—>产生一定的容量存储空间—>存储各种数据—>断电—>内存空间全部消失
内存的空间是临时的,而硬盘的空间是持久的
内存包含2个数据:内存存储的数据(一般数据/地址数据)和内存地址值数据
内存分类:
- 栈结构: 全局变量,局部变量(空间较小) - 堆结构(列表结构): 对象 (空间较大)
2. 对象
具有一定的属性或者方法,特指的某个事物
看得见或者是摸得到,特指的某个东西
识别对象,抽象出对象有什么特征和行为
抽象出当前的对象属于什么类别
以上都是抽象出对象和类别的过程,抽象性
抽象的过程要转变成代码的方式,写代码:先有类别(构造函数),创建对象并设置对象的属性及方法的初始值,对象的初始化过程,最终对象调用相关方法,操作相关属性及某些行为
特征就是属性,行为就是方法,类别就是构造函数,创建对象就是实例化的过程(初始化属性及方法的中)
工厂模式:自定义函数实现对象的创建
通过构造函数实现对象的创建
字面量的方式创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 1.通过字面量创建对象
var obj ={}
// 2.通过工厂模式创建对象
function createObject(name,age){
var obj = new Object()
obj.name = name
obj.age = age
return obj
}
var obj1 =createObject('小明',10)
var obj2 =createObject('小红',20)
console.log(obj1,obj2)
// 3.构造函数的方式创建对象
function Person(name,gender){
this.name = name
this.gender = gender
this.sayHi=function(){
console.log('您好,我是:'+this.name)
}
}
var per = new Person('小明','男')
per.sayHi()
console.log(per)通过ES6中的class来创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 类的方式
class Student{
constructor(name,age,gender){
this.name = name
this.age =age
this.gender = gender
}
// 在原型上
sayHi(){
console.log(`您好,我是${this.name},几年${this.age}岁了,是${this.gender}生`)
}
// 在实例上
eat=()=>{
console.log('吃东西啊')
}
}单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function createObj() {
var instance = null
return function (name) {
if (!instance) {
instance = new Object()
instance.name =name
}
return instance
}
}
var getObj = createObj()
var obj1 = getObj('小明')
var obj2 = getObj('小红')
console.log(obj1,obj2)
console.log(obj1===obj2)1
2
3
4
51. 对象.属性名字
2. 对象['属性名字']
3. 什么时候使用对象[属性名字]的写法
- 不确定属性名字是什么(属性名字是变量)
- 属性名字不太规范的时候1
2
3
4
5
6js是弱类型语言,声明变量都用var
js是脚本语言 直接执行
js是解释性语言 直接解释
js是动态类型语言 变量在执行的时候才知道具体的类型,对象没有这个属性,点了,就有了
js是单线程语言 执行的时候安装一定的顺序,之前的代码执行完毕后,后面才执行
js是基于对象的语言,最终所有的对象都指向了object
3. 原型
原型就是对象,JS中原型一共有两个,一个是prototype,一个是**
__proto__属性**1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20- prototype: 浏览器的标准属性,程序员使用的,显示原型,存在于函数中
- __proto__: 浏览器的非标准属性,浏览器使用的,隐式原型,存在于实例对象中
- 函数中有prototype,实例对象中有__proto__
- 实例对象也是对象,里面就有__proto__
- 实例的__proto__与对应函数的prototype都指向原型对象
- 无论是构造函数还是普通函数,或者是方法,只要是函数,内部就有prototype
2. 原型的作用之一:共享数据,节省内存空间
1.实例对象一般都是通过构造函数进行创建的,实例化对象的时候做的四件事:
- var per = new Person('卡卡西',20)
1) 申请一块空闲的空间,用来存储当前的实例对象
2) 设置this为当前的实例对象(修改this的指向)
3) 初始化实例对象中的属性和方法的值
4) 把this作为当前对象进行返回
2. 在构造函数中定义的属性及方法,仅仅是编写代码进行定义而已,而实际上里面定义的属性及方法是属于每个实例对象的,所以,创建多个对象,就会开辟多个空间,每个空间中的每个对象都有自己的属性及方法,大量创建对象,对象的方法都不是同一个方法(方法也是函数,函数代码也占用空间),为了节省内存空间,那么可以使用原型的方式,实现数据共享,节省内存空间
3. 原型的作用之二:实现JS中的继承
1). 通过改变原型指向实现继承
2). 借用构造函数显示继承
3). 组合继承
4). 拷贝继承:浅拷贝和深拷贝(递归后再说)原型对象上有一个
constructor属性指向对应的构造函数function Fn () {}
const fn = new Fn()

: 区别执行函数定义与执行函数
执行函数定义: 也就是去创建函数对象, 只是有可能被JS引擎提升预处理执行
执行函数: 执行函数体中所有语句
先有函数定义的执行,才有执行函数
常见的回调
- DOM事件的回调
- 定时器中的回调
- ajax回调函数(后面讲)
- 生命周期的回调(后面讲)
- 立即执行函数(Immediately-Invokey Function Expression) 匿名函数自调用
- 作用:隐藏内部实现,减少命名空间的污染
说说函数对象上的prototype属性?(prototype什么时候出现,执行函数定义的时候)
执行函数定义定义(有可能被提升执行)创建函数对象
给函数对象添加prototype属性, 属性值为空的Object实例对象, 也就是原型对象
给原型对象添加constructor属性, 值为函数
伪代码:
1
2
3
4
5
6
7
8
9// 给函数对象添加prototype属性, 属性值为空的Object实例对象, 也就是原型对象
function Fn(){}
console.dir(Fn)
var obj ={}
console.log(obj.__proto__===Object.prototype)
console.log(Fn.prototype.__proto__===Object.prototype)
// 伪代码
this.prototype = {} // this就是函数对象
this.prototype.constructor = Fn
说说实例对象上的**
__proto__属性**?(_proto-什么时候出现,实例化对象的时候) JS引擎在创建实例对象时内部自动执行时, 会自动给实例对象添加
__proto__属性, 值为构造函数的 prototype属性的值1
this.__proto__ = Fn.prototype // this是实例对象
原型链(实际上是隐式原型链,言外之意就是和显示原型没毛关系,显示原型产生实例的一瞬间起的作用)
从对象的
__proto__开始, 连接的所有对象, 就是我们常说的原型链, 也可称为隐式原型链查找对象属性简单说: 先在自身上查找, 找不到就沿着原型链查找,如果还找不到返回undefined

查找对象上属性的基本流程
先在对象自身上查找, 如果有, 直接返回
如果没有, 根据
__proto__在原型对象上查找, 如果有, 直接返回如果没有根据原型对象的
__proto__在原型对象的原型对象上查找, 一直查找到Object原型对象为止如果找到了返回, 如果查找不到由于它的
__proto__为null, 只能返回undefined查找对象属性时候,会不会读函数的prototype?(会还是不会)
1
2
3
4
5
6
7
8
9
10
11
12// 简单的原型链
function F1(){}
F1.prototype.number =100
function F2(){}
F2.prototype=new F1()
F2.prototype.number =200
function F3(){}
F3.prototype=new F2()
F3.prototype.number =300
var f3 = new F3()
console.log(f3.number)
console.dir(f3)
表达式a.b的解析流程
- 查看a变量: 作用域链查看
- 不存在 ==> 报错
- 存在, 得到它的值
- 基本类型(var a=null/a=undefined,此时a.b报错)
- null/undefined ===> 报错
- number/string/boolean ==> 创建一个包含此值的包装类型对象, 进入下面流程
- 地址值 ===> 解析.b ===> 查找b属性
- 先在自身找, 找到了返回, 如果没有找到
- 原型链查找
- 找到了返回
- 没找到, 返回undefined
- 基本类型(var a=null/a=undefined,此时a.b报错)
- 查看a变量: 作用域链查看
instanceOf
作用: 判断一个任意类型对象的具体类型
如何判断?
- 对于 A instanceof B
- A是实例对象, B是构造函数
- 如果B的prototype属性所指向的原型对象是A实例对象的原型链接上的某个对象, 返回true, 否则返回false
原型与原型链结构图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Foo() { }
const f1 = new Foo()
const f2 = new Foo()
const o1 = new Object()
const o2 = {}
// 下面的结果
console.log(Foo instanceof Object)
console.log(Foo instanceof Function)
console.log(Object instanceof Object)
console.log(Function instanceof Function)
console.log(Function instanceof Object)
console.log(Object instanceof Foo)
console.log(f1 instanceof Function)
console.log(f1 instanceof Object)
1
2
3
4
5
61. 对象中有__proto__,函数中有prototype
- 实例对象中__proto__指向的是当前实例对象对应的构造函数中的prototype
- 而每个prototype都是一个对象,所以,内部必然有__proto__,普通函数中的prototype的__proto__指向的是Object的prototype
- 每个函数是Function的实例对象,所以,只要是函数,那么函数对象中__proto__指向的都是Function的prototype,那么这个prototype中的__proto__指向的仍然是Object的prototype
- 但是,Object这个构造函数也是函数,所以,Object的__proto__指向的是Function的prototype
- Function这个构造函数也是对象,所以里面的__proto__指向的是Function的prototype
继承(改变原型指向/借用构造函数/组合/拷贝)
- 方式一: 基于构造函数的继承: 原型链 + 借用构造函数的组合式继承
- 借用父类型构造函数: Person.call(this, name, age)
- 让子类的原型为父类的实例: Student.prototype = new Person()
- 让子类型原型的构造器为子类型: Student.prototype.constructor = Student
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// 通过原型实现继承
function Person(name,age,gender){
this.name = name
this.age =age
this.gender = gender
}
Person.prototype.sayHi=function(){
console.log('您好')
}
Person.prototype.eat=function(){
console.log('好吃')
}
function Student(name,age,gender,score){
// 借用构造函数实现属性的继承
Person.call(this,name,age,gender)
this.score = score
}
// 原型实现方法的继承
Student.prototype=new Person()
Student.prototype.constructor = Student
// 重写方法
Student.prototype.eat=function(){
console.log('学生吃')
}
var stu = new Student('小明',20,'男',100)
stu.sayHi()
stu.eat()
console.log(stu)- 方式二: 基于class/类的继承
- 子类 extends 父类: class Teacher extends Person2
- 子类构造器中调用父类的构造: super(name, age)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37// ES6的方式实现继承
class Person{
constructor(name,age){
this.name = name
this.age = age
}
// 原型上的方法
sayHi(){
console.log('您好')
}
// 赋值的写法就是给实例添加属性或者方法--------
// 实例属性
sex = '男'
// 实例上的方法 run =function(){}
eat=()=>{
console.log('好吃啊')
}
// 静态属性
static gender='男'
}
var per = new Person('小明明',30)
console.log(per)
console.dir(Person)
class Student extends Person{
constructor(name,age,gender){
super(name,age)
this.gender = gender
}
// 重写父类中的方法
sayHi(){
console.log('我很好')
}
}
var stu = new Student('小明',20,'男')
console.log(stu)- 方式一: 基于构造函数的继承: 原型链 + 借用构造函数的组合式继承
4. 预解析
js引擎在js代码正式执行之前会做一些预解析的操作
先找关键字var,function
找到var以后将var后面的变量提前声明,但是不赋值
找到function以后将function后面的函数提前声明,但是不赋值,也就是说函数在解析之前已经定义完毕了
变量的提升
- 浏览器在解析js代码之前,先把变量的声明提升
6. 函数的提升
- 浏览器在解析js代码之前,先把函数的声明提升
- 函数提升 ==> 变量提升, 同名的变量忽略
1
2
3
4
5
6
7
// 先提升的谁?
a()
var a = 100
function a(){
console.log('函数')
}
console.log(a)
7. 注意:f2() var f2=function(){}; 报错:因为f2是undefined
8. 预解析:全局预解析和局部预解析
5. 执行上下文
执行上下文(动态的):就是一个代码的执行环境(全局执行上下文和函数执行上下文,eval函数执行上下文)
1
2
3
4
5
6
7
8
9
10
11
12
131. 执行上下文概念:代表了代码执行的环境,包含:执行环境,变量对象,this,作用域链
2. 流程:
- js引擎在js代码正式执行前会先创建一个执行环境
- 进入该环境以后会创建一个变量对象,该对象用于收集:变量,函数,函数的参数,this
- 找关键字var,function
- 确认this
- 创建作用域链
3. 在全局代码执行前,js引擎就会创建一个栈来存储管理所有的执行上下文
4. 在全局执行上下文(window)确定后,将其添加到栈中(压栈)
5. 在函数执行上下文创建后,将其添加到栈中(压栈)
6. 在当前函数执行完毕后,将栈顶的对象移除(出栈)
7. 当所有的代码执行完毕后,栈中只剩下window
8. 重点:执行上下文是动态创建的,尤其是针对函数,每调用一次函数都会创建一次执行上下文总结执行上下文: 当代码要执行,但是没有执行,或者将要执行,在预解析之后,此时出现了全局执行上下文环境(全局执行上下文),创建了一个变量对象,用来收集var , function ,函数参数,确定this的指向,默认全局执行上下文是确定了this是window,这个变量对象会被压入到栈中(全局执行上下文的变量对象在栈中的最下面),如果出现了函数调用,此时出现了局部执行上下文环境(局部执行上下文),再次创建一个变量对象,用来收集函数参数,var ,function,改变this的指向,这个变量对象会被再次压入栈中,在全局执行上下文的变量对象的上面,如果当前函数调用完毕,此时出栈(把局部上下文的变量对象干掉),依次弹出变量对象,就结束了
6. 作用域
概念:变量的使用范围,静态的(编写代码的时候就已经确定了)
全局作用域和局部作用域
全局作用域:函数外部变量的使用范围
局部作用域:函数内变量的使用范围(一个函数就是一个作用域)
块级作用域(ES6新增): const / let
作用:隔离变量,不同的作用域下同名的变量不会冲突
变量分为:全局变量(非函数内部定义的变量)和局部变量(函数内部定义的变量)
多个嵌套的作用域形成的由内向外的结构, 用于查找变量

作用域于执行上下文
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
221. 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时已经确定了,而不是函数调用时
2. 全局执行上下文环境在全局作用域确定之后,js代码马上执行之前创建
3. 函数执行上下文环境是在调用函数时,函数体代码执行之前创建
4. 作用域是静态的,只要函数定义好了就一直存在,且不会再变化
5. 执行上下文是动态的,调用函数时创建,函数调用结束时上下文环境就会释放
6. 上下文环境(对象)是从属于所在的作用域
7. 全局上下文环境-->全局作用域
8. 作用域链
1. 用来决定代码执行的范围,变量的作用范围
2. 作用域是代码定义的时候决定的
3. 作用域链是如何产生的
- 函数在定义的时候自动添加一个属性'[[Scopes]]'该属性保存的是其上级作用域链
- 当函数执行的时候,进入执行上下文环境,将创建的变量对象添加到'[[Scopes]]'数组的第一个位置,形成新的数组
4. 查找变量的规则
- 先在当前作用域的变量对象中查找,找到则使用
- 如果没有则沿着作用域链的数组去上级作用域中的变量对象中查找
- 找到就返回对应的值,如果没有继续向上查找,知道找到最后一个变量对象(全局的变量对象),如果没有则报错
变量的查找:
代码编写的时候确定了当前全局作用域及局部作用域
在代码马上执行,还没执行,执行上下文环境就出现了,函数调用完毕后,局部执行上下文没了,整个代码结束,全局的执行上下文环境也没了
全局作用域---->预解析--->全局执行上下文环境--->全局的变量对象{var ,function ,函数参数,this的指向}----->函数定义---->局部作用域---->出现了函数调用----->局部局解析--->局部的执行上下文环境---->局部的变量对象{var ,function ,函数参数,this的指向}
7. 闭包
- 理解:
- 当嵌套的内部函数引用了外部函数的变量时就产生了闭包(执行外部函数,不一定就会产生闭包)
- 什么时候产生的闭包? 执行内部函数定义(创建内部函数对象)后
- 通过chrome工具得知: 闭包本质是内部函数中的一个对象(非js的容器), 这个容器中包含引用的变量
1. 闭包内部包含了被引用变量(函数)的对象
2. 说白了,闭包其实就是一种引用关系,引用关系存在于内部函数中,引用的是外部函数的变量的对象(深入理解)
2. 函数内部本身是个局部作用域,如果出现闭包,延长了局部作用域的生命周期
3. 闭包延长局部变量的生命周期后,如果不及时释放会出现内存泄漏
4. 闭包作用:
- 闭包的作用:延长外部函数变量对象的生命周期
- 让函数外部可以操作(读写)函数内部的数据(变量/函数)
5. 闭包什么产生的?
- 闭包在嵌套内部函数定义执行完成时就产生了(不是调用)
6. 闭包什么时候挂的?
- 在嵌套的内部函数成为垃圾对象的时候
7. 闭包的优点/缺点及如何清除闭包
- 优点/缺点: 延长外部函数变量对象的生命周期(不及时清除容易造成内存溢出、泄漏)
- 释放闭包: 让内部函数对象成为垃圾对象, 断开指向它的所有引用
注意问题:函数中定义函数,内部函数没有调用,则不会出现在局部执行上下文的变量对象中
1 | function fn1() { |
闭包什么时候产生的? 执行函数定义产生引用变量的的时候产生闭包

下面的没有产生闭包(没有产生内部函数引用变量,函数引用变量是执行函数定义才能有该应用变量,fn2就是内部引用变量)

闭包的应用:
举删除删除列表中的的某个商品的例子(带确定框)

内存溢出和内存泄漏
1. 内存泄露 :是指程序在申请内存后,无法释放已申请的内存空间就造成了内存泄漏,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
2. 内存溢出: 指程序申请内存时,没有足够的内存供申请者使用,或者说,一个杯子里你非要装一桶的水,那么结果就是内存不够用,即所谓的内存溢出,简单来说就是自己所需要使用的空间比我们拥有的内存大内存不够使用所造成的内存溢出。
this在不同场景下的取值?
this学习的2个目标:
- 能判断出函数中的this是谁
- 能控制函数的this是需要的特定对象
常规情况下, 函数中的this取决于执行函数的方式
- fn(): 直接调用 ==> this是? window
- new fn(): new调用 ==> this是? 新建的对象
- obj.fn(): 通过对象调用 ==> this是? obj obj.fn()()
- fn.call/apply(obj): 通过函数对象的call/apply来调用 ==> this是? obj (调用fn这个函数,指定或绑定了内部的this为obj)
特殊情况:
- bind(obj)返回的函数 ==> this是? obj
- 箭头函数 ==> this是? 外部作用域的this
- 回调函数
- 定时器/ajax/promise/数组遍历相关方法回调 ==> this是? window(非严格模式,否则都是undefined)
- vue控制的回调函数 ==> this是? 组件的实例
- React控制的生命周期回调, 事件监听回调 ==> this是? 组件对象 / undefined
如何控制函数的this?
利用函数的bind()
利用箭头函数
也可以用外部保存了this的变量
1 | var m = 1 |
1 | var obj2= { |
9. 同步/异步
进程: 程序的一次执行,它占有一片独有的内存空间
线程: CPU的基本调度单位,是程序执行的一个完整流程
1. 一个进程中一般至少有一个运行的线程:主线程
2. 一个进程中也可以同时运行多个线程,我们会说程序是多线程的
3. 一个进程中的数据可以供其多个线程直接共享
4. 多个进行质检的数据是不能直接共享的
浏览器运行是单进程还是多进程
1. 有的是单进程的
- firefox
- 老版本IE
2. 有的是多进程
- chrome
- 新版IE
- 新版火狐
3. 如何查看浏览器是否是多进程运行的
- 任务管理器----->进程
- 都是多线程运行的
浏览器内核
1 | 1. 支持浏览器运行的核心的程序 |
js是单线程的
1 | 1. 如何证明JS执行是单线程的 |
JS是单线程编程语言, 只能同时做一件事(普通人->单线程,影分身–>多线程)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 同步回调
[1, 2, 3].forEach(item => {
console.log(item)
})
console.log('forEach()之后')
new Promise((resolve, reject) => { // excutor 执行器函数,作用:执行异步代码
console.log('执行excutor')
// 执行异步任务
})
console.log('new Promise()之后')
// 异步回调,宏任务
setTimeout(() => {
console.log('执行timout回调')
}, 0);
console.log('setTimeout()之后')
// 微任务
Promise.resolve(1).then(() => {
console.log('promise成功的回调')
})
console.log('.then之后')js引擎是在一个线程(可以称为JS线程)上解析执行js代码的(web worker除外), 无论是同步代码还是异步代码
界面第一次渲染: 初始化同步代码 ==> 所有的微任务==> 渲染界面==> 执行第一个宏任务
==> 所有的微任务==> 渲染界面==> 执行第一个宏任务
- 界面更新渲染: 所有的微任务==> 渲染界面==> 执行第一个宏任务
总结: 清空微队列中所有微任务 ==> 渲染界(UI线程) ==> 执行宏队列中的第一个宏任务
- 界面更新渲染: 所有的微任务==> 渲染界面==> 执行第一个宏任务
浏览器在另一个线程(GUI渲染线程)进行页面渲染操作,
GUI渲染线程与js线程是互斥(不会同时执行), 因为 JS 可以修改 DOM 结构
遇到需要等待 (网络请求, 定时任务) 不能卡住,需要异步
回调callback函数
浏览器多线程演示
1 | <ul> |
使用Promise解决回调地狱问题(可阅读性很差,仍然要用回调,但是没有嵌套了)
Promise相对纯回调形式, 指定回调函数的时机更灵活(可以在发送请求后或请求结束后)



10. 事件轮询机制event loop1(异步实现的原理统称:事件循环(轮询机制))
- JS是通过事件循环机制来实现JS的单线程异步
- js确实是单线程的,也确实可以异步执行函数,之所以能做到(浏览器中有辅助它单线程异步执行的分线程管理模块)
- js是单线程运行的
- 异步要基于回调来实现
- event loop 就是异步回调的实现原理
.png)
事件循环机制的2个重要部分
在分线程执行的管理模块: 定时器/ajax/dom事件
保存待执行的回调函数的事件队列(Event queue)/任务队列(Task queue)
事件轮询的执行过程:首先执行初始化代码,就是先执行同步代码,执行同步代码的时候,有可能启动定时器,有可能发送ajax请求,有可能绑定事件监听,执行这些代码的时候,会把回调函数交给对应的管理模块进行管理,而对应的管理模块在分线程执行,不会影响js执行,js会继续向下执行,比如启动一个setTimeout定时器(有个定时器的管理模块),假设1秒后执行,就会在1秒后把回调放在待执行的回调队列里,此时js有可能还在执行初始化代码,只有初始化代码全部的执行完毕后,一个一个,依次的取出执行
宏任务与微任务(任务就是回调,任务的本质就是回调)
- 宏任务: setTimeout, setInterval, Ajax, DOM事件监听
- 微任务: Promise, async/await, mutationobserver(H5)(用来监视标签的变化的,标签只要有任何的变化,对应的回调就会微任务异步执行)
宏队列与微队列(队列本质就是数组)
- 宏队列: 用来保存n个宏任务的队列容器
- 微队列: 用来保存n个微任务的队列容器
event loop2
- js-web中的dom事件回调, 定时器回调与ajax回调异步执行, 都是基于event loop ===> 宏任务
- H5新的MutationObserver也基于event loop ==> 微任务
- promise成功和失败的回调也是异步执行的, 也是基于event loop ==> 微任务
- 执行顺序:
- 第一步: 先执行script下的所有同步代码
- 第二步: 再依次取出微列中的所有微任务执行
- 第三步: 再取出宏队列中第一个宏任务执行
- 再循环第二步与第三步
.png)

1 | 定时器注意: |
11. Promise
ES6推出的新的更好的异步编程解决方案(相对于纯回调的方式)
- 可以异步操作启动后或完成后, 再指定回调函数得到异步结果数据
- 解决嵌套回调的回调地狱问题 —promise链式调用
promise对象有3种状态
- pending
- resolved/fulfilled
- rejected
promise状态的2种变化
- pending –> resolved
- pending –> rejected
- 注意: 变化是不可逆
4..promise的then()的理解
then()总是返回一个新的promise
新promise的结果状态由then指定的回调函数执行的结果决定
- 抛出错误
- 返回失败的promise
- 返回成功的promise
- 返回其它任何值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37/*
.then()返回的promise的结果状态由什么决定?
简单说: 由.then()指定并执行的回调函数的执行结果决定
详细说:
什么情况导致promise是失败的?
执行出错(抛异常了), 失败的reason就是抛出的错误
返回一个失败的promise, 失败的reason就是返回的promise的reason
什么情况导致promise是成功的?
返回一个成功的promise, 成功的value就是返回promise的value
返回一个非promise任务值, 也可以不返回(返回undefined), 成功的value就是返回值
*/
new Promise((resolve, reject) => {
// 成功
// resolve(1)
// 失败
reject(2)
}).then(
value => {
console.log('onResolved1', value)
}, // onResolved
reason => {
console.log('onRejected1', reason)
// 抛出错误
// throw 100
// 返回一个失败的promise
// return Promise.reject(200)
// 返回一个成功的promise
// return Promise.resolve(300)
// 返回一个其他的值
// return 1000
Promise.resolve(2000)
}
).then(value=>{
console.log('onResolved1', value)
},reason=>{
console.log('onRejected2', reason)
})
1 |
|
6.async/await与promise的关系
async/await是消灭异步回调的终极武器
作用: 简化promise对象的使用, 不用再使用then/catch来指定回调函数
但和Promise并不互斥
反而, 两者相辅相成
执行async函数, 返回promise对象
await相当于promise的then
try…catch可捕获异常, 相当于promise的catch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35// function ajax(url) {
// return axios.get(url)
// }
// async function getProduct() {
// try {
// const response = await ajax('/product2.json')
// return response.data
// } catch (error) {
// console.log('请求出错', error.message)
// // throw error
// return Promise.reject(error)
// }
// }
// // 等同于上面async&await的函数
// function getProduct() {
// return ajax('/product.json')
// .then(response => {
// return response.data
// })
// .catch(error => {
// console.log('请求出错', error.message)
// // throw error
// return Promise.reject(error)
// })
// }
// function test() {
// getProduct().then(value => {
// console.log(value)
// }).catch(error => {
// console.log('error', error.message)
// })
// }
// test()
1
2
3
4
##12.ES6
ECMA组织:欧洲计算机制造商协会,制定和发布的脚本语言规范
ECMAscript是基于Netscape javaScript的一种标准脚本语言。
JavaScript包含3个部分:
1)ECMAScript核心(JS标准语法)
2)浏览器端的扩展
BOM(浏览器对象模型)
DOM(文档对象模型)
3)服务器端的扩展
Node.jsES的几个重要版本
ES5:09年发布
ES6:15年发布,也叫ECMA2015
ES7:16年发布,也叫ECMA2016ES5给Object扩展了一些静态方法,常用的2个:
Object.create(prototype,[descriptors]) 创建对象并继承
作用: 以指定对象为原型创建新的对象
为新的对象指定新的属性,并对属性进行描述
value: 指定的值
weitable:标识当前属性值是否是可修改的,默认为false
configurable:标识当前属性是否可以被删除,默认是false
enumberable: 标识当前属性是否能用for in 枚举,默认为false
for-in(性能问题)不仅枚举自身属性,也可以枚举原型对象上的属性,一般配合对象.hasOwnProperty()方法
1
2
3
4
5
6
7
8
9
10var person = {
name: '小明',
sayHi: function () {
console.log('您好')
}
}
// 新创建的stu对象和person对象是继承关系
var stu = Object.create(person) // stu.__proto__---->person
console.log(stu.name)
stu.sayHi()
Objectt.defineProperties(object, descriptors)
作用:为指定对象定义扩展多个属性
get: 用来获取当前属性值的回调函数
set: 修改当前属性值的触发的回调函数,并且实参为修改后的值
存储器属性: settter,getter一个用来存值,一个用来取值
- 对象本身有两个方法
- get propertyName(){} 用来得到当前属性值的回调函数
- set propertyName(){} 用来监视当前属性值变化的回调函
Array的方法扩展
1
2
3
4
51. Array.prototype.indexOf(value):得到数组中的某个数据的第一个下标,用来找数据的
2. Array.prototype.lastIndexOf(value):得到数组中某个数据的最后一个小标
3. Array.prototype.forEach(function(item,index){}):遍历数组
4. Array.prototype.map(function(item,index){}):遍历数组,返回新数组
5. Array.prototype.filter(function(item,index){}):遍历数组,过滤后的数组
ES6+
箭头函数
- 作用: 定义匿名函数
- 基本语法: ()=>{console.log(‘xxxx’)}
- 一个参数: msg=> msg+2
- 多个参数: (m,n)=>m+n
- 函数体不用大括号:默认有返回结果
- 函数体如果有多个语句,需要使用{}包裹,如果内部有需要返回的值,则手动return
- 使用场景: 多用来定义回调函数
- 箭头函数的特定:
- 简洁
- 箭头函数没有自己的this,箭头函数的this不是调用的时候决定的,而是在定义的时候处在自己对象就是它的this
- 扩展理解: 箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有则this是window
7.三点运算符: 拆包和打包
8.形参默认值: 简化函数的形参语法
9.Symbol:
1. ES5中对象的属性名都是字符串,容易造承重名,污染环境
2. 概念:ES6中的添加了一种原始数据类型Symbol(已有的原始数据类型:String,Number,Boolean,null,undefined,对象)
3. 特点:
- Symbol 属性对应的值是唯一的,解决命名冲突问题
- Symbol 值不能与其他数据进行计算,包括同字符串拼串
- for in , for of 遍历时不会遍历symbol 属性
4. 使用:
- 调用Symbol 函数得到symbol 值
- let symbol =Symbol()
- let obj ={}
- obj[symbol]='hello';
5. 传参标识
- let symbol = Symbol('one')
- let symbol2 = Symbol('two')
- console.log(symbol) // Symbol('one')
- console.log(symbol2) // Symbol('two')
6. 定义常量标识
- 可以定义常量,就是标识
- const person_key = Symbol('person_key')
- console.log(person_key)
7. 内置Symbol值
- 除了定义自己使用的Symbol值以外,ES6还提供了11个内置的Symbol值,指向语言内部使用的方法
- Symbol.iterator
- 对象的Symbol.iterator属性,指向该对象的默认遍历器方法(很快就讲了)
10.iterator 是一种接口机制,为各种不同的数据结构提供统一的访问机制
1. 作用:
- 为各种数据结构,提供一个统一的,简便的访问接口
- 使得数据结构的成员能够按某种次序排列
- ES6创造了一种新的遍历命令,for..of循环,Iterator接口主要提供for...of消费
2. 工作原理
- 创建一个指针对象(遍历器对象),指向数据结构的起始位置
- 第一次调用next方法,指针自动指向数据结构的第一个成员
- 接下来不断调用next方法,指针会一直往后移动,知道指向最后一个成员
- 每调用next方法返回的是一个包含value和done的对象,{value:当前成员的值,done:布尔值}
- value表示当前成员的值,done对应的布尔值表示当前的数据的结构是否遍历结束
- 当遍历结束的时候返回的value值是undefine,done值为false
- 原生具备iterator接口的数据(可用for of遍历)
- 扩展理解:
- 当数据结构上部署了Symbol.iterator接口,该数据可以用for-of遍历
- 当使用for of去遍历目标数据的时候,该数据会自动去找Symbol.iterator
- Symbol.iterator属性指向对象的默认遍历器方法(iterator接口)
- Array
- arguments
- set容器
- map容器
- String
....
1 | // 模拟遍历器对象(指针对象) |
11.Generator函数
1. 概念:
- ES6提供的解决异步编程的方案之一
- Generator函数是一个状态,内部封装了不同状态数据
- 用来生成遍历对象
- 可暂停函数(惰性求值),yield可暂停,next方法可启动,每次返回的是yield后的表达式结果
2. 特点:
- function 与函数名之间有一个星号
- 内部用yield表达式来定义不同的状态
- 例如:
function * generatorExample(){
let result = yield 'hello'; // 状态值为hello
yield 'generator'; // 状态值为generator
}
- generator函数返回的是指针对象(接iterator),而不会执行函数内部逻辑
- 调用next方法函数内部逻辑开始执行,遇到yield表达式停止,返回{value:yield后的表达式结果/undefined,done:true}
- 再次调用next方法会从上一次停止时的yield处开始,直到最后
- yield语句返回结果通常为undefine,当调用next方法时传参内容会作为启动时yield语句的返回值
1 | // 需求,先做第一件事,然后做第二件事,最后最第三件事 |
12.async 函数是generator函数的语法糖
1. 概念: 真正意义上去解决异步回调问题,同步流程表达异步操作
2. 本质: Generator的语法糖
3. 语法:
- async function foo(){
await 异步操作;
await 异步操作;
}
4. 特点:
- 不需要像Generator去调用next方法,遇到await等待,当恰你的异步操作完成就往下执行
- 返回的总是Promise对象,可以用then方法进行下一步操作
- async 取代Generator函数的星号*,await 取代Generator的yield
- 语义上更为明确,使用简单,经临床验证,暂时没有副作用及不良反应
1. await 是暂停,但是必须跟着promise对象才会暂停,其他的都不暂停
1 | // async 函数 |
12.字符串扩展
1. includes(str) 判断是否包含指定的字符串
2. startsWith(str) 判断是否以指定字符串开头
3. endsWith(str) 判断是否以指定字符串结尾
4. repeat(count) 重复指定的次数
13.二进制与八进制数值表示法:二进制用0b,八进制用0o
1. Number.isFinite(i) 判断是否是有限大的数
2. Number.isNaN(i) 判断是否是NaN
3. Number.isInteger(i) 判断是否是整数
4. Number.parseInt(str) 将字符串转换为对应的数值
5. Math.trunc(i) 直接去除小数部分
14.数组扩展
1. Array.form(伪数组) 伪数组转真数组
2. Array.of(val1,val2,val3)将一些列数值转换为数组
3. find(回调) 找出第一个满足条件的元素
4. findIndex(回调) 找出第一个满足条件的元素的下标
15.对象扩展
1. Object.is(v1,v2)
2. Object.assign(target,obj1,obj2) 将对象的属性复制到目标对象上
3. 直接操作__proto__属性
- let obj2={}
- obj2.__proto__=obj1
16.拷贝数据
1. 基本数据类型
- 拷贝后悔生成一份新的数据,修改拷贝以后的数据不会影响原数据
2. 对象/数组
- 拷贝后不会生成新的数据,而是拷贝的是引用,修改拷贝以后的数据会影响原来的数据
3. 拷贝数据的方法:
- 直接赋值给一个变量 浅拷贝
- Object.assgin() 浅拷贝
- Array.prototype.concat() 浅拷贝
- Array.pototype.slice() 浅拷贝
- JSON.parse(JSON.stringify()) 深拷贝(深度克隆),拷贝的数据里不能有函数,处理不了
4. 浅拷贝(对象/数组)
- 特点: 拷贝引用,修改拷贝以后的数据会影响原数据
5. 深拷贝(深度克隆)
- 特点: 拷贝的时候生成新数据,修改拷贝以后的数据不会影响原数据
- 深度克隆会进行深度的遍历(会用到递归)
- 需要进行类型的检测 : typeof 返回数据类型:String,Number,Boolean,Undefined,Object,Function
- Object.prototype.toString.call(obj) 返回的是该对象到底是什么类型
- console.log(Object.prototype.toString.call(result))
- 截取获取的真正的数据类型
- console.log(Object.prototype.toString.call(result).slice(8, -1))
- for-in循环 对象(属性名) 数组(下标)
17.Set容器:无序不可重复的多个值的集合体
1. Set()
2. Set(array)
3. add(value)
4. delete(value)
5. has(value)
6. clear()
7. size
18.Map容器: 无序的key不重复的多个key-value的集合体
1. Map()
2. Map(array)
3. set(key,value) 添加
4. get(key)
5. delete(key)
6. has(key)
7. clear()
8. size
19.for-of循环可以遍历下面内容:
1. 遍历数组
2. 遍历Set
3. 遍历Map
4. 遍历字符串
5. 遍历伪数组
20.ES7
Array.prototype.includes() 判断数组中是否包含指定的value
指数运算符(幂): **
21.ES8
Object.values(对象) 获取对象中所有的属性的值
Object.entries(对象) 把对象转数组
22.ES9
Promise.finally
let promise = new Promise((resolve,reject)=>{
console.log('开始执行')
resolve('111')
console.log('结束执行')
})
promise.then((data)=>{
console.log('成功:'+data)
}).catch((errorMsg)=>{
console.log('报错啦:'+errorMsg)
}).finally(()=>{
console.log('成功失败都会执行的')
})
23.总结ES6+面试题
1 | ES6+常用语法 |
JS其他内容
事件冒泡与事件委托
1) 事件冒泡的流程
- 基于DOM树形结构
- 事件在目标元素上处理后, 会由内向外(上)逐层传递
- 应用场景: 事件代理/委托/委派
2) 事件委托
- 减少内存占用(事件监听回调从n变为1)
- 动态添加的内部元素也能响应
- 不要滥用
3) 封装一个绑带事件监听的函数(有难度)
1 | /* |
ajax
xhr.status
- 2XX: 表示成功处理请求, 如200, 201
- 3XX: 需要生定向, 浏览器直接跳转, 如302
- 4XX: 客户端请求错误, 如: 401, 404
- 5XX: 服务器端错误, 如: 500
区别ajax请求与一般HTTP请求
- ajax请求是一种特别的http请求
- 对服务器端来说, 没有任何区别, 区别在浏览器端
- 浏览器端发请求: 只有XHR或fetch发出的才是ajax请求, 其它所有的都是非ajax请求
- 浏览器端接收到响应
- 一般请求: 浏览器一般会直接显示响应体数据, 也就是我们常说的刷新/跳转页面
- ajax请求: 浏览器不会对界面进行任何更新操作, 只是调用监视的回调函数并传入响应相关数据
封装一个简易的ajax异步请求函数
1 | /* |
跨域
- 什么是跨域(同源策略)
- JSONP
- CORS
- 代理服务器
同源策略
- 同源: 协议, 域名, 端口, 三者都相同
- ajax请求时, 浏览器要求当前网页和Server必须同源(安全), 否则会抛出跨域的错误
- 加载image/link/script不受同源策略限制
JSONP原理
前台:
<script src="目标url?callback=fn" />- 接收响应数据的函数: function fn (data) {}
后台
- 处理请求, 产生需要返回的数据data
- 读取callback请求参数得到前台处理响应数据的函数名fn
- 返回执行函数fn的js代码: ‘fn && fn(data)’
不足
- 只能处理GET请求
- 每个请求在后台都要做处理, 麻烦
1
2
3
4
5
6
7
8
9
10
11
12
13// 发送jsonp请求的函数
function jsonp() {
var script = document.createElement('script')
script.type = 'text/javascript'
// 传参并指定回调执行函数为backFn
script.src = 'http://localhost:4000/getUserInfo?id=100&callback=fn'
document.body.appendChild(script)
}
// 回调函数, 接收响应数据
function fn(data) {
console.log(data)
}
CORS原理
- 后台: 返回允许浏览器在某个域上发送跨域请求的相关响应头
1 | // 使用cors, 允许跨域, 且允许携带跨域cookie |
- 前台: 不需要做特别任何处理
1 | axios.defaults.withCredentials = true // 允许携带cookie |
代理服务器
- 开发环境: 利用webpack-dev-server中的http-proxy-middle 进行正向代理
- vue脚手架项目
- react脚手架项目
- 自定义webpack配置
- 直接使用http-proxy-middle配置
- 生产环境: 利用nigix 进行反向代理
- https://www.cnblogs.com/taostaryu/p/10547132.html
使用axios发送ajax请求
1 | axios(url) |
axios二次封装
- 配置通用的基础路径和超时
- 显示请求进度条
- 显示进度条: 请求拦截器回调
- 结束进度条: 响应拦截器回调
- 成功返回的数据不再是response, 而直接是响应体数据response.data
- 统一处理请求错误, 具体请求也可以选择处理或不处理
- 每个请求自动携带userTempId的请求头: 在请求拦截器中实现
- 如果当前有token, 自动携带token的请求头
- 对token过期的错误进行处理
1 | import axios from 'axios' |
Restless API 与 Restful API
Restless API
- 传统的API, 把每个url当作一个功能操作 /deleteUser
- 同一个url, 后台只进行CRUD的某一种操作
- 请求方式不决定请求的CRUD操作
- 一个请求路径只对应一个操作
- 一般只有GET/POST
Restful API
- 新式的API, 把每个url当作一个唯一资源 /user/2
- 同一个url, 可以通过不同类型的请求对后台资源数据进行CRUD四种操作
- 请求方式来决定了请求在后台进行CRUD的哪种操作
- GET: 查询
- POST: 添加
- PUT: 更新
- DELETE: 删除
- 同一个请求路径可以进行多个操作
- 请求方式会用到GET/POST/PUT/DELETE
测试: 可以使用json-server快速搭建模拟的rest api 接口
前台数据存储
存储方式
- cookie
- sessionStorage
- localStorage
注意: session后台数据存储
cookie
- 本身用于浏览器和Server通讯
- 被 “借用” 到本地存储
- 可用document.cookie读取或保存
- 可以利用cookies工具库简化编码
cookie的缺点
- 存储大小有限, 最大4KB
- http请求时会自动发送给服务器, 增加了请求的数据量
- 原生的操作语法不太方便操作cookie
- 浏览器可以设置禁用
localStoarge与sessionStorage
- 相同点:
- 纯浏览器端存储, 大小不受限制, 请求时不会自动携带
- 只能保存文本, 如果是对象或数组, 需要转换为JSON
- API相同:
- setItem(key, value)
- getItem(key, value)
- removeitem(key, value)
- 浏览器不能禁用
- 不同点:
- localStorage保存在本地文件中, 除非编码或手动删除, 否则一直存在
- sessonStorage数据保存在当前会话内存中, 关闭浏览器则清除
区别cookie 与 localStorage和sessionStorage
- 容量
- 请求时是否自动携带
- API易用性
- 浏览器是否可禁用
区别cookie与session
- cookie保存在浏览器端(前台可以操作)
- session保存在服务器端(前台不能操作)
- session依赖于cookie(session的id以cookie的形式保存在浏览器端)
从输入url到渲染出页面的整个过程
- DNS 解析(查询):将域名地址解析 ip 地址
- 浏览器 DNS 缓存
- 计算机 DNS 缓存
- 路由器 DNS 缓存
- 网络运营商 DNS 缓存
- 递归查询
- TCP 链接:TCP 三次握手 ===> 建立连接
- 客户端发送服务端:我准备好了,请你准备一下
- 服务端发送客户端:我也准备好了,请你确认一下
- 客户端发送服务端:确认完毕

- 发送请求
- 将请求报文发送过去
- 返回响应
- 将响应报文发送过来
- 解析渲染页面
- 遇到 HTML,调用 HTML 解析器,解析成 DOM 树
- 遇到 CSS,调用 CSS 解析器,解析成 CSSOM 树
- 遇到 JS,调用 JS 解析器(JS 引擎),解析 JS 代码
- 可能要修改元素节点,重新调用 HTML 解析器,解析更新DOM 树
- 可能要修改样式节点,重新调用 CSS 解析器,解析更新 CSSOM 树
- 将 DOM + CSSOM = Render Tree(渲染树)
- layout 布局:计算元素的位置和大小信息
- render 渲染:将颜色/文字/图片等渲染上去
- 断开链接:TCP 四次挥手 (断开请求链接 2 次, 断开响应链接 2 次)
- 客户端发送服务端:请求数据发送完毕,可以断开了
- 服务端发送客户端:请求数据接受完毕,可以断开了
- 服务端发送客户端:响应数据发送完毕,可以断开了
- 客户端发送服务端:响应数据接受完毕,可以断开了

手写代码
函数的call() / apply() / bind()
1 | /* |
函数的节流(throttle)与防抖(debounce)
1 | /* |
数组去重(unique)
1 | /* |
数组扁平化(flatten)
1 | /* |
深拷贝
1 | /* |
自定义new和instanceof工具函数
1 | /* |
字符串处理
1 | /* |
简单排序: 冒泡 / 选择 / 插入
1 | /* |
