1.重绘和回流

1.浏览器是如何进行界面渲染的

  1. 解析 (Parser) HTML, 生成DOM树(DOM Tree)
  2. 同时解析 (Parser) CSS, 生成样式规则(Style Rules)
  3. 根据DOM树和样式规则, 生成渲染树(Render Tree)
  4. 进行布局 Layout(回流/重排):根据生成的渲染树, 得到节点的几何信息 (位置,大小)
  5. 进行绘制 Painting(重绘): 根据计算和获取的信息进行整个页面的绘制
  6. Display: 展示在页面上

2.重绘和回流(重排)

  1. 回流(重排): 当 Render Tree 中部分或者全部元素的尺寸、结构、布局等发生改变时,浏览器就会重新渲染部分或全部文档的过 程称为 回流
  2. 重绘: 由于节点(元素)的样式的改变并不影响它在文档流中的位置和文档布局时(比如:color、background-color、 outline等), 称为重绘
  3. 重绘不一定引起回流,而回流一定会引起重绘

3.会导致回流(重排)的操作:

  1. 页面的首次刷新
  2. 浏览器的窗口大小发生改变
  3. 元素的大小或位置发生改变
  4. 改变字体的大小
  5. 内容的变化(如:input框的输入,图片的大小)
  6. 激活css伪类 (如::hover)
  7. 脚本操作DOM(添加或者删除可见的DOM元素) 简单理解影响到布局了,就会有回流
1
2
3
4
5
6
let a = document.body.style
a.padding = '2px' // 重排 + 重绘
a.border = '1px solid red'// 再一次 重排 + 重绘
a.color = 'red' // 再一次重绘
a.backgroundColor = 'red' // 再一次重绘
a.fontSize = '16px' // 再一次 重排 + 重绘

2.两个定时器对比

  1. 间歇函数 重复执行 首次延迟执行
  2. 延迟函数 只执行1次

3.JS执行机制

同步: 前一个任务结束后再执行后一个任务,程序的执行顺序与任务的排列顺序是一致的、同步的

异步: 你在做一件事情时,因为这件事情会花费很长时间,在做这件事的同时,你还可以去处理其他事情

  • 同步: 同步任务都在主线程上执行,形成一个执行栈
  • 异步: 任务/消息队列, JS异步通过回调函数实现, (事件、资源加载load、error、定时器)
  • 异步任务相关添加到任务队列中 (任务队列也称为消息队列)
  1. 先执行执行栈的同步任务
  2. 异步任务放入任务队列中
  3. 同步任务执行完 系统按次序读取异步任务, 异步任务结束等待状态, 进入执行栈执行
  4. 主线程重复获得任务、执行任务、再获取任务、再执行 这种机制称事件循环event loop

4.作用域链

  1. 作用域链本质上是底层的变量查找机制
  2. 在函数被执行时,会优先查找当前函数作用域中查找变量
  3. 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
  4. 嵌套关系的作用域串联起来形成了作用域链
  5. 相同作用域链中按着从小到大的规则查找变量
  6. 子作用域能够访问父作用域,父级作用域无法访问子级作用域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = 1
let b = 2
function say() {
let a = 11
console.log(a)
function say1() {
// 子作用域可以访问父作用域 父不能访问子
// 逐级查找子父级 直到找到全局作用域
a = 22
console.log(a)
}
say1()
}
say()

5.闭包

  1. 一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
  2. 简单理解:闭包 = 内层函数 + 外层函数的变量
  3. 闭包作用:封闭数据,提供操作,外部也可以访问函数内部的变量
  4. 闭包应用:实现数据的私有: 比如统计函数调用次数, 函数调用一次, 就++
  5. 闭包可能引起内存泄漏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 常见闭包形式 外部可访问使用 函数内部的变量
function say() {
let num2 = 10
function say1() {
console.log(num2)
}
return say1
}
// say() == say1() == function say() {}
const fun = say()
fun() // 调用函数
// 闭包的应用 统计函数被调次数(实现数据的私有)
function fnn() {
let num4 = 0
function fnn1() {
num4++
console.log(num4)
}
return fnn1
}
// 不会被回收 但会一直会被清除标记找到而调用 导致内存泄漏
const funs = fnn()
funs()
funs()

