Watch 中的 deep:true 是如何实现的
当用户指定了
watch
中的 deep 属性为true
时,如果以后监控的值是数组类型。会对对象中的每一项进行求值,此时会将以后watcher
存入到对应属性的依赖中,这样数组中对象发生变化时也会告诉数据更新
源码相干
get () {pushTarget(this) // 先将以后依赖放到 Dep.target 上
let value
const vm = this.vm
try {value = this.getter.call(vm, vm)
} catch (e) {if (this.user) {handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {throw e}
} finally {if (this.deep) { // 如果须要深度监控
traverse(value) // 会对对象中的每一项取值, 取值时会执行对应的 get 办法
}popTarget()}
delete 和 Vue.delete 删除数组的区别?
delete
只是被删除的元素变成了empty/undefined
其余的元素的键值还是不变。Vue.delete
间接删除了数组 扭转了数组的键值。
var a=[1,2,3,4]
var b=[1,2,3,4]
delete a[0]
console.log(a) //[empty,2,3,4]
this.$delete(b,0)
console.log(b) //[2,3,4]
params 和 query 的区别
用法:query 要用 path 来引入,params 要用 name 来引入,接管参数都是相似的,别离是 this.$route.query.name
和 this.$route.params.name
。
url 地址显示:query 更加相似于 ajax 中 get 传参,params 则相似于 post,说的再简略一点,前者在浏览器地址栏中显示参数,后者则不显示
留神:query 刷新不会失落 query 外面的数据 params 刷新会失落 params 外面的数据。
构建的 vue-cli 工程都到了哪些技术,它们的作用别离是什么
vue.js
:vue-cli
工程的外围,次要特点是 双向数据绑定 和 组件零碎。vue-router
:vue
官网举荐应用的路由框架。vuex
:专为Vue.js
利用我的项目开发的状态管理器,次要用于保护vue
组件间共用的一些 变量 和 办法。axios
(或者fetch
、ajax
):用于发动GET
、或POST
等http
申请,基于Promise
设计。vuex
等:一个专为vue
设计的挪动端 UI 组件库。- 创立一个
emit.js
文件,用于vue
事件机制的治理。 webpack
:模块加载和vue-cli
工程打包器。
vue-cli 工程罕用的 npm 命令有哪些
- 下载
node_modules
资源包的命令:
npm install
- 启动
vue-cli
开发环境的 npm 命令:
npm run dev
vue-cli
生成 生产环境部署资源 的npm
命令:
npm run build
- 用于查看
vue-cli
生产环境部署资源文件大小的npm
命令:
npm run build --report
在浏览器上自动弹出一个 展现
vue-cli
工程打包后app.js
、manifest.js
、vendor.js
文件外面所蕴含代码的页面。能够具此优化vue-cli
生产环境部署的动态资源,晋升 页面 的加载速度
v-once 的应用场景有哪些
剖析
v-once
是 Vue
中内置指令,很有用的API
,在优化方面常常会用到
体验
仅渲染元素和组件一次,并且跳过将来更新
<!-- single element -->
<span v-once>This will never change: {{msg}}</span>
<!-- the element have children -->
<div v-once>
<h1>comment</h1>
<p>{{msg}}</p>
</div>
<!-- component -->
<my-component v-once :comment="msg"></my-component>
<!-- `v-for` directive -->
<ul>
<li v-for="i in list" v-once>{{i}}</li>
</ul>
答复范例
v-once
是vue
的内置指令,作用是仅渲染指定组件或元素一次,并跳过将来对其更新- 如果咱们有一些元素或者组件在初始化渲染之后不再须要变动,这种状况下适宜应用
v-once
,这样哪怕这些数据变动,vue
也会跳过更新,是一种代码优化伎俩 - 咱们只须要作用的组件或元素上加上
v-once
即可 vue3.2
之后,又减少了v-memo
指令,能够有条件缓存局部模板并管制它们的更新,能够说控制力更强了- 编译器发现元素下面有
v-once
时,会将首次计算结果存入缓存对象,组件再次渲染时就会从缓存获取,从而防止再次计算
原理
上面例子应用了v-once
:
<script setup>
import {ref} from 'vue'
const msg = ref('Hello World!')
</script>
<template>
<h1 v-once>{{msg}}</h1>
<input v-model="msg">
</template>
咱们发现 v-once
呈现后,编译器会缓存作用元素或组件,从而防止当前更新时从新计算这一部分:
// ...
return (_ctx, _cache) => {return (_openBlock(), _createElementBlock(_Fragment, null, [
// 从缓存获取 vnode
_cache[0] || (_setBlockTracking(-1),
_cache[0] = _createElementVNode("h1", null, [_createTextVNode(_toDisplayString(msg.value), 1 /* TEXT */)
]),
_setBlockTracking(1),
_cache[0]
),
// ...
参考 前端进阶面试题具体解答
vue-loader 是什么?它有什么作用?
答复范例
vue-loader
是用于解决单文件组件(SFC
,Single-File Component
)的webpack loader
- 因为有了
vue-loader
,咱们就能够在我的项目中编写SFC
格局的Vue
组件,咱们能够把代码宰割为<template>
、<script>
和<style>
,代码会异样清晰。联合其余loader
咱们还能够用Pug
编写<template>
,用SASS
编写<style>
,用TS
编写<script>
。咱们的<style>
还能够独自作用以后组件 webpack
打包时,会以loader
的形式调用vue-loader
vue-loader
被执行时,它会对SFC
中的每个语言块用独自的loader
链解决。最初将这些独自的块装配成最终的组件模块
原理
vue-loader
会调用 @vue/compiler-sfc
模块解析 SFC
源码为一个描述符(Descriptor
),而后为每个语言块生成 import
代码,返回的代码相似上面
// source.vue 被 vue-loader 解决之后返回的代码
// import the <template> block
import render from 'source.vue?vue&type=template'
// import the <script> block
import script from 'source.vue?vue&type=script'
export * from 'source.vue?vue&type=script'
// import <style> blocks
import 'source.vue?vue&type=style&index=1'
script.render = render
export default script
咱们想要 script
块中的内容被作为 js
解决(当然如果是 <script lang="ts">
被作为 ts
理),这样咱们想要 webpack
把配置中跟 .js
匹配的规定都利用到形如 source.vue?vue&type=script
的这个申请上。例如咱们对所有 *.js
配置了babel-loader
,这个规定将被克隆并利用到所在Vue SFC
import script from 'source.vue?vue&type=script
将被开展为:
import script from 'babel-loader!vue-loader!source.vue?vue&type=script'
相似的,如果咱们对 .sass
文件配置了style-loader + css-loader + sass-loader
,对上面的代码
<style scoped lang="scss">
vue-loader
将会返回给咱们上面后果:
import 'source.vue?vue&type=style&index=1&scoped&lang=scss'
而后 webpack
会开展如下:
import 'style-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'
- 当解决开展申请时,
vue-loader
将被再次调用。这次,loader
将会关注那些有查问串的申请,且仅针对特定块,它会选中特定块外部的内容并传递给前面匹配的loader
-
对于
<script>
块,解决到这就能够了,然而<template>
和<style>
还有一些额定工作要做,比方- 须要用
Vue
模板编译器编译template
,从而失去render
函数 - 须要对
<style scoped>
中的CSS
做后处理(post-process
),该操作在css-loader
之后但在style-loader
之前
- 须要用
实现上这些附加的 loader
须要被注入到曾经开展的 loader
链上,最终的申请会像上面这样:
// <template lang="pug">
import 'vue-loader/template-loader!pug-loader!source.vue?vue&type=template'
// <style scoped lang="scss">
import 'style-loader!vue-loader/style-post-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'
Vue 为什么没有相似于 React 中 shouldComponentUpdate 的生命周期
- 考点:
Vue
的变动侦测原理 - 前置常识: 依赖收集、虚构
DOM
、响应式零碎
根本原因是
Vue
与React
的变动侦测形式有所不同
- 当 React 晓得发生变化后,会应用
Virtual Dom Diff
进行差别检测,然而很多组件实际上是必定不会发生变化的,这个时候须要shouldComponentUpdate
进行手动操作来缩小diff
,从而进步程序整体的性能 Vue
在一开始就晓得那个组件产生了变动,不须要手动管制diff
,而组件外部采纳的diff
形式实际上是能够引入相似于shouldComponentUpdate
相干生命周期的,然而通常正当大小的组件不会有适量的 diff,手动优化的价值无限,因而目前Vue
并没有思考引入shouldComponentUpdate
这种手动优化的生命周期
Vue 为什么须要虚构 DOM?优缺点有哪些
因为在浏览器中操作
DOM
是很低廉的。频繁的操作DOM
,会产生肯定的性能问题。这就是虚构Dom
的产生起因。Vue2
的Virtual DOM
借鉴了开源库snabbdom
的实现。Virtual DOM
实质就是用一个原生的JS
对象去形容一个DOM
节点,是对实在DOM
的一层形象
长处:
- 保障性能上限:框架的虚构
DOM
须要适配任何下层API
可能产生的操作,它的一些DOM
操作的实现必须是普适的,所以它的性能并不是最优的;然而比起粗犷的DOM
操作性能要好很多,因而框架的虚构DOM
至多能够保障在你不须要手动优化的状况下,仍然能够提供还不错的性能,即保障性能的上限; - 无需手动操作 DOM:咱们不再须要手动去操作
DOM
,只须要写好View-Model
的代码逻辑,框架会依据虚构DOM
和 数据双向绑定,帮咱们以可预期的形式更新视图,极大进步咱们的开发效率; - 跨平台:虚构
DOM
实质上是JavaScript
对象, 而DOM
与平台强相干,相比之下虚构DOM
能够进行更不便地跨平台操作,例如服务器渲染、weex
开发等等。
毛病:
- 无奈进行极致优化:尽管虚构
DOM
+ 正当的优化,足以应答绝大部分利用的性能需求,但在一些性能要求极高的利用中虚构DOM
无奈进行针对性的极致优化。 - 首次渲染大量
DOM
时,因为多了一层虚构DOM
的计算,会比innerHTML
插入慢。
虚构 DOM 实现原理?
虚构 DOM
的实现原理次要包含以下 3
局部:
- 用
JavaScript
对象模仿实在DOM
树,对实在DOM
进行形象; diff
算法 — 比拟两棵虚构DOM
树的差别;pach
算法 — 将两个虚构DOM
对象的差别利用到真正的DOM
树。
说说你对虚构 DOM 的了解?答复范例
思路
vdom
是什么- 引入
vdom
的益处 vdom
如何生成,又如何成为dom
- 在后续的
diff
中的作用
答复范例
- 虚构
dom
顾名思义就是虚构的dom
对象,它自身就是一个JavaScript
对象,只不过它是通过不同的属性去形容一个视图构造 - 通过引入
vdom
咱们能够取得如下益处: -
将实在元素节点形象成
VNode
,无效缩小间接操作dom
次数,从而进步程序性能- 间接操作
dom
是有限度的,比方:diff
、clone
等操作,一个实在元素上有许多的内容,如果间接对其进行diff
操作,会去额定diff
一些没有必要的内容;同样的,如果须要进行clone
那么须要将其全部内容进行复制,这也是没必要的。然而,如果将这些操作转移到JavaScript
对象上,那么就会变得简略了 - 操作
dom
是比拟低廉的操作,频繁的dom
操作容易引起页面的重绘和回流,然而通过形象VNode
进行两头解决,能够无效缩小间接操作dom
的次数,从而缩小页面重绘和回流
- 间接操作
-
不便实现跨平台
- 同一
VNode
节点能够渲染成不同平台上的对应的内容,比方:渲染在浏览器是dom
元素节点,渲染在Native(iOS、Android)
变为对应的控件、能够实现SSR
、渲染到WebGL
中等等 Vue3
中容许开发者基于VNode
实现自定义渲染器(renderer
),以便于针对不同平台进行渲染
- 同一
vdom
如何生成?在 vue 中咱们经常会为组件编写模板 –template
,这个模板会被编译器 –compiler
编译为渲染函数,在接下来的挂载(mount
)过程中会调用render
函数,返回的对象就是虚构dom
。但它们还不是真正的dom
,所以会在后续的patch
过程中进一步转化为dom
。
- 挂载过程完结后,
vue
程序进入更新流程。如果某些响应式数据发生变化,将会引起组件从新render
,此时就会生成新的vdom
,和上一次的渲染后果diff
就能失去变动的中央,从而转换为最小量的dom
操作,高效更新视图
为什么要用 vdom?案例解析
当初有一个场景,实现以下需要:
[{ name: "张三", age: "20", address: "北京"},
{name: "李四", age: "21", address: "武汉"},
{name: "王五", age: "22", address: "杭州"},
]
将该数据展现成一个表格,并且轻易批改一个信息,表格也跟着批改。用 jQuery 实现如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change"> 扭转 </button>
<script src="https://cdn.bootcss.com/jquery/3.2.0/jquery.js"></script>
<script>
const data = [{
name: "张三",
age: "20",
address: "北京"
},
{
name: "李四",
age: "21",
address: "武汉"
},
{
name: "王五",
age: "22",
address: "杭州"
},
];
// 渲染函数
function render(data) {const $container = $('#container');
$container.html('');
const $table = $('<table>');
// 重绘一次
$table.append($('<tr><td>name</td><td>age</td><td>address</td></tr>'));
data.forEach(item => {
// 每次进入都重绘
$table.append($(`<tr><td>${item.name}</td><td>${item.age}</td><td>${item.address}</td></tr>`))
})
$container.append($table);
}
$('#btn-change').click(function () {data[1].age = 30;
data[2].address = '深圳';
render(data);
});
</script>
</body>
</html>
- 这样点击按钮,会有相应的视图变动,然而你审查以下元素,每次改变之后,
table
标签都得从新创立,也就是说table
上面的每一个栏目,不论是数据是否和原来一样,都得从新渲染,这并不是现实中的状况,当其中的一栏数据和原来一样,咱们心愿这一栏不要从新渲染,因为DOM
重绘相当耗费浏览器性能。 - 因而咱们采纳 JS 对象模仿的办法,将
DOM
的比对操作放在JS
层,缩小浏览器不必要的重绘,提高效率。 - 当然有人说虚构 DOM 并不比实在的
DOM
快,其实也是有情理的。当上述table
中的每一条数据都扭转时,显然实在的DOM
操作更快,因为虚构DOM
还存在js
中diff
算法的比对过程。所以,上述性能劣势仅仅实用于大量数据的渲染并且扭转的数据只是一小部分的状况。
如下 DOM
构造:
<ul id="list">
<li class="item">Item1</li>
<li class="item">Item2</li>
</ul>
映射成虚构 DOM
就是这样:
{
tag: "ul",
attrs: {id: "list"},
children: [
{
tag: "li",
attrs: {className: "item"},
children: ["Item1"]
}, {
tag: "li",
attrs: {className: "item"},
children: ["Item2"]
}
]
}
应用 snabbdom 实现 vdom
这是一个繁难的实现
vdom
性能的库,相比vue
、react
,对于vdom
这块更加繁难,适宜咱们学习vdom
。vdom
外面有两个外围的api
,一个是h
函数,一个是patch
函数,前者用来生成vdom
对象,后者的性能在于做虚构dom
的比对和将vdom
挂载到实在DOM
上
简略介绍一下这两个函数的用法:
h('标签名', {属性}, [子元素])
h('标签名', {属性}, [文本])
patch(container, vnode) // container 为容器 DOM 元素
patch(vnode, newVnode)
当初咱们就来用 snabbdom
重写一下方才的例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="container"></div>
<button id="btn-change"> 扭转 </button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
<script>
let snabbdom = window.snabbdom;
// 定义 patch
let patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
]);
// 定义 h
let h = snabbdom.h;
const data = [{
name: "张三",
age: "20",
address: "北京"
},
{
name: "李四",
age: "21",
address: "武汉"
},
{
name: "王五",
age: "22",
address: "杭州"
},
];
data.unshift({name: "姓名", age: "年龄", address: "地址"});
let container = document.getElementById('container');
let vnode;
const render = (data) => {let newVnode = h('table', {}, data.map(item => {let tds = [];
for(let i in item) {if(item.hasOwnProperty(i)) {tds.push(h('td', {}, item[i] + ''));
}
}
return h('tr', {}, tds);
}));
if(vnode) {patch(vnode, newVnode);
} else {patch(container, newVnode);
}
vnode = newVnode;
}
render(data);
let btnChnage = document.getElementById('btn-change');
btnChnage.addEventListener('click', function() {data[1].age = 30;
data[2].address = "深圳";
//re-render
render(data);
})
</script>
</body>
</html>
你会发现,只有扭转的栏目才闪动,也就是进行重绘,数据没有扭转的栏目还是放弃原样,这样就大大节俭了浏览器从新渲染的开销
vue 中应用
h 函数
生成虚构DOM
返回
const vm = new Vue({
el: '#app',
data: {user: {name:'poetry'}
},
render(h){// h()
// h(App)
// h('div',[])
let vnode = h('div',{},'hello world');
return vnode
}
});
</details>
Vue 我的项目性能优化 - 具体
Vue
框架通过数据双向绑定和虚构DOM
技术,帮咱们解决了前端开发中最脏最累的DOM
操作局部,咱们不再须要去思考如何操作DOM
以及如何最高效地操作DOM
;但Vue
我的项目中依然存在我的项目首屏优化、Webpack
编译配置优化等问题,所以咱们依然须要去关注Vue
我的项目性能方面的优化,使我的项目具备更高效的性能、更好的用户体验
代码层面的优化
1. v-if 和 v-show 辨别应用场景
v-if
是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块v-show
就简略得多,不论初始条件是什么,元素总是会被渲染,并且只是简略地基于CSS
display
的none/block
属性进行切换。- 所以,
v-if
实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show
则实用于须要十分频繁切换条件的场景
2. computed 和 watch 辨别应用场景
computed
:是计算属性,依赖其它属性值,并且computed
的值有缓存,只有它依赖的属性值产生扭转,下一次获取computed
的值时才会从新计算 computed 的值;watch
:更多的是「察看」的作用,相似于某些数据的监听回调,每当监听的数据变动时都会执行回调进行后续操作
使用场景:
- 当咱们须要进行数值计算,并且依赖于其它数据时,应该应用
computed
,因为能够利用computed
的缓存个性,防止每次获取值时,都要从新计算; - 当咱们须要在数据变动时执行异步或开销较大的操作时,应该应用
watch
,应用watch
选项容许咱们执行异步操作 (拜访一个API
),限度咱们执行该操作的频率,并在咱们失去最终后果前,设置中间状态。这些都是计算属性无奈做到的
3. v-for 遍历必须为 item 增加 key,且防止同时应用 v-if
-
v-for
遍历必须为item
增加key
- 在列表数据进行遍历渲染时,须要为每一项
item
设置惟一key
值,不便Vue.js
外部机制精准找到该条列表数据。当state
更新时,新的状态值和旧的状态值比照,较快地定位到diff
- 在列表数据进行遍历渲染时,须要为每一项
-
v-for
遍历防止同时应用v-if
vue2.x
中v-for
比v-if
优先级高,如果每一次都须要遍历整个数组,将会影响速度,尤其是当之须要渲染很小一部分的时候,必要状况下应该替换成computed
属性
举荐:
<ul>
<li
v-for="user in activeUsers"
:key="user.id">
{{user.name}}
</li>
</ul>
computed: {activeUsers: function () {return this.users.filter(function (user) {return user.isActive})
}
}
不举荐:
<ul>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id">
{{user.name}}
</li>
</ul>
4. 长列表性能优化
Vue
会通过Object.defineProperty
对数据进行劫持,来实现视图响应数据的变动,然而有些时候咱们的组件就是纯正的数据展现,不会有任何扭转,咱们就不须要 Vue 来劫持咱们的数据,在大量数据展现的状况下,这可能很显著的缩小组件初始化的工夫,那如何禁止Vue
劫持咱们的数据呢?能够通过Object.freeze
办法来解冻一个对象,一旦被解冻的对象就再也不能被批改了
export default {data: () => ({users: {}
}),
async created() {const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};
5. 事件的销毁
Vue
组件销毁时,会主动清理它与其它实例的连贯,解绑它的全副指令及事件监听器,然而仅限于组件自身的事件。如果在 js
内应用 addEventListener
等形式是不会主动销毁的,咱们须要在组件销毁时手动移除这些事件的监听,免得造成内存泄露,如:
created() {addEventListener('click', this.click, false)
},
beforeDestroy() {removeEventListener('click', this.click, false)
}
6. 图片资源懒加载
对于图片过多的页面,为了减速页面加载速度,所以很多时候咱们须要将页面内未呈现在可视区域内的图片先不做加载,等到滚动到可视区域后再去加载。这样对于页面加载性能上会有很大的晋升,也进步了用户体验。咱们在我的项目中应用 Vue
的 vue-lazyload
插件
npm install vue-lazyload --save-dev
在入口文件 man.js
中引入并应用
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload)
// 或者增加自定义选项
Vue.use(VueLazyload, {
preLoad: 1.3,
error: 'dist/error.png',
loading: 'dist/loading.gif',
attempt: 1
})
在 vue
文件中将 img
标签的 src
属性间接改为 v-lazy
,从而将图片显示方式更改为懒加载显示
<img v-lazy="/static/img/1.png">
以上为 vue-lazyload
插件的简略应用,如果要看插件的更多参数选项,能够查看 vue-lazyload 的 github 地址(opens new window)
7. 路由懒加载
Vue 是单页面利用,可能会有很多的路由引入,这样应用 webpcak
打包后的文件很大,当进入首页时,加载的资源过多,页面会呈现白屏的状况,不利于用户体验。如果咱们能把不同路由对应的组件宰割成不同的代码块,而后当路由被拜访的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,然而可能其余的页面的速度就会降下来
路由懒加载:
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [{ path: '/foo', component: Foo}
]
})
8. 第三方插件的按需引入
咱们在我的项目中常常会须要引入第三方插件,如果咱们间接引入整个插件,会导致我的项目的体积太大,咱们能够借助 babel-plugin-component
,而后能够只引入须要的组件,以达到减小我的项目体积的目标。以下为我的项目中引入 element-ui
组件库为例
npm install babel-plugin-component -D
将 .babelrc
批改为:
{"presets": [["es2015", { "modules": false}]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
在 main.js
中引入局部组件:
import Vue from 'vue';
import {Button, Select} from 'element-ui';
Vue.use(Button)
Vue.use(Select)
9. 优化有限列表性能
如果你的利用存在十分长或者有限滚动的列表,那么须要采纳 虚构列表
的技术来优化性能,只须要渲染少部分区域的内容,缩小从新渲染组件和创立 dom
节点的工夫。你能够参考以下开源我的项目 vue-virtual-scroll-list (opens new window) 和 vue-virtual-scroller (opens new window)来优化这种有限列表的场景的
10. 服务端渲染 SSR or 预渲染
服务端渲染是指 Vue
在客户端将标签渲染成的整个 html
片段的工作在服务端实现,服务端造成的 html
片段间接返回给客户端这个过程就叫做服务端渲染。
- 如果你的我的项目的
SEO
和首屏渲染
是评估我的项目的要害指标,那么你的我的项目就须要服务端渲染来帮忙你实现最佳的初始加载性能和SEO
- 如果你的
Vue
我的项目只需改善多数营销页面(例如/
,/about
,/contact
等)的SEO
,那么你可能须要预渲染,在构建时简略地生成针对特定路由的动态HTML
文件。长处是设置预渲染更简略,并能够将你的前端作为一个齐全动态的站点,具体你能够应用 prerender-spa-plugin (opens new window) 就能够轻松地增加预渲染
Webpack 层面的优化
1. Webpack 对图片进行压缩
对小于 limit
的图片转化为 base64
格局,其余的不做操作。所以对有些较大的图片资源,在申请资源的时候,加载会很慢,咱们能够用 image-webpack-loader
来压缩图片
npm install image-webpack-loader --save-dev
{test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[
{
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
loader: 'image-webpack-loader',
options: {bypassOnDebug: true,}
}
]
}
2. 缩小 ES6 转为 ES5 的冗余代码
Babel 插件会在将 ES6 代码转换成 ES5 代码时会注入一些辅助函数,例如上面的 ES6 代码
class HelloWebpack extends Component{...}
这段代码再被转换成能失常运行的 ES5 代码时须要以下两个辅助函数:
babel-runtime/helpers/createClass // 用于实现 class 语法
babel-runtime/helpers/inherits // 用于实现 extends 语法
在默认状况下,Babel
会在每个输入文件中内嵌这些依赖的辅助函数代码,如果多个源代码文件都依赖这些辅助函数,那么这些辅助函数的代码将会呈现很屡次,造成代码冗余。为了不让这些辅助函数的代码反复呈现,能够在依赖它们时通过 require('babel-runtime/helpers/createClass')
的形式导入,这样就能做到只让它们呈现一次。babel-plugin-transform-runtime
插件就是用来实现这个作用的,将相干辅助函数进行替换成导入语句,从而减小 babel 编译进去的代码的文件大小
npm install babel-plugin-transform-runtime --save-dev
批改 .babelrc
配置文件为:
"plugins": ["transform-runtime"]
3. 提取公共代码
如果我的项目中没有去将每个页面的第三方库和公共模块提取进去,则我的项目会存在以下问题:
- 雷同的资源被反复加载,节约用户的流量和服务器的老本。
- 每个页面须要加载的资源太大,导致网页首屏加载迟缓,影响用户体验。
所以咱们须要将多个页面的公共代码抽离成独自的文件,来优化以上问题。Webpack
内置了专门用于提取多个Chunk
中的公共局部的插件 CommonsChunkPlugin
,咱们在我的项目中 CommonsChunkPlugin
的配置如下:
// 所有在 package.json 外面依赖的包,都会被打包进 vendor.js 这个文件中。new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function(module, count) {
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(path.join(__dirname, '../node_modules')
) === 0
);
}
}),
// 抽取出代码模块的映射关系
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})
4. 模板预编译
- 当应用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。通常状况下这个过程曾经足够快了,但对性能敏感的利用还是最好防止这种用法。
- 预编译模板最简略的形式就是应用单文件组件——相干的构建设置会主动把预编译解决好,所以构建好的代码曾经蕴含了编译进去的渲染函数而不是原始的模板字符串。
- 如果你应用 webpack,并且喜爱拆散 JavaScript 和模板文件,你能够应用 vue-template-loader (opens new window),它也能够在构建过程中把模板文件转换成为 JavaScript 渲染函数
5. 提取组件的 CSS
当应用单文件组件时,组件内的 CSS 会以 style 标签的形式通过 JavaScript 动静注入。这有一些小小的运行时开销,如果你应用服务端渲染,这会导致一段“无款式内容闪动 (fouc)”。将所有组件的 CSS 提取到同一个文件能够防止这个问题,也会让 CSS 更好地进行压缩和缓存
6. 优化 SourceMap
咱们在我的项目进行打包后,会将开发中的多个文件代码打包到一个文件中,并且通过压缩、去掉多余的空格、babel 编译化后,最终将编译失去的代码会用于线上环境,那么这样解决后的代码和源代码会有很大的差异,当有 bug 的时候,咱们只能定位到压缩解决后的代码地位,无奈定位到开发环境中的代码,对于开发来说不好调式定位问题,因而 sourceMap
呈现了,它就是为了解决不好调式代码问题的
SourceMap
的可选值如下(+
号越多,代表速度越快,-
号越多,代表速度越慢,o
代表中等速度)
- 开发环境举荐:
cheap-module-eval-source-map
- 生产环境举荐:
cheap-module-source-map
起因如下:
cheap
:源代码中的列信息是没有任何作用,因而咱们打包后的文件不心愿蕴含列相干信息,只有行信息能建设打包前后的依赖关系。因而不论是开发环境或生产环境,咱们都心愿增加cheap
的根本类型来疏忽打包前后的列信息;module
:不论是开发环境还是正式环境,咱们都心愿能定位到bug
的源代码具体的地位,比如说某个Vue
文件报错了,咱们心愿能定位到具体的Vue
文件,因而咱们也须要module
配置;soure-map
:source-map
会为每一个打包后的模块生成独立的soucemap
文件,因而咱们须要减少source-map
属性;eval-source-map
:eval
打包代码的速度十分快,因为它不生成map
文件,然而能够对eval
组合应用eval-source-map
应用会将map
文件以DataURL
的模式存在打包后的js
文件中。在正式环境中不要应用eval-source-map
, 因为它会减少文件的大小,然而在开发环境中,能够试用下,因为他们打包的速度很快。
7. 构建后果输入剖析
Webpack 输入的代码可读性十分差而且文件十分大,让咱们十分头疼。为了更简略、直观地剖析输入后果,社区中呈现了许多可视化剖析工具。这些工具以图形的形式将后果更直观地展现进去,让咱们疾速理解问题所在。接下来解说咱们在 Vue 我的项目中用到的剖析工具:webpack-bundle-analyzer
if (config.build.bundleAnalyzerReport) {var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}
执行 $ npm run build --report
后生成剖析报告如下
根底的 Web 技术优化
1. 开启 gzip 压缩
gzip
是GNUzip
的缩写,最早用于UNIX
零碎的文件压缩。HTTP
协定上的gzip
编码是一种用来改良web
应用程序性能的技术,web
服务器和客户端(浏览器)必须独特反对 gzip。目前支流的浏览器,Chrome,firefox,IE 等都反对该协定。常见的服务器如 Apache,Nginx,IIS 同样反对,zip
压缩效率十分高,通常能够达到70%
的压缩率,也就是说,如果你的网页有30K
,压缩之后就变成了9K
左右
以下咱们以服务端应用咱们相熟的 express
为例,开启 gzip
非常简单,相干步骤如下:
npm install compression --save
var compression = require('compression');
var app = express();
app.use(compression())
重启服务,察看网络面板外面的 response header
,如果看到如下红圈里的字段则表明 gzip
开启胜利
Nginx 开启 gzip 压缩
# 是否启动 gzip 压缩,on 代表启动,off 代表开启
gzip on;
#须要压缩的常见动态资源
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
#因为 nginx 的压缩产生在浏览器端而微软的 ie6 很坑爹, 会导致压缩后图片看不见所以该选
项是禁止 ie6 产生压缩
gzip_disable "MSIE [1-6]\.";
#如果文件大于 1k 就启动压缩
gzip_min_length 1k;
#以 16k 为单位, 依照原始数据的大小以 4 倍的形式申请内存空间, 个别此项不要批改
gzip_buffers 4 16k;
#压缩的等级, 数字抉择范畴是 1 -9, 数字越小压缩的速度越快, 耗费 cpu 就越大
gzip_comp_level 2;
要想配置失效,记得重启 nginx
服务
nginx -t
nginx -s reload
2. 浏览器缓存
为了进步用户加载页面的速度,对动态资源进行缓存是十分必要的,依据是否须要从新向服务器发动申请来分类,将 HTTP 缓存规定分为两大类(强制缓存,比照缓存)
3. CDN 的应用
浏览器从服务器上下载 CSS、js 和图片等文件时都要和服务器连贯,而大部分服务器的带宽无限,如果超过限度,网页就半天反馈不过去。而 CDN 能够通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且 CDN 具备更好的可用性,更低的网络提早和丢包率
4. 应用 Chrome Performance 查找性能瓶颈
Chrome
的 Performance
面板能够录制一段时间内的 js
执行细节及工夫。应用 Chrome
开发者工具剖析页面性能的步骤如下。
- 关上
Chrome
开发者工具,切换到Performance
面板 - 点击
Record
开始录制 - 刷新页面或开展某个节点
- 点击
Stop
进行录制
Vue3.2 setup 语法糖汇总
提醒:vue3.2
版本开始能力应用语法糖!
在 Vue3.0
中变量必须 return
进去,template
中能力应用;而在 Vue3.2
中只须要在 script
标签上加上 setup
属性,无需 return
,template
便可间接应用,十分的香啊!
1. 如何应用 setup 语法糖
只需在 script
标签上写上 setup
<template>
</template>
<script setup>
</script>
<style scoped lang="less">
</style>
2. data 数据的应用
因为 setup
不需写 return
,所以间接申明数据即可
<script setup>
import {
ref,
reactive,
toRefs,
} from 'vue'
const data = reactive({
patternVisible: false,
debugVisible: false,
aboutExeVisible: false,
})
const content = ref('content')
// 应用 toRefs 解构
const {patternVisible, debugVisible, aboutExeVisible} = toRefs(data)
</script>
3. method 办法的应用
<template >
<button @click="onClickHelp"> 帮忙 </button>
</template>
<script setup>
import {reactive} from 'vue'
const data = reactive({aboutExeVisible: false,})
// 点击帮忙
const onClickHelp = () => {console.log(` 帮忙 `)
data.aboutExeVisible = true
}
</script>
4. watchEffect 的应用
<script setup>
import {
ref,
watchEffect,
} from 'vue'
let sum = ref(0)
watchEffect(()=>{
const x1 = sum.value
console.log('watchEffect 所指定的回调执行了')
})
</script>
5. watch 的应用
<script setup>
import {
reactive,
watch,
} from 'vue'
// 数据
let sum = ref(0)
let msg = ref('hello')
let person = reactive({
name:'张三',
age:18,
job:{
j1:{salary:20}
}
})
// 两种监听格局
watch([sum,msg],(newValue,oldValue)=>{console.log('sum 或 msg 变了',newValue,oldValue)
},
{immediate:true}
)
watch(()=>person.job,(newValue,oldValue)=>{console.log('person 的 job 变动了',newValue,oldValue)
},{deep:true})
</script>
6. computed 计算属性的应用
computed
计算属性有两种写法(简写和思考读写的残缺写法)
<script setup>
import {
reactive,
computed,
} from 'vue'
// 数据
let person = reactive({
firstName:'poetry',
lastName:'x'
})
// 计算属性简写
person.fullName = computed(()=>{return person.firstName + '-' + person.lastName})
// 残缺写法
person.fullName = computed({get(){return person.firstName + '-' + person.lastName},
set(value){const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
</script>
7. props 父子传值的应用
父组件代码如下(示例):
<template>
<child :name='name'/>
</template>
<script setup>
import {ref} from 'vue'
// 引入子组件
import child from './child.vue'
let name= ref('poetry')
</script>
子组件代码如下(示例):
<template>
<span>{{props.name}}</span>
</template>
<script setup>
import {defineProps} from 'vue'
// 申明 props
const props = defineProps({
name: {
type: String,
default: 'poetries'
}
})
// 或者
//const props = defineProps(['name'])
</script>
8. emit 子父传值的应用
父组件代码如下(示例):
<template>
<AdoutExe @aboutExeVisible="aboutExeHandleCancel" />
</template>
<script setup>
import {reactive} from 'vue'
// 导入子组件
import AdoutExe from '../components/AdoutExeCom'
const data = reactive({aboutExeVisible: false,})
// content 组件 ref
// 对于零碎暗藏
const aboutExeHandleCancel = () => {data.aboutExeVisible = false}
</script>
子组件代码如下(示例):
<template>
<a-button @click="isOk">
确定
</a-button>
</template>
<script setup>
import {defineEmits} from 'vue';
// emit
const emit = defineEmits(['aboutExeVisible'])
/**
* 办法
*/
// 点击确定按钮
const isOk = () => {emit('aboutExeVisible');
}
</script>
9. 获取子组件 ref 变量和 defineExpose 裸露
即 vue2
中的获取子组件的ref
,间接在父组件中管制子组件办法和变量的办法
父组件代码如下(示例):
<template>
<button @click="onClickSetUp"> 点击 </button>
<Content ref="content" />
</template>
<script setup>
import {ref} from 'vue'
// content 组件 ref
const content = ref('content')
// 点击设置
const onClickSetUp = ({key}) => {content.value.modelVisible = true}
</script>
<style scoped lang="less">
</style>
子组件代码如下(示例):
<template>
<p>{{data}}</p>
</template>
<script setup>
import {
reactive,
toRefs
} from 'vue'
/**
* 数据局部
* */
const data = reactive({
modelVisible: false,
historyVisible: false,
reportVisible: false,
})
defineExpose({...toRefs(data),
})
</script>
10. 路由 useRoute 和 useRouter 的应用
<script setup>
import {useRoute, useRouter} from 'vue-router'
// 申明
const route = useRoute()
const router = useRouter()
// 获取 query
console.log(route.query)
// 获取 params
console.log(route.params)
// 路由跳转
router.push({path: `/index`})
</script>
11. store 仓库的应用
<script setup>
import {useStore} from 'vuex'
import {num} from '../store/index'
const store = useStore(num)
// 获取 Vuex 的 state
console.log(store.state.number)
// 获取 Vuex 的 getters
console.log(store.state.getNumber)
// 提交 mutations
store.commit('fnName')
// 散发 actions 的办法
store.dispatch('fnName')
</script>
12. await 的反对
setup
语法糖中可间接应用 await
,不须要写async
,setup
会主动变成async setup
<script setup>
import api from '../api/Api'
const data = await Api.getData()
console.log(data)
</script>
13. provide 和 inject 祖孙传值
父组件代码如下(示例):
<template>
<AdoutExe />
</template>
<script setup>
import {ref,provide} from 'vue'
import AdoutExe from '@/components/AdoutExeCom'
let name = ref('py')
// 应用 provide
provide('provideState', {
name,
changeName: () => {name.value = 'poetries'}
})
</script>
子组件代码如下(示例):
<script setup>
import {inject} from 'vue'
const provideState = inject('provideState')
provideState.changeName()
</script>
组件中写 name 属性的益处
能够标识组件的具体名称不便调试和查找对应属性
// 源码地位 src/core/global-api/extend.js
// enable recursive self-lookup
if (name) {Sub.options.components[name] = Sub // 记录本人 在组件中递归本人 -> jsx
}
你有对 Vue 我的项目进行哪些优化?
(1)代码层面的优化
- v-if 和 v-show 辨别应用场景
- computed 和 watch 辨别应用场景
- v-for 遍历必须为 item 增加 key,且防止同时应用 v-if
- 长列表性能优化
- 事件的销毁
- 图片资源懒加载
- 路由懒加载
- 第三方插件的按需引入
- 优化有限列表性能
- 服务端渲染 SSR or 预渲染
(2)Webpack 层面的优化
- Webpack 对图片进行压缩
- 缩小 ES6 转为 ES5 的冗余代码
- 提取公共代码
- 模板预编译
- 提取组件的 CSS
- 优化 SourceMap
- 构建后果输入剖析
- Vue 我的项目的编译优化
(3)根底的 Web 技术的优化
- 开启 gzip 压缩
- 浏览器缓存
- CDN 的应用
- 应用 Chrome Performance 查找性能瓶颈
Vue 模板编译原理
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)第二步是对 AST 进行动态节点标记,次要用来做虚构 DOM 的渲染优化(优化器)第三步是 应用 element ASTs 生成 render 函数代码字符串(代码生成器)
相干代码如下
export function compileToFunctions(template) {
// 咱们须要把 html 字符串变成 render 函数
// 1. 把 html 代码转成 ast 语法树 ast 用来形容代码自身造成树结构 不仅能够形容 html 也能形容 css 以及 js 语法
// 很多库都使用到了 ast 比方 webpack babel eslint 等等
let ast = parse(template);
// 2. 优化动态节点
// 这个有趣味的能够去看源码 不影响外围性能就不实现了
// if (options.optimize !== false) {// optimize(ast, options);
// }
// 3. 通过 ast 从新生成代码
// 咱们最初生成的代码须要和 render 函数一样
// 相似_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world"))))
// _c 代表创立元素 _v 代表创立文本 _s 代表文 Json.stringify-- 把对象解析成文本
let code = generate(ast);
// 应用 with 语法扭转作用域为 this 之后调用 render 函数能够应用 call 扭转 this 不便 code 外面的变量取值
let renderFn = new Function(`with(this){return ${code}}`);
return renderFn;
}
Vue 模版编译原理
vue 中的模板 template 无奈被浏览器解析并渲染,因为这不属于浏览器的规范,不是正确的 HTML 语法,所有须要将 template 转化成一个 JavaScript 函数,这样浏览器就能够执行这一个函数并渲染出对应的 HTML 元素,就能够让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。
- 解析阶段:应用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为形象语法树 AST。
- 优化阶段:遍历 AST,找到其中的一些动态节点并进行标记,不便在页面重渲染的时候进行 diff 比拟时,间接跳过这一些动态节点,优化 runtime 的性能。
- 生成阶段:将最终的 AST 转化为 render 函数字符串。
Vue.extend 作用和原理
官网解释:
Vue.extend
应用根底Vue
结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。
其实就是一个子类结构器 是 Vue
组件的外围 api
实现思路就是应用原型继承的办法返回了 Vue 的子类 并且利用 mergeOptions
把传入组件的 options
和父类的 options
进行了合并
extend
是结构一个组件的语法器。而后这个组件你能够作用到Vue.component
这个全局注册办法里还能够在任意vue
模板里应用组件。也能够作用到vue
实例或者某个组件中的components
属性中并在外部应用apple
组件。Vue.component
你能够创立,也能够取组件。
相干代码如下
export default function initExtend(Vue) {
let cid = 0; // 组件的惟一标识
// 创立子类继承 Vue 父类 便于属性扩大
Vue.extend = function (extendOptions) {
// 创立子类的构造函数 并且调用初始化办法
const Sub = function VueComponent(options) {this._init(options); // 调用 Vue 初始化办法
};
Sub.cid = cid++;
Sub.prototype = Object.create(this.prototype); // 子类原型指向父类
Sub.prototype.constructor = Sub; //constructor 指向本人
Sub.options = mergeOptions(this.options, extendOptions); // 合并本人的 options 和父类的 options
return Sub;
};
}
谈谈 Vue 和 React 组件化的思维
- 1. 咱们在各个页面开发的时候,会产生很多反复的性能,比方 element 中的 xxxx。像这种纯正非页面的 UI,便成为咱们罕用的 UI 组件,最后的前端组件也就仅仅指的是 UI 组件
- 2. 随着业务逻辑变得越来多是,咱们就想要咱们的组件能够解决很多事,这就是咱们常说的组件化,这个组件就不是 UI 组件了,而是包具体业务的业务组件
- 3. 这种开发思维就是分而治之。最大水平的升高开发难度和保护老本的成果。并且能够多人合作,每个人写不同的组件,最初像撘积木一样的把它形成一个页面
怎么实现路由懒加载呢
这是一道应用题。当打包利用时,JavaScript 包会变得十分大,影响页面加载。如果咱们能把不同路由对应的组件宰割成不同的代码块,而后当路由被拜访时才加载对应组件,这样就会更加高效
// 将
// import UserDetails from './views/UserDetails'
// 替换为
const UserDetails = () => import('./views/UserDetails')
const router = createRouter({
// ...
routes: [{path: '/users/:id', component: UserDetails}],
})
答复范例
- 当打包构建利用时,JavaScript 包会变得十分大,影响页面加载。利用路由懒加载咱们能把不同路由对应的组件宰割成不同的代码块,而后当路由被拜访的时候才加载对应组件,这样会更加高效,是一种优化伎俩
- 一般来说,对所有的 路由都应用动静导入 是个好主见
- 给
component
选项配置一个返回Promise
组件的函数就能够定义懒加载路由。例如:{path: '/users/:id', component: () => import('./views/UserDetails') }
- 联合正文
() => import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
能够做webpack
代码分块
Vue 的基本原理
当一个 Vue 实例创立时,Vue 会遍历 data 中的属性,用 Object.defineProperty(vue3.0 应用 proxy)将它们转为 getter/setter,并且在外部追踪相干依赖,在属性被拜访和批改时告诉变动。每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会告诉 watcher 从新计算,从而以致它关联的组件得以更新。
diff 算法
<details open=””><summary>答案 </summary>
<p>
</p><p> 工夫复杂度: 个树的齐全 diff
算法是一个工夫复杂度为 O(n*3)
,vue 进行优化转化成 O(n)
。</p>
<p> 了解:</p>
<ul>
<li>
<p> 最小量更新, key
很重要。这个能够是这个节点的惟一标识,通知 diff
算法,在更改前后它们是同一个 DOM 节点 </p>
<ul>
<li> 扩大 v-for
为什么要有 key
,没有 key
会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改 DOM),加 key
只会挪动缩小操作 DOM。</li>
</ul>
</li>
<li>
<p> 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。</p>
</li>
<li>
<p> 只进行同层比拟,不会进行跨层比拟。</p>
</li>
</ul>
<p>diff 算法的优化策略:四种命中查找,四个指针 </p>
<ol>
<li>
<p> 旧前与新前(先比结尾,后插入和删除节点的这种状况)</p>
</li>
<li>
<p> 旧后与新后(比结尾,前插入或删除的状况)</p>
</li>
<li>
<p> 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)</p>
</li>
<li>
<p> 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)</p>
</li>
</ol>
<p></p>
</details>
— 问完下面这些如果都能很分明的话,根本 O 了 —
以下的这些简略的概念,你必定也是没有问题的啦😉