跳到主要内容Vue 框架核心语法与响应式原理详解 | 极客日志JavaScript大前端
Vue 框架核心语法与响应式原理详解
Vue 框架核心语法与响应式原理详解。涵盖 MVVM 分层思想、数据驱动视图、双向绑定机制。详细解析 v-bind、v-model、v-if、v-for 等指令用法,对比计算属性 computed 与侦听属性 watch 的差异。深入探讨 Object.defineProperty 实现的数据代理与响应式劫持原理,以及 Vue 生命周期钩子函数的应用场景。包含虚拟 DOM 与 diff 算法基础,表单数据收集及自定义指令实现示例。
神经兮兮1 浏览 1. Vue2 简介
1.1 数据驱动视图
1.1.1 单向数据绑定
单向的数据绑定,当页面数据发生变化时,页面会自动重新渲染。
1.1.2 双向数据绑定
在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源中。
1.1.3 MVVM 分层思想
问题:MVVM 模型当中倡导了 Model 和 View 进行了分离,为什么要分离?
将 Model 和 View 分离之后,出现了一个 VM 核心,这个 VM 把所有的脏活累活给做了,也就是说,当 Model 发生改变之后,VM 自动去更新 View。当 View 发生改动之后,VM 自动去更新 Model。再也不需要编写操作 DOM 的 JS 代码了。开发效率提高了很多。
<div id="app">姓名:<input type="text" v-model="name"></div>
<script>
const vm = new Vue({
el: '#app',
data: {
name: 'zhangsan'
}
})
</script>
2. 第一个 Vue 程序
首先,使用 script 标签引入 vue.js 文件
<script =>
src
"../js/vue.js"
</script>
<body>
<div id="app"></div>
<script>
const myVue = new Vue({
template: '<h1>Hello World!</h1>'
})
myVue.$mount('#app')
</script>
</body>
当使用 script 引入 vue.js 之后,Vue 会被注册为一个全局变量。首先必须要 new 一个 Vue 实例。
2.1 Vue 构造函数的参数:options
options 翻译为多个选项,Vue 框架要求 options 参数必须是一个纯粹的 JS 对象{}
在当前对象中编写大量的键值对 key:value;每个键值对都是配置项。
2.2 Vue 实例挂载
- Vue 实例都有一个$mount() 方法,这个方法的作用是什么?
将 Vue 实例挂载到指定位置,将 Vue 实例挂载到指定位置。也就是说将 Vue 编译后的 HTML 代码渲染到页面的指定位置。注意:指定位置的元素被替换。
- #app 显然是 ID 选择器
2.3 Template 语句的数据来源 data
<body>
<div id="app"></div>
<script>
new Vue({
template: `<h1>{{ name }}{{releaseTime}}开始学 Vue!! {{lead.age}}的{{lead.name}}也在学习!! 班里还有{{classmates[0].age}}岁的{{classmates[0].name}}和{{classmates[1].age}}岁的{{classmates[1].name}}。</h1>`,
data: {
name: '张三',
releaseTime: '2025 年 8 月 2 日',
lead: { name: '高齐强', age: 40 },
classmates: [
{ name: '李四', age: 18 },
{ name: '王五', age: 80 }
]
}
}).$mount('#app')
</script>
</body>
2.4 Template 配置项
template 只能有一个根元素;template 编译后进行渲染时会将挂载位置的元素替换。只要 data 中的数据发生变化,模板语句一定会重新编译。(只要 data 变,template 就会重新编译,重新渲染)template 后面的代码如果需要换行的话,建议将代码写到``符号当中,不建议使用 + 进行字符串的拼接。将 Vue 实例挂载时,也可以不用$mount 方法,可以使用 Vue 的 el 配置项。el 配置项主要是用来指定 Vue 实例关联的容器。也就是说 Vue 所管理的容器是哪个。
<body>
<div id="app">
<div>
<h1>{{msg}}</h1>
<h2>{{name}}</h2>
</div>
</div>
<script>
/* 关于$mount('#app')? VUE 中有一个配置项:el
el 配置项和$mount() 可以达到同样的效果
el 配置项的作用:告诉 VUE 实例去接管哪个容器.
*/
new Vue({
// template: '<h1>{{msg}}</h1><h2></h2>', //错误
template: `
<div>
<h1>{{msg}}</h1>
<h2>{{name}}</h2>
</div>
`,
data: {
msg: "hello world!",
name: "明天不 tm 学了!"
},
el: '#app' // el: document.getElementById('app')
})
</script>
</body>
2.5 Vue 实例和容器
<body>
<div class="app"><h1>{{msg}}</h1></div>
<div id="app2"><h1>{{msg}}</h1></div>
<div id="app3"><h1>{{name}}</h1></div>
<script>
new Vue({
el: '.app',
data: { msg: 'hell0' }
})
new Vue({
el: '#app2',
data: { name: '张三' }
})
new Vue({
el: '#app2',
data: { name: '李四' }
})
</script>
</body>
3. Vue 核心语法
3.1 插值{{ }}语法
<script>
var i = 100
function sum() { console.log('sum.....'); }
new Vue({
el: '#app',
data: {
number: 1,
gender: true,
msg: 'abcdef',
sayHello: function () { console.log('hello vue!'); }
}
})
</script>
<div id="app">
<h1>{{msg}}</h1>
<h1>{{sayHello()}}</h1>
<h1>{{100}}</h1>
<h1>{{'hello vue!'}}</h1>
<h1>{{3.14}}</h1>
<h1>{{1 + 1}}</h1>
<h1>{{'hello' + 'vue'}}</h1>
<h1>{{msg + 1}}</h1>
<h1>{{'msg' + 1}}</h1>
<h1>{{gender ? '男' : '女'}}</h1>
<h1>{{number + 1}}</h1>
<h1>{{'number' + 1}}</h1>
<h1>{{msg.split('').reverse().join('')}}</h1>
<h1>{{Date}}</h1>
<h1>{{Date.now()}}</h1>
<h1>{{Math}}</h1>
<h1>{{Math.ceil(3.14)}}</h1>
</div>
3.2 Vue 指令
- 什么是指令?作用是什么?
指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
- Vue 框架中的所有指令的名字都以'v-'开始。
- Vue 框架中所有的指令都是以 HTML 标签的 属性形式 存在的。
- 指令的语法规则:
<HTML v-指令名:参数="javascript 表达式"></HTML>
不是所有的指令都有参数和表达式:
有的指令,不需要参数,也不需要表达式,例如:v-once
有的指令,不需要参数,但是需要表达式,例如:v-if="表达式"
有的指令,既需要参数,又需要表达式,例如:v-bind:参数="表达式"
3.2.1 v-once 指令
作用:只渲染元素一次。
随后的重新渲染,元素及其所有的子节点将被视为静态内容并跳过。
3.2.2 条件渲染指令
3.2.2.1 v-if 指令
作用:表达式的执行结果需要是一个布尔类型的数据:true 或者 false
true:这个指令所在的标签,会被渲染到浏览器当中。
false:这个指令所在的标签,不会被渲染到浏览器当中。
<div id="app">
<h1>{{msg}}</h1>
<h1 v-once>{{msg}}</h1>
<h1 v-if="a <= b">v-if 测试:{{msg}}</h1>
</div>
<script>
new Vue({
el: '#app',
data: { msg: 'Hello Vue!', a: 10, b: 11 }
})
</script>
3.2.2.2 v-show 指令
<body>
<div id="app">
<h1>{{msg}}</h1>
<div v-if="false">{{msg}}</div>
<div v-if="2 === 1">{{msg}}</div>
<button @click="counter++">点我加 1</button>
<h3>{{counter}}</h3>
<img :src="imgPath1" v-if="counter % 2 === 1">
<img :src="imgPath2" v-else>
<br><br>
温度:<input type="number" v-model="temprature"><br><br>
天气:<span v-if="temprature <= 10">寒冷</span>
<span v-else-if="temprature <= 25">凉爽</span>
<span v-else>炎热</span>
<br><br><br>
<div v-show="false">你可以看到我吗?</div>
<template v-if="counter === 10">
<input type="text">
<input type="checkbox">
<input type="radio">
</template>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '条件渲染',
counter: 1,
imgPath1: '../img/1.jpg',
imgPath2: '../img/2.jpg',
temprature: 0
}
})
</script>
</body>
</html>
3.2.3 v-bind 属性绑定指令
可以让 HTML 标签的某个属性的值产生动态的效果。
<HTML v-bind:参数="表达式"></HTML>
<HTML :参数="表达式"></HTML>
<HTML 参数="表达式的执行结果"></HTML>
第一:在编译的时候 v-bind 后面的'参数名'会被编译为 HTML 标签的**'属性名'**
第二:表达式会关联 data ,当 data 发生改变之后,表达式的执行结果就会发生变化。
所以,连带的就会产生动态效果。
什么时候使用插值语法?什么时候使用指令?
- 凡是标签体当中的内容要想动态,需要使用插值语法。
- 只要想让 HTML 标签的属性动态,需要使用指令语法。
<div id="app">
<span v-bind:xyz="msg"></span>
<span v-bind:xyz="'msg'"></span>
<img src="../img/1.jpg">
<br>
<img v-bind:src="imgPath">
<br>
<img :src="imgPath">
<br>
<input type="text" name="username" value="zhangsan">
<br>
<input type="text" name="username" :value="username">
<br>
<a href="https://www.baidu.com">走起</a>
<br>
<a :href="url">走起 2</a>
<br>
<h1>{{msg}}</h1>
</div>
<script>
new Vue({
el: '#app',
data: {
msg: 'Hello Vue!',
imgPath: '../img/1.jpg',
username: 'jackson',
url: 'https://www.baidu.com'
}
})
</script>
3.2.4 v-model 指令
<div id="app">
v-bind 指令:<input type="text" :value="name1"><br>
v-model 指令:<input type="text" v-model:value="name2"><br>
v-bind 指令:<input type="text" :value="name1"><br>
v-model 指令:<input type="text" v-model="name2"><br>
消息 1:<input type="text" :value="msg"><br>
消息 2:<input type="text" v-model="msg"><br>
</div>
<script>
new Vue({
el: '#app',
data: {
name1: 'zhangsan',
name2: 'wangwu',
url: 'https://www.baidu.com',
msg: 'Hello Vue!'
}
})
</script>
3.2.4.1 v-model 指令修饰符
3.2.5 事件绑定指令 v-on
<div id="app">
<h1>{{msg}}</h1>
<button onclick="alert('hello')">hello</button>
<button v-on:click="sayHello()">hello</button>
<button @click="sayHi()">hi button</button>
<button @click="sayHi($event, 'jack')">hi button2</button>
<button @click="sayWhat">what button</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data: { msg: 'Vue 的事件绑定' },
methods: {
sayHello() { alert('hello2') },
sayHi(event, name) { console.log(name, event)
sayWhat(event) {
console.log(event)
console.log(event.target.innerText)
}
}
})
</script>
3.2.5.1 事件修饰符
<head>
<style>
.divList { width: 300px; height: 200px; background-color: aquamarine; overflow: auto; }
.item { width: 300px; height: 200px; }
</style>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
<a href="https://www.baidu.com" @click.prevent="yi">百度</a>
<br><br>
<div @click="san">
<div @click.stop="er">
<button @click="yi">事件冒泡</button>
</div>
</div>
<br><br>
<div @click.capture="san">
<div @click="er">
<button @click="yi">添加事件监听器的时候采用事件捕获模式</button>
</div>
</div>
<br>
<div @click="san">
<div @click.self="er">
<button @click="yi">self 修饰符</button>
</div>
</div>
<br>
<div @click="san">
<div @click="er">
<button @click.self.stop="yi">self 修饰符</button>
</div>
</div>
<br>
<button @click.once="yi">事件只发生一次</button>
<div @wheel.passive="testPassive">
<div>div1</div>
<div>div2</div>
<div>div3</div>
</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: { msg: '事件修饰符' },
methods: {
yi(event) {
alert(1)
},
er() { alert(2) },
san() { alert(3) },
testPassive(event) {
for (let i = 0; i < 100000; i++) { console.log('test passive') }
}
}
})
</script>
</body>
3.2.5.2 按键修饰符
<body>
<div id="app">
<h1>{{msg}}</h1>
回车键:<input type="text" @keyup.enter="getInfo"><br>
回车键(键值):<input type="text" @keyup.13="getInfo"><br>
delete 键:<input type="text" @keyup.delete="getInfo"><br>
esc 键:<input type="text" @keyup.esc="getInfo"><br>
space 键:<input type="text" @keyup.space="getInfo"><br>
up 键:<input type="text" @keyup.up="getInfo"><br>
down 键:<input type="text" @keyup.down="getInfo"><br>
left 键:<input type="text" @keyup.left="getInfo"><br>
right 键:<input type="text" @keyup.right="getInfo"><br>
tab 键: <input type="text" @keyup.tab="getInfo"><br>
tab 键(keydown): <input type="text" @keydown.tab="getInfo"><br>
PageDown 键: <input type="text" @keyup.page-down="getInfo"><br>
huiche 键: <input type="text" @keyup.huiche="getInfo"><br>
ctrl 键(keydown): <input type="text" @keydown.ctrl="getInfo"><br>
ctrl 键(keyup): <input type="text" @keyup.ctrl="getInfo"><br>
ctrl 键(keyup+i 触发): <input type="text" @keyup.ctrl.i="getInfo"><br>
</div>
<script>
Vue.config.keyCodes.huiche = 13
const vm = new Vue({
el: '#app',
data: { msg: '按键修饰符' },
methods: {
getInfo(event) {
console.log(event.target.value)
console.log(event.key)
}
}
})
</script>
</body>
3.2.6 v-for 列表渲染指令
v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为 (item, index) in items。
<body>
<div id="app">
<h1>{{msg}}</h1>
<h2>遍历对象的属性</h2>
<ul>
<li v-for="(value, propertyName) of user"> {{propertyName}},{{value}} </li>
</ul>
<h2>遍历字符串</h2>
<ul>
<li v-for="(c,index) of str"> {{index}},{{c}} </li>
</ul>
<h2>遍历指定的次数</h2>
<ul>
<li v-for="(num,index) of counter"> {{index}}, {{num}} </li>
</ul>
<h2>遍历数组</h2>
<ul>
<li>张三</li>
<li>李四</li>
<li>王五</li>
</ul>
<ul>
<li v-for="fdsafds in names"> {{fdsafds}} </li>
</ul>
<ul>
<li v-for="name of names"> {{name}} </li>
</ul>
<ul>
<li v-for="(name,index) of names"> {{name}}-{{index}} </li>
</ul>
<ul>
<li v-for="(vip,index) of vips"> 会员名:{{vip.name}},年龄:{{vip.age}}岁 </li>
</ul>
<table>
<tr>
<th>序号</th>
<th>会员名</th>
<th>年龄</th>
<th>选择</th>
</tr>
<tr v-for="(vip,index) in vips">
<td>{{index+1}}</td>
<td>{{vip.name}}</td>
<td>{{vip.age}}</td>
<td><input type="checkbox"></td>
</tr>
</table>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '列表渲染',
names: ['jack', 'lucy', 'james'],
vips: [
{ id: '111', name: 'jack', age: 20 },
{ id: '222', name: 'lucy', age: 30 },
{ id: '333', name: 'james', age: 40 }
],
user: { id: '111', name: '张三', gender: '男' },
str: '动力节点',
counter: 10
}
})
</script>
</body>
3.2.7 虚拟 DOM 与 diff 算法 (:key 属性)
<body>
<div id="app">
<h1>{{msg}}</h1>
<table>
<tr>
<th>序号</th>
<th>英雄</th>
<th>能量值</th>
<th>选择</th>
</tr>
<tr v-for="(hero,index) in heros" :key="hero.id">
<td>{{index+1}}</td>
<td>{{hero.name}}</td>
<td>{{hero.power}}</td>
<td><input type="checkbox"></td>
</tr>
</table>
<button @click="add">添加英雄麦文</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '虚拟 dom 与 diff 算法',
heros: [
{ id: '101', name: '艾格文', power: 10000 },
{ id: '102', name: '麦迪文', power: 9000 },
{ id: '103', name: '古尔丹', power: 8000 },
{ id: '104', name: '萨尔', power: 6000 }
]
},
methods: {
add() {
this.heros.unshift({ id: '105', name: '麦文', power: 9100 })
}
}
})
</script>
</body>
3.2.7.1 列表插入删除方法
3.2.8 v-text v-html 指令
<body>
<div id="app">
<h1>{{msg}},test</h1>
<h1 v-text="msg">test</h1>
<h1 v-text="name">test</h1>
<h1 v-text="s1"></h1>
<h1 v-html="s1"></h1>
<ul>
<li v-for="m, index of messageList" :key="index" v-html="m"></li>
</ul>
<textarea cols="50" rows="30" v-model.lazy="message"></textarea>
<br><br>
<button @click="save">保存留言</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Vue 的其它指令',
name: 'jack',
s1: '<h1>欢迎大家学习 Vue!</h1>',
message: '',
messageList: []
},
methods: {
save() { this.messageList.push(this.message) }
}
})
</script>
</body>
3.2.9 v-cloak 指令
<head>
<style>
[v-cloak] { display: none; }
</style>
</head>
<body>
<div id="app">
<h1 v-cloak>{{msg}}</h1>
</div>
<script>
setTimeout(() => {
let scriptElt = document.createElement('script')
scriptElt.src = '../js/vue.js'
document.head.append(scriptElt)
}, 3000)
setTimeout(() => {
const vm = new Vue({
el: '#app',
data: { msg: 'Vue 的其它指令' }
})
}, 4000)
</script>
</body>
3.2.10 v-pre 指令
<body>
<div id="app">
<h1 v-cloak>{{msg}}</h1>
<h1 v-pre>欢迎学习 Vue 框架!</h1>
<h1 v-pre>{{msg}}</h1>
<ul>
<li v-for="user,index of users" :key="index" v-once> {{user}} </li>
</ul>
<ul>
<li v-for="user,index of users" :key="index"> {{user}} </li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Vue 的其它指令',
users: ['jack', 'lucy', 'james']
}
})
</script>
</body>
3.2.11 自定义指令
在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。
在使用自定义指令时,需要加上 v- 前缀。
在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值。
<div id="app">
<h1>自定义指令</h1>
<div v-text="msg"></div>
<div v-text-danger="msg"></div>
用户名:<input type="text" v-bind:value="username">
<div>
用户名:<input type="text" v-bind-blue="username">
</div>
</div>
<div>
<div v-text-danger="msg"></div>
<div>
用户名:<input type="text" v-bind-blue="username">
</div>
</div>
私有自定义指令
const vm = new Vue({
el: '#app',
data: {
msg: '自定义指令',
username: 'jackson'
},
directives: {
'text-danger': function(element, binding){
console.log('@')
element.innerText = binding.value
element.style.color = 'red'
},
'bind-blue': function(element, binding){
element.value = binding.value
console.log(element)
console.log(element.parentNode)
element.parentNode.style.backgroundColor = 'blue'
}
'bind-blue': {
bind(element, binding){
element.value = binding.value
},
inserted(element, binding){
element.parentNode.style.backgroundColor = 'blue'
},
update(element, binding){
element.value = binding.value
}
}
}
})
全局自定义指令
Vue.directive('text-danger', function (element, binding) {
console.log(this)
element.innerText = binding.value
element.style.color = 'red'
})
Vue.directive('bind-blue', {
bind(element, binding) {
element.value = binding.value
console.log(this)
},
inserted(element, binding) {
element.parentNode.style.backgroundColor = 'skyblue'
console.log(this)
},
update(element, binding) {
element.value = binding.value
console.log(this)
}
})
4. 过滤器
过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。
过滤器可以用在两个地方:插值表达式和 v-bind 属性绑定,过滤器应该被添加在 JavaScript 表达式的尾部。
4.1 filters 节点中定义局部过滤器
const vm = new Vue({
el: '#app',
data: { msg: '过滤器', price: 50.6 },
filters: {
filterA(val){
if(val === null || val === undefined || val === ''){
return '-'
}
return val
},
filterB(val, number){
return val.toFixed(number)
}
}
})
4.2 配置全局过滤器
Vue.filter('filterA', function (val) {
if (val === null || val === undefined || val === '') {
return '-'
}
return val
})
Vue.filter('filterB', function (val, number) {
return val.toFixed(number)
})
<body>
<div id="app">
<h1>{{msg}}</h1>
<h2>商品价格:{{formatPrice}}</h2>
<h2>商品价格:{{formatPrice2()}}</h2>
<h2>商品价格:{{price | filterA | filterB(3)}}</h2>
<input type="text" :value="price | filterA | filterB(3)">
</div>
<hr>
<div id="app2">
<h2>商品价格:{{price | filterA | filterB(3)}}</h2>
</div>
<script>
Vue.filter('filterA', function (val) {
if (val === null || val === undefined || val === '') {
return '-'
}
return val
})
Vue.filter('filterB', function (val, number) {
return val.toFixed(number)
})
const vm2 = new Vue({
el: '#app2',
data: { price: 20.3 }
})
const vm = new Vue({
el: '#app',
data: {
msg: '过滤器',
price: 50.6
},
methods: {
formatPrice2() {
if (this.price === '' || this.price === undefined || this.price === null) {
return '-'
}
return this.price
}
},
computed: {
formatPrice() {
if (this.price === '' || this.price === undefined || this.price === null) {
return '-'
}
return this.price
}
},
})
</script>
</body>
5. 计算属性 computed
虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性;
使用 Vue 的原有属性,经过一系列的运算/计算,最终得到了一个全新的属性,叫做计算属性。
Vue 的原有属性:data 对象当中的属性可以叫做 Vue 的原有属性。
全新的属性:表示生成了一个新的属性,和 data 中的属性无关了,新的属性也有自己的属性名和属性值。
5.1 反转字符串 methods 实现
<body>
<div id="app">
<h1>{{msg}}</h1>
输入的信息:<input type="text" v-model="info">
<br>
反转的信息:{{reverseInfo()}}
<br>
反转的信息:{{reverseInfo()}}
<br>
反转的信息:{{reverseInfo()}}
<br>
反转的信息:{{reverseInfo()}}
<br>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '计算属性 - 反转字符串案例',
info: ''
},
methods: {
reverseInfo() {
console.log('@')
return this.info.split('').reverse().join('');
}
}
})
</script>
</body>
5.2 反转字符串计算属性实现
<div id="app">
<h1>{{msg}}</h1>
输入的信息:<input type="text" v-model="info">
<br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
{{hehe}} <br>
{{hehe}} <br>
{{hehe}} <br>
{{hehe}} <br>
{{hehe}} <br>
{{hello()}} <br>
{{hello()}} <br>
{{hello()}} <br>
{{hello()}} <br>
{{hello()}} <br>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '计算属性 - 反转字符串案例',
info: ''
},
methods: {
hello() {
console.log('hello 方法执行了')
return 'hello'
}
},
computed: {
hehe: {
get() {
console.log('getter 方法调用了')
return 'haha' + this.info
},
set(val) {
console.log('setter 方法调用了')
}
},
reversedInfo: {
get() {
return this.info.split('').reverse().join('')
},
set(val) {
this.info = val.split('').reverse().join('')
}
}
}
})
</script>
6. 监视属性
- 监视属性:监视哪个属性,就把属性放入 watch 中即可。
- 可以监视 Vue 的原有属性。
6.1 监视属性的变化
<body>
<div id="app">
<h1>{{msg}}</h1>
数字:<input type="text" v-model="number"><br>
数字:<input type="text" v-model="a.b"><br>
数字:<input type="text" v-model="a.c"><br>
数字:<input type="text" v-model="a.d.e.f"><br>
数字 (后期添加监视): <input type="text" v-model="number2"><br>
{{hehe}}
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
number2: 0,
msg: '侦听属性的变化',
number: 0,
a: {
b: 0,
c: 0,
d: { e: { f: 0 } }
}
},
computed: {
hehe() {
return 'haha:' + this.number
}
},
watch: {
number: {
immediate: true,
handler(newValue, oldValue) {
console.log(newValue, oldValue)
console.log(this)
}
},
a: {
deep: true,
handler(newValue, oldValue) {
console.log('@')
}
},
number(newValue, oldValue) {
console.log(newValue, oldValue)
},
hehe: {
handler(a, b) {
console.log(a, b)
}
}
}
})
</script>
</body>
6.2 后期添加监视
vm.$watch('number2', {
immediate: true,
deep: true,
handler(newValue, oldValue){
console.log(newValue, oldValue)
}
})
vm.$watch('number2', function (newValue, oldValue) {
console.log(newValue, oldValue)
})
6.3 immediate 选项
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。
如果想让 watch 侦听器立即被调用,则需要使用 immediate 选项。
immediate : true 页面首次加载完毕就触发 handler:0, undefined
6.4 deep 选项
如果 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。
此时需要使用 deep 选项。
7. 数据代理
7.1 VM(View Model)
<body>
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
let dataObj = { msg: 'Hello Vue!' }
const vm = new Vue({
el: '#app',
data: dataObj
})
console.log('dataObj 的 msg', dataObj.msg);
console.log('vm 的 msg', vm.msg);
</script>
</body>
7.2 Object.defineProperty() 方法
<script>
let phone = {}
let temp
Object.defineProperty(phone, 'color', {
get: function () {
console.log('getter 方法执行了@@@');
return temp
},
set: function (val) {
console.log('setter 方法执行了@@@', val);
temp = val
}
})
</script>
7.3 数据代理机制
- 什么是数据代理机制?
通过访问 代理对象的属性 来间接访问 目标对象的属性。
数据代理机制的实现需要依靠:Object.defineProperty() 方法。
- ES6 新特性:
在对象中的函数/方法 :function 是可以省略的。
<body>
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello Vue!'
}
})
</script>
<script>
let target = { name: 'zhangsan' }
let proxy = {}
Object.defineProperty(proxy, 'name', {
get(){
console.log('getter 方法执行了@@@@');
return target.name
},
set(val){
target.name = val
}
})
</script>
</body>
7.3.1 Vue 数据代理机制对属性名的要求
7.3.2 手写 Vue 框架数据代理的实现
class Vue {
constructor(options) {
Object.keys(options.data).forEach((propertyName, index) => {
let firstChar = propertyName.charAt(0)
if (firstChar != '_' && firstChar != '$') {
Object.defineProperty(this, propertyName, {
get() {
return options.data[propertyName]
},
set(val) {
options.data[propertyName] = val
}
})
}
})
}
}
<body>
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello Vue!',
_name: 'jackson',
$age: 30
}
})
</script>
</body>
8. Vue 框架源代码
var data = vm.$options.data;
- 创建 Vue 实例时,原始数据会被存储在
vm._data 属性中。
- vm 是一个 Vue 实例,vm.$options.data 就是创建该实例时传入的 data 选项。
data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
isFunction(data)判断传入的 data 选项是否是函数类型
如果 data 是函数,则调用 getData(data, vm) 来获取真正的数据对象 data。
如果 data 不是函数,则直接使用 data。如果是 undefined或 null,使用空对象 {} 作为默认值。
双重赋值:将处理后的数据对象同时赋值给:局部变量 data 和 Vue 实例的 _data属性。
8.1 为什么要给 vm 扩展_data 属性?
8.2 重点函数
<div id="app">
<h1>姓名:{{name}}</h1>
<h1>年龄:{{age}}岁</h1>
</div>
<script>
function isReserved(str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5f;
}
const vm = new Vue({
el: '#app',
data: {
name: 'jackson',
age: 35
}
})
console.log('name = ' + vm.$data.name)
console.log('age = ' + vm.$data.age)
</script>
8.3 data(函数形式)
<body>
<div id="app">
<h1>{{msg}}</h1>
</div>
<script>
const vm = new Vue({
el: '#app',
data() {
return {
msg: 'Hello Zhangsan!'
}
}
})
let phone = { name: '苹果 X' }
Object.defineProperty(phone, 'color', {
value: '奶奶灰',
enumerable: false,
configurable: false
})
</script>
</body>
9. 数据绑定
9.1 Class 绑定
9.1.1 字符串形式
<head>
<title>Class 绑定之字符串形式</title>
<script src="../js/vue.js"></script>
<style>
.static { border: 1px solid black; background-color: aquamarine; }
.big { width: 200px; height: 200px; }
.small { width: 100px; height: 100px; }
</style>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
<div>{{msg}}</div>
<br><br>
<button @click="changeBig">变大</button>
<button @click="changeSmall">变小</button>
<div :class="c1">{{msg}}</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Class 绑定之字符串形式',
c1: 'small'
},
methods: {
changeBig() {
this.c1 = 'big'
},
changeSmall() {
this.c1 = 'small'
}
},
})
</script>
</body>
9.1.2 数组形式
<head>
<title>Class 绑定之数组形式</title>
<script src="../js/vue.js"></script>
<style>
.static { border: 1px solid black; width: 100px; height: 100px; }
.active { background-color: green; }
.text-danger { color: red; }
</style>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
<div>{{msg}}</div>
<br>
<div :class="['active','text-danger']">{{msg}}</div>
<br>
<div :class="[c1, c2]">{{msg}}</div>
<br>
<div :class="classArray">{{msg}}</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Class 绑定之数组形式',
c1: 'active',
c2: 'text-danger',
classArray: ['active', 'text-danger']
}
})
</script>
</body>
9.1.3 对象形式
<head>
<title>Class 绑定之对象形式</title>
<script src="../js/vue.js"></script>
<style>
.static { border: 1px solid black; width: 100px; height: 100px; }
.active { background-color: green; }
.text-danger { color: red; }
</style>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
<div :class="classObj">{{msg}}</div>
<br>
<div :class="{active:true,'text-danger':false}">{{msg}}</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Class 绑定之对象形式',
classObj: {
active: false,
'text-danger': true
}
}
})
</script>
</body>
9.2 style 绑定
<head>
<title>Style 绑定</title>
<script src="../js/vue.js"></script>
<style>
.static { border: 1px solid black; width: 100px; height: 100px; }
</style>
</head>
<body>
<div id="app">
<h1>{{msg}}</h1>
<div>{{msg}}</div>
<br>
<div :style="myStyle">{{msg}}</div>
<br>
<div :style="{backgroundColor: 'gray'}">{{msg}}</div>
<br>
<div :style="styleObj1">{{msg}}</div>
<br>
<div :style="styleArray">{{msg}}</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Style 绑定',
myStyle: 'background-color: gray;',
styleObj1: {
backgroundColor: 'green'
},
styleArray: [
{ backgroundColor: 'green' },
{ color: 'red' }
]
}
})
</script>
</body>
10. 列表渲染
<body>
<div id="app">
<h1>{{msg}}</h1>
<h2>遍历对象的属性</h2>
<ul>
<li v-for="(value, propertyName) of user"> {{propertyName}},{{value}} </li>
</ul>
<h2>遍历字符串</h2>
<ul>
<li v-for="(c,index) of str"> {{index}},{{c}} </li>
</ul>
<h2>遍历指定的次数</h2>
<ul>
<li v-for="(num,index) of counter"> {{index}}, {{num}} </li>
</ul>
<h2>遍历数组</h2>
<ul>
<li>张三</li>
<li>李四</li>
<li>王五</li>
</ul>
<ul>
<li v-for="fdsafds in names"> {{fdsafds}} </li>
</ul>
<ul>
<li v-for="name of names"> {{name}} </li>
</ul>
<ul>
<li v-for="(name,index) of names"> {{name}}-{{index}} </li>
</ul>
<ul>
<li v-for="(vip,index) of vips"> 会员名:{{vip.name}},年龄:{{vip.age}}岁 </li>
</ul>
<table>
<tr>
<th>序号</th>
<th>会员名</th>
<th>年龄</th>
<th>选择</th>
</tr>
<tr v-for="(vip,index) in vips">
<td>{{index+1}}</td>
<td>{{vip.name}}</td>
<td>{{vip.age}}</td>
<td><input type="checkbox"></td>
</tr>
</table>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '列表渲染',
names: ['jack', 'lucy', 'james'],
vips: [
{ id: '111', name: 'jack', age: 20 },
{ id: '222', name: 'lucy', age: 30 },
{ id: '333', name: 'james', age: 40 }
],
user: { id: '111', name: '张三', gender: '男' },
str: '动力节点',
counter: 10
}
})
</script>
</body>
列表排序
<body>
<div id="app">
<h1>{{msg}}</h1>
<input type="text" placeholder="请输入搜索关键字" v-model="keyword">
<br>
<button @click="type = 1">升序</button>
<button @click="type = 2">降序</button>
<button @click="type = 0">原序</button>
<table>
<tr>
<th>序号</th>
<th>英雄</th>
<th>能量值</th>
<th>选择</th>
</tr>
<tr v-for="(hero,index) in filteredHeros" :key="hero.id">
<td>{{index+1}}</td>
<td>{{hero.name}}</td>
<td>{{hero.power}}</td>
<td><input type="checkbox"></td>
</tr>
</table>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
type: 0,
keyword: '',
msg: '列表排序',
heros: [
{ id: '101', name: '艾格文', power: 10000 },
{ id: '102', name: '麦迪文', power: 9000 },
{ id: '103', name: '古尔丹', power: 8000 },
{ id: '104', name: '萨尔', power: 11000 }
]
},
computed: {
filteredHeros() {
const arr = this.heros.filter((hero) => {
return hero.name.indexOf(this.keyword) >= 0
})
if (this.type === 1) {
arr.( {
a. - b.
})
} (. === ) {
arr.( {
b. - a.
})
}
arr
}
}
})
arr = [, , , , , , ]
arr.( {
b - a
})
.(arr)
</script>
</body>
12. 表单数据的收集
<body>
<div id="app">
<h1>{{msg}}</h1>
<form @submit.prevent="send">
用户名:<input type="text" v-model.trim="user.username"><br><br>
密码:<input type="password" v-model="user.password"><br><br>
年龄:<input type="number" v-model.number="user.age"><br><br>
性别:
男<input type="radio" name="gender" value="1" v-model="user.gender">
女<input type="radio" name="gender" value="0" v-model="user.gender"><br><br>
爱好:
旅游<input type="checkbox" v-model="user.interest" value="travel">
运动<input type="checkbox" v-model="user.interest" value="sport">
唱歌<input type="checkbox" v-model="user.interest" value="sing"><br><br>
学历:
<select v-model="user.grade">
<option>请选择学历</option>
<option value="zk">专科</option>
<option value="bk">本科</option>
<option value="ss">硕士</option>
</select><br><br>
简介:
<textarea cols="50" rows="15" v-model.lazy="user.introduce"></textarea><br><br>
<input type="checkbox" v-model="user.accept">阅读并接受协议<br><br>
<button>注册</button>
</form>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
user: {
username: '',
password: '',
age: '',
gender: '1',
interest: ['travel'],
grade: 'ss',
introduce: '',
accept: ''
},
msg: '表单数据的收集'
},
methods: {
send() {
alert('ajax...!!!!')
console.log(JSON.stringify(this.user))
}
}
})
</script>
</body>
13. 响应式与数据劫持
修改 data 后,页面自动改变/刷新。这就是响应式。Vue 的响应式是如何实现的?数据劫持:Vue 底层使用了 Object.defineProperty,配置了 setter 方法,当去修改属性值时 setter 方法则被自动调用,setter 方法中不仅修改了属性值,而且还做了其他的事情,例如:重新渲染页面。setter 方法就像半路劫持一样,所以称为数据劫持。
<body>
<div id="app">
<h1>{{msg}}</h1>
<div>姓名:{{name}}</div>
<div>年龄:{{age}}岁</div>
<div>数字:{{a.b.c.e}}</div>
<div>邮箱:{{a.email}}</div>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '响应式与数据劫持',
name: 'jackson',
age: 20,
a: {
b: {
c: {
e: 1
}
}
}
}
})
vm.$set(vm.a, 'email', '[email protected]')
</script>
</body>
13.1 数组的响应式处理
<body>
<div id="app">
<h1>{{msg}}</h1>
<ul>
<li v-for="user in users"> {{user}} </li>
</ul>
<ul>
<li v-for="vip in vips" :key="vip.id"> {{vip.name}} </li>
</ul>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '数组的响应式处理',
users: ['jack', 'lucy', 'james'],
vips: [
{ id: '111', name: 'zhangsan' },
{ id: '222', name: 'lisi' }
]
}
})
</script>
</body>
13.1.1 响应式处理代码
vm.users.push('newUser')
vm.users.pop()
vm.users.splice(0, 1)
vm.users.splice(1, 0, 'inserted')
Vue.set(vm.users, 0, 'modified')
vm.users[0] = 'newName'
vm.users.length = 2
Vue.set(vm.vips[0], 'name', '张三')
vm.vips[0].age = 30
vm.vips.push({ id: '333', name: '王五' })
vm.vips.splice(1, 1)
Vue.set(vm.vips[0], 'gender', 'male')
vm.vips[0] = { id: '111', name: '张三' }
vm.users = ['new', 'array', 'items']
vm.vips = [
{ id: '999', name: '新用户' },
{ id: '888', name: '测试用户' }
]
vm.users = vm.users.filter(user => user !== 'lucy')
vm.vips = vm.vips.map(vip => ({ ...vip, name: vip.name.toUpperCase() }))
14. Vue 的生命周期
<body>
<div id="app">
<h1>{{msg}}</h1>
<h3>计数器:{{counter}}</h3>
<h3 v-text="counter"></h3>
<button @click="add">点我加 1</button>
<button @click="destroy">点我销毁</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Vue 生命周期',
counter: 1
},
methods: {
add() {
console.log('add....')
this.counter++
},
destroy() {
this.$destroy()
},
},
watch: {
counter() {
console.log('counter 被监视一次!')
}
},
beforeCreate() {
console.log('beforeCreate', this.counter)
},
created() {
console.log('created', this.counter)
debugger
},
beforeMount() {
console.log('beforeMount')
},
mounted() {
console.log('mounted')
console.log(this.$el)
console.log(this.$el instanceof HTMLElement)
},
beforeUpdate() {
console.log('beforeUpdate')
},
updated() {
console.log('updated')
},
beforeDestroy() {
console.log('beforeDestroy')
console.log(this)
},
destroyed() {
console.log('destroyed')
console.log(this)
},
})
</script>
</body>
14.1 创建阶段
beforeCreate:实例刚被创建,数据观测和事件配置之前调用.
- 使用场景:插件初始化、全局事件总线设置
- 特点:无法访问
data、methods 和 computed 等属性
beforeCreate() {
console.log('beforeCreate: 实例刚创建');
console.log('data: ', this.message);
}
created:实例创建完成,数据观测和计算属性等已配置.
- 使用场景:API 请求、初始化数据、访问响应式数据
- 特点:可以访问数据,但 DOM 尚未生成
created() {
console.log('created: 实例创建完成');
console.log('data: ', this.message);
this.fetchData();
}
14.2 挂载阶段
beforeMount:挂载开始之前调用,模板已编译但未渲染到页面.
beforeMount() {
console.log('beforeMount: 挂载之前');
console.log('$el: ', this.$el);
}
- 使用场景:操作 DOM 前的准备工作
- 特点:
$el 属性尚未生成
- 使用场景:操作 DOM、集成第三方库、启动定时器
- 特点:可以访问渲染后的 DOM 元素
mounted() {
console.log('mounted: 挂载完成');
console.log('$el: ', this.$el);
this.initChart();
this.timer = setInterval(this.updateData, 1000);
}
14.3 更新阶段
beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前.
beforeUpdate() {
console.log('beforeUpdate: 数据更新前');
console.log('当前值:', this.count);
console.log('DOM 值:', this.$refs.counter.textContent);
}
- 使用场景:获取更新前的 DOM 状态
- 特点:可以访问当前数据,但 DOM 尚未更新
updated:数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用.
updated() {
console.log('updated: 数据更新完成');
console.log('DOM 值:', this.$refs.counter.textContent);
}
- 使用场景:操作更新后的 DOM
- 注意事项:避免在此钩子中修改状态,可能导致无限循环
14.4 销毁阶段
- 使用场景:清理工作(清除定时器、取消事件监听、销毁第三方库实例)
- 特点:实例仍然完全可用
beforeDestroy() {
console.log('beforeDestroy: 销毁之前');
clearInterval(this.timer);
this.chart.destroy();
window.removeEventListener('resize', this.handleResize);
}
- 使用场景:最后的清理工作
- 特点:所有绑定已解除,事件监听器已移除
destroyed() {
console.log('destroyed: 销毁完成');
}
sort
(a, b) =>
return
power
power
else
if
this
type
2
sort
(a, b) =>
return
power
power
return
let
8
9
5
4
1
2
3
sort
(a, b) =>
return
console
log
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online