6.this指向

  1. 普通函数this指向调用者 window
  2. 箭头函数不会创建this 是上一层作用域链的this

7.构造函数实例化过程

  • 构造函数是创建对象的函数 可快速创建多个类似对象 命名大写开头 通过new操作
  • 通过new关键字调用行为叫: 实例化
  • 构造函数无需return 如果返回就是新对象 且返回的值无效
  • new Object new Data 都是实例化构造函数
  • 没有参数时可省略() 不建议
  1. 创建空对象

  2. this指向空对象

  3. 执行函数代码 修改this 添加属性

  4. 返回新对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function Pig(uname, age) {
    // this指向obj对象
    // name, ages是属性 name是形参
    this.name = uname
    this.ages = age
    }
    console.log(new Pig('佩奇', 18))
    console.log(new Pig('乔治', 15))

    function Fn(name, age) {
    this.n = name
    this.a = age
    }
    console.log('小城', 18)

8.基本包装类型也有属性和方法 为啥?

为什么字符串可以使用length 为什么有属性? 不是简单数据类型吗?

因为JS底层把基本数据类型包装成了复杂数据类型

1
2
3
4
5
6
7
let num1 = '你好'
console.log(num1.length)
let num2 = 10
console.log(num2.toFixed(2)) // 保留几位小数
// JS底层做了包装 把简单数据类型包装成引入用数据类型
let num3 = new String('你好啊') // 实例化对象
console.log(num3.length)

9.内置构造函数方法

  1. keys(获取所有属性名)
  2. values(获取所有属性值)
  3. assign(对象的拷贝)
  4. reduce(reduce累计器 返回累计处理的结果)

10.数组常见方法

  1. join(‘/‘) 把数组根据分隔符转换为字符串
  2. find(查找对象符合条件的数据返回其对象)
  3. every(每个是否符合条件 都符合返回true 不符合返回false)
  4. some(只要有一个符合 就返回true)
  5. 静态方法from() 伪数组转换真数组

11.字符串常见方法

  1. split(,) 把字符串转换为数组 和join()相反

  2. 字符串的截取: n2.substring(开始, 的索引号 不包含想要截取的部分)

  3. startsWith判断是否以某个字符开头, 返回布尔值

  4. includes 判断字符串是否包含在字符串里, 返回布尔值

  5. Number数字 保留小数方法: toFixed(), 不写会四舍五入, 写则保留小数

  6. 数字转换为字符串方法: String(1)、1.toString()

12. 原型对象prototype

  1. 公共的方法写在原型对象里
  2. 原型是一个对象 把不变的方法定义在prototype对象上
  3. 可挂载函数 实例化不会多次创建原型的函数 节约内存
  4. 构造函数的this指向实例化对象
  5. 原型对象的this也指向实例化的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function fn(a, b) {
this.a = a
this.b = b
this.c = function () {
console.log('你好')
}
}
let n1 = new fn('小城', 10)
let n2 = new fn('小东', 20)
console.log(n1 == n2) // false
// 会导致内存浪费问题
console.log(n1.c == n2.c) // false

fn.prototype.d = function () {
console.log('你好啊')
console.log(this)
}
console.log(fn.prototype)
n1.d() // 调用fn方法的原型
n2.d()
console.log(n1.d == n2.d) // true说明函数共享

13.constructor属性

  • 构造函数prototype指向constructor, 然后constructor再指向回来
1
2
3
4
5
function fn() {

}
console.log(fn.prototype)
console.log(fn.prototype.constructor == fn) // true

constructor使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn.prototype = {
// 在里面创建方法可以 但必须指回构造函数
constructor: fn,
n1: function () {
console.log('你好')
},
n2: function () {
console.log('你好1')
}
}
// 指向了prototype里的对象
// 重新指回构造函数后 即指向constructor 也添加了方法
console.log(fn.prototype)
console.log(fn.prototype.constructor) // 指向fn

14.对象原型_proto__

  1. 对象都有proto属性 指向原型对象的prototype
  2. 之所以对象可使用原型的方法 是因为有__proto__原型的存在
