01、基本概念
1.1、先了解下MVVM
VUE是基于MVVM思想实现的,❓那什么是MVVM呢?—— MVVM,是Model-View-ViewModel的缩写,是一种软件架构模式。其核心思想就是分离视图、数据、逻辑,VUE框架解决了数据Model
到视图View
的双向绑定,我们只关注业务逻辑ViewModel
即可,极大的提高的编程效率。
- M:Model,数据模型,通常来自后端服务、数据库。
- V:View,视图,就是HTML页面,Dom。
- VM:ViewModel,视图模型,连接模型和视图,实现数据和视图的相互绑定。包含了视图状态、行为,如页面展示的内容(逻辑)、页面响应事件、数据获取更新等,都封装在 ViewModel 中。
VUE实现双向绑定的基本原理:
?Dom Listeners:通过对DOM元素(表单输入元素)的值变更监听,同步视图变化的数据到模型。
?Data Bindings:通过对模型数据拦截监听,监测数据变化进行更新视图。原理是基于基于ES5的特性Object.defineProperty(obj, propertyName, descriptor)
,通过其get、set访问器来监听数据变更,这也是Vue不支持IE8的原因。
?一个双向绑定的简单示例:
<div id="app"> | |
<h2>{{message}}--{{message}}</h2> | |
</div> | |
<form action="#"> | |
<input type="text" v-model="message"> | |
</form> | |
<script> | |
//定义一个数据ViewModel,数据实际存储在$data中。 | |
let app = { | |
$data: {} | |
} | |
//通过defineProperty的get、set拦截,检测数据变化》更新数据 | |
Object.defineProperty(app, "message", { | |
get() { return this.$data.message; }, | |
set(value) { | |
this.$data.message = value; | |
updateView(this); //更新视图 | |
} | |
}) | |
//保存模板、绑定了数据的表单元素 | |
let template = document.querySelector("#app").innerHTML; | |
let vinputs = document.querySelectorAll("input[v-model]"); | |
//更新视图 | |
function updateView(data) { | |
const html = template.replace(/{{(w+)}}/g, (match, $1) => { | |
return data[$1] || ""; | |
}) | |
document.querySelector("#app").innerHTML = html; | |
//更新表单元素的数据绑定 | |
vinputs.forEach(el => { | |
el.value = data[el.getAttribute('v-model')]; | |
}); | |
} | |
//监听输入表单的值变更 | |
vinputs.forEach(el => { | |
el.addEventListener("input", function (e) { | |
app[e.target.getAttribute('v-model')] = e.target.value; | |
}); | |
}); | |
//赋值 | |
app.message = "hello"; | |
</script> |
» 基本过程如图:
1.2、虚拟DOM
直接操作DOM,DOM频繁变动会使得页面不停的布局、渲染,是很消耗性能的,虚拟DOM就是来解决这个问题的。虚拟DOM 就是先在内存中构建一个虚拟DOM
结构(JS对象表示),批量操作完成后再一次性的更新到浏览器DOM
树上。VUE中的虚拟DOM操作思路也是如此,只是为了更高效,实际要稍微复杂一点点。
- ❶ 用JS 对象模拟
虚拟DOM树
。 - ❷ 比较DOM树差异(修改、删除、新增...)
- ❸ 将差异更新到真正的DOM树中。
1.3、VUE是什么?
中文官网:https://cn.vuejs.org | Vue3版本手册 | Vue2版本手册
Vue(读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架,同他周边的生态共同构成了一个灵活的、渐进式的前端框架。
- 2013年底作为尤雨溪个人实验项目开始开发。
- 2014年公开发布。
- 2016年10月发布2.0版本。
- 2020年发布3.0版本。
?核心特性:
- 响应式数据驱动(双向绑定),数据>视图(数据监听),视图>数据(事件监听)。
- 组件化:组件化的应用构建。
- 申明式渲染:采用模板语法申明式的将数据渲染到DOM。
- 轻量级框架:只关注视图层,是一个构建数据的视图集合,压缩后30+K;
- 高效率:基于虚拟化Dom技术,DOM操作效率很高。
02、准备开始
2.1、安装使用Vue2
- 通过
<script>
标签直接引用vue.js
:
<!-- 开发环境版本,包含了有帮助的命令行警告 --> | |
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> | |
<!-- 生产环境版本,优化了尺寸和速度 --> | |
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script> |
- 通过
vue-cli
脚手架搭建vue
的开发框架。
2.2、命名规范
前端无非就是HTML、CSS、JS,坑爹的是他们的命名规范并不相同。前端常用的两种命名方式:
- 小驼峰命名(lowerCamelCase):首字母小写,后面的单词首字母大写:
userName
。 - kebab-case命名:烤串命名法(kebab /kɪˈbɑːb/ 烤肉串),都小写,单词之间用短线连接:
user-name
。
类型 | 命名方式 | 说明 |
---|---|---|
HTML | kebab-case | 按照W3C规范,HTMl中(包括标签名、attribute)不区分大小写,HTML解析时都会转换为小写,支持字母数字、下划线_ 、短线- 。自定义名称一般都采用烤串命名,加短线主要是避免和HTML原生冲突(包括以后新的名称) |
CSS | kebab-case | 字母开头,字母数字、下划线_ 、短线- ,一般都采用烤串命名 |
JavaScript | lowerCamelCase | 字母开头,字母数字、下划线_ ,不支持短线- (作为对象属性得引起来)。还有些细分:? 类、构造函数采用大驼峰命名 ? 常量采用全大写+下划线_ |
在Vue中的命名就有一点特殊了(混合),默认是JavaScript的小驼峰命名,但当会用于Dom时,就要采用烤串命名了,比如:
- 组件的
id
:作为元素标签名 props
参数:作为元素attribute
- 自定义事件名:作为元素
attribute
- 自定义指令(包括指令参数、修饰符):作为元素
attribute
?自动转换:参数props、组件id可以采用小驼峰命名,Vue会自动转换。如果使用字符模板,就不归浏览器管了,就没有命名的限制问题了!
Vue中其他命名规范:
- 组件名,大驼峰,首字母大写。
2.3、开发插件
- Vue Devtools:浏览器调试插件 ,在浏览器的调试面板中使用,推荐Edge浏览器安装。
- Vetur:VSCode插件,Vue开发必备⭐,核心功能:
- ✅代码高亮:语法、语义代码高亮。
- ✅代码片段:常用代码片段的提示输入,以及自定义代码片段。
- ✅代码检查和格式化。
- ✅自定义代码模板。
- Volar,Vue3版本的vetur
03、Vue基础结构
3.1、创建一个Vue实例
每一个Vue组件都是通过Vue()
函数创建的一个Vue实例,他就是对应页面Dom的 ViewModel。
<div id="app2"> | |
<h2>{{title}}</h2> | |
<div> | |
<span v-text="user.name"></span>---<span v-text="age"></span> | |
<button v-on:click="showUpperCase">提交</button> | |
</div> | |
</div> | |
<script> | |
let vm = new Vue({ | |
el: "#app2", | |
data: { user: { name: "sam", birthday: '2000-12-1'}, title: "hello world!" }, | |
computed: { | |
age() { return (new Date().getFullYear() - new Date(this.user.birthday).getFullYear()); } | |
}, | |
methods: { | |
showUpperCase: function (e) { this.title = this.title.toUpperCase(); }, | |
} | |
}) | |
</script> |
Vue()
的构造函数参数,就是Vue的选项对象,提供了Vue实例的各项配置,常用配置:
el
:绑定视图Dom元素,通过app.$el
访问。data
:数据模型,会被Vue递归实现数据响应(数据监听),通过Vue对象的get、set访问器访问数据,数据实际都存放在vm.$data
上。这里的数据都是响应式的,支持数据双向绑定,数据变化会自动更新同步。computed
:计算属性(支持逻辑的属性),值是一个(getter)函数,使用同data
。会基于其依赖的响应式属性进行缓存:响应式属性变更会触发更新,缓存计算值,重复调用(绑定)复用结果。methods
:方法,实现各种业务逻辑,常用于绑定事件。
3.2、Vue选项?
?Vue的构造函数参数=Vue选项:
Vue选项 | 描述 |
---|---|
?选项-数据 | |
data | Vue的数据对象,会被递归实现propert 响应,vm.$data 访问原始数据。 |
computed | 计算属性,值是一个(getter)函数,使用同data 。会基于其依赖的响应式属性进行缓存:响应式属性变更会触发更新;缓存计算值,重复调用(绑定)复用结果。 |
methods | 方法,定义方法的地方,可以绑定事件、表达式调用、访问。 |
watch | 对property的监听回调,当属性值变更时触发。也可通过实例方法添加vm.$watch(pName,func) |
props | 定义组件的参数,作为组件特性使用:Array<string> | Object ,props: ['item','title'] |
propsData | 创建实例时传递 props,便于测试。{ [key: string]: any } |
?选项-Dom | |
el | Vue的DOM根元素,值为一个已存在Dom,(id )选择器或元素实例。 |
template | 字符串模板,优先于el 元素内容模板,template: <h3>{{user.name}}</h3>` |
render | 渲染函数,优先于template |
renderError | render的替身, render 函数错误时替补 |
?选项-资源 | |
directives | 指令,注册局部指令,directives: {id:{}} |
filters | 过滤器,filters{id:func(value,...arg)} |
components | 注册局部组件,components: { 'todo-box': com_todoBox } |
?选项-组合 | |
parent | 指定父级实例 |
mixins | 混入(复用)选项,Array<Object> ,mixins:[cmixin] |
extends | 继承另一个组件,复用代码。extends: CompA |
provide / inject | 依赖注入,父类向子类发红包,在父级定一个provider: {key:obj} ,后代inject :key ,获得父类的红包。用于一些特定对象的注入,后代需要才接收、可跨级。 |
?选项-其他 | |
inheritAttrs | bool值-默认true,是否继承组件上定义的(非Prop)特性Attribute,不会影响class、style绑定 |
model | 修改v-model 绑定的Prop和event,对于文本input默认v-model绑定的是value 和input 事件。 |
computed计算属性
计算属性在 computed
选项中,用于一些复杂逻辑数据的计算。值为(getter)函数,使用同data
作为vm
的属性用。
主要特点:会基于其依赖的响应式属性进行缓存:响应式属性变更会触发更新,缓存计算值,重复调用(绑定)复用结果。如果用方法methods,每次有任何更新都会被调用。
<div id="app4"> | |
<p>age:{{age}},{{age}},{{getAge()}},{{getAge()}}</p> | |
</div> | |
<script> | |
let app4 = new Vue({ | |
el: "#app4", | |
data: { user: { name: "sam", birthday: "2000-1-1" } }, | |
computed: { | |
age() { //多次调用缓存属性值 | |
console.log("computed.age"); //computed.age | |
return new Date().getFullYear() - new Date(this.user.birthday).getFullYear(); | |
} | |
}, | |
methods: { | |
getAge() { //多次调用会重复执行 | |
console.log("methods.getAge"); //methods.getAge methods.getAge | |
return new Date().getFullYear() - new Date(this.user.birthday).getFullYear(); | |
} | |
} | |
}) | |
</script> |
计算属性默认是没有setter的,不过也可以实现setter。
computed: { | |
age: { | |
get: function () { }, | |
set: function (value) { }, | |
} | |
}, |
watch监听器
针对data
中的响应式属性的值变更监听,也可以监听计算属性,存放在watch
选项里,用于实现值变更的一些自定义业务逻辑。不需要返回值,支持异步处理,computed
计算属性是必须要返回值的,异步就没有意义了。
基础语法:属性名: function (newValue, oldValue)
,更多参考 vm.$watch API
watch: { | |
question: function (newValue) { | |
//当用户问题变更时触发 | |
this.answer = "正在思考..."; | |
// ajax请求搜索答案,这里应该用防抖函数处理一下,避免频繁无意义的调用。 | |
fetchJsonp("https://www.baidu.com/s?wd=" + newValue) | |
.then(res => res.text()) | |
.then(text => { console.log(text); this.answer = text; }); //跨域请求失败 | |
}, | |
//或者对象的形式 | |
question:{deep:true,handler(newValue,oldValue){}} | |
} |
- 属性名可以支持链式属性路径
"user.age":function(){ }
,不支持数组内部对象。 - 参数
{deep:true}
可以深度监听对象下面的所有属性,但oldValue
可能不准确。{immediate: true}
可以立即触发一次监听。
3.3、Vue选项-实例生命周期
Vue实例从①创建阶段、到②运行阶段、再到③销毁终结的过程,就是Vue辉煌的一生。在这个过程中Vue提供了一些钩子函数,这些函数都没有参数,this指向组件自身。
Vue选项-生命周期钩子 | |
---|---|
beforeCreate | 创建前:各项属性配置都还不可用,几乎不能干什么 |
created | 创建后:实例化完成后同步调用。数据、事件、计算属性已完成,还未挂载模板,可发起ajax请求 |
beforeMount | 挂载el前:模板已完成编译(各项指令都解析完成了),但还未加入到页面Dom中 |
mounted | 挂载后-完成渲染:实例被挂载后调用,vm.$el 已创建,浏览器Dom完成渲染,用户看到正式的页面了 |
beforeUpdate | 更新前:数据发生改变后(数据最新),虚拟DOM 更新之前(页面未更新) |
updated | 更新后:页面Dom已更新完成,数据和页面都是最新的,首次渲染不会触发 |
beforeDestroy | 实例销毁之前调用,此时组件还是正常的。 |
destroyed | 实例销毁后调用,组件已销毁并从Dom中移除。 |
activated | 组件激活/显示,被 <keep-alive> 缓存的组件激活时调用 |
deactivated | 组件失活/隐藏,被 <keep-alive> 缓存的组件失活时调用 |
errorCaptured | 捕获后代组件的错误 |
» 画个图:VUE生命周期(ProcessOn版本)
?注意:不要在选项 property 或回调上使用箭头函数,箭头函数没有this,会导致默认的this(vm)丢失。
3.4、Vue实例?
?实例属性 | 描述 |
---|---|
$data | vue对象的data 实际存储的数据,vue代理了他的数据访问 |
$el | el 挂载的视图Dom元素 |
$options | Vue() 实例化的选项参数 |
$refs | 通过过ref attribute 注册的所有 DOM 元素和组件实例的一个对象,this.$refs.header |
$parent | 父实例,根实例的 $parent 为null |
$root | 组建树的根实例,根实例的 $root 为自己 |
$children | 子组件示例数组Array<Vue instance> |
$slots | 插槽分发的内容,{ [name: string]: ?Array<VNode> } |
$scopedSlots | 作用域插槽 { [name: string]: props => Array<VNode> | undefined } |
$listeners | 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器 |
$props | 组件定义的参数列表 |
$attrs | 父作用域中的非Prop的Attribute特性(不含class、style) |
$isServer | 是否运行于服务器,服务端渲染 |
?实例方法 | |
---|---|
$watch( expOrFn, callback, [options] ) | 添加属性变更监听器vm.$watch("title", function (val, old) {}); |
$set( target, proName/index, value ) | 添加响应式的property,并更新视图。用于后续增加的属性,同Vue.set() |
$delete( target, proName/index ) | 删除对象的 property,同Vue.delete() |
?实例事件 | |
---|---|
$on( eventName, callback ) | 添加一个事件监听,监听当前实例上的自定义事件,事件可以由 vm.$emit 触发 |
$once( eventName, callback ) | 添加一个事件监听,但是只触发一次,触发后被移除。 |
$off( [eventName, callback] ) | 移除自定义事件监听器 |
$emit( eventName, […args] ) | 触发当前实例上的事件,参数eventName 为事件名称,注意采用小写命名方式 |
?实例方法-生命周期 | |
---|---|
$mount( [elementOrSelector] ) | vue 实例挂载一个Dom元素,元素或CSS选择器 |
$forceUpdate() | 强制更新渲染 |
$nextTick( [callback] ) | 同Vue.nextTick |
$destroy() | 完全销毁一个实例 |
Vue静态API
Vue全局静态属性/方法 | 描述 |
---|---|
Vue.extend( options ) | 创建一个Vue“子类”(不是实例),选项的data必须是函数,避免组件实例共享数据 |
Vue.nextTick( [callback, context] ) | Dom更新后立即调用回调,常用于手动操作Dom(需等Dom更新完),他返回的是一个Promise ?Vue更新都是异步的,类似JS的事件循环队列,不会立即执行,便于去重提高性能 |
Vue.set( target, proName/index, value ) | 向响应式对象中添加一个响应式 property,并触发视图更新。可用于强制更新视图 |
Vue.delete( target, proName/index ) | 删除对象的 property,会触发更新 |
Vue.directive( id, [definition] ) | 注册或获取全局指令 |
Vue.filter( id, [definition] ) | 注册或获取全局过滤器 |
Vue.component( id, [definition] ) | 注册或获取全局组件 |
Vue.use( plugin ) | 安装 Vue.js 插件,为Vue添加一些全局的功能,如 vue-router |
Vue.mixin( mixin ) | 全局注册一个混入,小心使用,会影响后面所有的Vue实例。 |
Vue.compile( template ) | 将一个模板字符串编译成 render 函数 |
Vue.observable( object ) | 让一个对象可响应 |
Vue.version | 版本号,Vue.version //'2.7.14' |