html
1.DIV+CSS布局的好处
1.代码精简,样式和结构相分离,易于维护
2.代码量减少,页面加载更快,提升用户体验
3.允许更多酷炫的页面效果,丰富页面
4.符合W3C标准,保证网站不会因网页升级而淘汰
2.H5的新标签元素有哪些
结构标签
- article 用于定义一篇文章
- header 用于定义页面的头部
- footer 用于定义页面的底部
- nav 导航条链接
- section 定义一个区域
多媒体标签
- vedio 定义一个视频
- autoPlay 是否自动播放
- controls 是否展示控制器
- audio 音频
- autoPlay 是否自动播放
- loop 重复播放次数(loop = '-1' 表示无限播放,等于其他数字等于播放次数)
- source 定义媒体资源
- 用于audio/video标签内部,并支持不同格式的媒体文件,
type属性用于转码格式
<audio> <source src="音频资源" type="audio/转码格式"> </audio> <audio> <source src="horse.mp3" type="audio/mpeg"> </audio> - 用于audio/video标签内部,并支持不同格式的媒体文件,
canvas 绘图
Web 标签
- mark 用于标黄
<p>
<mark>这条内容会有黄色背景</mark>
</p>
- progress 显示任务过程,安装,加载
- value 当前状态值
- max 最大状态值
<progress max="100"></progress>
Form 新表单类型和属性
- 类型 (email,number,url,range,tel,search,date...)
<input type="email"/> // 输入邮箱地址
- 属性(placeholder,required,min,max,step,multiple...)
<input placeholder="请输入数字" step="1"/>
CSS
1.如何解决a标签点击后hover事件失效的问题?
改变a标签CSS属性的排列顺序,遵循 LoVe HAte原则
link->visited->hover->active
错误代码示范:
a:hover{
color: green;
}
a:visited{ /*visited在hover后面,此时的hover事件失效 */
color: red;
}
正确代码:
a:visited{
color: red;
}
a:hover{
color: green;
}
- a:link 未访问时的样式,一般省略用a
- a:visited 已经访问后的样式
- a:hover 鼠标移上去的样式
- a:actived 鼠标按下时的样式
2.盒模型
盒模型是由 margin,padding,content,border 组成
两者区别:在设置了宽高属性时,两者所包含的范围不同,标准模式下只包含content内容;怪异模式下则是包含了content+padding+border
标准盒模型(W3C盒模型)
box-sizing:content-box // 盒子的大小就是content的大小
IE盒模型(怪异盒模型)
box-sizing:border-box // 盒子的大小是content+padding+border
3.垂直居中的实现方式
- 当盒子没有宽高
.parent {
position: relative
}
.child {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
- 当盒子有宽高
.parent {
position: relative
}
.child {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
- 当盒子有宽高已知
.parent {
position: relative
}
.child {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px; /*负自身height的一半*/
margin-left: -50px; /*负自身width的一半*/
}
- flex布局
.parent {
display: flex;
justify-content: center;
align-items: center;
}
4. 组件样式穿透
普通样式穿透
.search-box >>> button:focus{
color: lightsalmon;
border: 1px solid #dedede;
}
scss语法
::v-deep button{
border:none;
}
less语法
/deep/ button{
border:none;
}
5. px、em、rem的区别及使用场景
区别:
- px是固定像素,无法根据页面的大小改变
- rem是相对于根元素来设置字体大小,其长度不是固定,更适用于响应式布局
- em是相对于其父元素来设置字体大小,其长度不是固定,更适用于响应式布局
6. 响应式
响应式网站设计是一个网站能够兼容多端,而不是为每个终端做一个特定的版本
- 原理:通过媒体查询
@media查询不同设备屏幕尺寸做处理 - 兼容:页面头部需要设置
meta声明的viewport
7. Flex
即弹性布局,C3新增的一种布局方式,通过设置display: flex使盒子成为一个flex容器。在flex布局下,float,vertical-align属性都将失效。
-
flex-direction:设置主轴方向
1.row(默认值): 从左往右 2.column: 垂直从上往下 3.row-reverse: 从右往左 4.column-reverse: 从下往上 -
jusity-content: 在主轴的对齐方向
1. flex-start(默认值): 左对齐 2. center: 居中对齐 3. flex-end: 右对齐 4. space-between: 两端对齐,项目之间间隔相等 5. space-around: 每个项目木两侧的间隔相等 -
align-items: 在交叉轴的对齐方向
1. flex-start: 起点对齐 2. center: 居中对齐 3. flex-end: 终点对齐 4. baseline: 项目的第一行文字的基线对齐 5. stretch(默认值): 如果项目未设置高度或设为auto,将占满整个容器的高度。 -
flex-wrap: 指定排行排列不下时的换行方式
1. nowrap(默认值): 不换行 2. wrap: 换行,第一行在上方 3. wrap-reverse: 换行,第一行在下方
8. 回流和重绘
定义:当js对页面的节点进行操作时,则会产生重绘和回流
- 重绘:颜色等不影响布局改变就是重绘
- 回流:位置大小等改变就是回流
tips:重绘不一定会造成回流,但回流一定会触发重绘
引起回流的原因
1. DOM的增加或者删除
2. 元素尺寸,大小,边距,宽高等改变
3. 浏览器窗口的变化
4. 操作某些样式(offset,client,scroll等等)
9. BFC的理解,如何创建BFC
BFC(Block Formatting Context),即块级格式化上下文,它是页面中渲染的一个区域,并且有属于自己的渲染规则:
- 内部的盒子会在垂直方向上,自上而下的排列
- 对于同一个BFC中,上下相邻的两个盒子的margin会发生重叠
- BFC的区域不会与浮动元素的区域块重叠
- 计算BFC的高度时需要计算浮动元素的高度
- 每个元素的左margin值和容器的左border相接触
- BFC是独立的容器,容器内部元素不会影响外部元素
触发BFC的条件包含但不限于:
- 根元素: body
- 浮动元素: float除none以外的值
- 设置绝对定位: position(absolute、fixd)
- overflow: hidden、auto、scroll
- display: flex、inline-block、table等
应用场景
1. 防止盒子塌陷(margin重叠)
<style>
p {
color: #f55;
background: #fcc;
width: 200px;
line-height: 100px;
text-align:center;
margin: 100px;
}
</style>
<body>
<p>Haha</p >
<p>Hehe</p >
</body>
页面效果:
两个P元素之间的间距为100px,正确应该是200px,此时发生了margin重叠(塌陷),如果第一个p的margin: 80px,两者间的间距还是100px,盒子塌陷以最大的值为准
解决: 将其中一个p标签包裹起来使其成为一个独立的BFC
<style>
.wrap {
overflow: hidden;// 新的BFC
}
p {
color: #f55;
background: #fcc;
width: 200px;
line-height: 100px;
text-align:center;
margin: 100px;
}
</style>
<body>
<p>Haha</p >
<div class="wrap">
<p>Hehe</p >
</div>
</body>
页面效果:
2. 清除内部浮动
<style>
.par {
border: 5px solid #fcc;
width: 300px;
}
.child {
border: 5px solid #f66;
width:100px;
height: 100px;
float: left;
}
</style>
<body>
<div class="par">
<div class="child"></div>
<div class="child"></div>
</div>
</body>
页面效果:
解决: BFC在计算高度时,浮动元素也会参与,所以可以给.par区域块设置成BFC,则内部浮动元素计算高度时候也会计算
.par {
overflow: hidden;
}
页面效果:
3. 自适应多栏布局
举个栗子:两栏布局<style>
body {
width: 300px;
position: relative;
}
.aside {
width: 100px;
height: 150px;
float: left;
background: #f66;
}
.main {
height: 200px;
background: #fcc;
}
</style>
<body>
<div class="aside"></div>
<div class="main"></div>
</body>
页面如下:
此时盒子两个元素的左margin值和容器的左border相接触,虽然.aside为浮动元素,但是.main的左边距依旧会与容器盒子的左border相接触
解决: 通过设置.main区域为新的BFC,这时新的BFC不会与浮动的.aside元素重叠。会根据容器的宽度和.aside的宽度自动变窄
页面如下:
小结: 可以看到上面几个案例,都体现了BFC实际就是页面一个独立的容器,里面的子元素不影响外面的元素
10. iframe标签
规定一个内联框架。内联框架就是在一个页面中嵌入另一个页面
优点:
- 可以减少数据的传输,减少网页的加载时间
- 使用起来方便
- 方便开发,减少代码的重复率
缺点:
- 部分使用会产生跨域
- 会产生很多的页面,不易于管理
- 浏览器的后退按钮无效
iframe的常用属性:
- height:框架作为普通元素的高度
- width: 框架作为普通元素的宽度
- src:框架的地址,可使用页面的地址,也可以是图片地址
- scrolling: 框架是否滚动
- name: 框架名称
- frameborder: 框架是否显示边框
JS
1.null 和 undefined 的区别
null表示无,此处不应该有值;undefined表示未定义- 转换Number类型中结果不同
Number(null) -> 0
Number(undefined) -> NaN
使用场景上:
null:
- 作为函数的参数,表示该函数的参数不是对象
- 作为对象原型链的终点
undefined:
- 变量被声明了但没有赋值,代码运行时就会报该变量
undefined的错误 - 调用函数时,应该提供的参数没有提供,该参数则
undefined - 对象没有赋值属性,该属性的值为
undefined - 函数没有返回值时,默认返回
undefined
2.数组去重
- Array.from(new Set(arr))或[...new Set(arr)]
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(Array.from(new Set(arr))
// console.log([...new Set(arr)])
- 循环嵌套利用splice去重
function unique(origin) {
let arr = [].concat(origin);
for(let i = 0; i < arr.length; i++) {
for(let j = i + 1; j < arr.length; j++) {
if(arr[i] === arr[j]) {
arr.splice(j,1);
j--;
}
}
}
return arr;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(unique(arr));
- 利用includes去重
function unique(arr) {
let res = [];
for(let i = 0; i < arr.length; i++) {
if(!res.includes(arr[i]) {
res.push(arr[i])
}
}
return res;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
console.log(unique(arr));
3.如何理解JS中的this关键字
this指向最后调用函数的那个对象,是函数运行时内部自动生成的一个内部对象,只能在函数内部使用。
4.call、apply、bind相同和不同
call 和 apply 的不同在于传入的参数不同
call 接收单个的参数, apply 接收数组
call 的语法:
f.call(thisObj,arg1,arg2,...)
call 的作用:
- 改变函数的this指向
var a = {
name:'完美日记'
}
function b () {};
b.call(a); // 输出 {name:'完美日记'}
- 调用函数
var a = {
name:'完美日记'
}
function b (a, b) {
console.log(a + b);
};
b.call(a, 1, 2); // 3
- 实现继承
function Father (name,age) {
this.name = name;
this.age = age;
}
function Son (name,age) {
Father.call(this,name,age);
}
var son = new Son('南瓜',1);
console.log(son); // Son {name: "南瓜", age: 1}
apply 的语法
f.apply(thisObj,[...args])
例子:
function Father (name,age) {
this.name = name;
this.age = age;
}
function Son (name,age) {
Father.apply(this,[name,age]);
}
var son = new Son('南瓜',1);
console.log(son); // Son {name: "南瓜", age: 1}
bind 的语法
f.bind(thisObj,args1,arigs2,...)
例子:
var a = {
name: '南瓜'
}
function b (c, d) {
console.log(this);
console.log(c + d);
}
b.bind(a)(); // {name: "南瓜"}
b.bind(a,1,2)(); // 3
总结
相同:都是用来改变函数执行时的上下文(改变函数运行时this的指向)
不同:
- apply 和 call 都可以调用函数,call 接收的足一个参数,而 apply 接收的是个数组
- bind 和 call 的传参方式相同,但不会调用函数,且返回值是一个原函数拷贝
5.什么是闭包?在什么场景中使用过。
闭包指有权访问另一个函数作用域中变量的函数。闭包的主要作用:延伸了变量的作用范围。
function fn () {
var num = 10;
function fun () {
console.log(num);
}
fun();
}
fn();
// fun 函数作用域 访问了另外一个 fn 里的局部变量,就产生了闭包,此时fn也叫闭包函数。
function fn () {
var num = 10;
return function fun () {
console.log(num);
}
}
fn()(); // 10
// 外部函数调用另外函数内部的变量
6.节流和防抖
节流与防抖的实现直接涉及到了闭包知识
节流
// 时间戳版本
function throttle(fn,delay) {
// 记录上一次函数触发的时间
var lastTime = Date.now();
return function() {
// 记录当前函数触发的时间
var nowTime = Date.now();
if(nowTime - lastTime >= delay) {
// 修正this指向问题
fn.call(this);
// 同步时间
lastTime = Date.now();
}
}
}
节流使用的场景: 1.拖拽功能实现。 2.搜索联想
防抖
function deboounce(fn,delay) {
// 记录上一次的延时器
var timer = null;
return function() {
// 如果此时存在定时器,则取消之前的定时器重新计时
if(timer){
clearTimeout(timer);
timer = null;
}
// 设置定时器,使事件间隔指定事件后执行
timer = setTimeout(function() {
fn.apply(this);
},delay)
}
}
防抖使用的场景:重复ajax请求。
7. 深浅拷贝
function DeepClone(obj){
if(!obj || typeof obj !== 'object') return;
let newObj = Array.isArrary(Obj) ? [] : {};
for(let key in obj){
if(Object.hasOwnProperty(key)){
newObj[key] =
typeof obj[key] === 'Object' ? DeepClone(obj[key]) : obj[key];
}
}
return newObj;
}
8. 描述一下Promise
promise是一个对象,它代表了一个异步操作的最终完成或失败。由于它的then方法和catch,finally方法会返回一个新的Promise所以允许我们链式调用,解决了传统的回调地狱问题。含有三个状态resolve,reject,pendding。
9. EvenLoop 的执行过程
- 一开始整个脚本作为宏任务开始执行
- 执行过程中同步代码直接执行,宏任务进宏任务队列,微任务进微任务队列
- 当前宏任务执行完出队,检查微任务列表,列表依次执行,直到全部执行完
- 执行浏览器
UI线程的渲染工作 - 检查
web worker任务是否存在,有则执行 - 执行完本轮宏任务出队,回到2,依次执行,直到宏微队列全部清空
微任务包括:
MutationObserver、Promise.then()或者catch()、Promise。宏任务包括:
script、setTimeout、setInterval、setImmediate、I/O、UI rendering。10. 如何使0.1+0.2===0.3
i. 浮点数相加结果正确的方法
const mathFloat = (float:number, digit:number):number => {
// .pow(x,y)返回x的y次幂
const math = Math.pow(10, digit);
// parseInt(string, radix)函数解析字符串并返回整数。第一个参数为要解析的字符串,第二个为要转换的进制基数,默认是十进制。
// 使用.toString()是参数在TS的情况下会报错,在JS的情况下参数会进行隐式转换parseInt(100)不会报错
return parseInt((float*math).toString())/math
}
return (
<div className="test">
{mathFloat(0.1 + 0.2, 3)} // 输出0.3
</div>
)
ii. 判断值是否正确
// Number.EPSILON 表示 1 和比最接近 1 且大于 1 的最小 Number 之间的差别
const NumberEpsilon = (num,res) => {
return Math.abs(num - res) < Number.EPSILON
}
NumberEpsilon(0.1 + 0.2, 0.3)
11. 数组有哪些原生方法
- 数组转字符串方法: toString()、toLocalString()、join(),其中join()可以指定转换字符串时的分隔符号
- 数组尾部操作方法: pop()、push(), 其中push()可以传入多个参数
- 数组首部操作方法: shift()<从第一项移除>、unshift()<从第一项添加>
- 数组的排序方法: reverse()、sort(),其中sort()方法可以传入一个函数
// 数组的升序和降序
const points = [40,100,1,5,25,10]
points.sort((a,b)=>(a-b)) // 1,5,10,25,40,100
points.sort((a,b)=>(b-a)) // 100,40,25,10,5,1
- 数组的连接方法: concat(),返回拼接好的数组,
不影响原数组 - 数组的截取方法: slice(),用于截取数组中的一部分返回,
不影响原数组 - 数组的插入方法: splice()
- 数组查找特定项的方法: includes()、filter()、some()、every()、map()、forEach()、indexOf()、lastIndexOf()、find()、findIndex()
- 数组并归:reduce()、reduceRight()
12. Object.is()、"=="、"==="的区别
"=="判断两边值是否相等,不判断数据类型,如果类型不一样时会进行强制转换后再比较。比如:0 == "0" // true"==="严格判断,判断两边值是否相当,且判断数据类型。比如:"0"=== 0 // falseObject.is()判断两个值是否相等,一般情况下和"==="严格相等相同,处理了特殊情况比如:0和-0不相等,两个NaN是相等的
13. let、const、var的区别
- var不存在块级作用域,存在变量提升,变量覆盖的问题,声明的变量为全局变量,定义的变量可以不赋初始值
- let存在块级作用域,变量不会提升,不允许重复声明变量,定义的变量可以不赋初始值
- const常量,存在块级作用域,变量不会提升,不允许重复声明变量,变量必须有初始值且不可修改
计算机网络
1. HTTP 和 HTTPS 区别
- https协议需要CA证书,费用较高,而http协议不用
- http协议是超文本传输协议,信息是明文传输的,https则是具有安全性的SSL加密传输协议
- http协议的默认端口是
80,ttps协议的默认端口为443 - http协议的链接是无状态的,https协议使用SSL/TLS协议构建的可加密传输,身份认证的网络协议比http更加安全
2. HTTP1.0 和 HTTP 1.1 区别
-
链接方面:
http1.0默认非持久链接,http.1通过使用持久链接来使多个http请求复用同一个TCP链接,以此来避免使用非持久链接时每次需建立链接的时延 -
资源请求方面:
http1.0存在一些浪费带宽的现象,比如客户端只需要请求某个对象的一部分,而服务器却将整个对象传送过来了,并且不支持断点续传。http1.1则在请求头中引入range头域,它允许只请求资源的某个部分,这样就方便了开发者自由的选择以便于充分利用带宽和连接 -
缓存方面:在强缓存中
http1.0通过设置Expires: Wed, 22 Nov 2019 08:41:00 GMT(具体时间)进行缓存,http1.1通过设置Cache-Control:max-age=3600(具体过期时间点)进行缓存 -
http1.1新增了host字段用来指定服务器的域名 -
http1.1比http1.0新增了请求方法:PUT,HEAD,OPTIONS等
3. 状态码
- 1xx:信息类,表示服务器已接收到请求,需要继续处理
- 2xx:成功类,表示服务器已成功处理请求,返回响应内容
- 3xx:重定向类,表示请求的资源已被移动或需要进一步操作才能完成
- 4xx:客户端错误类,表示客户端请求有语法错误或者无法被服务器满足
- 5xx:服务器错误类,表示服务器在处理请求时发生的内部错误
常见的状态码有以下几种:
- 200 OK:表示请求成功,返回响应内容
- 301 Moved Permanently: 表示请求的资源已被
永久重定向到另一个URI,客户端应使用新的URI访问资源 - 302 Found: 表示请求的资源已被
临时重定向到另一个URI,客户端应使用原有URI做访问资源 - 304 Not Modified: 表示请求的资源未被修改,客户端可以使用缓存中的资源
- 400 Bad Request: 表示客户端的请求有语法错误或无法被服务器理解
- 401 Unauthorized: 表示请求需要用户身份认证
- 403 Forbidden: 表示服务器拒绝执行请求,通常是因为客户端没有权限访问资源
- 404 Not Found: 表示请求的资源不存在,或者服务器不想让客户端知道该资源的存在
- 500 Internal Server Error: 表示服务器在处理请求时发生的内部错误,通常是因为程序异常或配置错误
- 503 Service Unavailable: 表示服务器暂时无法处理请求,通常是因为过载或维护
4. 什么是XSS攻击?
XSS(跨站脚本)攻击是指浏览器中执行恶意脚本(不论是跨域还是同域),从而拿到用户信息并进行操作
- 窃取
cookie - 监听用户行为,比如输入账号密码后直接发送到黑客服务器
- 修改DOM伪造表单登录
- 在页面中生成浮窗广告
XSS攻击的实现有三种方式:存储型、反射型、文档型。
存储型
顾名思义就是将恶意脚本存储了起来,存储型的XSS将脚本存储在了服务端的数据库,然后在客户端执行这些脚本时,从而达到攻击的效果
反射型
反射型XSS是指恶意脚本做为网络请求的一部分
http://sanyuan.com?q=<script>alert("你完蛋了")</script>
比如输入此段代码,服务器端会拿到q参数,然后将内容返回给浏览器端,浏览器将这些内容作为HTML的一部分解析,发现是一个脚本,直接执行,这样就被攻击了。
文档型
文档型XSS攻击并不会经过服务器端,而是作为中间人的角色,在数据传输过程中劫持网络包,并通过修改里面的HTML文档达到一种攻击效果
防范措施:一个信念,两个利用
- 一个信念
- 不要相信用户的输入,前后端都必须对用户的输入进行
转码或者过滤
- 不要相信用户的输入,前后端都必须对用户的输入进行
- 两个利用
- 利用
CSP: 即浏览器中的内容安全策略-
- 限制其他域下的资源加载
-
- 禁止向其他域提交数据
-
- 提供上报机制,能够帮助及时发现XSS攻击
-
- 利用
HttpOnly: 设置Cookie的HttpOnly属性
- 利用
浏览器工作原理
1. 浏览器缓存
当我们访问一个网站时会加载各种资源(html文档,js,css,图片等)。浏览器会将一些不经常改变的资源保存在本地,这样下次访问相同的网站时,就直接的从本地加载资源,并不通过请求服务器,这就是浏览器缓存。
-
强缓存
- expires 设置具体时间来规定缓存的有效期
Expires: Wed, 22 Nov 2019 08:41:00 GMT - cache-control 设置 max-age 最大时间的值来进行缓存
Cache-Control:max-age=3600
- expires 设置具体时间来规定缓存的有效期
-
协商缓存
- Last-Modified 在请求头中携带
If-Modified-Since字段,这个字段的值也就是服务器传来的最后修改时间,如果请求头中的这个值小于最后修改时间,说明是时候更新了,返回新的资源,跟常规的HTTP请求响应的流程一样。否则返回304,告诉浏览器直接用缓存。 - Etag 会在下次请求时,将这个值作为
if-None-Match这个字段的内容,并放到请求头中,然后发给服务器,如果两者不一样,则说明要更新了。否则返回304,告诉浏览器直接用缓存。
- Last-Modified 在请求头中携带
tips:
1. 精准度上Etag更强
2. 性能上Last-Modified更好
3. 两者都支持的情况下,Etag的优先级更高
4. 一般较好的缓存方案是JS、CSS、图片使用强缓存,html文件使用协商缓存
2. 当浏览器中输入一个URL发生了什么
- 浏览器的输入和解析:浏览器会根据用户输入的网址,判断是否是合法的URL,如果是,就进行URL解析,获取协议,域名,端口,路径等信息;如果不是,就将输入作为搜索关键词,使用默认的搜索引擎进行搜索。
- 缓存判断:浏览器会检查请求的资源是否存在缓存里,如果命中则直接使用,否则则向服务器发送新的请求
- DNS解析:浏览器根据域名获取对应的IP地址,这个过程就叫DNS解析。如果一个域名已经解析过,那会把解析的结果直接缓存下来,下次处理直接走缓存,不需要经过DNS解析。
- TCP链接建立:浏览器根据ip和端口号(默认80或443)向服务器发起TCP连接请求,这个过程涉及到三次握手,即客户端发送SYN包,服务器回复SYN+ACK包,客户端再发送ACK包。
- HTTP发送请求:浏览器在TCP连接建立后,向服务器发送HTTP请求报文,这个报文包含请求行,请求头和请求体等信息。
- HTTP响应接收:服务器在收到HTTP请求后,根据请求内容进行处理,并返回HTTP响应报文,这个报文包含了状态行,响应头和响应体等信息。
- TCP连接释放:浏览器在接收完HTTP响应后,根据响应头中的Connection字段判断是否需要关闭TCP连接,如果是,则发起四次挥手,即客户端发送FIN包,服务器回复ACK包,服务器发送FIN包,客户端回复ACK包。
- HTTP缓存处理:浏览器根据响应头中的缓存字段(如last-modified,Etag,Cache-Control,Expires)判断响应内容是否可以缓存,如果可以,则将其存储在本地缓存中,以便下次访问时直接使用。
- HTML文档解析:浏览器根据响应体中的HTML文档进行解析,构建DOM树和CSSOM树,并合并为渲染树。在解析过程中,如果遇到外部资源(如图片,样式表,脚本等),则会发起额外的HTTP请求,并根据资源类型进行相应的处理。
- 页面渲染展示:浏览器根据渲染树计算每个节点的布局和样式,并绘制到屏幕上。在渲染过程中如果遇到脚本执行或者用户交互等事件,则可能会触发重绘或者回流。
Vue
1.Vue 为什么要用 vm.$set() 解决对象新增属性不能响应的问题 ?你能说说如下代码的实现原理么?
-
Vue 使用Object.defineProperty实现双向数据绑定
-
在初始化实例时对属性进行setter/getter的转化
-
属性必须在data对象上存在才能让Vue将它转为响应式(这也就造成了Vue无法检测到对象属性的添加或删除)
所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
// 实现源码
export function set (target: Array<any> | Object, key: any, val: any): any {
// target 为数组
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 修改数组的长度, 避免索引>数组长度导致splcie()执行有误
target.length = Math.max(target.length, key)
// 利用数组的splice变异方法触发响应式
target.splice(key, 1, val)
return val
}
// key 已经存在,直接修改属性值
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
// target 本身就不是响应式数据, 直接赋值
if (!ob) {
target[key] = val
return val
}
// 对属性进行响应式处理
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
vm.$set 的实现原理:
1.如果目标是数组,直接使用数组的 splice 方法触发响应式
2.如果目标是对象,判断属性是否存在、对象是否响应式
3.最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理
defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 getter 和 setter 的功能所调用的方法
2. vue2和vue3实现原理 (区别)
1. vue2 vue3 数据绑定原理不同
- vue2 的双向数据绑定是使用ES5中API Object.definePropert() 对数据进行 劫持 结合发布订阅模式的方式来实现的
- vue3 则是通过 Proxy 来实现数据挟持,创建一个对象的代理,实现基本操作的拦截和自定义,本质是通过拦截对象的内部方法的执行实现代理。vue3 中提供了 reactive() 和 ref() 两个方法将目标数据变为响应式数据。
Object.definePropert()存在的问题
1. 深度监听需要一次性递归
2. 无法监听新增属性和删除属性 ($set()/$delect())
3. 无法监听数组下标的变化
Proxy 的优势
1. 对深度监听无需一次性递归,提高性能
2. 对整个拦截对象直接进行挟持,无需遍历属性一次添加定义 get 和 set 属性
3. 对于原对象生成拦截对象,然后对拦截对象进行相应监听行为确保原对象不变 (可监听数组的变化)
2. vue3 支持碎片,vue2 不支持
vue3 中组件的 template 下可以包含多个根节点,vue2 中只能包含一个根节点否则报错
// vue2中在template里存在多个根节点会报错
<template>
<header></header>
<main></main>
<footer></footer>
</template>
// 只能存在一个根节点,需要用一个<div>来包裹着
<template>
<div>
<header></header>
<main></main>
<footer></footer>
</div>
</template>
// vue3
<template>
<header></header>
<main></main>
<footer></footer>
</template>
3. vue2 是选项式API,vue3 是组合式API
vue2 选项式API 在代码里分割了不同的属性:data, methods, computed等,同一块业务逻辑会把数据和方法拆分到不同的代码块中
vue3 组合式API 能让相同的业务的数据方法写在同一块代码区域,使代码更加简便整洁
4. 生命周期不同
- vue3 删除了
beforeCreate/created在vue2 生命周期钩子名称+ on前缀,将beforeDestroy/destroyed改为onBeforeUnmount/onUnmounted
| vue2 | vue3 |
|---|---|
| beforeCreate | - |
| created | - |
| boforeMount | onBoforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeDestroy | onBeforeUnmount |
| destroyed | onUnmounted |
- vue3 在使用生命周期钩子时需按需引入,vue2则可直接调用
// vue3
<script setup>
import { onMounted } from 'vue'; // 使用前需引入生命周期钩子
onMounted(() => {
// ...
});
// 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不会被覆盖
onMounted(() => {
// ...
});
</script>
// vue2
<script>
export default { mounted() { // 直接调用生命周期钩子
// ...
}, }
</script>
5. vue异步组件(Suspense)
vue3 提供 Suspense 组件,允许程序在等在异步组件加载完成前渲染兜底的内容,如 loading,使用户体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。
<tempalte>
<suspense>
<template #default>
<List />
</template>
<template #fallback>
<div>
Loading... </div>
</template>
</suspense>
</template>
6. vue3 新增 Teleport 传送门组件
Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗。
<button @click="dialogVisible = true">显示弹窗</button>
<teleport to="body">
<div class="dialog" v-if="dialogVisible">
我是弹窗,我直接移动到了body标签下 </div>
</teleport>
将 Teleport 组件标签内部的 dom 插入到 body 中 to 允许接收值: 期望接收一个 CSS 选择器字符串或者一个真实的 DOM 节点。
7. diff 算法不同
vue2 diff算法是就虚拟节点进行同级节点对比再比较子节点,并返回一个patch对象,用来储存节点中的不同,最后用patch记录的消息去更新dom,diff算法会比较每一个vnode,而对于不参与更新的元素是消耗内存的
vue3 diff 算法是在初始化的时候会给每个虚拟节点添加一个patchFlags优化标识,只会比较patchFlags发生变化的虚拟节点,进行视图更新,对于没有变化的元素做静态标记,在渲染的时候直接复用
3. 为什么Proxy一定要配合Reflect使用
Proxy 中的 receiver 会改变this指向,使用 Reflect 可避免这个问题
4. ref 和 reactive 的区别
ref是一个函数,它可以将一个普通数据类型(如字符串、数字等)转换成一个响应式对象,从而让这个数据在vue响应式模式下被追钟,ref返回一个对象,对象有一个.value属性,用来获取和设置这个响应式对象的值
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const count = ref(0);
function addCount() {
count.value += 1;
console.log(count.value)
}
return {
count,
addCount,
};
},
});
reactive它可以将一个普通的JS对象转换为响应式对象。它会递归地将这个对象的所有属性都转换为响应式对象,从而让整个对象在vue响应式中被追踪。reactive返回一个Proxy对象,用来代替原始对象的访问和修改
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const state = reactive({
count: 0,
message: 'hello'
});
console.log(state.count)// 0
console.log(state.message)// hello
state.count = 1 ;
state.message = 'world' ;
console.log(state.count)// 1
console.log(state.message)// world
return {
state,
};
},
});
5. MVVM
MVVM 分为 Model,View,ViewModel:
6. 组件中的data为什么是一个函数
一个组件被多次调用会创建多个实例,如果data是一个引用类型,共用一个data,会对实例中的数据产生污染,所以组件中的data必须是一个函数。
7. SPA单页面首屏加载速度慢如何解决
- 减小入口文件的体积,
在vue-router的时间使用动态加载路由的形式,以函数的形式加载路由,这样可以把各自的路由文件分别打包,在解析给定的路由时,才会加载路由组件
routes:[
path: 'Blogs',
name: 'ShowBlogs',
component: () => import('./components/ShowBlogs.vue')
]
-
静态资源本地缓存
- 采用
HTTP的缓存方式:cache-control,Etag,Last-modify - 合理利用LocalStrage
- 采用
-
UI框架按需引入
import { Button, Input, Pagination, Table, TableColumn, MessageBox } from 'element-ui';
Vue.use(Button)
Vue.use(Input)
Vue.use(Pagination)
- 组件重复打包
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3 // 使用CommonsChunkPlugin,配置minchuncks:3,会把使用3次及以上的包抽离出来,放进公共依赖,避免重复加载组件
}),
使用CommonsChunkPlugin,配置minchuncks:3,会把使用3次及以上的包抽离出来,放进公共依赖,避免重复加载组件
- 使用GZip压缩 - 使用CompressionWebpackPlugin配置
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp('\\.(' + config.build.productionGzipExtensions.join('|') + ')$'),
threshold: 10240,
minRatio: 0.8
})
);
- 图片资源的压缩
- 适当压缩图片
- icon尽量使用在线字体图标或者雪碧图
8. NextTick是什么
在下一次DOM更新循环结束后执行的延迟回调
{{num}}
for(let i=0; i<100000; i++){
num = i
}
举个例子:如果一个num要循环上万次在页面上打印,没有nextTick,每一次都会触发试图的更新,就更新了上万次的试图,如果有了nextTick,只需要更新一次。
nextTick主要使用了宏微任务,根据不同环境分别执行:
- Promise
- MutationObserver
- setImmediate
- 如果以上都不执行则采用setTimeout