1
2
3
4
5
6
7
8
function fn() {}
let n1 = new fn()
// prototype就是__proto 是JS非标准属性 只可读
console.log(n1)
// 对象原型指向 原型对象 true
console.log(n1.__proto__ == fn.prototype)
// 对象原型的constructor 指向构造函数 true
console.log(n1.__proto__.constructor == fn)

15.原型继承

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function fn1() {

}
let n1 = new fn1()
console.log(n1)

function fn2() {

}
let n2 = new fn2()
console.log(n2)

// n1和n2都要继承a和b
let fn3 = {
a: 1,
b: 2
}
// 通过原型来继承 fn
n1.prototype = fn3 // 通过构造函数解决: 原型继承
console.log(n1)
// 覆盖了没有constructor
console.log(n1.prototype)

// 指回原来的构造函数
n1.prototype.constructor = fn1
console.log(n1.prototype)

// 给n1添加一个方法
n1.prototype.say = function () {
console.log('你好啊')
}
console.log(n1)

// 但是n2也继承了n1的方法 为什么?
// 它们都用了同一对象 根据引用类型特点 就指向同一对象 修改也会影响
n2.prototype = fn3
console.log(n2)

// let fn4 = {
// a: 1,
// b: 2
// } 重新在写一个方法 就不会继承 但很麻烦也失去了本质

// 用构造函数解决 new的对象结构一样 对象不一样
function fns() {
this.a = 1
this.b = 2
}
n1.prototype = new fns()
console.log(n1)

n2.prototype = new fns()
console.log(n2)

n1.prototype.constructor = fn1
n2.prototype.constructor = fn2

16.原型链与instanceof运算符

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
// 1. 原型链
function fn() {

}
let n1 = new fn()
// new的实例化对象等于fn.prototype
console.log(n1.__proto__ == fn.prototype)

// 因为Object也是构造函数 那么就有prototype和constructor
// fn.protptype里的__proto__就等于 Object.constructor
// Object.constructor 在指回Obj 所以__proto__指向Object
console.log(fn.prototype.__proto__ == Object.prototype)

// Object的对象有prototype 那么protytype就一定有__proto__
// Object对象最大 所以指向null
console.log(Object.prototype.__proto__) // null

// 对象原型__proto__的意义是为对象成员查找机制提供一条路线
// 1. 原型链是一个查找规则
// 2. 可查找一些属性和方法 沿着一条路走
// 3. 先看当前原型对象上面有没有
// 4.如果没有再往上一层的原型对象查找
// 5. 如果有 就可使用
// 6. 往上查找最终找到Object为止(null)

// 2. instanceof运算符用于检测构造函数prototype属性
// 是否出现在某个实例对象的原型对象上
// n1 属于 fn
console.log(n1 instanceof fn) // true
// n1 属于 Object
console.log(n1 instanceof Object) // true
// n1 不属于 Array
console.log(n1 instanceof Array) // false
// 数组 属于 Array
console.log([1,2,3] instanceof Array) // true
// 数组 属于 Object
console.log(Array instanceof Object) // true

17.浅拷贝

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
38
39
40
41
42
// 1. 直接复制对象问题 
// 只要是对象都会互相影响 拷贝的是对象栈里的地址
let n1 = {
name: '小城',
age: 18
}
let n2 = n1
console.log(n2)
n2.age = 20
console.log(n2)
console.log(n1) // n1的对象改变了

// 2. 浅拷贝 复制对象可以解决
// 展开运算符
let n3 = {...n1}
console.log(n3) // 拷贝n1对象
n3.age = 21
console.log(n3) // 21
console.log(n1) // 这样不会影响 20

// 对象assign方法
let n4 = {}
Object.assign(n4, n1)
n4.age = 100
console.log(n4)
console.log(n1)

// 但有问题 浅拷贝只针对单层对象不影响 多重对象就有问题
// 如果属性值是引用数据类型 则浅拷贝的是地址
let obj = {
name: '小东',
age: 10,
sex: {
num: 1
}
}
let n5 = {}
Object.assign(n5, obj)
n5.age = 100
n5.sex.num = 2
console.log(n5)
console.log(obj)

18.深拷贝

1.浅拷贝和深拷贝只针对引用类型

  • 深拷贝: 拷贝的是对象 不是地址, 通过递归实现深拷贝

