第一章
js基础-变量类型和计算
1.值类型和引用类型
typeof能判断哪些类型?考点(js变量类型)
何时使用=== 何时使用==?考点(强制类型转换)
值类型和引用类型的区别
手写深拷贝
1.1值类型
值类型占用的内存比较少
let a = 100
let b = a
a = 200
console.log(b)//100
1.2常见的值类型
const a//不赋值的情况下这里会报错
let a //不赋值的情况下这里输出 undefined
const s = 'abc'
const n = 100
const b = true
const s = Symbol('s')
值类型就是单纯的把值赋给另外一个变量 它们彼此值的修改互不影响
1.3引用类型
1.4常见引用类型
函数不一定是一个引用类型 也可以单独归类为一类
//引用类型
let a = {age:20}
let b = a
b.age = 21
console.log(a.age)//21
console.log(b.age)//21
引用类型中b=a就是把a的地址赋值给了b
a,b指向的是同一个内存地址 修改了b的值就是修改了a,b对应的内存地址指向的值
1.5 堆栈模型
1.6两者的区别是什么?
1.7手写深拷贝 深拷贝?浅拷贝?
注意判断值类型和引用类型
注意判断是数组还是对象
注意递归
深拷贝就是将一个对象的内容从内存中完整的拷贝一份,并且从内存中新开辟一个地址用来存放拷贝得来的新对象 ,并且修改新的对象的属性不影响原来的对象
浅拷贝 拷贝到的对象里面的属性改变了会影响原来的对象的属性
2.typeof运算符
认识所有值类型
识别函数
判读是否是引用类型(不可再细分是数组还是对象)
3.变量计算-类型转换
3.1字符串拼接
const a = 100 + 10 //110
const b = 100 + '10'//'110'
const c = true + '10'//'true10'
const c = true + 10//11
只要有字符串 那么上面就不是加法了 都会看成字符串拼接
3.2==运算符
==会进行类型转换 尝试让它们转换后去相等
在判断是否是null时才用== 其余情况都用===
4.if语句和逻辑运算
两步非运算的变量结果为true就是truly变量
两步非运算的变量结果为false就是falsely变量
const a = 10
!a//false
!!a//true truly变量
const b = 0
!b//true
!!b//false
4.1if语句里面判断的就是truly变量和falsely变量
4.2逻辑运算(也是判断truly和falsely变量)
console.log(2 && 3)//3
&& 遇到falsely变量直接返回
|| 遇到truly变量直接返回
5小结
js基础-原型和原型链
js本身是基于原型继承的语言
题目
如何准确判断一个变量是不是数组?
手写一个简易的jQuery,考虑插件和扩展性
class的原型本质,怎么理解?
1.class和继承
es5之前是没有类的概念 但是JavaScript的开发者,为了使实现面向对象更加简单 就创建了类class 类可以看作是代替了es6之前的构造函数面向对象的另一种写法
es6之前对象不是基于类class创建的 而是使用构造函数模拟面向对象的过程的 之后es6出现了class class里面也是通过构造函数的方式实现的
es5创建类的方法
//新建构造函数 默认开头是大写
function Person(name){
this.name = name
}
//定义一个方法并且赋值给构造函数的原型上
Person.prototype.sayHi{
return this.name
}
const p = new Person('yang')
console.log(Person.sayHi())
ES6 实现类的方法
class是面向对象的一个实现:新建一个构造函数 ,定义一个方法并且赋值给构造函数的原型
//类
class Student{
//属性
constructor(name,age) {
this.name = name;
this.age = age;
}
//方法
sayhi() {
console.log(
`姓名${this.name},年龄${this.age}`//es里面的模板字符串
);
console.log(
'姓名' + this.name+'年龄' + this.age//上面不用模板字符串就是这样的写法 会笔记凌乱
);
}
}
//通过类声明对象/实例
const xialuo = new Student('xialuo', 20)
console.log(xialuo.name);
console.log(xialuo.age);
xialuo.sayhi();
和es5不同的是 es6我们把原型的实现写在了类中,但是本质上还是和es5一样的,还是需要新建一个类名,然后实现构造函数,然后往原型上添加方法
2.类型判断instanceof
Object包括了Array
instance of 可以判断出数组或者对象 而不是像type of那样只能判断出Object
3.原型和原型链
原型-原型关系
原型-原型关系在es6的类和es5的构造函数上的实现道理都一样 都是通过原型链
每个构造函数里面都有显式原型prototype
每个实例都有隐式原型proto 指向构造函数的prototype == 的关系 实例对象上的某个属性如果找不到的话就会去实例的隐式原型上面找(即去对应构造函数的显示原型上面找)
原型链
//父类
class People{
constructor(name) {
this.name = name;
}
eat() {
console.log(
`${this.name} eat something`
);
}
}
//子类
class Student extends People{
constructor(name,age) {
super(name);//把它的name交给父类去处理 实现继承
this.age = age
}
sayhi() {
console.log(
`我是学生,我叫${this.name},我${this.age}岁了`
);
}
}
const xialuo = new Student('xialuo', 20);
console.log(xialuo.name);
console.log(xialuo.age);
xialuo.sayhi();
xialuo.eat();//继承父类的方法
student继承于people
下面的原型链中还有一根线没有显示 即Student.prototype.constructor 可以指向Student
在获取属性xialuo.sayhi()时首先会在自身寻找 找不到的话就自动去自己的隐式原型proto里面找 而xialuo.proto指向的是student.prototype
xialuo instanceof student 类型判断的原理是
原型链中顺着xialuo的隐式原型是否可以找到student的显式原型
4.重要提示
class只是语法规范 具体的实现方式还是得看v8引擎
js基础-作用域和闭包
题目
this的不同应用场景,如何取值?
手写bind函数
实际开发中闭包的应用场景,举例说明
1.隐藏数据
2.做一个简单的cache工具
4.1作用域和自由变量
作用域就是变量的使用范围 一旦使用超出范围就会报错
4.1.1全局作用域
全局作用域为程序的最外层作用域,一直都存在
4.1.2函数作用域
函数作用域只有函数被定义的时候才会被创建,包含在父级作用域/全局作用域内
4.1.3块级作用域(es6新增)
es6标准提出了使用let和const代替var关键字,来创建块级作用域
//es6之前 js作者没有考虑到块级作用域
if(true){
var x = 100
}
console.log(x)//这里会打印出来 100
//es6块级作用域
if(true){
let x = 100
}
console.log(x)//这里会报错打印不出来 因为x只在上面if的块级作用域{}中起作用
4.1.4自由变量
一个变量在当前作用域没有被定义,但是却被使用了
那么它会向上级作用域一层一层的寻找,直至找到为止
如果找到全局作用域都没有被找到的话,则会报错xx is not defined
4.2闭包
闭包:无论何时声明新函数并将其赋值给变量,都要存储函数定义和闭包。
闭包包含在函数创建时作用域中的所有变量,它类似于背包。函数定义附带一个小背包,它的包中存储了函数定义创建时作用域中的所有变量。
闭包的产生场景(要注意):
1.函数作为参数被传递
//函数作为参数被传递
function print (fn){
let a = 100
fn()
}
let a = 200
function fn(){
console.log(a)
}
print(fn)//打印结果为200
//打印a的时候a在当前的function fn()下没有被定义 故a是一个自由变量 会向所在地的上一级作用域寻找 找到全局作用域 let a = 200 即a就是fn的闭包
//如果没有let a = 200 打印会报错
function print (fn){
let a = 100
fn()
}
let a = 200
function fn(){
let a = 300
console.log(a)
}
print(fn)//打印结果为200
//let a = 200 let a = 300为fn的两个闭包 fn在调用的时候 fn()里面定义了a=300就打印这个 如果a=300没有的话就打印另外的一个闭包中的变量
//a=200 和a=300都没有的话就打印报错 注意是报错而不是undefined
2.函数作为返回值被返回
//函数作为返回值被返回
function create (){
let a = 100
return function (){//这里的function处于create的函数作用域下
console.log(a)
}
}
const fn = create()
const a = 200//全局作用域下
fn()//这里的fn()为全局作用域下 打印结果为100
//打印a的时候a在当前的function下没有被定义 故a是一个自由变量 会向所在地的上一级作用域寻找 找到函数作用域中的let a = 100
自由变量的查找,是在函数定义的地方向上级作用域查找 而不是在执行的地方向上级作用域查找
4.3this
调用场景
1.作为普通函数
this指向它们绑定的对象 但是对箭头函数是没有用的 箭头函数的this是改变不了的
3.作为对象方法被调用
函数有所属对象的时候,函数的this指向所属对象
如getvalue()属于myObject,并由myObject执行的话 那么this指向myObject
函数没有所属对象的时候那么this指向的是全局对象
2.使用call apply bind 并且手写
是什么?
apply,call,bind都是js给函数内置的一些api,调用他们可以为函数指定this的执行,同时也可以传参.
怎么用?
//apply
func.apply(thisArg, [argsArray])//统一传入
//call
fun.call(thisArg, arg1, arg2, ...)//分开传入
//bind
const newFun = fun.bind(thisArg, arg1, arg2, ...)
newFun()
复制代码
apply和call就是传参不一样,但是两个都是会在调用的时候同时执行调用的函数,但是bind则会返回一个绑定了this的函数.
4.在class方法中调用
通过new 类来调用构造函数从而获得的新对象,this绑定在新对象上
5.箭头函数
this取什么值是在函数执行的时候确认的 而不是在函数定义的时候确认的
//作为普通函数
function fn1(){
console.log(this)
}
fn1()//打印结果window
//使用call apply bind
fn1.call({x:100})//打印结果{x:100}
const fn2 = fnn1.bind({x:200})
fn2()//打印结果为{x:200}
//bind和call 可以改变this的指向 但是bind会返回一个的新的函数fn2()
//作为对象方法被调用
const zhangsan = {
sayhi(){
console.log(this)//这里的this结果为当前对象zhangsan
},
wait(){
setTimeout(function()=>{
console.log(this)//这里的this结果为window 定时器函数是作为一个普通函数被执行的 而不是作为对象(zhangsan)的方法 记住定时器函数的this永远指向window
})
}
//箭头函数
waitAgain(){
setTimeout(()=>{
console.log(this)//这里的this结果为当前对象 ()=>箭头函数是被setTimeout触发的 箭头函数本身没有this指向 故它会向上级作用域寻找
})
}
}
//在class方法中调用
class People {
constructor(name){
this.name = name
this.age = 21
}
sayhi(){
console.log(this)//这里的this指向实例
}
}
const zhangsan = new People('zhangsan')
zhangsan.sayhi()//这里的打印结果为zhangsan
4.4小结
\
js基础-异步
题目
同步和异步的区别是什么?
它们都是js单线程的语言 但是异步不会阻塞代码的执行 但是同步会阻塞代码的执行
手写用promise加载一张图片?
前端使用异步的场景有哪些?
网络请求&定时任务
单线程和异步
js是单线程语言,同时只能做一件事
浏览器和node.js已支持js启动进程,如Web Worker
js和DOM渲染共用一个线程,因为js可以修改DOM结构 DOM渲染的过程中js必须停止 js执行的过程中DOM渲染必须停止
遇到等待(网络请求,定时任务)不能卡住 需要异步
执行js代码时 异步任务会被放到一个异步队列(这个队列分宏任务队列和微任务队列 队列是先进先出 宏任务执行完再DOM渲染然后再执行微任务)里面 在同步任务完成之后是再通过回调callback函数形式执行的
代码举例
异步和同步:基于js是单线程的语言 异步不会阻塞代码执行 同步会阻塞代码执行
应用场景
网络请求,如ajax图片加载
定时任务,如setTimeout
setTimeout是一次性的 setInterval是重复循环的
callback hell
callback hell(回调地狱)促进了Promise的产生
function getData(url){
return new Promise ((resolve,reject)=>{
$.ajax({
url,
success(data){
resolve(data)
},
error(err){
reject(err)
}
})
})
}
宏任务和微任务
事件循环 event loop?
执行js代码时 异步任务会被放到一个异步队列(这个队列分宏任务队列和微任务队列 队列是先进先出 宏任务执行完再DOM渲染然后再执行微任务)里面 在同步任务完成之后是再通过回调callback函数形式执行的
event loop是监听任务队列的它是一直再不停的监听任务队列并且不停的循环上面的步骤 当再次有任务被放到队列里面的时候 就按照上面的顺序再次执行
Node.js异步
- Node.js 同样使用ES语法,也是单线程,也需要异步
- 异步任务也分:宏任务+微任务
- 但是,它的宏任务和微任务,分不同类型,有不同优先级
Promise是什么?
介绍
1.promise是es6引进的异步编程的新的解决方案
从语法上来说就是一个构造函数用来封装异步的任务 并且可以对结果进行处理
是书写异步代码的一种新方式,解决回调函数嵌套(回调地狱)的问题
旧的方法就是使用回调函数 (回调函数就是函数做为参数,函数的执行需要依赖另外一个函数的执行来调用,它是异步执行的)
2.异步编程:fs 文件操作,数据库操作,ajax,定时器
3.指定回调函数的方式更加灵活
旧的:必须在启动异步任务前指定
promise:启动异步任务=>返回promise对象=>给promise对象绑定回调函数(甚至可以在异步任务结束之后指定/多个)
promise的基本语法
1.如何创建一个promise对象
const p = new Promise((resolve,reject) =>{
//promise 内部一般可以封装一个异步操作
//resolve,reject 是 promisse内部提供好给你的两个函数
//成功调用 resolve
//失败调用 reject
})
2.如何使用一个promise对象
p.then(res => {...})//处理成功
.catch(res => {...})//处理失败
promise的三个状态
Promise的状态
即指实例对象中的一个属性 PromiseState
pending:等待(进行中)
fulfilled:成功(已完成),调用了resolve,promise的状态就会被标记为成功
rejected:失败(拒绝),调用了reject,promise的状态就会被标记为失败
tips:一旦promise的状态发生变化,状态就会被凝固
promise 的状态改变
\1. pending 变为 resolved
\2. pending 变为 rejected
说明: 只有这 2 种, 且一个 promise 对象只能改变一次
无论变为成功还是失败, 都会有一个结果数据
成功的结果数据一般称为 value, 失败的结果数据一般称为 reason
Promise 对象的值
实例对象中的另一个属性 PromiseResult
保存着对象 成功/失败 的结果
流程
Promise API
见pdf文档
Promise关键问题?
1.如何改变 promise 的状态?
(1) resolve(value): 如果当前是 pending 就会变为 resolved
(2) reject(reason): 如果当前是 pending 就会变为 rejected
(3) 抛出异常: 如果当前是 pending 就会变为 rejected
\2. 一个 promise 指定多个成功/失败回调函数, 都会调用吗?
当 promise 改变为对应状态时都会调用
<script>
let p = new Promise((resolve, reject) => {
resolve('OK');
//如果注掉上面的resolve Promise的状态为pending 那么下面指定的回调也不会执行
});
///指定回调 - 1
p.then(value => {
console.log(value);
});
//指定回调 - 2
p.then(value => {
alert(value);
});
</script>
\3. 改变 promise 状态和指定回调函数后 谁先谁后执行?
(1) 都有可能, 正常情况下是先改变状态再指定回调, 但也可以先指定回调再改变状态
//回调先执行 改变状态后执行
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');//改变状态
}, 1000);
});
p.then(value => {
console.log(value);
},reason=>{
})
</script>
(2) 如何先改状态再指定回调?
① 在执行器中直接调用 resolve()/reject()
//先改变状态 再执行回调
<script>
let p = new Promise((resolve, reject) => {
resolve('OK');//改变状态
});
p.then(value => {
console.log(value);
},reason=>{
})
</script>
② 延迟更长时间才调用 then() 即给then()里面添加定时器
(3) 什么时候才能得到数据? ------其实就是问回调函数什么时候执行
① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
② 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
\4. promise.then()返回的新 promise 的结果状态由什么决定?
promise.then()返回的新 promise 的结果状态 即下面的result的状态
(1) 简单表达: 由 then()指定的回调函数执行的结果决定
<script>
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
// console.log(value);//状态为 resolve
//1. 抛出错误
// throw '出了问题';//状态为 rejected
//2. 返回结果是非 Promise 类型的对象
// return 521;
//3. 返回结果是 Promise 对象
// return new Promise((resolve, reject) => {
// // resolve('success');
// reject('error');
// });
}, reason => {
console.warn(reason);
});
console.log(result);
</script>
(2) 详细表达:
① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
② 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
③ 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
\5. promise 如何串连多个操作任务?
(1) promise 的 then()返回一个新的 promise, 可以开成 then()的链式调用
(2) 通过 then 的链式调用串连多个同步/异步任务
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
return new Promise((resolve, reject) => {//异步任务
resolve("success");
});
}).then(value => {//这里的value是上面成功状态返回的success
console.log(value);//这里没有返回结果
}).then(value => {
console.log(value);//undefined 上一个回调函数没有返回结果
})
</script>
\6. promise 异常传透?
(1) 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,
(2) 前面任何操作出了异常, 都会传到最后失败的回调中处理
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
//resolve('OK');
reject('Err');
}, 1000);
});
p.then(value => {
//console.log(111);
throw '失败啦!';
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});//前面任何操作出了异常, 都会传到最后失败的回调中处理 所以中间的then方法不用指定失败回调 只需再最后来一个catch
</script>
\7. 中断 promise 链?
(1) 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
(2) 办法: 在回调函数中返回一个 pendding 状态的 promise 对象
<script>
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
//输出111 之后就不再输出后面的了
p.then(value => {
console.log(111);
//有且只有一个方式 即返回一个pending状态的promise对象(那么then方法返回的结果p 也变成了一个pending的promise对象)才能终止 因为当p是一个pending状态的promise对象时p的状态没有改变了 后面的then方法才不会执行
return new Promise(() => {});
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
</script>
8.猜猜输出结果
p1.then 是异步执行的 js会先执行同步代码再执行异步代码
即先打印同步 111 333
再打印异步:打印成功的回调 222
小结
async函数
函数的返回值为promise对象
promise对象的结果由async函数执行的返回结果决定
<script>
//和then的返回结果一样
async function main() {
//1. 如果返回值是一个非Promise类型的数据
// return 521;
//2. 如果返回的是一个Promise对象
// return new Promise((resolve, reject) => {
// // resolve('OK');
// reject('Error');
// });
//3. 抛出异常
throw "Oh NO";
}
let result = main();
console.log(result);
</script>
await函数
注意:
await必须写在async函数中,但async函数中可以没有await
如果await的promise失败了,就会抛出异常,需要通过try...catch捕获处理
用法:
await右侧的表达式一般为promise对象,但也可以是其他的值
如果右侧的表达式是promise对像,await返回的是promise成功的结果,如果失败了,就会抛出异常,需要通过try...catch捕获处理
如果表达式是其它值,直接将此值作为await的返回值
<script>
async function main(){
let p = new Promise((resolve, reject) => {
// resolve('OK');
reject('Error');
})
//1. 右侧为promise的情况
// let res = await p;
//2. 右侧为其他类型的数据 直接返回20
// let res2 = await 20;
//3. 如果promise是失败的状态
try{
let res3 = await p;
}catch(e){
console.log(e);
}
}
main();
</script>
第二章
JS Web API
两者的联系
DOM(文档对象模型)
前言
题目
DOM是哪种数据结构?
树(DOM树)
DOM操作的常用API
dom节点操作
dom结构操作
attribute和property的区别?
下面有
一次性插入多个DOM节点,要怎样考虑性能?
可以通过创建一个文档片段的方法去实现 以避免对dom的重复操作 同时要考虑缓存
const list = document.getElementById('list')
//创建一个文档片段(它不会影响dom的性能 因为它游离于dom结构之外 但是存在于js内存中),此时还没有插入到DOM树中
const frag = document.createDocumentFragment()
for (let i = 0; i < 10; i++){
const li = document.createElement('li')
li.innerHTML = `List item${i}`
frag.appendChild(li)
}
//都完成之后 在统一插入dom树中
list.appendChild(frag)
DOM本质(就是一颗树)
DOM和html是不一样的 DOM是html文件解析出来的一颗树 html就是一段代码
DOM节点操作
获取DOM节点
attribute(修改或者增加DOM元素的节点属性)
//setAttribute 增加修改节点
p1.setAttribute('data-name', 'imooc')//key-value的形式 data-name='imooc'会出现到p标签里面
//getAttribute 获取节点
console.log(p1.getAttribute('data-name'));//打印结果为imooc
p1.setAttribute('style', 'font-size:30px')
console.log(p1.getAttribute('style'));//打印结果为font-size:30px
console.log(p1);
property
//DOM节点的property形式-通过获取修改DOM元素的js属性的形式 去改变页面元素的样式
//但是不会体现到html结构当中
两者比较
比较推荐property修改节点属性的方法
DOM结构操作
新增节点 插入节点 移动节点 删除节点
新增节点 document.creatElement()
插入节点 appendChild 插入到最后
获取子元素列表,获取父元素
nodeType:1(元素节点) 2(属性节点) 3(文本节点)
删除子元素
DOM性能(面试常考)
DOM操作非常‘昂贵’,因此我们要避免频繁的DOM操作
我们可以对DOM查询做缓存
将频繁操作改为一次性操作
BOM(浏览器对象模型)
题目
如何识别浏览器的类型
分析拆解url各个部分
navigator
navigator对象 是用来获取浏览器的属性,区分浏览器的类型
screen
location
location对象主要是用来操作URL相关的一些信息,除了修改hash之外的任何URL属性
history
history对象保存着用户上网的历史记录 可以跳转到用户的历史访问页面
事件
DOM事件流的三个阶段是
先捕获阶段(捕获阶段指事件响应从最外层的window开始,逐渐向内层前进,直到具体事件目标元素)
,然后是目标阶段(目标阶段指触发事件的最底层元素)
,最后才是冒泡阶段(冒泡阶段于捕获阶段相反 由内层逐渐向外层window触发事件)
event.stopPropagation();//这行代码的作用是阻止冒泡
题目
编写一个通用的事件监听函数
描述事件冒泡的流程
基于DOM树形结构
事件会顺着触发元素往上冒泡
应用场景:代理
无限下拉的图片列表,如何监听每个图片的点击?
事件绑定
事件冒泡
事件代理
事件代理就是利用事件冒泡或事件捕获的机制把一系列的内层元素相同的一些事件绑定到外层元素
代码简洁 减少浏览器占用 但是不要滥用
JS-Web-API-Ajax
题目
手写一个简易的ajax
跨域的常用实现方式
JSONP
CORS
XMLHttpRequest
状态码
跨域:同源策略,跨域解决方案
XMLHttpRequest(Ajax的核心)
//post请求
const xhr = new XMLHttpRequest()
xhr.open('POST', '/login', false)//false代表异步
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(
JSON.parse(xhr.responseText)//结构化 方便处理
);
} else {
console.log('其他情况');
}
}
}
const postDate = {
userName: 'zhangsan',
passWord:'xxx'
}
xhr.send(JSON.stringify(postDate))
xhr.readyState
xhr.status
重定向不需要我们去处理 是服务器返回状态码3xx后浏览器会自己去跳转
301(永久重定向):如我们想要访问一个a地址 服务器返回一个301 即a的地址定到了地址b 每次访问a时浏览器都会跳转到b
302(临时重定向):如我们想要访问一个a地址 服务器返回一个302 即a的地址定到了地址b 访问a时浏览器跳转到b 但是下次访问a时就不会跳转到b了
304:如我们想要访问一个a地址 服务器返回一个304 即我们想要访问的资源没有被改变 浏览器会用自身缓存的资源
404(客户端请求错误-地址错误):
403(客户端没有权限):
5xx(服务端出错)
同源策略和跨域
什么是跨域(同源策略)?
JSONP
CORS(服务端支持)
同源策略
浏览器的同源策略就是为了去保护用户的隐私
ajax请求时,浏览器要求当前网页和server端必须同源(安全)
同源:协议,域名,端口,三者必须一致
例如:下面的前端和server 它们的协议,域名,端口都不一样 即不同源
前端:a.com:8080/ ;server:b.com/api/xxx (server端没有写端口都是默认80端口)
但是浏览器允许下面这三个b跨域
加载跨域的图片时 如果对方的服务器端给图片加了防盗链的话 那么也就获取不到了 但是浏览器是不限制加载跨域图片的
跨域
只要协议,域名,端口三者有一个不同 都被当做不同的域
跨域即浏览器试图执行其他网站的脚本。但是由于同源策略的限制,导致我们无法实现跨域(脚本是由一种特定的描述性语言,依据一定的格式编写的可执行文件,脚本通常是以文本(ASCII)保存,只是在被调用时进行解释或编译)
JSONP(面试会问)
JSONP是一种跨域请求方式。主要原理是利用了script标签跨域跨域请求的特性,由其src属性发送请求到服务器,服务器返回js代码,浏览器接收响应,然后执行
JSONP由两部分组成即回调函数和数据,回调函数一般是浏览器控制,回调函数作为参数发往服务器端,然后服务器端注入参数后再返回 实现服务器端和客户端通信。当服务器响应时,服务器端就会把该函数和数据拼成字符串返回。
服务器可以拼接任何数据返回
JSONP的请求过程:
请求阶段:浏览器创建一个script标签,并给src赋值
发送请求:给script的src赋值时,浏览器就会发送一个请求(回调函数作为参数被传递)
数据响应:服务器端将要返回的数据作为参数和函数名称拼接在一起返回。客户端接收到js数据并且开始解析执行
//实现流程
//1.浏览器创建一个script标签,并给src赋值 回调函数作为参数被传递
<script src="http://jsonp.js?callback=xxx"></script>
JS-Web-API-存储
描述cookie localStorage sessionStorage 区别
cookie(最早的存储)
本身用于浏览器和server通讯
被“借用”到本地存储
可用document.cookie = '...'来修改
保存cookie值
document.cookie = 'a = 100; c = 300';
document.cookie = 'b = 100';
//下面是打印一下
document.cookie
'a = 100; b = 100';//追加的信息是叠加的 一次只能加一个
localStorage
sessionStorage
三者异同:
cookie-可设置失效时间,没有设置的话,默认是关闭浏览器后失效
localStorage-除非被手动清除,否则将会永久保存
sessionStorage-仅在当前网页会话下有效,关闭页面或浏览器后就会被清除
存放数据大小:
cookie:4KB左右
localStorage和sessionStorage:html5专门为存储而设计的,可以保存5MB的信息。
http请求:
cookie:每次都会携带在HTTP头中,如果使用cookie保存过多数据会带来性能问题
localStorage和sessionStorage:仅在客户端(即浏览器)中保存,不参与和服务器的通信
易用性:
cookie:需要程序员自己封装,源生的Cookie接口不友好
localStorage和sessionStorage:源生接口可以接受,亦可再次封装来对Object和Array有更好的支持
应用场景:
从安全性来说,因为每次http请求都会携带cookie信息,这样无形中浪费了带宽,所以cookie应该尽可能少的使用,另外cookie还需要指定作用域,不可以跨域调用,限制比较多。但是用来识别用户登录来说,cookie还是比stprage更好用的。其他情况下,可以使用storage,就用storage。
storage在存储数据的大小上面秒杀了cookie,现在基本上很少使用cookie了,因为更大总是更好的
localStorage和sessionStorage唯一的差别一个是永久保存在浏览器里面,一个是关闭网页就清除了信息。localStorage可以用来夸页面传递参数,sessionStorage用来保存一些临时的数据,防止用户刷新页面之后丢失了一些参数。
第三章
关于开发环境
git
控制台提交代码的方式 假如创建了一个分支(mimall-start)并且修改过东西 我们需要怎样同步到master上面?并且实时提交到码云上面?
1.git pull (把远程仓库拉到本地)
2.git checkout mimall-start(切换到刚刚创建的分支)
3.git add . (把分支上开发的代码添加 上准备提交到码云仓库)
4.git commit -m '日志名字'
5.git push (这一步是把分支修改的东西先提交到码云仓库里面)
6.git checkout master(这一步是切换到主分支上面 准备把分支上面修改的东西合并到主分支上面来)
7.git merge mimall-start(这一步就是把分支上面修改的东西合并到主分支上面来)
8.git push (把合并后的主分支也提交到码云仓库上面去)
常见的其他git命令
git branch 列出本地的所有分支,当前的分支会以” * “被标出
创建新分支,新的分支基于上一次提交建立 git branch <分支名>
修改分支名称 # 如果不指定原分支名称则为当前所在分支 git branch -m [<原分支名称>] <新的分支名称>
强制修改分支名称 git branch -M [<原分支名称>] <新的分支名称>
删除指定的本地分支 git branch -d <分支名称>
git diff 用于显示提交和工作树等之间的更改
git fetch 从远程仓库获取最新的版本到本地的tmp分支上
git init 初始化项目所在目录,初始化后会在当前目录下出现一个名为.git的目录 (此目录存储了所有项目历史数据和元信息。)
chrome调试工具
Elements-看样式
Console
debugger-断点
Network-看资源的加载成功与否
Application
Sources-调试代码
抓包
抓包的过程
手机和电脑连同一个局域网
将手机代理到电脑上
手机浏览网页,即可抓包
查看网络请求 网址代理 https
webpack和babel
webpack是一个打包的工具 babel是将es6转换为es5的工具 解决浏览器的兼容问题 这两个经常一起使用
es6模块化
linux常用命令
\
第四章
运行环境
网页加载过程
题目
从输入url到渲染出页面的整个过程?
下载资源:各个资源类型,下载过程
渲染页面:结合html css javascript图片等
window.onload和DOMContentLoaded的区别?
加载资源的形式
主要加载以下这三块
html代码
媒体文件,如图片,视频
javascript css
加载资源的过程
DNS解析:域名 - > IP地址 (把域名解析为ip地址)
浏览器根据ip地址向服务器发起http请求
服务器处理http请求,并返回给浏览器
渲染页面的过程
根据html代码生成DOM Tree
根据css代码生成CSSOM
将DOM Tree 和CSSOM整合形成 Render Tree(渲染树)
根据Render Tree渲染页面
遇到
直至把Render Tree渲染完成
性能优化,体验优化
是一个综合性问题,没有标准答案,但要求尽量全面
某些细节问题可能会单独提问:手写防抖,节流
性能优化原则: 多使用内存,缓存或其他方法;减少cpu计算量,减少网络加载耗时
(使用于所有编程的性能优化-空间换时间)
防抖,节流
防抖就是防止抖动,什么时候停止抖动了再执行下一步
例如一个搜索框,等你输入停止后再触发搜索相关的产品
//debounce 防抖
function debounce1(fn,delay = 500) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this.arguments)
timer = null
}, delay);
}
}
节流就是节省交互沟通。流,不一定指流量
一个一个来,按时间节奏来,插队的无效
列如,drag或scroll期间触发某个回调,要设置一个时间间隔
//throttle 节流
function throttle(fn,delay = 200) {
let timer = null
return function (){
if(timer) {return}//一个一个来 必须等它执行完
timer = setTimeout(()=>{
fn.apply(this,arguments)
timer = null
})
}
}
区别:
节流是通过限制执行的频率,来有节奏的执行
防抖是通过限制执行的次数,时多次密集的触发只执行一次
节流关注过程,防抖关注结果
实际工作中有一个专门的库让我们实现节流防抖,不需要我们手写 lodash.com
性能优化如何入手?
让加载更快
1.减少资源体积:压缩代码
2.减少访问次数:合并代码,SSR服务器端渲染,缓存
缓存:
静态资源加hash后缀,根据文件内容计算hash
文件内容不变,则hash不变,则url不变
url和文件不变,则会自动触发http缓存机制,返回304
SSR:
服务器端渲染:将网页和数据一起加载,一起渲染
非SSR(前后端分离):先加载网页,再加载数据,再渲染数据
服务端渲染从早先的JSP,ASP,PHP到现在的Vue React SSR
3.使用更快的网络:CDN(根据区域去做服务器的处理-做静态文件)
让渲染更快-1
1.CSS放在head里面,js放在body最下面
2.尽早开始执行js,用DOMContentLoaded触发
3.懒加载(图片懒加载,上滑加载更多)
一开始只加载一个预览图preview.png 当图片加载好了再显示
让渲染更快-2
1.对DOM查询进行缓存
2.频繁DOM操作,合并到一起插入DOM结构
3.节流throttle 防抖debounce
前端安全
题目
1.常见的web前端攻击方式有哪些?
XSS跨站请求攻击
XSRF跨站请求伪造
XSS攻击
xss预防
XSRF攻击-1
XSRF攻击-2
XSRF预防
面试真题
题目-1
var和 let const的区别
var是ES5语法,let const 是ES6语法;var有变量提升
var 和 let是变量可以修改;const是常量,不可修改
let const 有块级作用域,var 没有
typeof返回哪些类型
值类型:undefined string number boolean symbol
引用类型:object(注意typeof null === ‘object’)
function(有引用类型的特点 但是一般不作为引用类型数据 因为function是一个可执行的东西它会作为一种方法)
列举强制类型转换和隐式类型转换
==就是数字和字符串进行比较时 会把字符串转换为数字型再去比较即转换为一样的类型再比较
题目-2
手写深度比较,模拟lodash isEqual
见代码
split()和join()的区别
split() 把字符串分割成数组
join() 把数组拆分为字符串
数组的pop push unshift shift 分别做什么
const arr = [10, 20, 30, 40]
// // pop
const popRes = arr.pop()
console.log(popRes, arr)//40 [10, 20, 30]
// // shift
const shiftRes = arr.shift()
console.log(shiftRes, arr)//10 [20,30,40]
// push
const pushRes = arr.push(50) // 返回 length
console.log(pushRes, arr)//5 [10,20,30,40,50]
// // unshift
const unshiftRes = arr.unshift(5) // 返回 length
console.log(unshiftRes, arr)//5 [5,10,20,30,40]
pop:去掉数组最后一个元素 返回去掉的这个元素 会改变原数组
push:在数组最后面添加一个元素 返回增加后的数组长度 会改变原数组
shift:去掉数组最前面的一个元素 返回去掉后的这个元素 会改变原数组
unshift:在数组最前面添加一个元素 返回添加后的数组长度 会改变原数组
数组的API有哪些是纯函数?
纯函数:1.操作数组后不改变原数组(没有副作用);2.返回一个操作后的新数组; 3.原数组和新数组的改变互不影响
// // 纯函数:1. 不改变源数组(没有副作用);2. 返回一个数组
const arr = [10, 20, 30, 40]
// // concat
const arr1 = arr.concat([50, 60, 70])//在原数组后添加若干个元素 返回给一个新数组arr1
// // map
const arr2 = arr.map(num => num * 10)//把原数组的元素都扩大十倍 返回给一个新数组arr2
// // filter
const arr3 = arr.filter(num => num > 25)//把原数组中>25的元素过滤出来 返回给一个新数组arr3
// // slice
const arr4 = arr.slice()//类似于做了一个深拷贝
题目-3
数组slice和splice的区别
功能区别 (slice-切片 splice-剪接)
// // slice 纯函数 不会改变原数组 返回值是一个新数组
const arr1 = arr.slice()
const arr2 = arr.slice(1, 4)//在数组中 从索引号1开始截取 截到索引号为4 1要4不要
const arr3 = arr.slice(2)//从索引号2开始截取后面的全部 [30, 40, 50]
const arr4 = arr.slice(-3)//截取最后三个
// // splice 非纯函数会改变原数组 返回值是被剪切掉的数组部分
const spliceRes = arr.splice(1, 2, 'a', 'b', 'c')//剪接 20,30的位置替换成字符串'a', 'b', 'c'
// const spliceRes1 = arr.splice(1, 2)//只剪不接
// const spliceRes2 = arr.splice(1, 0, 'a', 'b', 'c')//只接不剪
console.log(spliceRes, arr)
参数和返回值
是否是纯函数?
[10,20,30].map(parseInt)返回结果是什么?
map的参数和返回值
parseInt参数和返回值
const res = [10, 20, 30,12].map(parseInt)
console.log(res)//10 NaN NaN 5
// // 上面的拆解
[10, 20, 30,12].map((num, index) => {
return parseInt(num, index)//将num转换为index进制的整数 所以第二个参数代表的是进制 num的每个数字应该小于index进制 否则输出的就是NaN
})
parseInt(10, 0)//这里写0其实和写10区别是一样的 即把10转换为10进制 那么就是10
parseInt(20, 1)//这里的转换就不符合进制
ajax请求get和post的区别?
- 从功能上讲,
GET一般用来从服务器上获取资源,POST一般用来在服务器上新增资源; - 从
REST服务角度上说,GET是幂等的,而POST不是幂等的; - 从请求参数形式上看,
GET请求的数据会附在URL之后,POST请求会把提交的数据放置在是HTTP请求报文的请求体中; - 就安全性而言,
POST的安全性要比GET的安全性高,因为 GET 请求提交的数据将明文出现在URL上,而且POST请求参数则被包装到请求体中,相对更安全; - 从请求的大小看,
GET请求的长度受限于浏览器或服务器对URL长度的限制,允许发送的数据量比较小,而POST请求理论上是没有大小限制。
post和put的区别?
2. POST和PUT请求的区别
- PUT请求是向服务器端发送数据,从而修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次PUT操作,其结果并没有不同。(可以理解为时更新数据)
- POST请求是向服务器端发送数据,该请求会改变数据的种类等资源,它会创建新的内容。(可以理解为是创建数据)
题目-4
函数call和apply的区别?
call参数是零散的传进去
apply的参数是以一个集合或数组的形式整体传进去
事件代理(委托)是什么?
事件代理就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类的所有事件
闭包是什么,有什么特性?有什么负面影响?
闭包:在新函数被创建并且赋值给一个变量时,它都要存储自己的函数定义和闭包,闭包就是包含了函数创建时作用域中的所有变量,类似于一个背包
负面影响:
变量会常驻内存,得不到释放。闭包不要乱用
题目-5
如何阻止事件冒泡和默认行为?
阻止冒泡:event.stopPropagation()
阻止默认行为:event.preventDefault()
查找,添加,删除,移动DOM节点的方法?
添加节点:先create一个元素再appendChild()去目的地
移动节点:先get要移动的那个元素再appendChild(那个元素)去目的地
删除节点:removeChild()
如何减少DOM操作?
为什么这样做-因为DOM查询和操作是很耗性能的
缓存DOM查询结果;将多次DOM操作合并到一个文档片段 之后再一次性插入到DOM树中
题目-6
理解jsonp的原理,为何它不是真正的ajax?
jsonp执行时定义一个全局函数然后去访问一个js 返回的是一个函数的执行 它没有用到XMLHttpRequest()
document load和ready(DOMContentLoaded)的区别
- css加载不会阻塞DOM树的解析
- css加载会阻塞DOM树的渲染
- css加载会阻塞后面js语句的执行
==和===的不同
==会尝试类型转换
===是严格相等 即值和地址
题目-7
函数声明和函数表达式的区别
// // 函数声明
const res1 = sum(10, 20)
console.log(res1)//这里正常输出 30 因为下面的函数声明会在代码执行前进行预加载
function sum(x, y) {
return x + y
}
// // 函数表达式
var res2 = sum(10, 20)
console.log(res2)//下面用var这里报错显示sum不是一个函数而是一个undefined 因为上面执行函数时 函数表达式没有加载但是变量提升了
//下面用const这里报错显示sum找不到 因为上面执行函数时 函数表达式没有加载也不会变量提升
var(const) sum = function (x, y) {
return x + y
}
new Object和Object.create()的区别
后者可以指定原型 前者
关于this的场景题
题目-8
关于作用域和自由变量的场景题-1
判断字符串以字母开头,后面字母数字下划线,长度6-30
考的是正则表达式
关于作用域和自由变量的场景题-2
// let a = 100
// function test() {
// alert(a)
// a = 10
// alert(a)
// }
// test()//100 10
// alert(a)//10
//函数定义的时候 不需要分析函数里面的变量的赋值内容
//第一步 执行test()
//第二步 此时函数执行里面的第三行alert(a) 此时a就是第一行赋值的let a = 100
//第三步 第四行的a由100被改变赋值为10
//第四步 执行第五行alert(a) 此时 弹出10
//第五步 执行第九行 此时a已经由100改变赋值为为10 弹出10
题目-9
手写字符串trim方法,保证浏览器兼容性
JS的trim方法用于删除字符串的头尾空格,兼容性为IE9+,使用起来也很简单:
string.trim()
复制代码
其实自己实现起来也很简单,使用正则即可,我们定义一个函数:
funchtion myTrim(str) {
return str.replace(/^\s+|\s+$/gm,'');
}
//把正则表达式/^\s+|\s+$/gm命中的替换为空字符串
trim() 方法用于删除字符串的头尾空白符,空白符包括:空格、制表符 tab、换行符等其他空白符等。
trim() 方法不会改变原始字符串。
trim() 方法不适用于 null, undefined, Number 类型。
如何获取多个数字中的最大值?
方法一
方法二
如何用js实现继承
class继承
prototype继承(es5的方法 现在已经不太使用了)
题目-10
如何捕获js程序中的异常?
有两种方式 都在下面的图里
什么是JSON?
json是一种数据格式,本质是一段字符串
json格式和js对象结构一致,对js语言更友好
window.JSON是一个全局对象:JSON.stringify JSON.parse
json里面都用的" "
获取当前页面参数?
1.传统方式查找location.search
2.新API,URLSearchParams
题目-11
将url参数解析为JS对象
手写数组flatern(拍平),考虑多层级
完成下面的功能 无论层级多深 都变为一层铺开
concat拍平方式 但是只能拍平一层
数组去重
传统方式,遍历元素挨个比较,去重
见代码
使用Set
见代码
考虑计算效率
题目-12
手写深拷贝
手写深拷贝见代码
Object.assign 只是深拷贝了第一层的数据 再往深了的层数就是浅拷贝了
介绍一下RAF requestAnimationFrame
requestIdleCallback 在浏览器空闲的时候才调用执行 是低优的
这是两个都是异步任务(宏任务 在DOM渲染完再去执行)
iframe?优缺点?
iframe全称 inline frame。顾名思义就是嵌套在一个HTML上下文中的窗口。尽管是嵌入到另一个文档中的上下文,但iframe仍然拥有属于自己的滚动条
优点:
1.作为一个完全独立的窗口去运行另一个页面,而不用去担心污染
2.解决加载缓慢的第三方内容如图标和广告等的加载问题
3.并行的加载资源文件
缺点:
1.占用同源连接数,对于每个浏览器,都会去控制并发的同源的连接数。
2.iframe无法使用浏览器的前进和后退键
3.对SEO不友好
4.阻塞onload的加载
前端性能如何优化?一般从哪几个方面考虑?
原则:多使用内存,缓存,减少计算
方向:加载页面,页面渲染,页面操作流畅度
for...in和for...of 有什么区别
for...in遍历 拿到的是key即index 用于可枚举数据,如对象,数组,字符串(里面的enumerable为true)
for...of遍历 拿到的是value 用于可迭代(有next)数据,如数组,字符串,Map,Set
总结:
- for in 循环主要是为了遍历对象而生的(会遍历对象的整个原型链,而for of只会遍历当前的对象)
- for...of(es6新增的遍历方式)遍历可用来遍历数组,类数组对象,字符串,Map,Set以及generator对象
遍历对象:for...in可以但是for...of不可以
遍历Map Set:for...of可以,但是for...in不可以
遍历generator:for...of可以,for...in不可以
连环问:for await of有什么作用
它是Promise.all这个API的一个替代品可以用来遍历promise
JS内存泄漏如何检测?场景有哪些?
为什么考?近几年前端功能不断复杂,内存问题也要重点考虑
首先我们要了解一下GC垃圾回收:主要是回收一些函数执行完成后用不到的一些数据,垃圾回收的算法之前是用引用计数的算法,现在是用标记清除的算法
引用计数:当数据的引用计数为0时就清除
//对象被a引用
let a = {x:100}//对象{x:100}的引用为1
let a1 = a//{x:100}的引用为2
a = 10//{x:100}的引用为1
a1 = null//{x:100}的引用为0 则会清除它
标记清除:遍历window下的各个属性 遍历完之后依然能拿到的数据就不清除
内存泄漏就是程序员非预期存在的变量在程序执行完之后依然存在
连环问:闭包是内存泄漏吗? 一般来说不属于内存泄漏(即不能被垃圾回收),因为闭包是一种符合预期的存在情况,接下来返回的一些函数可能还会用到闭包里面的数据
检测:在控制台的performance里面看
内存泄漏的场景(Vue为例)
- 被全局变量,函数引用,组件销毁时未被清除
- 被全局事件,定时器引用,组件销毁时未清除
- 被自定义事件引用,组件销毁时未清除
连环问:WeakMap WeakSet
WeakMap是一种弱引用 它引用的数据可能会被清除 没有size,forEach等功能,只能通过get尝试去获取
const wmap = new WeakMap()//弱引用
function fn1(){
const obj = {x:100}
wmap.set(obj,100)//WeakMap的key是能是引用类型
}
WeakSet 当大家都存在的时候就在建立联系 并且不影响大家的销毁 不影响垃圾回收不带来内存泄漏的风险
题目-13
遍历数组,for和forEach哪个快?
事件复杂度都是O(n)
上图for更快
因为forEach每次都要创建一个函数来调用,而for不会创建函数,
函数需要独立的作用域,会有额外的开销
越低级的代码,性能往往越好
循环vs递归:循环是一次性计算没有创建函数 递归每一次都会创建一个独立的函数
什么是JS Bridge
- js无法直接调用native API
- 需要通过一些特定的“格式”来调用
- 这些“格式”就统称JS-Bridge,例如微信JSSDK
map和forEach的区别?
这两个方法都是用来遍历数组的
map:map方法不会改变原数组的值,返回一个新的数组,新数组里面的值为原数组调用回调函数处理之后返回的值
forEach:forEach方法不会改变原数组的值,不会返回一个新的数组
map与forEach其实都是JS中,对array进行遍历的方法,区别在于map是存在返回值的,而forEach返回值为undefined
- 针对于原数组里面的值类型的数组元素 它们不会修改原数组
- 针对于原数组里面的引用类型的数组元素 它们会修改原数组
const arr1 = [1, 2, 3, 4]//数组里面是值类型的数组元素
const arr2 = [{ name: '111' }, { name: '222' }, { name: '333' }]//数组里面是引用类型的数组元素
//针对于原数组里面的值类型的数组元素 它们不会修改原数组
const result1 = arr1.map((item, index, arr) => {
console.log(item);
console.log(index);
// arr[index] += 2//不管是值类型或者是引用类型 通过下标都会修改原数组
item = 123//这里不会改变数组的值类型数组元素
console.log(arr);
return item + 1//要return 不然result里面都是undefined
})
console.log('result1', result1);
console.log('arr1', arr1);
//针对于原数组里面的引用类型的数组元素 它们会修改原数组
const result2 = arr2.map((item, index, arr) => {
console.log(item);
console.log(index);
// arr[index] += 2//不管是值类型或者是引用类型 通过下标都会修改原数组
item.name = '123'//这里会改变数组的引用类型的数组元素
console.log(arr);
return item + 1//要return 不然result里面都是undefined
})
console.log('result2', result2);
console.log('arr2', arr2);
Ajax Fetch Axios三者的区别?
三者都用于网络请求,但是不同维度
Ajax是一种技术统称 可以用XML实现也可以用fetch实现
Fetch是一个具体的API:
- 浏览器原生的APi,用于网络请求
- 和XMLHttpRequest一个级别的 是它的升级版本
- Fetch语法更加简洁,易用,支持Promise
function ajax2(url){
return fetch(url).then(res=>res.json())
}
Axios是一个第三方库 可以通过下载安装使用
- 最常用的网络请求库(随着vue火起来的 可以在node.js里面使用)
- 内部可用XML和Fetch实现
lib(库)和API的区别:库是一个第三方的工具 API是一个原生的函数
重排(回流)和重绘?
重排 重排是指部分或整个渲染树需要重新分析,并且节点的尺⼨需要重新计算。
表现为 重新⽣成布局,重新排列元素。
重绘 重绘是由于节点的⼏何属性发⽣改变,或由于样式发⽣改变(例如:改变元素背景⾊)。
表现为某些元素的外观被改变。或者重排后, 会进行重新绘制!
两者的关系
重绘不⼀定会出现重排,重排必定会触发重绘。
每个页面至少需要一次回流+重绘。(初始化渲染)
重排和重绘的代价都很⾼昂,频繁重排重绘, 会破坏⽤户体验、让界面显示变迟缓。
我们需要尽可能避免频繁触发重排和重绘, 尤其是重排
何时会触发重排?
重排什么时候发生?
1、添加或者删除可见的DOM元素;
2、元素位置改变;
3、元素尺寸改变——边距、填充、边框、宽度和高度
4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;
5、页面渲染初始化;
6、浏览器窗口尺寸改变——resize事件发生时
总结:总的来说布局发生了改变就会触发重排(回流),样式发生了改变就会触发重绘 但是重绘不⼀定会出现重排,重排必定会触发重绘。