官方帮助文档:
MVVM:双向数据绑定
页面输入改变数据,数据改变影响页面数据展示与渲染
M(model):普通的javascript数据对象
V(view):前端展示页面
VM(ViewModel):用于双向绑定数据与页面,对于我们的课程来说,就是vue的实例
VM就是 [视图模型] ,它做了两件事:事件监听,数据绑定
<!-- 引入vue.js类库 -->
<script src="./js/vue.js"></script>
<!-- 指定vue要去解析的模板挂载点 -->
<div id="app">
<!-- 渲染视图,解析指定变量,让它成功你声明的数据 -->
<h3>{{ msg }}</h3>
<hr>
<!-- 动态绑定数据 -->
<input type="text" v-model="msg">
</div>
<script>
// 实例化Vue
const vm = new Vue({
// 指定解析挂载点 document.querySelector
el: '#app',
// 声明一个变量
// 指定数据源 vue2中数据在new Vue中可以写对应也可以写函数,在Vue3中只能写函数
data(){
return {
msg: '你好Vue'
}
}
})
</script>
插件
谷歌商店,搜vue devtools,第二个
https://chrome.google.com/webstore?utm_source=chrome-ntp-icon
或 极简插件
在vscode中安装插件 vetur (工程化有提示)
双向绑定原理 / MVVM实现
实现 视图模型 必须要用到 ES5 中的 Object.defineProperty (vue2) 或者是 ES6 提供的 Proxy (vue3) .这两个都能做到 数据劫持.
Object.defineProperty() - JavaScript | MDN
defineProperty和Proxy的区别
defineProperty和Proxy的区别_tby_pr的博客-CSDN博客
冻结对象
冻结对象1. freeze
let target = { id: 100 } // 冻结对象 对象就没有办法修改了 target = Object.freeze(target) target.id = 200
冻结对象2. Object.defineProperty 的 writable:false
当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变。 默认为 false。
let target = {} Object.defineProperty(target, 'id', { value:100, writable :false }); target.id = 200
实现mvvm(第一版)只能修改一个数据msg
<div id="app">
<h3 v-text="msg"></h3>
<h3 v-text="age"></h3>
<hr>
<input type="text" v-model="msg">
</div>
<script>
var data = {
msg: '你好mvvm',
age: 20
}
// let target = { id: 100 }
// 冻结对象 对象就没有办法修改了
// target = Object.freeze(target)
// target.id = 200
let target = {}
// 劫持当前的对象
// defineProperty 它只能对对象中的属性进行劫持,不能劫持数组
Object.defineProperty(target, 'msg', {
// 获取器
get() {
console.log('get')
return data.msg
},
// 修改器
set(newV) {
if (newV != data.msg) {
console.log('set')
data.msg = newV
compileRender()
}
}
});
Object.defineProperty(target, 'age', {
// 获取器
get() {
console.log('get')
return data.age
},
// 修改器
set(newV) {
if (newV != data.age) {
console.log('set')
data.age = newV
compileRender()
}
}
});
compileRender()
function compileRender() {
// 模板编译
// 文本
document.querySelectorAll(`[v-text]`).forEach(node => {
// 要去数据源中查找的数据key
let key = node.getAttribute('v-text')
let value = target[key] || 0
node.innerHTML = value
})
// 输入框
document.querySelectorAll(`[v-model]`).forEach(node => {
let key = node.getAttribute('v-model')
let value = target[key] || 0
node.value = value
// 绑定一个事件
node.addEventListener('input', function () {
target[key] = this.value.trim()
})
})
}
</script>
实现mvvm(第二版)封装
数据为两层以上时需要递归 : 优化方案==>数据扁平化.
<div id="app">
<h3 v-text="msg"></h3>
<h3 v-text="age"></h3>
<hr>
<input type="text" v-model="msg">
</div>
<script>
var data = {
msg: '你好mvvm',
age: 20,
// 不推荐数据有层级嵌套
user: { id: 1 },
// 数据扁平化,优化,不用递归了
user_id: 1,
user_name: 'aa'
}
// 数据劫持
observe(data)
// 模板编译
compileRender(data)
function observe(target) {
// 只劫持json对象
if (Object.prototype.toString.call(target) != '[object Object]') return;
// 遍历
for (let key in target) {
defineRactive(target, key, target[key])
}
}
function defineRactive(target, key, value) {
observe(value)
/* if (Object.prototype.toString.call(value) == '[object Object]') {
observe(value)
return;
} */
// 劫持当前的对象
// defineProperty 它只能对对象中的属性进行劫持,不能劫持数组
Object.defineProperty(target, key, {
// 获取器
get() {
console.log('get')
return value
},
// 修改器
set(newV) {
if (newV != value) {
console.log('set')
value = newV
// 通知模板编译一下
compileRender(target)
}
}
});
}
function compileRender(target) {
// 模板编译
// 文本
document.querySelectorAll(`[v-text]`).forEach(node => {
// 要去数据源中查找的数据key
let key = node.getAttribute('v-text')
let value = target[key] || 0
node.innerHTML = value
})
// 输入框
document.querySelectorAll(`[v-model]`).forEach(node => {
let key = node.getAttribute('v-model')
let value = target[key] || 0
node.value = value
// 绑定一个事件
node.addEventListener('input', function () {
target[key] = this.value.trim()
})
})
}
</script>
封装--小型Vue
=> mvvm(第三版)
插值表达式
# 支持写法 {{变量、js表达式、三目运算符、方法调用等}}
指令 ==> v- 前缀
就是vue给html标签提供的一些自定义属性,这样属性都是带有 v- 前缀的特殊属性
指令作用:操作dom
v-html 解析html指令
v-html它就是不转义输出 指令
v-html一般用它来对于后台添加的富文本内容进行展示所用,用它一定要小心
注:尽量避免使用,容易造成危险 (XSS跨站脚本攻击)
v-text 输出文本信息
解决有的时候,模板解析过慢会有{{}}闪现问题,用属性不会有闪现
v-once指令
只渲染元素和组件一次,之后元素和组件将失去响应式功能
v-if和v-show
<!--
v-if
true:显示
false:隐藏
false时它会把dom删除掉
如果你是后台管理菜单建议用v-if,没有可能性来回切换
v-if初始性能好,切换性能稍差一些
-->
<h3 v-if="n>10">显示内容 -- if</h3>
<!--
v-show
true:显示
false:隐藏
false时会把dom通过css给隐藏起来
如果你是频繁的切换操作,建议使用v-show
v-show初始性能稍差,切换性能好一些
-->
<h3 v-show="n>10">显示内容 -- show</h3>
<!--
多分支 标签之间只能是多分支,不能有别的
v-if v-else-if v-else
-->
<div v-if="n<10">儿童</div>
<div v-else-if="n<20">少年</div>
<div v-else>成年</div>
v-bind指令 // 简写 :
v-for
<!-- 指定vue要去解析的模板挂载点 -->
<div id="app">
<!--
数组循环
v-for="(数组元素,索引) in 数据" :key='唯一不重复的'
v-for="(数组元素,索引) of 数据" :key='唯一不重复的'
key是vue进行dom比对时的依据,有它则提升性能,没有也可以,但是性能比较低,不写会警告
key不要用循环中的index索引,索引有塌陷问题
如果你和后端合作时,一定要求后台给你的数据中有一个唯一不变化值(id)
在vue2.x时如果你的v-if和v-for在一个标签写,v-for优先于v-if
template标签 它可以写v-if或v-for,但它还会编译生成html
vue3.x中v-if优先级高于v-for
-->
<ul>
<template v-if="n>=2">
<li v-for="(item,index) in arr" :key="item">{{index}} -- {{item}}</li>
</template>
<template v-else>
<li>数据加载中...</li>
</template>
</ul>
<!--
循环对象 vue对于 for of进行增强,它可以循环对象了
v-for="(元素,key,index) in obj" :key="唯一值"
v-for="(元素,key,index) of obj" :key="唯一值"
-->
<div v-for="(item,key,index) in obj">
{{index}} ---
{{key}} ---
{{item}}
</div>
</div>
<script>
// 实例化Vue
const vm = new Vue({
el: '#app',
data: {
n: 1,
arr: [1, 2, 3, 4, 5, 6],
obj: { id: 1, name: '张三' }
}
})
</script>
v-on // 简写 @
<div id="app">
<!--
vue中绑定事件 v-on:事件类型="方法" v-on简写 @
方法可以不写小括号
绑定的方法可以定义在data或methods方法中,但是data中不建议去定义,data数据劫持
如果不写小括号,vue把自动把event对象给传入参中,不写小括号事件没有办法传参数
如果你小括号,vue不会自动把event对象传给入参,需要手动传入,提供【$event】变量,
有小括号传参数就很方便
-->
<!-- username这不是button标签默认属性,自定义属性,获取时一定要用getAttribute -->
<!-- <button username='张三' v-on:click="fn">点击事件</button> -->
<!-- html5中提供dataset对象 -->
<button data-username='张三' v-on:click="fn">点击事件</button>
<!-- <button v-on:click="fn($event,1,2,3)">点击事件</button> -->
<hr>
<!-- <input type="text" v-model='todo' @keyup.enter.ctrl="onenter"> -->
<!-- <input type="text" v-model='todo' @keyup.ctrl.90.prevent="onenter"> -->
<input type="text" v-model='todo' @keyup.ctrl.zzz="onenter">
</div>
<script>
Vue.config.keyCodes = {zzz:90}
// 实例化Vue
const vm = new Vue({
el: '#app',
data: {
msg: '你好Vue',
todo: ''
},
methods: {
/* fn(ev, ...args) {
console.log('fn', ev, args)
} */
fn(ev) {
// console.log(ev.target.getAttribute('username'))
console.log(ev.target.dataset.username)
},
onenter(ev) {
// console.log(ev.keyCode == 13)
// console.log(this.todo)
console.log(ev.keyCode)
}
}
})
</script>
绑定事件监听器(事件绑定)
# 绑定好事件实现方法后需要在Vue对象中的methods对象中实现对应的绑定方法
methods: {
functionName(arg1,arg2,arg3,...){
// something to do
},
....
}
事件处理函数传参 -- 事件对象--- event对象
<!-- 事件处理函数调用:直接写函数名 -->
<button @click="say"></button>
<!-- 事件处理函数调用:常规调用 -->
<button @click="say($event)"></button>
# 注:如果没有参数时,可以不写小括号,默认就会把event事件对象绑定到实现的方法中,如果需要传参数时,则通过 $event 来手动传递到实现的方法中
事件修饰符
用来处理事件的特定行为
<!-- 阻止冒泡 -->
<button @click.stop="doThis"></button>
<!-- 阻止默认行为 -->
<a @click.prevent="doThis"></a>
<!-- 只执行一次 -->
<div @click.once="incr()">自增一下</div>
<!-- 绑定的元素本身触发时才触发回调 -->
<ul @click.self="incr()">
<li>你好世界</li>
</ul>
<!-- 串联修饰符 -->
<button @click.stop.prevent="doThis"></button>
按键修饰符
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符。
<!-- 只有在 `key` 是 `Enter` 回车键的时候调用 -->
<input @keyup.enter="submit">
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
你还可以通过全局 config.keyCodes 对象自定义按键修饰符别名:
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes = {f2:113}
<input @keyup.f2="add()" value="aaaa">
样式绑定
1. class样式处理(对象)
=> 如果为true时,样式生效,如果为false不生效
=> 对象动态绑定样式,一般用于开关操作,不建议去追加样式操作
<style>
.active {
color: red;
}
.f20 {
font-size: 50px;
}
</style>
<!-- 指定vue要去解析的模板挂载点 -->
<div id="app">
<!-- 对象动态绑定样式,一般用于开关操作,不建议去追加样式操作 -->
<h3 :class="activeClass">样式为高亮</h3>
<button @click="toggle">切换</button>
</div>
<script>
// 实例化Vue
const vm = new Vue({
el: '#app',
data: {
// 它的数据引用类型,劫持引用(栈)
activeClass: { active: true }
},
methods: {
toggle() {
this.activeClass.active = !this.activeClass.active
// 修改(堆)中的数据,引用不会改变,劫持就不会知道
// this.activeClass.f20 = true
// Vue提供的 , 动态给对象添加属性,有劫持不到的情况 $set
// this.$set(this.activeClass, 'f20', true)
// 深复制 引用地址发现改变
this.activeClass = { ...this.activeClass, f20: true }
// this.activeClass = Object.assign({}, this.activeClass, { f20: true })
}
}
})
</script>
2. class样式处理(数组)
=> 数组绑定样式,不建议有开关操作,只做追加
=> Object.defineProperty它不支持劫持数组
=> 针对于数组,vue提供了常用的7个数组方法进行升级(变异/变更) 这7方法的操作会让视图重新渲染
=> pop shift push unshift sort splice reverse
<div id="app">
<!--
数组绑定样式,不建议有开关操作,只做追加
-->
<h3 :class="activeArr">样式为高亮</h3>
<button @click="addClass">追加样式</button>
</div>
<script>
// 实例化Vue
const vm = new Vue({
el: '#app',
data: {
activeArr: ['active']
},
methods: {
// Object.defineProperty它不支持劫持数组
// 针对于数组,vue提供了常用的7个数组方法进行升级(变异/变更)
// 这7方法的操作会让视图重新渲染
// pop shift push unshift sort splice reverse
addClass() {
this.activeArr.push('f20')
}
}
})
</script>
3. 绑定style
- 对象语法
<div :style="{color: redColor, fontSize: '20px'}">对象写法</div>
data: {
redColor: 'red'
}
- 数组语法
<div v-bind:style="[color, fontSize]">数组写法</div>
data: {
color: {
color: 'red'
},
fontSize: {
'font-size': '20px'
}
}
v-model
定义: v-model就是它们语法糖
<input type="text" :value='msg' @input='setValue'>
<input type="text" v-model="msg">
作用: 表单元素的绑定,实现了双向数据绑定,通过表单项可以更改数据。
绑定多行文本框 textarea
注意:多行文本框中使用插值表达式 无效
<div id="app">
<div v-html="showBody()"></div>
<!-- 多行文本使用v-model -->
<textarea v-model="body"></textarea>
</div>
<script>
// 实例化Vue
const vm = new Vue({
el: '#app',
data: {
body: ''
},
methods: {
showBody() {
// 正则判断替换 换行
return this.body.replace(/(\r\n|\n)/g, '<br />')
}
}
})
</script>
绑定多个复选框/全选多选 => 数组
<div id="app">
<input type="checkbox" v-model="all" @change="allfn">全选
<hr>
<ul>
<li v-for="(item,index) in source" :key="item">
<span>
<input type="checkbox" :value="item" v-model="lessons" @change='selectFn'>
</span>
<span>{{item}}</span>
<span @click="del(index)">删除</span>
</li>
</ul>
</div>
<script>
// 实例化Vue
const vm = new Vue({
el: '#app',
data: {
all: false,
source: ['html', 'css', 'js', 'vue'],
lessons: []
},
methods: {
allfn() {
if (this.all) {
this.lessons = this.source
} else {
this.lessons = []
}
},
selectFn(ev) {
// if (ev.target.checked) {// 勾选中时
this.all = this.lessons.length == this.source.length
// }
},
del(index) {
// 变更方法
// this.source = this.source.filter((item, i) => i != index)
confirm('真的要删除') && this.source.splice(index, 1)
}
}
})
</script>
绑定单选框/下拉框
修饰符
.lazy 失去焦点时触发(延时更新数据源)
.number 输入值转为数值类型
.trim 自动过滤用户输入的首尾空白字符
案例vue--简易购物车
封装--localStorage本地化存储[持久化]
封装--localStorage本地化存储_tby_pr的博客-CSDN博客
案例--vue_todolist
案例--vue_todolist_tby_pr的博客-CSDN博客
Vue自定义指令
封装--vue自定义组件表单项验证
封装--vue自定义组件表单项验证_tby_pr的博客-CSDN博客
封装 -- vue自定义修饰符
封装 -- vue自定义修饰符_tby_pr的博客-CSDN博客
案例 -- vue自定义指令 实现 按钮权限
案例 -- vue自定义指令 实现 按钮权限_tby_pr的博客-CSDN博客
计算属性 - computed
是基于它们的响应式依赖进行缓存的.
计算属性定义在Vue对象中,通过关键词computed属性对象中定义一个个函数,并返回一个值,使用计算属性时和data中的数据使用方式一致
<div id="app">
<h3>{{n1+n2}}</h3>
<h3>{{sum()}}</h3>
<h3>{{sum()}}</h3>
<h3>计算属性:{{total}}</h3>
<h3>计算属性:{{total}}</h3>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
n1: 1,
n2: 2
},
computed: {
// 此计算属性依赖于n1和n2的值,只有它俩发生了改变则,计算会重新计算
total() {
console.log('total')
return this.n1 + this.n2
}
},
methods: {
// 方法计算的结果没有缓存,如果重复调用,则会执行N次
sum() {
console.log('sum')
return this.n1 + this.n2
}
}
})
</script>
计算属性其实是可以改变数据的/计算属性改值(工作用不到,面试可能问)
=> 标准写法
+ set
computed: {
/* totalPrice() {
return this.carts.reduce((p, item) => p += item.price * item.num, 0)
} */
// 标准写法
totalPrice: {
get() {
return this.carts.reduce((p, item) => p + item.price * item.num, 0)
},
set(v) {
console.log(v)
this.n = v
}
}
},
侦听器 -- watch
用侦听器做表单项验证
<div id="app">
<input type="text" v-model='num'>
<span>{{num_msg}}</span>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
num: '10',
num_msg: ''
},
// 侦听器 1对1 依赖项只有一个
watch: {
num(newValue, oldValue) {
// [^] =>写在中括号中,表示取反,不是0-9的数据则匹配成功
if (/[^0-9]/.test(newValue)) {
this.num_msg = '全数字'
} else {
this.num_msg = ''
}
},
// 默认不会对于引用类型进行监听,除非你修改了引用地址
// 监听对象中的具体字段的值的变化 // 'user.name'(newValue,oldValue){}
监听对象的标椎版本
// 标准版本
user: {
// 深度监听
deep: true,
handler(n, o) {
console.log(n)
}
}
过滤器 - filters(vue3中没有)
在数据被渲染之前,可以对其进行进一步处理
除了 组件 是局部定义的,其他基本都是 全局定义 的.
案例: 超过几个变成...
<div id="app">
<!-- 过滤器 -->
<h3>{{title | substr(2)}}</h3>
<h3>{{title | substr}}</h3>
</div>
<script>
// 全局过滤器
Vue.filter('substr', (value, len = 10) => {
if (value.length <= len) return value;
return value.substr(0, len) + '...'
})
const vm = new Vue({
el: '#app',
data: {
title: '这段时间娱乐圈可谓是抢占了头条,抖音,相继出现'
}
})
</script>
封装 -- 对象合并
混入(工作不用,因为乱) // 就是对象合并
全局混入!
!!!! 可以通过全局混入的方式,给东西塞到当前实例对象里面去,想在任何地方用都能用,方便其他人去用,通过自定义属性对当前实例添加了自定义属性
局部混入
// 混入vue配置选项中的大部份配置,只有el不能混
// 混入时 data配置选项一定要是函数方式,且函数一定要返回一个对象
// 数据如果本组件中有配置则以本组件为主
!!! 方法(函数)是 合并,属性值 是以 本组件 为主进行 覆盖
Vue生命周期
在 模板编译 和 数据劫持 的 过程 暴露了一些 钩子!
vue3放弃了el,直接用$mount()
网络请求写在哪
created beforeMount mounted
都可以写网络请求,但是mounted是属于真实dom已经渲染,页面结构已经出来了,然后进行的网络请求,用户知道已经打开了,推荐使用。
在虚拟dom进行网络请求,会在请求后再渲染,页面是大白板,网络请求要是慢的话,用户会觉得是网速慢,就F5刷新了,无形中负载变高了。
beforeUpdate和updated也能写,但是不能改变数据源。
销毁里写就是完全没意义。
网络请求
Vue组件
Vue插槽!!!--(封装组件使用)
Vue工程化!!!***
Vue路由!!!***
Vue代码 异步懒加载 和 拆分
Vue代码 异步懒加载 和 拆分_tby_pr的博客-CSDN博客
Vue @占位符的代码提示
Vue @占位符的代码提示_tby_pr的博客-CSDN博客
案例 -- web移动端项目(vue)
案例 -- web移动端项目(vue)_tby_pr的博客-CSDN博客