2.深拷贝如何实现的?

  1. 深拷贝拷贝的新对象不会影响旧对象 实现深拷贝用递归函数
  2. 当普通对象拷贝没问题 但遇到有数组的再次调用递归函数就ok
  3. 当用的是对象形式 再次利用递归函数解决
  4. if判断 先Array在Object
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 浅拷贝和深拷贝只针对引用类型
// 深拷贝: 拷贝的是对象 不是地址
// 1. 通过递归实现深拷贝
// 1. 简版递归 浅拷贝函数 实现深拷贝
let obj = {
a: 1,
b: 2,
c: ['苹果1', '苹果2'],
d: {
name: '小'
}
}
let n1 = {}
function f(x, y) {
// k 属性名 obj[k]属性值
for (let k in y) {

// 解决数组问题
// 注意: if一定要先写数组 再写对象
if (y[k] instanceof Array) {
// x[k] 接收 [] c
// y[k] ['苹果1','苹果2']
x[k] = []
f(x[k], y[k])

} else if (y[k] instanceof Object) {
x[k] = {}
f(x[k], y[k])

} else {
x[k] = y[k]
}
// x[k] = n1.a 给新对象添加属性
// 把代码放入if判断里 如果不是Array 则执行以下代码
// x[k] = y[k]
}
}
f(n1, obj) // 调用函数 n1新对象 obj旧对象
n1.a = 10
// 但如果函数有数组类型就有问题
n1.c[0] = '香蕉'
// 也可以处理对象类型
n1.d.name = '大'
// 注意: if一定要先写数组 再写对象
console.log([1,2] instanceof Object)
console.log(n1)
console.log(obj)

// 2. lodash库cloneDeep内部实现深拷贝
let n2 = _.cloneDeep(obj)
n2.d.name = '大'
console.log(n2)
console.log(obj)

// 3. JSON实现深拷贝
// 把对象转换为JSON字符串
console.log(JSON.stringify(obj))
// 先转换为字符串 然后parse生成新对象 新对象和旧对象没有关系
let n3 = JSON.parse(JSON.stringify(obj))
n3.d.name = '大'
console.log(n3)

19.this指向的三种方法

1.call和apply的区别

  1. 都能调用函数

  2. 都能改变this指向

  3. 参数不一样 apply传递必须数组

  4. 严格模式下指向undefined

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 1. 改变this 函数中允许this指向有三个方法
// 1. call() 调用函数 同时指定被调函数的this值
let n1 = {name: '小城'}
function fn(x, y) {
console.log(this)
console.log(x + y)
}
// 1. 调用函数 2. 改变this指向
fn.call(n1, 1, 2)

// 2. apply() 调用函数 同时指定被调函数的this值
// 1. 调用函数
// 2. 改变this指向
// 3. 传递的值必须包含数组里
// 4. 本身就是调用函数 所以返回值就是函数返回值
// fn2.apply(this指向谁, 数组参数)
fn.apply(n1, [10, 20])

// 使用场景: 求数组大小值
let arr = [1,2,3]
let max = Math.max.apply(Math, arr)
let min = Math.min.apply(Math, arr)
console.log(max, min)

// 4. this指向
// 1. 普通this指向 谁调用我 this指向谁
// 严格模式下指向undefined
console.log(this) // window

function fn1() {
console.log(this) // window
}
fn1()

window.setTimeout(function () {
console.log(this) // window
}, 100)

document.querySelector('button').addEventListener('click', function () {
console.log(this) // button
})

let obj = {
a: function () {
console.log(this) // obj
}
}
obj.a()

// 5. 箭头函数this指向 沿用上一层this指向
// 向外层作用域查找this 直到有this指向为止
document.querySelector('button').addEventListener('click', () => {
console.log(this) // window
})

function fn2() {

}
fn2.prototype.say = () => {
console.log(this) // window
}
let n2 = new fn2()
n2.say()

2. this.bind方法

  1. call、apply、bind的区别: 相同点: 都可改变this指向
  2. call和apply会调用函数 可修改this
  3. bind不会调用函数 可修改this
  4. call和apply传递参数不一样 call是(1,2) apply是数组形式

3.应用场景:

  1. call 想要调用函数 还要传递参数
  2. apply 想要调用函数 并且要改变this 还想传递数组
  3. bind 不想调用函数 只想改变this 比如改变定时器的this
  4. 普通参数用call 数组用apply 改变this不想调用用bind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 1. bind方法改变this
