一、Vue基础语法
Vue.js基础结构
使用el方式:
使用render函数方式:
Vue生命周期
Vue.js语法和概念
插值表达式
插值表达式相当于一个占位符,只会替换掉其中的占位置的内容。
v-text只能显示Vue对象传递过来的数据,会替换掉节点里已有的内容。
插值表达式和v-text不能够解析html标签,v-html能够解析html标签。
结论:
- 如果只是单独展示Vue对象里的数据,建议使用“v-text”指令。
- 如果要同时展示用户前台数据,那么就需要用插值表达式,但是不要忘记和“v-cloak”属性一起使用(同时需要设置样式[v-cloak]{display:none;})。
- 如果Vue对象传递过来的数据含有HTML标签,则使用v-html
指令
VUE提供的14个指令:
- v-model :双向数据绑定,表单元素常用 input select radio checkbox textarea 等,v-model有三个修饰符,例如input元素 v-model.trim去掉输入值的前后空格和v-model.number,将输入的字符串转换为number,v-model.lazy 输入的数据不再实时更新,而是数据失去焦点的时候再更新输入的数据
- v-show: 元素的显示和隐藏,频繁操作元素的显示和隐藏,就用v-show ,原理是操作的dom 的css样式 display的值是true还是false
- v-if:元素的显示和隐藏,原理是,是否创建元素的dom,例如表格中某条数据是否显示编辑,删除按钮,由后台传的数据解决的,这种不频繁操作的情况可用v-if,v-if 可以加入template标签中判断 v-show 不可以
- v-else : 和v-if 搭配使用
- v-else-if :条件满足v-if ?不满足判断v-else-if 如果还不满足直接走v-else 这个的使用方式和我们的js 中的 if ,else if() ,else 是类似的使用方式
- v-bind: 绑定 v-bind:class v-bind:style v-bind:attribute v-bind可以省略成: 最后写成 :class, :style, :attribute
- v-on :绑定常用事件 下面的常用事件去掉on 改为@click:点击某个对象时触发@clickondblclick:双击某个对象时触发@dblclickonmouseover:鼠标移入某个元素时触发@mouseoveronmouseout:鼠标移出某个元素时触发@mouseoutonmouseenter:鼠标进入某个元素时触发@onmouseenter
- v-for:项目中常用循环数组的指令。
- v-html :将字符串html 转换为结构显示,项目中基本不这种方式去处理,涉及到安全性问题
- v-text:防止为了{ {}} 闪烁问题 项目不常用
- v-once: 指令指的是元素仅仅绑定一次,只是渲染一次
- v-cloak:指的是cloak 等元素编译结束以后才会显示dom
- v-pre :跳过当前元素及子元素的编译过程,先进行编译,项目中基本没有用过
- v-slot:插槽
计算属性和侦听器
- 当模板中有太多逻辑需要处理时,推荐使用计算属性。计算属性的结果会被缓存,下次再访问该计算属性时,会从缓存中获取相应的结果,提高性能。
- 如果我们需要监听数据的变化,做一些比较复杂的操作,例如异步操作或者开销比较大的操作,此时我们可以使用侦听器。
Class和Style绑定
当绑定样式时,我们可以使用Class和Style。它们分别可以绑定数组或者对象,实际开发中,我们推荐使用Class绑定,可以实现样式复用。
条件渲染/列表渲染
- v-if :控制元素显示/隐藏。条件为false时,是不会输出相应的元素。
- v-show:控制元素显示/隐藏。元素会渲染到页面,通过样式控制其隐藏。
- v-for:列表渲染。Vue推荐我们给循环项都设置一个key,用来跟踪每个节点的身份,让每一项都能最大程度地被重用,从而提高性能。
表单输入绑定
当我们使用v-model绑定表单元素时,它负责去监听用户的输入事件,以及更新数据,即双向绑定。
组件
组件是可复用的Vue实例,一个组件封装了html,css,js,它可以实现页面上的一个功能区域,可以无限次的被重用。
插槽
插槽一般用于在自定义组件中挖坑,使用这个组件时去填坑。这样可以使组件更灵活。
插件
如vuex、vue-router都是插件。也可以自己开发插件。
混入mixin
如果多个组件都有相同的选项,就可以使用mixin方式,把相同的选项进行合并,让代码重用。是让组件重用的一种方式。
深入响应式原理
Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。
二、Vue Route原理分析与实现
Vue Router使用步骤

