(b) 遍历插入
© 链表式插入
图5.2 DOM Diff算法示例
网页的变化本质上是DOM节点的变化,因此虚拟DOM的核心之一在于新旧DOM树的比较,称为DOM Diff算法。DOM Diff算法只对DOM树的变化部分进行渲染,而对其余部分不作改动,避免遍历,从而大大提高渲染效率。如图5.2所示,DOM Diff只需变化新插入的节点,这意味着虚拟DOM树并非数组类型的顺序数据结构,而是链表型的无序数据结构,因此要高效应用虚拟DOM,需要为DOM节点指定key属性。
5.2 Vue运行流程
图5.3 Vue运行流程
可以看出在Vue架构中,最终渲染都由渲染函数完成。运行Vue框架有两种模式:
(1) runtime-only
:不包含模板编译阶段;
(2) runtime-compiler
:包含模板编译阶段。
// runtime-only
new Vue({
el: '#app',
render: h => h(App)
})
// runtime-compiler
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
runtime-only运行时不包含模板编译阶段,所有模板只能在.Vue文件中借助Vue插件vue-template-compiler完成开发时编译,在项目打包时模板只已render函数形式存在于工程中,运行时不会二次编译,因此runtime-only运行更快、项目更轻,但该模式下不能存在于非.Vue文件中,否则无法渲染。
runtime-compiler函数不限制模板存在的文件形式,但模板仅在运行时编译,性能低于runtime-only模式。
5.3 生命周期钩子
Vue中生命周期指一个Vue实例从创建到销毁的全部过程,如图5.4所示。在Vue实例的生命周期中有很多特殊的时间节点,且往往需要在这些时间节点执行一定的逻辑。封装了这些逻辑的函数称为钩子函数。钩子函数不同于回调函数,前者在事件发生的第一时间激活,后者在事件发生后激活。
常见的生命周期钩子函数如表5.1所示。
图5.4
序号 | 生命周期钩子 | 含义 |
---|---|---|
1 | beforeCreate() | 实例初始化之后,数据观测和事件配置之前调用,此时组件的options还未初始化 |
2 | created() | 实例初始化且完成options配置后,挂载开始前调用,$el属性还未初始化 |
3 | beforeMount() | 挂载阶段开始后,渲染网页前调用,实例已完成模板编译,生成虚拟DOM树(此时$el为VDOM属性) |
4 | mounted() | 实例挂载完成,渲染到网页后调用 |
5 | beforeUpdate() | 数据更新前调用,由于VDOM还未打补丁,此时进一步更改状态不会触发重渲染 |
6 | updated() | 数据更新后使用 |
7 | beforeDestroy() | 实例销毁前调用,此时仍可访问实例及其子代的各属性 |
8 | destroyed() | 实例销毁后调用 |
9 | activated() | 被keep-alive缓存的组件激活时调用 |
10 | deactivated() | 被keep-alive缓存的组件停用时调用 |
表5.1
6 Vue-cli脚手架
6.1 Webpack工具
6.1.1 基本配置
在大型前端工程中,模块化有助于降低代码耦合性,防止功能冲突。前端模块化的方案有AMD
、CMD
、CommonJS
、ES6
等,其中除ES6外的模块化开发方案都需要特定的运行环境。webpack是一个基于Node.js的现代JS应用的静态模块打包工具,统一了各模块化方案,并且可以自动处理模块间的相互依赖。
对于新工程,webpack打包的流程如下:
npm init
// 安装本地webpack包(@指定版本)
npm install webpack@3.5 --save-dev
// 安装全局webpack包
// npm install webpack -g
npm install
为便于不同项目的管理,通常在每个工程下安装局部webpack包,防止版本冲突。但终端执行webpack却会使用全局webpack包,因此要在产生的package.json中配置webpack启动项:
"scripts": {
"build": "webpack --config ./build/prod.config.js",
"dev": "webpack-dev-server --config ./build/dev.config.js --open "
},
此时运行npm run build即可进行生产模式下项目的打包。
6.1.2 转换器Loader
原生webpack只能处理JS文件及其依赖,但前端开发中还有CSS、Vue文件等,此时需要webpack转化器loader对其他格式的文件进行扩展,大部分文件的转化器可在webpack官网查询。以安装CSS、Vue扩展转换器为例:
// 安装CSS loader
cnpm install style-loader --save-dev
cnpm install css-loader --save-dev
// 安装Vue(运行时依赖,不加-dev)
cnpm install vue@2.6.14 –save
// 安装Vue loader
cnpm install vue-loader vue-template-compiler --save-dev
在配置文件.config.js中作如下配置:
module: {
rules: [
{
test: /\.css$/, // 使能CSS打包
// 使用多个loader时,从右向左读
// css-loader: 加载CSS文件
// style-loader: 将样式加载到DOM
use: ['style-loader', 'css-loader' ]
},
{
test: /\.vue$/, // 使能vue打包
use: ['vue-loader']
}
]
},
resolve: {
alias: {
// 引入vue
'vue$': 'vue/dist/vue.esm.js'
}
}
6.1.3 插件Plugin
webpack插件是对webpack现有功能的各种扩展,使webpack使用更方便,常用插件如表6.1所示。
名称 | 功能 |
---|---|
HtmlWebpackPlugin | 自动生成项目html文件(可指定生成模板),并将打包的js文件以 |
表6.1 webpack常用插件
下面给出配置扩展插件的实例。
安装插件:
cnpm install html-webpack-plugin --save-dev
cnpm install uglifyjs-webpack-plugin@1.1.1 --save-dev
配置.config.js:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const webpack = require('webpack');
const uglifyJsWebpack = require('uglifyjs-webpack-plugin');
module.exports = {
...
plugins:[
// vue-loader15以上的版本需要此插件解析.vue文件
new VueLoaderPlugin(),
// 版权声明
new webpack.BannerPlugin('@Copyright Winter 2018 - 2021'),
// 打包html到发布文件夹dist
new HtmlWebpackPlugin({
template: 'index.html' // html模板
})
],
// 压缩JS代码
optimization: {
minimizer: [new uglifyJsWebpack()],
}
}
6.1.4 搭建本地服务器
安装:
cnpm install webpack-dev-server --save-dev
配置.config.js:
// 服务器配置
devServer: {
// 指定提供本地服务的文件夹
static: {
directory: path.join(__dirname, '../dist'),
},
// 实时监听
compress: true,
// 指定服务器端口
port: 8080
}
6.2 vue-cli
在大型项目开发时,需要使用Vue-CLI辅助完成代码目录结构、项目结构的部署、热加载、单元测试、插件与与依赖安装等配置。
在Vue-CLI中通过以下指令初始化项目:
// Vue-CLI 2.x
vue init webpack project-name
// Vue-CLI >= 3.0
vue create project-name
以Vue-CLI 4.5为例介绍主要代码结构,如表6.2所示。
序号 | 文件 | 含义 |
---|---|---|
1 | dist | 最终打包发布的生产版本应用 |
2 | node_modules | npm加载的项目所需要的各种依赖模块 |
3 | public | 静态资源目录,如图片字体等(Vue-CLI 2.x中为static) |
4 | src | 开发源目录 |
5 | assets | 放置一些图片,如logo等 |
6 | components | 组件文件 |
7 | router | 路由配置 |
8 | App.vue | 项目入口组件,构建了与其他组件的关联关系 |
9 | main.js | 项目入口文件,配置依赖 |
10 | .browserslistrc | 浏览器适配性配置 |
11 | .gitignore | Git提交项目的忽略文件 |
12 | babel.config.js | 检测ES6语法,并转化为ES5以提高浏览器兼容性 |
13 | package-lock.json | 项目依赖的实际版本信息 |
14 | package.json | 项目依赖的版本要求信息 |
表6.2
6.3 Vue-router
实现前端路由主要分为以下步骤:
(1) 路由管理器index.js中配置路由映射表
const routes = [
{
path: '/',
redirect: '/home', // 重定向
},
{
path: '/home',
name: 'Home',
component: Home,
children: [
{
path: 'news',
component: () => import('../views/HomeNews') // 路由懒加载
},
{
path: 'message',
component: () => import('../views/HomeMessage')
}
]
},
{
path: '/user/:id',
name: 'User',
component: () => import('../views/User.vue')
}
]
注意:
① 采用组件化开发,一条前端路由对应渲染一个组件(可嵌套其他组件)。
② 打包构建Web应用时,JS包通常体积较大,影响页面加载。因此Vue提供了一种路由懒加载机制——将不同路由对应的组件分为不同代码块,当路由被访问时才对该组件进行渲染和加载。通过箭头函数导入的组件属于懒加载模式。
(2) 路由器实例化并传入路由表
const router = createRouter({
history: createWebHistory(process.env.BASE\_URL),
routes
})
export default router
(3) 将路由器挂载到根实例
createApp(App).use(router).mount('#app')
注意:
将路由器挂载到Web应用后会产生全局路由器实例router和局部路由实例route,通过this. r o u t e r 和 t h i s . router和this. router和this.route的语法访问。
全局路由器对象常用的属性和方法如表6.3所示,局部路由对象常用的属性和方法如表6.4所示。
属性/方法 | 含义 |
---|---|
push() | URL跳转(向浏览器history对象入栈记录) |
go() | 以当前路由为基准,在history栈中前进或后退若干路由 |
replace() | URL替换(不向浏览器history对象入栈记录) |
routes | 全局路由映射表 |
currentRoute | 当前激活的路由 |
表6.3
属性/方法 | 含义 |
---|---|
path | 当前路由的绝对路径 |
params | 当前路由中动态片段和全匹配片段的键值对 |
query | 当前路由中查询参数的键值对 |
表6.4
(4) 路由渲染
<router-link to="/home" replace>Home</router-link>
<router-link to="/about" replace>About</router-link>
<!-- 动态路由 -->
<router-link :to="{path:'/user/'+ userId}" replace>User</router-link>
注意:
默认会被渲染成标签;可视为组件占位符,根据当前路由在该位置动态渲染出不同的组件,而保持其他内容不变。
6.4 Vuex
Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用所有组件的状态,并以一定的规则保证状态以一种可预测的方式发生变化,提高可维护性。Vuex适用于存储多个组件共享、依赖的状态,或多个组件的行为都会变更的状态,此时全局管理可以避免繁琐的组件通信。
图6.4
核心概念 | 解释 |
---|---|
state | 单一状态树,用于存储全局状态。可通过this.$store.state访问。可通过Vue.set(obj, key, val)与Vue.delete(obj, key)来响应式地添加或删除state中的属性 |
mutations | 用于定义对状态改变的同步操作行为,便于Vuex跟踪状态变化。通过this.$store.commit()来修改状态 |
actions | 用于定义对状态改变的异步操作行为,便于Vuex跟踪状态变化。注意actions事件内部通过context.commit()修改状态,外部通过this.$store.dispatch()来触发异步行为 |
modules | 用于将复杂应用的大量状态模块化管理,每个模块拥有自己的state、mutations等属性。通过this.$store.state.module访问模块module中的属性 |
注意:
(1) 一般采用对象风格向Vuex进行提交状态改变,mutations中定义的方法可使用载荷payload来获取提交时额外传入的参数,例如:
// mutation-types.js
export const INCREMENT = "increment"
// index.js
// 导入事件常量
import {INCREMENT} from './mutation-types'
// Vuex配置
state: {
count: 1
},
mutations: {
[INCREMENT](state, payload) {
state.count = payload.amount;
}
},
// 提交状态变化
this.$store.commit({
type: INCREMENT,
amount: 10
})
推荐使用事件常量替代mutations事件类型,这样可以使linter之类的工具发挥作用,同时把这些常量放在单独的文件中便于维护整个应用包含的mutations事件。
(2) actions使用实例
actions: {
[INFOUPDATE](context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit({
type: INCREMENT,
amount: payload.num
});
resolve('完成了');
}, 1000)
})
}
},
// 建议使用Promise对异步操作的结果进行处理
this.$store.dispatch({
type: INFOUPDATE,
num: 10
}).then(res => {
console.log(res)
})
(3) modules使用实例
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
### 更多面试题
**《350页前端校招面试题精编解析大全》**内容大纲主要包括 **HTML,CSS,前端基础,前端核心,前端进阶,移动端开发,计算机基础,算法与数据结构,项目,职业发展等等**
**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**
![](https://img-blog.csdnimg.cn/img_convert/7ce167285c5be5df4dfe3f2f2b0bf8bc.webp?x-oss-process=image/format,png)
');
}, 1000)
})
}
},
// 建议使用Promise对异步操作的结果进行处理
this.$store.dispatch({
type: INFOUPDATE,
num: 10
}).then(res => {
console.log(res)
})
(3) modules使用实例
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
### 更多面试题
**《350页前端校招面试题精编解析大全》**内容大纲主要包括 **HTML,CSS,前端基础,前端核心,前端进阶,移动端开发,计算机基础,算法与数据结构,项目,职业发展等等**
**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**
[外链图片转存中...(img-nAgVYlNk-1714761913550)]