let obj = {name: '小城'}
function fn() {
console.log(this)
}
// 1. bind 不会调用函数
// 2. 能改变this指向
// 3. 返回值是个函数 但函数里的this是更改过的obj
fn.bind()
let n1 = fn.bind(obj)
console.log(n1) // 返回fn函数
n1() // 但this指向obj

// 2. 有一个按钮 点击就禁用 2秒后开启
let btn = document.querySelector('button')
btn.addEventListener('click', function () {
this.disabled = true
setTimeout(function () {
// 在普通函数里 将原来的window.this指向btn
this.disabled = false
}.bind(this), 300)
// bind(this)在延迟函数外层写的 所以外层指向btn
})

20.防抖以及底层实现

  1. 在点击事件内的时间 频繁触发事件 只执行一次
  2. 比如王者的回城 被打断就会重来
  3. 使用场景: 搜索框输入联想词、手机号邮箱验证 可减少服务器请求
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
// 2. 鼠标在盒子上移动 数字就会加1 使用防抖实现性能优化
let box = document.querySelector('.box')
let num = 1
function fn() {
// 如果有很多DOM操作 大量数据处理 可能会卡
box.innerHTML = num++
}
// box.addEventListener('mousemove', fn)

// 3. 利用lodash库实现防抖 500ms之后加1
// _.debounce(函数, 时间)
// box.addEventListener('mousemove', _.debounce(fn, 100))

// 4. 手写防抖函数 核心是利用setTimeout定时器实现
// 1. 声明定时器变量
// 2. 每次鼠标事件 先判断是否有定时器 有就清除以前的定时器
// 3. 没有则开启定时器 存到定时器变量里
// 4. 定时器里写函数调用
function fn1(fn, time) {
let t = 0
// return 返回一个匿名函数
return function () {
// 判断是否有定时器 有就清除
if (t) clearTimeout(t)
// 没有定时器则开启定时器 再调用fn()
t = setTimeout(function () {
fn()
}, time)
}
}
// 函数一调用 就会返回一个值
// fn1(fn, 500) = return function()
box.addEventListener('mousemove', fn1(fn, 500))

21.节流以及底层实现

  1. 在3秒内频繁触发事件只执行一次
  2. 比如王者的技能冷却期间是无法释放的
  3. 使用场景: 鼠标mousemove、页面尺寸缩放resize、滚动条scroll滚动…
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
38
39
let box = document.querySelector('.box')
let num = 1
function fn() {
box.innerHTML = num++
}
// box.addEventListener('mousemove', fn)

// 2. 利用lodsah库实现 节流 500ms内只执行一次
// _.throttle(函数, 时间)
// box.addEventListener('mousemove', _.throttle(fn, 1000))

// 3. 手写节流函数 核心是利用setTimeout定时器实现
// 1. 声明一个变量
// 2. 每次鼠标事件 先判断是否有定时器 有则不开启定时器
// 3. 没有则开启定时器 存入变量里
// 4. 定时器里调用fn()函数
// 5. 定时器里把定时器清空 null 为下次在开启定时器
function fn1(fn, time) {
let t = 0
return function () {
if (!t) {
t = setTimeout(function () {
fn()
// 设置为null 覆盖定时器 就可以开启下次定时器了
t = null
}, time)
}
}
}
box.addEventListener('mousemove', fn1(fn, 200))

// 4. 清除定时器问题
// 在setTimeout里是无法删除定时器的 因为定时器还在运作
// 所以使用 t = null 而不是clearTimeout(t)
let n1 = null
n1 = setTimeout(function () {
clearTimeout(n1)
console.log(n1) // 打印为1
}, 200)

22.Ajax接口请求过程

  1. 用户向服务器提交数据
  2. 通过Ajax载体发起get请求
  3. 服务器处理Ajax请求 然后响应get请求
  4. 在通过Ajax处理返回给用户
  5. 什么是Ajax? 网页中利用XMLHttpRequest对象和服务器的数据交互方式
  6. 通信协议 服务器名称 具体存放位置 http:// baidu.com /index.html
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
// 1. $.post() 请求函数语法 向服务器提交数据
// $.post('提交数据的地址', '要提交的数据', '数据提交成功的回调函数')
$(function () {
$('.btn2').click(function () {
$.post('http://ajax-api.itheima.net/api/books',
{bookname: '水浒传', author: '出版社', publisher: '施耐庵'},
function (res) {
console.log(res) // 数据提交成功返回的函数
})
})
})