动态路由

嵌套路由
当多个路由组件都有相同的内容,我们可以把这个相同的内容提取到一个公共的组件当中。
编程式导航
常用方法:
$router.push()
- 参数为跳转路由地址
this.$router.push('/')
- 参数为对象
this.$router.push({
name: 'Detail', params: {
id: 1 } })
push方法会记录本次历史。
$router.replace()
this.$router.replace('/login')
注意:和push方法有些类似,都可以跳转到指定的路径,参数形式也是一样的。但是,replace方法不会记录本次历史,它会把我们当前的历史改变为我们指定的路径。
$router.go()
go是跳转到历史中的某一次,负数为后退。
this.$router.go(-2)
Hash和History模式区别
这两种模式都是客户端路由的实现方式,即当路径发生变化时,不会像服务器发送请求,是由JS监视路径的变化,然后根据不同的地址渲染不同的内容。如果需要服务端内容,会发送Ajax请求去获取。
表现形式的区别
-
Hash模式
URL中#后面的内容作为路径地址;监听hashchange事件;根据当前路由地址找到对应组件重新渲染。 -
History模式
History模式是一个正常的url。要用好History模式,还需要服务端配置支持。
通过调用history.pushState()改变地址栏;监听popstate事件;根据当前路由地址找到对应组件重新渲染。
原理的区别
- Hash模式
Hash模式是基于瞄点,以及onhashchange事件。
Vue Router 默认使用的是 hash 模式,使用 hash 来模拟一个完整的 URL,通过
onhashchange 监听路径的变化 - History模式
基于HTML5中的History API
history.pushState() //IE10以后才支持
history.replaceState()
history.go()
History模式的使用
- 开启 History 模式:
const router = new VueRouter({
// mode: 'hash',
mode: 'history',
routes
})
- History需要服务器的支持
- 单页应用中,服务端不存在 http://www.testurl.com/login 这样的地址会返回找不到该页面
- 在服务端应该除了静态资源外都返回单页应用的 index.html
History模式-Node.js服务器配置:const path = require('path') // 导入处理 history 模式的模块 const history = require('connect-history-api-fallback') // 导入 express const express = require('express') const app = express() // 注册处理 history 模式的中间件 app.use(history()) // 处理静态资源的中间件,网站根目录 ../web app.use(express.static(path.join(__dirname, '../web'))) // 开启服务器,端口是 3000 app.listen(3000, () => { console.log('服务器开启,端口:3000') })
History模式-nginx服务器配置:
- 从官网下载 nginx 的压缩包
- 把压缩包解压到 c 盘根目录,c:\nginx-1.18.0 文件夹
- 修改 conf\nginx.conf 文件
location / { root html; index index.html index.htm; #新添加内容 #尝试读取$uri(当前请求的路径),如果读取不到读取$uri/这个文件夹下的首页 #如果都获取不到返回根目录中的 index.html try_files $uri $uri/ /index.html; }
- 打开命令行,切换到目录c:\nginx-1.18.0
- nginx 启动、重启和停止
# 启动 start nginx # 重启 nginx -s reload # 停止 nginx -s stop
Vue Router模拟实现
Vue Router 的核心代码
//router/index.js
// 注册插件
// Vue.use() 内部调用传入对象的 install 方法
Vue.use(VueRouter)
// 创建路由对象
const router = new VueRouter({
routes: [
{
name: 'home', path: '/', component: homeComponent }
]
})
// 创建 Vue 实例,注册 router 对象
new Vue({
router,
render: h => h(App)
}).$mount('#app')
类图
实现思路
- 创建 LVueRouter 插件,静态方法 install
- 判断插件是否已经被加载/安装
- 把Vue构造函数记录到全局变量
- 当 Vue 加载的时候把创建Vue实例时传入的 router 对象注入/挂载到 Vue 实例上(注意:只执行一次)
- 创建 LVueRouter 类
- 初始化,options、routeMap、app(简化操作,创建 Vue 实例作为响应式数据记录当前路
径) - initRouteMap() 遍历所有路由信息,把组件和路由的映射记录到 routeMap 对象中
- 注册 popstate 事件,当路由地址发生变化,重新记录当前的路径
- 创建 router-link 和 router-view 组件
- 当路径改变的时候通过当前路径在 routerMap 对象中找到对应的组件,渲染 router-view
- 初始化,options、routeMap、app(简化操作,创建 Vue 实例作为响应式数据记录当前路
实现代码(History模式)
let _Vue = null
//创建 VueRouter 插件,即实现 VueRouter 类
export default class VueRouter {
static install(Vue) {
// 1. 判断插件是否已经被加载/安装
// 如果插件已经安装直接返回
if (VueRouter.install.installed) {
return
}
VueRouter.install.installed = true
// 2. 把Vue构造函数记录到全局变量
_Vue = Vue
// 3. 把创建Vue实例时传入的 router 对象注入/挂载到 Vue 实例上(注意:只执行一次)
//混入
_Vue.mixin({
beforeCreate() {
// 插件的 install() 方法中调用 init() 初始化
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
// 初始化插件的时候,调用 init
this.$options.router.init()
}
}
})
}
//构造函数
constructor(options) {
this.options = options
// 记录路径和对应的组件
this.routeMap = {
}
this.data = _Vue.observable({
// 当前的默认路径
current: '/'
})
}
//初始化
init() {
this.initRouteMap()
this.initComponents()
this.initEvent()
}
//解析路由规则成键值对形式
initRouteMap() {
// routes => [{ name: '', path: '', component: }]
//遍历所有的路由规则,把路由规则解析成键值对的形式,存储到routeMap中
this.options.routes.forEach(route => {
// 记录路径和组件的映射关系
this.routeMap[route.path] = route.component
})
}
//创建 router-link 和 router-view 组件
initComponents() {
_Vue.component('router-link', {
props: {
to: String
},
// template: '<a :href="to"><slot></slot></a>'
//运行时版本不支持template,需要使用render
render(h) {
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
},
methods: {
clickHandler(e) {
history.pushState({
}, '', this.to)
this.$router.data.current = this.to
e.preventDefault()
}
}
})
const self = this
_Vue.component('router-view', {
render(h) {
// 根据当前路径找到对应的组件,注意 this 的问题
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
//注册事件
initEvent() {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
}
Vue的构建版本
- 运行时版:不支持template模板,需要打包的时候提前编译
- 完整版:包含运行时和编译器,体积比运行时版大10K左右,程序运行的时候把模板转换成render函数
注意:
vue-cli 创建的项目默认使用的是运行时版本的 Vue.js
- 如果想切换成带编译器版本的 Vue.js,需要修改 vue-cli 配置
- 项目根目录创建 vue.config.js 文件,添加 runtimeCompiler
module.exports = {
runtimeCompiler: true
}
三、模拟 Vue.js 响应式原理
数据驱动
数据响应式、双向绑定、数据驱动。
- 数据响应式
数据模型仅仅是普通的 JavaScript 对象,而当我们修改数据时,视图会进行更新,避免了繁
琐的 DOM 操作,提高开发效率。 - 双向绑定
数据改变,视图改变;视图改变,数据也随之改变。
我们可以使用 v-model 在表单元素上创建双向数据绑定。 - 数据驱动
数据驱动是 Vue 最独特的特性之一。
开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图。
响应式的核心原理
Vue 2.x(基于Object.defineProperty)
- Vue 2.x深入响应式原理
- MDN - Object.defineProperty
- 浏览器兼容 IE8 以上(不兼容 IE8)
一个对象中一个属性需要转换 getter/setter ,处理方式:
// 模拟 Vue 中的 data 选项
let data = {
msg: 'hello'
}
// 模拟 Vue 的实例
let vm = {
}
// 数据劫持:当访问或者设置 vm 中的成员的时候,做一些干预操作
Object.defineProperty(vm, 'msg', {
// 可枚举(可遍历)
enumerable: true,
// 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义)
configurable: true,
// 当获取值的时候执行
get () {
console.log('get: ', data.msg)
return data.msg
},
// 当设置值的时候执行
set (newValue) {
console.log('set: ', newValue)
if (newValue === data.msg) {
return
}
data