// 2. $.ajax() 请求函数语法 是一个功能综合的函数 可get/post请求
$(function () {
$('.btn4').click(function () {
$.ajax({
type: 'POST',
url: 'http://ajax-api.itheima.net/api/books',
data: {
bookname: '西游记',
author: '人民出版社',
publisher: '孙悟空'
},
success: function (res) {
console.log(res)
}
})
})
})

23.HTTP状态码

  1. 200 成功 只要有2 就是成功
  2. 302 重定向 直接修改网址 把a网址改为b网址
  • 只要有4 前端问题
  1. 400 参数错误
  2. 401 未登录/未认证 没登录但输入了后台页
  3. 403 无权限 跟视频会员一样
  4. 404 请求行错误 url错误/请求method错误
  5. 413 文件上传限制 文件过大
  • 只要有5 服务器内部问题

24.restful接口规范

  1. 这三个一定是post请求体传参: post 新增数据、put 全局更新数据、patch 局部更新数据
  2. delete 删除数据 请求体get/post都可以传参

Get和Post请求区别:

1.传参方式不一样

  1. get在请求行传参
  2. post在请求体载荷传参 参数会进行切片处理 切多少取决于数据和带宽
  3. 204预检请求: 遇到请求体一次发不完 先通知服务器接收
  4. 在内存里准备空间 然后建立数据流传参

2.传参速度 get更快 直接url查找

3.传参数据大小不一样

  1. get有大小限制 2~4kb
  2. post无上限

4.安全性 post更高

25.Ajax的XMLHttpRequest原理

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
38
39
40
41
42
43
44
45
46
47
48
49
50
// 01. 使用XMLHttpRequest对象与服务器通信
// 1. 创建xhr对象
let xhr = new XMLHttpRequest()
// 2. 设置请求方法和url地址
xhr.open('get', 'http://hmajax.itheima.net/api/news')
// 3. 发送ajax请求
xhr.send()
// 4. 注册响应事件 loadend主流 load兼容性好
// 响应需要时间 不是秒拿 时间取决于带宽/网络
xhr.addEventListener('loadend', function () {
// 5. 响应数据
console.log(xhr.response)
})

// 02. XMLHttpRequest发送get参数
// 2.1 使用xhr携带查询参数的ajax请求 url?参数名=参数值
let xhr1 = new XMLHttpRequest()
// 2.2 xhr的url只能进行拼接 因为它是最底层的原理
xhr1.open('get', 'http://hmajax.itheima.net/api/city?pname=湖北省')
xhr1.send()
xhr1.addEventListener('loadend', function () {
console.log(xhr1.response)
// 2.3 服务器响应的数据一定是json格式 json -> js
console.log(JSON.parse(xhr1.response))
// 2.4 axios额外对象是axios额外包装的
// config请求报文 headers请求头
// request告诉你用的xhr对象 status成功/失败
})

// 03. XMLHttpRequest发送post参数
let xhr2 = new XMLHttpRequest()
xhr2.open('post', 'http://hmajax.itheima.net/api/register')

// 3.2 post需要设置原生请求头
xhr2.setRequestHeader('Content-Type', 'application/json; charset=utf-8')

// 3.1 post发送请求体 因为是最原生写法
// 3.3 请求头是json 但请求体是查询字符串 所以要写json格式字符串
// xhr2.send('username=admin123&password=123456')
xhr2.send('{"username": "admin123", "password": "123456"}')

xhr2.addEventListener('loadend', function () {
console.log(xhr2.response)
})

// 04. 小结
// get参数在请求行 url?参数名=参数值
// post参数在请求体 xhr.send(json格式)
// post请求需额外设置请求头
// setRequestHeader('Content-Type', 'application/json; charset=utf-8')

26.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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 01. Promise作用: 解决回调地狱
// 回调地狱: 异步回调 层层嵌套
// js代码分为2种: 同步(默认) 异步
// 同步: 按照顺序立即执行
// 异步: 没有顺序 延迟执行 (事件、定时器、ajax)

// 层层嵌套:
// 1.1 一级分类
let xhr1 = new XMLHttpRequest()
xhr1.open('get', 'http://123.57.109.30:3999/api/categoryfirst')
xhr1.send()
xhr1.onload = function () {
console.log('一级')
console.log(JSON.parse(xhr1.response))

// 1.2 二级分类
let xhr2 = new XMLHttpRequest()
xhr2.open('get', 'http://123.57.109.30:3999/api/categorySecond?firstId=621')
xhr2.send()
xhr2.onload = function () {
console.log('二级')
console.log(JSON.parse(xhr2.response))

// 1.3 三级分类
let xhr3 = new XMLHttpRequest()
xhr3.open('get', 'http://123.57.109.30:3999/api/categoryThird?secondId=622')
xhr3.send()
xhr3.onload = function () {
console.log('三级')
console.log(JSON.parse(xhr3.response))
}
}
}
// 1.4 浏览器刷新打印顺序会不同
console.log(666)

// 02. Promise语法
// 2.1 调用构造函数 创建Promise实例
// (resolve, reject) 是箭头函数的参数
let pro = new Promise((resolve, reject) => {
// 异步代码
setTimeout(function () {
// resolve(1) // 成功 1是实参
reject(2) // 失败
}, 500)
})

// 2.2 调用Promise实例对象的then/catch方法
// resolve本质就是调用res res是形参 resolve(1)是实参
// resolve和reject状态二选一的
pro.then(res => {
console.log(res)
}).catch(error => {
// reject失败则调用catch方法
console.log(error)
})

// 2.3 Promise在创建实例时 里面代码会立即执行
// Promise自己是同步的 只有then方法才是异步的

27.Promise工作原理

  1. Promise是什么? 是ES6新增的构造函数
  2. Promise作用: 解决回调地狱

1.Promise应用场景/原理 Promise对象有三种状态:

  1. pending 进行中(默认状态) 所以一旦创建Promise 里面代码会立即执行
  2. fuifilled 已完成
  3. rejected 已失败
  4. Promise相当于是一个容器 把异步代码放入容器中

2.Promise对象状态只有两种状态:

  1. 调用resolve()方法时: 从pending变为fuifilled
  2. 调用reject()方法时: 从pending变为rejected
  3. 状态只能改变一次 不管成功/失败 都会有一个数据结果

3.Promise状态发生改变后 在任何时候都可以获取结果

  1. 怎么拿Promise结果呢?
  2. Promise实例的then方法获取成功结果
  3. Promise实例的catch方法获取失败结果

4.Promise在创建实例时 里面代码会立即执行

  1. Promise自己是同步的 只有then方法才是异步的

28.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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// Promise使用链式语法解决回调地狱
// 01. 创建Promise实例
// let p = new Promise((resolve, reject) => {
// // 创建xhr对象
// let xhr = new XMLHttpRequest()
// // 设置请求方式和url
// xhr.open('get', 'http://123.57.109.30:3999/api/categoryfirst')
// // 发送ajax请求
// xhr.send()
// // 注册响应事件函数
// xhr.onload = function () {
// console.log('一级')
// let res = JSON.parse(xhr.response)
// // 1.1 Promise成功状态
// resolve(res)
// }
// })
// 1.2 取出Promise实例对象结果
// p.then(res => {
// console.log(res)
// })

// 02. 三个实例对象只有url不一样 可封装成函数 帮我们创建Promise实例对象
function fn(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest()
// 2.1 url是形参 调用时写入实参
xhr.open('get', url)
xhr.send()
xhr.onload = function () {
let res = JSON.parse(xhr.response)
// 2.2 Promise成功状态
resolve(res)
}
})
}
// 2.3 调用封装好的Promise实例对象函数
let p1 = fn('http://123.57.109.30:3999/api/categoryfirst')

let p2 = fn('http://123.57.109.30:3999/api/categorySecond?firstId=622')

let p3 = fn('http://123.57.109.30:3999/api/categoryThird?secondId=621')

// 3. 取出Promise实例对象的函数结果
// p1.then(res => {
// console.log(res)
// })
// p2.then(res => {
// console.log(res)
// })
// p3.then(res => {
// console.log(res)
// })

// 4. 但打印结果还是随机的 因为只要创建代码就会立即执行
// p1进行完后 return 返回p2的实例对象 .then可换行
p1.then(res => {
console.log(res)
return p2
})
.then(res => {
// p2的then
console.log(res)
return p3
})
.then(res => {
// p3的then
console.log(res)
})

// 5. Promise是如何解决回调地狱的呢?
// Promise通过链式调用解决回调地狱
// 链式调用: 在上一个then里 返回下一个Promise实例 就可以继续后面的then

29.JS异步微任务-宏任务

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
38
39
40
// 1. JS是单线程语言: CPU在同一时间只能做一个任务

// 2. JS代码分为: 同步任务和异步任务
// 同步任务(默认): 立即执行
// 异步任务: 会把异步任务放在任务队列中 (事件、定时器、ajax、Promise的then、await)

// 3. ES6之后异步做了划分: 微任务和宏任务
// 宏任务: 事件、定时器、ajax、script(是特殊的宏任务 不属于异步)
// 微任务: Promise的then、await

// 4. 事件循环: JS编译器 解析与执行代码的规则 所有JS代码都是事件循环规则
// 1. 默认解析script代码 (默认宏任务)
// 2. 判断代码是同步还是异步任务
// 3. 如果是同步代码: 立即执行
// 4. 如果是异步代码: 不会执行 而是放入任务队列中
// 5. 微任务放入微任务中 宏任务放入宏任务中
// 6. 当前所有同步都执行完毕后 才开始执行异步任务
// 7. 执行异步时 先执行微任务 后执行宏任务
// 8. 事件循环: 按照上面的规则反复的执行每一个宏任务
console.log(1)
setTimeout(() => {
console.log(2)
new Promise((resolve, reject) => {
console.log(3)
resolve()
console.log(4)
}).then(res => {
console.log(5)
})
console.log(6)
}, 0)
console.log(7)
new Promise((resolve, reject) => {
console.log(8)
resolve()
console.log(9)
}).then(res => {
console.log(10)
})
// 1 7 8 9 10 2 6 3 4 5

30.跨域的概念

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
// 1. 当页面发生跨域, 就会产生一个固定格式的报错
// 只要是跨域, 就一定会出现下面这种格式的报错, 但这种格式报错原因有很多, 比如基地址错误、服务器内部问题、跨度都有可能出现这种错误
// Access to XMLHttpRequest at 'ajax请求网址' from origin '页面网址' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

// 2. 跨域是什么: ajax地址 和 页面地址 不同源
// ajax地址: 跨域只会出现在ajax请求中, 其他的请求没有跨域
// 页面地址: location.href地址栏
// 不同源: 浏览器同源策略: 协议名、端口号、主机ip都一致

// 页面地址: http://127.0.0.1:3000
// ajax1: http://127.0.0.1:3000/list 同源
// ajax2: http://127.0.0.1:5000 不同源 端口号不一样
// ajax4: https://127.0.0.1:3000 不同源 协议名不一致
// ajax3: http://localhost:3000 不同源 ip不一致

// 3. 同源策略是一种安全策略: 浏览器为了安全
// 出于安全考虑, 浏览器不允许页面向不同源的接口请求数据, 因为如果接口和网页不同源, 浏览器认为是2个不同的 服务器
// 当使用ajax请求地址时, 与当前页面地址不一致时, 浏览器会认为给不同服务器发送了请求
// 可能导致数据泄露, 因此会拒绝接收服务器的数据
// 跨域: 服务器可以收到请求, 也响应了请求, 但响应的数据被浏览器拒收了

// 4. 如何解决跨域?
// 1. CORS技术: 后台设置允许跨域的响应头
// 应用场景: 前提是后台是自己的
// res.setHeader('Access-Control-Allow-Origin', '*')
// 参数: 1.响应头名字 2.响应头值 *为所有网站都可以

// 2. 代理服务器
// 代理服务器: 帮你转发请求的服务器
// 使用代理: 转发请求-使用axios库: 前后端通用
// 将数据返回给浏览器 (服务器不能直接给对象, 需转换为json)

31.

32.

33.

34.

35.