1. Vue2 简介
Vue.js 的核心概念与语法,涵盖 MVVM 模式、数据绑定、指令系统(v-bind, v-model, v-if 等)、事件处理、计算属性与侦听器、列表渲染、虚拟 DOM 及生命周期钩子。通过实例演示了响应式原理、数据代理机制及常见开发场景下的最佳实践,适合初学者快速掌握 Vue 框架基础。

单向的数据绑定,当页面数据发生变化时,页面会自动重新渲染。

在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源中。


// M:Model(模型/数据)
// V:View(视图)
// VM:ViewModel(视图模型):VM 是 MVVM 中的核心部分。
问题:MVVM 模型当中倡导了 Model 和 View 进行了分离,为什么要分离?
将 Model 和 View 分离之后,出现了一个 VM 核心,这个 VM 把所有的脏活累活给做了。也就是说,当 Model 发生改变之后,VM 自动去更新 View;当 View 发生改动之后,VM 自动去更新 Model。再也不需要编写操作 DOM 的 JS 代码了,开发效率提高了很多。

<!-- 准备容器 --> <!-- View V-->
<div id="app">姓名:<input type="text" v-model="name"></div>
<!-- vue 程序 -->
<script>
// ViewModel VM
const vm = new Vue({
el: '#app',
data: {
name: 'zhangsan'
}
})
</script>
首先,使用 script 标签引入 vue.js 文件:
<script src="../js/vue.js"></script>
<body>
<!-- 指定 VUE 实例的挂载位置 -->
<div id="app"></div>
<script>
const myVue = new Vue({
template: '<h1>Hello World!</h1>'
})
myVue.$mount('#app') // Vue 实例的挂载代码
</script>
</body>
当使用 script 引入 vue.js 之后,Vue 会被注册为一个全局变量。首先必须要 new 一个 Vue 实例。
options 翻译为多个选项,Vue 框架要求 options 参数必须是一个纯粹的 JS 对象 {}。在当前对象中编写大量的键值对 key:value;每个键值对都是配置项。
将 Vue 实例挂载到 id=app 的元素位置。
$mount() 方法,这个方法的作用是什么?
将 Vue 实例挂载到指定位置,将 Vue 实例编译后的 HTML 代码渲染到页面的指定位置。注意:指定位置的元素被替换。#app 显然是 ID 选择器。<body>
<div id="app"></div>
<script>
/* 模板语句的数据来源:
* 1. 谁可以给模板语句提供数据支持呢?data 选项。
* 2. data 选项的类型是什么?Object|Function(对象或者函数)
* 3. data 配置项的专业叫法:Vue 实例的数据对象。(data 实际上是给整个 Vue 实例提供数据来源的。)
* 4. 如果 data 是对象的话,对象必须是纯粹的对象 (含有零个或多个的 key/value 对)
* 5. data 数据如何插入到模板语句当中?{{}} 这是 Vue 框架自己搞的一套语法,别的框架看不懂的,浏览器也是不能够识别的。vue 框架自己是能够看懂的。这种语法在 vue 框架中被称为:模板语法中的插值语法。(有的人把他叫做胡子语法。) 怎么用?{{data 的 key}} 插值语法:{}不能有其他字符包括空格
*/
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>
template 只能有一个根元素;template 编译后进行渲染时会将挂载位置的元素替换。只要 data 中的数据发生变化,模板语句一定会重新编译。template 后面的代码如果需要换行的话,建议将代码写到 ```` 符号当中,不建议使用 + 进行字符串的拼接。将 Vue 实例挂载时,也可以不用 $mount 方法,可以使用 Vue 的 el 配置项。el 配置项主要是用来指定 Vue 实例关联的容器。也就是说 Vue 所管理的容器是哪个。
<body>
<div id="app">
<div>
<h1>{{msg}}</h1>
<h2>{{name}}</h2>
</div>
</div>
<!-- vue 程序 -->
<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: "明天不学了!"
},
el: '#app' // el: document.getElementById('app')
})
</script>
</body>
Vue 实例和容器的关系是:一夫一妻制。
<body>
<!-- 准备容器 -->
<div id="app"><h1>{{msg}}</h1></div>
<div id="app2"><h1>{{msg}}</h1></div>
<div><h1>{{name}}</h1></div>
<script>
/* 验证:一个 Vue 实例可以接管多个容器吗?
* 不能,一个 Vue 实例只能接管一个容器;不能重复接管,因此下面 app2 显示'张三'
*/
new Vue({
el: '.app',
data: { msg: 'hell0' }
})
new Vue({
el: '#app2',
data: { name: '张三' }
})
new Vue({
el: '#app2',
data: { name: '李四' }
})
</script>
</body>
<!-- 主要研究:{{这里可以写什么}}
* 1. 在 data 中声明的变量、函数等都可以。
* 2. 常量都可以。
* 3. 只要是合法的 javascript 表达式,都可以。
* 4. 模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 等。
* 'Infinity,undefined,NaN,isFinite,isNaN,'
* 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,'
* 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,'
* 'require'
-->
<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>
<!-- 在 data 中声明的 -->
<h1>{{msg}}</h1>
<h1>{{sayHello()}}</h1>
<!-- 不在 data 中定义不可用 -->
<!-- <h1>{{i}}</h1> -->
<!-- <h1>{{sum()}}</h1> -->
<!-- 常量 -->
<h1>{{100}}</h1>
<h1>{{'hello vue!'}}</h1>
<h1>{{3.14}}</h1>
<!-- javascript 表达式 -->
<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>
<!-- .split(''):将字符串分割成字符数组 -->
<!-- .join('')指的是将数组中的所有元素组合成一个字符串,使用空字符串连接 -->
<h1>{{msg.split('').reverse().join('')}}</h1>
<!-- 错误的:不是表达式,这是语句。 -->
<!-- <h1>{{var i = 100}}</h1> -->
<!-- 在白名单里面的 -->
<h1>{{Date}}</h1>
<h1>{{Date.now()}}</h1>
<h1>{{Math}}</h1>
<h1>{{Math.ceil(3.14)}}</h1>
</div>
<HTML v-指令名:参数="javascript 表达式"></HTML>
不是所有的指令都有参数和表达式: 有的指令,不需要参数,也不需要表达式,例如:v-once 有的指令,不需要参数,但是需要表达式,例如:v-if="表达式" 有的指令,既需要参数,又需要表达式,例如:v-bind:参数="表达式"
作用:只渲染元素一次。随后的重新渲染,元素及其所有的子节点将被视为静态内容并跳过。
作用:表达式的执行结果需要是一个布尔类型的数据:true 或者 false。
<!-- 准备一个容器 -->
<div>
<h1>{{msg}}</h1>
<h1 v-once>{{msg}}</h1>
<h1 v-if="a <= b">v-if 测试:{{msg}}</h1>
</div>
<!-- vue 程序 -->
<script>
new Vue({
el: '#app',
data: {
msg: 'Hello Vue!',
a: 10,
b: 11
}
})
</script>
<body>
<div>
<h1>{{msg}}</h1>
<!-- v-if 指令的值:true/false
* true: 表示该元素会被渲染到页面上。
* false: 表示该元素不会被渲染到页面上。(注意:不是修改了 CSS 样式,是这个元素压根没有加载)
-->
<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">
<!-- 提醒:v-if 和 v-else 之间不能断开。 -->
<!-- <div></div> -->
<!-- <img :src="imgPath2" v-if="counter % 2 === 0"> -->
<!-- 为了提高效率,可以使用 v-else 指令 -->
<img :src="imgPath2" v-else>
<br><br>
温度:<input type="number" v-model="temprature"><br><br>
<!-- 天气:<span v-if="temprature <= 10">寒冷</span>
<span v-if="temprature > 10 && temprature <= 25">凉爽</span>
<span v-if="temprature > 25">炎热</span> -->
天气:<span v-if="temprature <= 10">寒冷</span>
<!-- v-if v-else-if v-else三者在使用的时候,中间不能断开。 -->
<!-- <br> -->
<span v-else-if="temprature <= 25">凉爽</span>
<span v-else>炎热</span>
<br><br><br>
<div v-show="false">你可以看到我吗?</div>
<!-- template 标签/元素只是起到占位的作用,不会真正的出现在页面上,也不会影响页面的结构。 -->
<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>
/* v-if 指令会动态地创建或移除 DOM 元素,从而控制元素在页面上的显示与隐藏;
* v-show 指令会动态为元素添加或移除 样式,从而控制元素的显示与隐藏;
* v-if 和 v-show 应该如何选择?
* 1. 如果一个元素在页面上被频繁的隐藏和显示,建议使用 v-show,因为此时使用 v-if 开销比较大。
* 2. v-if 的优点:页面加载速度快,提高了页面的渲染效率。
*/
可以让 HTML 标签的某个属性的值产生动态的效果。
<HTML v-bind:参数="表达式"></HTML>
<!--简写方式-->
<HTML :参数="表达式"></HTML>
编译后:
<HTML 参数="表达式的执行结果"></HTML>
第一:在编译的时候 v-bind 后面的'参数名'会被编译为 HTML 标签的**'属性名'** 第二:表达式会关联 data ,当 data 发生改变之后,表达式的执行结果就会发生变化。所以,连带的就会产生动态效果。
什么时候使用插值语法?什么时候使用指令?
- 凡是标签体当中的内容要想动态,需要使用插值语法。
- 只要想让 HTML 标签的属性动态,需要使用指令语法。
<!-- 准备一个容器 -->
<div>
<!-- 注意:以下代码中 msg 是变量名。 -->
<!-- 注意:原则上 v-bind 指令后面的这个参数名可以随便写。 -->
<!-- 虽然可以随便写,但大部分情况下,这个参数名还是需要写成该 HTML 标签支持的属性名。这样才会有意义。 -->
<span v-bind:xyz="msg"></span>
<!-- 这个表达式带有单引号,这个'msg'就不是变量了,是常量。 -->
<span v-bind:xyz="'msg'"></span>
<!-- v-bind 实战 -->
<img src="../img/1.jpg">
<br>
<img v-bind:src="imgPath">
<br>
<!-- v-bind 简写形式 -->
<img :src="imgPath">
<br>
<!-- 这是一个普通的文本框 -->
<input type="text" name="username" value="zhangsan">
<br>
<!-- 以下文本框可以让 value 这个数据变成动态的:这个就是典型的动态数据绑定。 -->
<input type="text" name="username" :value="username">
<br>
<!-- 使用 v-bind 也可以让超链接的地址动态 -->
<a href="https://www.baidu.com">走起</a>
<br>
<a :href="url">走起 2</a>
<br>
<!-- 不能采用以下写法吗? -->
<!-- 不能这样,报错了,信息如下:Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead. For example, instead of <div>, use <div :id="val"> 属性内部插值这种语法已经被移除了。(可能 Vue 在以前的版本中是支持这种写法的,但是现在不允许了。)请使用 v-bind 或冒号速记来代替。请使用 <div :id="val"> 来代替 <div> -->
<!-- <a href="{{url}}">走起 3</a> -->
<h1>{{msg}}</h1>
</div>
<!-- vue 程序 -->
<script>
// 赋值的过程就可以看做是一种绑定的过程。
//let i = 100
new Vue({
el: '#app',
data: {
msg: 'Hello Vue!',
imgPath: '../img/1.jpg',
username: 'jackson',
url: 'https://www.baidu.com'
}
})
</script>
<!-- v-bind 和 v-model 的区别和联系:
* 1. v-bind 和 v-model 这两个指令都可以完成数据绑定。
* 2. v-bind 是单向数据绑定。
* data ===> 视图
* 3. v-model 是双向数据绑定。
* data <===> 视图
* 4. v-bind 可以使用在任何 HTML 标签当中。v-model 只能使用在表单类元素上,例如:
* input 标签、select 标签、textarea 标签。
* 为什么 v-model 的使用会有这个限制呢?
* 因为表单类的元素才能给用户提供交互输入的界面。
* v-model 指令通常也是用在 value 属性上面的。
-->
<!-- v-bind 和 v-model 都有简写方式:
* v-bind 简写方式:v-bind:参数="表达式" 简写为 :参数="表达式"
* v-model 简写方式:v-model:value="表达式" 简写为 v-model="表达式"
-->
<!-- 准备一个容器 -->
<div>
v-bind 指令:<input type="text" :value="name1"><br>
v-model 指令:<input type="text" v-model:value="name2"><br>
<!-- 以下报错了,因为 v-model 不能使用在这种元素上。 -->
<!-- <a v-model:href="url">百度</a> -->
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>
<!-- vue 程序 -->
<script>
new Vue({
el: '#app',
data: {
name1: 'zhangsan',
name2: 'wangwu',
url: 'https://www.baidu.com',
msg: 'Hello Vue!'
}
})
</script>

<!-- Vue 事件处理:
* 1. 指令的语法格式:
* <标签 v-指令名:参数名="表达式">{{插值语法}}</标签>
* '表达式'位置都可以写什么?
* 常量、JS 表达式、Vue 实例所管理的 XXX
* 2. 在 Vue 当中完成事件绑定需要哪个指令呢?v-on 指令。
* 语法格式:v-on:事件名="表达式"
* 例如:v-on:click="表达式" 表示当发生鼠标单击事件之后,执行表达式。
* v-on:keydown="表达式" 表示当发生键盘按下事件之后,执行表达式。
* 3. 在 Vue 当中,所有事件所关联的回调函数,需要在 Vue 实例的配置项 methods 中进行定义。
* methods 是一个对象:{}
* 在这个 methods 对象中可以定义多个回调函数。
* 4. v-on 指令也有简写形式
* v-on:click 简写为 @click
* v-on:keydown 简写为 @keydown
* v-on:mouseover 简写为 @mouseover ....
* 5. 绑定的回调函数,如果函数调用时不需要传递任何参数,小括号 () 可以省略。
* 6. Vue 在调用回调函数的时候,会自动给回调函数传递一个对象,
* 这个对象是:当前发生的事件对象。
* 7. 在绑定回调函数的时候,可以在回调函数的参数上使用 $event 占位符,
* Vue 框架看到这个 $event 占位符之后,会自动将当前事件以对象的形式传过去。
-->
<div>
<h1>{{msg}}</h1>
<!-- 使用 javascript 原生代码如何完成事件绑定。 -->
<button onclick="alert('hello')">hello</button>
<!-- 使用 Vue 来完成事件绑定 -->
<!-- 以下是错误的,因为 alert() 并没有被 Vue 实例管理。 -->
<!-- <button v-on:click="alert('hello')">hello</button> -->
<!-- 以下是错误的,因为 sayHello() 并没有被 Vue 实例管理。
此时 sayHello() 未被定义在 Vue 实例中 -->
<!-- <button v-on:click="sayHello()">hello</button> -->
<!-- 正确的写法 -->
<button v-on:click="sayHello()">hello</button>
<!-- v-on 指令的简写形式 -->
<button @click="sayHi()">hi button</button>
<button @click="sayHi($event, 'jack')">hi button2</button>
<!-- 绑定的回调函数,如果不需要传任何参数,小括号 () 可以省略 -->
<button @click="sayWhat">what button</button>
</div>
<!-- vue 代码 -->
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Vue 的事件绑定'
},
methods: {
// 回调函数
sayHello() {
alert('hello2')
},
sayHi(event, name) {
console.log(name, event)
//alert("hi " + name)
},
// Vue 在调用回调函数的时候,会自动给回调函数传递一个对象
// 这个对象是:当前发生的事件对象
// 传入的内容是 事件参数对象 event
sayWhat(event) {
console.log(event)
// console.log(event.target) // <button>what button</button>
console.log(event.target.innerText) // what button
//alert('what...')
}
}
})
</script>
<!-- Vue 当中提供的事件修饰符:
* .stop :停止事件冒泡,等同于 event.stopPropagation()。
* .prevent :等同于 event.preventDefault() 阻止事件的默认行为。
* .capture :添加事件监听器时使用事件捕获模式
* 添加事件监听器包括两种不同的方式:
* 一种是从内到外添加。(事件冒泡模式)
* 一种是从外到内添加。(事件捕获模式)
* .self:这个事件如果是'我自己元素'上发生的事件,这个事件不是别人给我传递过来的事件,
* 则执行对应的程序。
* .once:事件只发生一次
* .passive:passive 翻译为顺从/不抵抗。无需等待,直接继续(立即)执行事件的默认行为。
* .passive 和 .prevent 修饰符是对立的。不可以共存。(如果一起用,就会报错。)
* .prevent:阻止事件的默认行为。
* .passive:解除阻止。
-->
<head>
<style>
.divList {
width: 300px;
height: 200px;
background-color: aquamarine;
overflow: auto; // 自动在对应方向出现滚动条
}
.item {
width: 300px;
height: 200px;
}
</style>
</head>
<body>
<!-- 容器 -->
<div>
<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">
<!-- 这里没有添加.capture 修饰符,以下这个元素,以及这个元素的子元素,都会默认采用冒泡模式。
如果希望依次是 3,2,1 那下面这两个子元素都需要是.capture -->
<div @click="er">
<button @click="yi">添加事件监听器的时候采用事件捕获模式</button>
</div>
</div>
<br>
<!-- .self 修饰符:只执行 1 和 3 -->
<div @click="san">
<div @click.self="er">
<button @click="yi">self 修饰符</button>
</div>
</div>
<br>
<!-- 在 Vue 当中,事件修饰符是可以多个联合使用的。
但是需要注意:@click.self.stop:先.self,再.stop
@click.stop.self:先.stop,再.self -->
<div @click="san">
<div @click="er">
<button @click.self.stop="yi">self 修饰符</button>
</div>
</div>
<br>
<!-- .once 修饰符:事件只发生一次 -->
<button @click.once="yi">事件只发生一次</button>
<!-- .passive 修饰符 -->
<!-- @wheel 是数据滚动事件,会触发 testPassive 方法-->
<div @wheel.passive="testPassive">
<div>div1</div>
<div>div2</div>
<div>div3</div>
</div>
</div>
<!-- vue 代码 -->
<script>
const vm = new Vue({
el: '#app',
data: {
msg: '事件修饰符'
},
methods: {
yi(event) {
//alert('去百度!!!!!!')
// 手动调用事件对象的 preventDefault() 方法,可以阻止事件的默认行为。
// 在 Vue 当中,这种事件的默认行为可以不采用手动调用 DOM 的方式来完成,可以使用事件修饰符:prevent。
//event.preventDefault();
alert(1)
},
er() {
alert(2)
},
san() {
alert(3)
},
testPassive(event) {
for (let i = 0; i < 100000; i++) {
console.log('test passive')
}
// 阻止事件的默认行为
//event.preventDefault()
}
}
})
</script>
</body>
<body>
<!-- 9 个比较常用的按键修饰符:.enter .tab (必须配合 keydown 事件使用。)
.delete (捕获'删除'和'退格'键).esc .space .up .down .left .right
怎么获取某个键的按键修饰符?
第一步:通过 event.key 获取这个键的真实名字。
第二步:将这个真实名字以 kebab-case 风格进行命名。
PageDown 是真实名字。经过命名之后:page-down
按键修饰符是可以自定义的?
通过 Vue 的全局配置对象 config 来进行按键修饰符的自定义。
语法规则:Vue.config.keyCodes.按键修饰符的名字 = 键值
系统修饰键:4 个比较特殊的键 ctrl、alt、shift、meta
对于 keydown 事件来说:只要按下 ctrl 键,keydown 事件就会触发。
对于 keyup 事件来说:需要按下 ctrl 键,并且加上按下组合键,然后松开组合键之后,keyup 事件才能触发。
-->
<div>
<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 键无法触发 keyup 事件。只能触发 keydown 事件。 -->
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>
// 自定义了一个按键修饰符:.huiche 。代表回车键。
Vue.config.keyCodes.huiche = 13
const vm = new Vue({
el: '#app',
data: {
msg: '按键修饰符'
},
methods: {
getInfo(event) {
// 当用户键入回车键的时候,获取用户输入的信息。
//if(event.keyCode === 13){
// event.target 是触发事件的 DOM 元素本身。例子中,触发 keyup 事件的是<input>元素
console.log(event.target.value)
//}
console.log(event.key)
}
}
})
</script>
</body>
v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为 (item, index) in items。
<body>
<div>
<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>
<!-- 1. v-for 要写在循环项上。
2. v-for 的语法规则:v-for="(变量名,index) in/of 数组"
变量名 代表了 数组中的每一个元素 -->
<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>
<!-- v-for 指令所在的标签中,还有一个非常重要的属性::key
* 如果没有指定 :key 属性,会自动拿 index 作为 key。
* 这个 key 是这个 dom 元素的身份证号/唯一标识。
* 分析以下:采用 index 作为 key 存在什么问题?
* 第一个问题:效率低。
* 第二个问题:非常严重了。产生了错乱。尤其是对数组当中的某些元素进行操作。(非末尾元素。)
* 怎么解决这个问题?建议使用对象的 id 作为 key -->
<body>
<div>
<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>
// unshift()- 开头插入 arr.unshift('newItem'); // O(1) ~ O(n)
// push()- 结尾插入 arr.push('newItem'); // O(1) 时间复杂度
// splice()- 任意位置插入 arr.splice(index, 0, 'newItem'); // 平均 O(n)
// pop()- 删除最后一项 arr.pop(); // O(1)
// shift()- 删除第一项 arr.shift(); // O(n)
// splice()- 任意位置删除 arr.splice(index, 1); // O(n)
<body>
<div>
<h1>{{msg}},test</h1>
<!-- v-text 指令:
* 可以将指令的内容拿出来填充到标签体当中。和 JS 的 innerText 一样。
* 这种填充是以覆盖的形式进行的。先清空标签体当中原有的内容,填充新的内容。
* 即使内容是一段 HTML 代码,这种方式也不会将 HTML 代码解析并执行。
* 只会当做普通文本来处理。
-->
<h1 v-text="msg">test</h1>
<h1 v-text="name">test</h1>
<h1 v-text="s1"></h1>
<!-- v-html 指令:
* 和 v-text 一样,也是填充标签体内容。也是采用覆盖的形式进行。
* 只不过 v-html 会将内容当做一段 HTML 代码解析并执行。
-->
<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>
<head>
<style>
/* 刚开始不显示,加载之后将 v-cloak 干掉,所以就会显示 */
[v-cloak] { display: none; }
</style>
</head>
<body>
<div>
<!-- v-cloak 指令使用在标签当中,当 Vue 实例接管之后会删除这个指令。 -->
<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>
<body>
<div>
<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>
在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。 在使用自定义指令时,需要加上 v- 前缀。 在 template 结构中使用自定义指令时,可以通过等号(=)的方式,为当前指令动态绑定参数值。
<div>
<h1>自定义指令</h1>
<div v-text="msg"></div>
<div v-text-danger="msg"></div>
用户名:<input type="text" v-bind:value="username">
<!-- 需要一个指令,可以和 v-bind 指令完成相同的功能,
同时将该元素的父级元素的背景色设置为蓝色。 -->
<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: {
// 指令 1
// 指令 2
// ...
// 关于指令的名字:1. v- 不需要写。
// 2. Vue 官方建议指令的名字要全部小写。如果是多个单词的话,请使用 - 进行衔接。
// 这个回调函数的执行时机包括两个:第一个:标签和指令第一次绑定的时候。第二个:模板被重新解析的时候。
// 这个回调函数有两个参数:第一个参数是真实的 dom 元素。第二个参数是标签与指令之间绑定关系的对象。
// 函数式方式。
'text-danger': function(element, binding){
console.log('@')
element.innerText = binding.value // element 指的就是 div, binding 指的就是 v-text-danger
element.style.color = 'red'
},
'bind-blue': function(element, binding){
element.value = binding.value
console.log(element)
// 为什么是 null,原因是这个函数在执行的时候,指令和元素完成了绑定,
// 但是只是在内存当中完成了绑定,元素还没有被插入到页面当中。
console.log(element.parentNode)
element.parentNode.style.backgroundColor = 'blue'
},
// 对象式
'bind-blue': {
// 这个对象中三个方法的名字不能随便写。
// 这三个函数将来都会被自动调用。
// 元素与指令初次绑定的时候,自动调用 bind
// 注意:在特定的时间节点调用特定的函数,这种被调用的函数称为钩子函数。
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) {
//对于自定义指令来说,函数体当中的 this 是 window,而不是 vue 实例。
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) // Window
},
inserted(element, binding) {
element.parentNode.style.backgroundColor = 'skyblue'
console.log(this)
},
update(element, binding) {
element.value = binding.value
console.log(this)
}
})
过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。 过滤器可以用在两个地方:插值表达式和 v-bind 属性绑定,过滤器应该被添加在 JavaScript 表达式的尾部。
<!-- 需求:
* 从服务器端返回了一个商品的价格 price,这个 price 的值可能是这几种情况:''、null、undefined、60.5
* 要求:如果是''、null、undefined,页面上统一显示为 -
* 如果不是 ''、null、undefined,则页面上显示真实的数字即可。
* 在 Vue3 当中,已经将过滤器语法废弃了。
-->
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){
// 确保传递过来的数据 val,保留两位小数。
return val.toFixed(number)
}
}
})
// 配置全局的过滤器。
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>
<h1>{{msg}}</h1>
<!-- formatPrice 是计算属性-->
<h2>商品价格:{{formatPrice}}</h2>
<!--方法 formatPrice2() 的返回值-->
<h2>商品价格:{{formatPrice2()}}</h2>
<!--输入 price,首先经过第一层过滤 filterA,之后经过第二层过滤 filterB(3) 保留 3 位小数-->
<h2>商品价格:{{price | filterA | filterB(3)}}</h2>
<input type="text" :value="price | filterA | filterB(3)">
</div>
<hr>
<div>
<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>
虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性。
使用 Vue 的原有属性,经过一系列的运算/计算,最终得到了一个全新的属性,叫做计算属性。
**Vue 的原有属性:**data 对象当中的属性可以叫做 Vue 的原有属性。
**全新的属性:**表示生成了一个新的属性,和 data 中的属性无关了,新的属性也有自己的属性名和属性值。
<body>
<div>
<h1>{{msg}}</h1>
输入的信息:<input type="text" v-model="info">
<br>
<!-- 在插值语法中可以调用方法,小括号不能省略。这个方法需要是 Vue 实例所管理的。 -->
反转的信息:{{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>
<!--语法格式:需要一个新的配置项 computed
* computed : {
* // 这是一个计算属性
* 计算属性 1 : {
* // setter 和 getter 方法。
* // 当读取计算属性 1 的值的时候,getter 方法被自动调用。
* get(){ },
* // 当修改计算属性 1 的值的时候,setter 方法被自动调用。
* set(val){ }
* },
* }
-->
<div>
<h1>{{msg}}</h1>
输入的信息:<input type="text" v-model="info">
<br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
反转的信息:{{reversedInfo}}<br>
{{hehe}} <br>
<!-- 计算属性的缓存机制,所以只调用一次 get() 方法 -->
{{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 方法的调用时机包括两个
// 第一个时机:第一次访问这个属性的时候。
// 第二个时机:该计算属性所关联的 Vue 原有属性的值发生变化时,getter 方法会被重新调用一次。
get() {
console.log('getter 方法调用了')
//console.log(this === vm) TRUE
return 'haha' + this.info
},
// 不能使用箭头函数,使用箭头函数会导致 this 的指向是:window
// get:()=>{
// console.log('getter 方法调用了')
// console.log(this === vm)
// return 'haha'
// },
set(val) {
console.log('setter 方法调用了')
//console.log(this === vm) TRUE
}
},
// 完整写法
reversedInfo: {
get() {
return this.info.split('').reverse().join('')
},
// 当修改计算属性的时候,set 方法被自动调用。
set(val) {
//console.log('setter 方法被调用了。')
// 不能这么做,这样做就递归了。
//this.reversedInfo = val
// 怎么修改计算属性呢?原理:计算属性的值变还是不变,取决于计算属性关联的 Vue 原始属性的值。
// 也就是说:reversedInfo 变还是不变,取决于 info 属性的值变不变。
// 本质上:修改计算属性,实际上就是通过修改 Vue 的原始属性来实现的。
this.info = val.split('').reverse().join('')
}
}
// 简写形式:set 不需要的时候。
/* reversedInfo() {
return this.info.split('').reverse().join('')
} */
}
})
</script>
- 监视属性:监视哪个属性,就把属性放入 watch 中即可。
- 可以监视 Vue 的原有属性。
<!-- watch: {
* 1. 打开页面初始化时,会调用一次 handler 方法。
* 2. handler 方法的调用时间:
* 当被监视的属性发生变化的时候,handler 就会自动调用一次。
* 3. handler 方法上有两个参数:第一个参数 newValue,第二个参数是 oldValue。
* newValue 是属性值改变之后的新值,oldValue 是属性值改变之前的旧值。
* }
-->
<body>
<div>
<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 属性中保存的值是一个对象的内存地址。
// a = 0x2356
a: {
b: 0,
c: 0,
d: {
e: {
f: 0
}
}
}
},
computed: {
hehe() {
return 'haha:' + this.number
}
},
watch: {
// 可以监视多个属性
// 监视哪个属性,请把这个属性的名字拿过来即可。
// 可以监视 Vue 的原有属性
number: {
// 打开页面初始化的时候,调用一次 handler 方法。
immediate: true,
handler(newValue, oldValue) {
console.log(newValue, oldValue)
// this 是当前的 Vue 实例。
// 如果该函数是箭头函数,这个 this 是 window 对象。不建议使用箭头函数。
console.log(this)
}
},
// 无法监视 b 属性,因为 b 属性压根不存在。
/* b : {
handler(newValue, oldValue){
console.log('@')
}
} */
// 如果监视的属性具有多级结构,一定要添加单引号:'a.b'
/* 'a.b' : {
handler(newValue, oldValue){
console.log('@')
},
'a.c' : {
handler(newValue, oldValue){
console.log('@')
},
*/
a: {
// 启用深度监视,默认是不开启深度监视的。
// 什么时候开启深度监视:当你需要监视一个具有多级结构的属性,并且监视所有的属性,需要启用深度监视。
deep: true,
handler(newValue, oldValue) {
console.log('@')
}
},
// 注意:监视某个属性的时候,也有简写形式,什么时候启用简写形式?
// 当只有 handler 回调函数的时候,可以使用简写形式。
number(newValue, oldValue) {
console.log(newValue, oldValue)
},
// 也可以监视计算属性
hehe: {
handler(a, b) {
console.log(a, b)
}
}
}
})
</script>
</body>
// 如何后期添加监视?调用 Vue 相关的 API 即可。
// 语法:vm.$watch('被监视的属性名', {})
vm.$watch('number2', {
immediate: true,
deep: true,
handler(newValue, oldValue){
console.log(newValue, oldValue)
}
})
// 这是后期添加监视的简写形式 (能实现的功能也就是 handler)。
vm.$watch('number2', function (newValue, oldValue) {
console.log(newValue, oldValue)
})
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。 如果想让 watch 侦听器立即被调用,则需要使用 immediate 选项。
immediate : true 页面首次加载完毕就触发 handler:0, undefined
如果 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。 此时需要使用 deep 选项。
<body>
<!-- 1. 通过 Vue 实例都可以访问哪些属性?(通过 vm 都可以 vm. 什么。)
* Vue 实例中的属性很多,有的以 $ 开始,有的以 _ 开始。
* 所有以 $ 开始的属性,可以看做是公开的属性,这些属性是供程序员使用的。
* 所有以 _ 开始的属性,可以看做是私有的属性,这些属性是 Vue 框架底层使用的。一般我们程序员很少使用。
* 通过 vm 也可以访问 Vue 实例对象的原型对象上的属性,例如:vm.$delete...
-->
<div>
<h1>{{msg}}</h1>
</div>
<script>
let dataObj = { msg: 'Hello Vue!' }
const vm = new Vue({
el: '#app',
data: dataObj
})
// 按说 msg 是 dataObj 对象的属性。
console.log('dataObj 的 msg', dataObj.msg);
// 为什么 msg 属性可以通过 vm 来访问呢?
// 这是因为 Vue 框架底层使用了数据代理机制。
// 要想搞明白数据代理机制,必须有一个基础知识点要学会:Object.defineProperty()。
console.log('vm 的 msg', vm.msg);
</script>
</body>
给对象新增属性,或者设置对象原有的属性
<!-- Object.defineProperty()
* 1. 怎么用?
* Object.defineProperty(给哪个对象新增属性,'新增的这个属性名叫啥', {给新增的属性设置相关的配置项 key:value 对})
* 2. 第三个参数是属性相关的配置项,配置项都有哪些?每个配置项的作用是啥?
* value 配置项:给属性指定值
* writable 配置项:设置该属性的值是否可以被修改。true 表示可以修改。false 表示不能修改。
* getter 方法 配置项:不需要我们手动调用的。当读取属性值的时候,getter 方法被自动调用。
* * getter 方法的返回值非常重要,这个返回值就代表这个属性它的值。
* setter 方法 配置项:不需要我们手动调用的。当修改属性值的时候,setter 方法被自动调用。
* * setter 方法上是有一个参数的,这个参数可以接收传过来的值。
* 注意:当配置项当中有 setter 和 getter 的时候,value 和 writable 配置项都不能存在。
-->
<script>
// 这是一个普通的对象
let phone = {}
// 临时变量
let temp
// 给上面的 phone 对象新增一个 color 属性
Object.defineProperty(phone, 'color', {
//value : '太空灰',
//writable : true,
// 默认值是 false
// getter 方法配置项
get: function () {
console.log('getter 方法执行了@@@');
//return '动态'
//return this.color 会出现递归情况,不能使用
return temp
},
// setter 方法配置项
set: function (val) {
console.log('setter 方法执行了@@@', val);
//this.color = val // 会出现递归情况,不能使用
temp = val
}
})
</script>
<body>
<div>
<h1>{{msg}}</h1>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello Vue!'
}
})
</script>
<script>
// 目标对象
let target = { name: 'zhangsan' }
// 代理对象
let proxy = {}
// 如果要实现数据代理机制的话,就需要给 proxy 新增一个 name 属性。
// 注意:代理对象新增的这个属性的名字 和 目标对象的属性名要一致。
Object.defineProperty(proxy, 'name', {
// get : function(){
// // 间接访问目标对象的属性
// return target.name
// },
// set : function(val){
// target.name = val
// }
get(){
console.log('getter 方法执行了@@@@');
return target.name
},
set(val){
target.name = val
}
})
</script>
</body>
<!-- 1. Vue 实例不会给以_和$开始的属性名做数据代理。
* 2. 为什么?
* 如果允许给_或$开始的属性名做数据代理的话。
* vm 这个 Vue 实例上可能会出现_xxx 或$xxx 属性,
* 而这个属性名可能会和 Vue 框架自身的属性名冲突。
* 3. 在 Vue 当中,给 data 对象的属性名命名的时候,不能以_或$开始。
-->
// 定义一个 Vue 类 用于数据代理功能的实现
// 创建一个 Vue 实例的构造函数
class Vue {
// 定义构造函数
// options 是一个简单的纯粹的 JS 对象:{}
// options 对象中有一个 data 配置项
constructor(options) {
// 获取所有的属性名
// 首先遍历 data 对象的属性名列表
// 之后遍历对 data 对象中的每个属性(如 msg, count 等)进行迭代处理
// propertyName:当前属性的名称,index:当前属性在属性列表中的索引位置
Object.keys(options.data).forEach((propertyName, index) => {
//console.log(typeof propertyName, propertyName, index)
let firstChar = propertyName.charAt(0)
if (firstChar != '_' && firstChar != '$') {
Object.defineProperty(this, propertyName, {
// 数据代理
get() {
return options.data[propertyName]
},
// 数据劫持
set(val) {
//1. 修改内存中该对象的属性值
options.data[propertyName] = val
//2. 重新渲染页面
}
})
}
})
// 获取所有的方法名
/* Object.keys(options.methods).forEach((methodName, index) => {
// 给当前的 Vue 实例扩展一个方法
this[methodName] = options.methods[methodName]
}) */
}
}
<body>
<!-- 容器 -->
<div>
<h1>{{msg}}</h1>
</div>
<!-- Vue 代码 -->
<script>
// const vm = new Vue({
// el: '#app',
// data: {
// msg: 'Hello Vue!',
// name: 'jackson',
// age: 30
// }
// })
const vm = new Vue({
el: '#app',
data: {
msg: 'Hello Vue!',
_name: 'jackson',
$age: 30
}
})
</script>
</body>
var data = vm.$options.data;
vm._data 属性中。data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
isFunction(data)判断传入的 data 选项是否是函数类型<!--Vue 允许两种形式的 data:
* 对象形式:data: { message: 'Hello' }
* 函数形式:data() { return { message: 'Hello' } }
-->
如果 data 是函数,则调用 getData(data, vm) 来获取真正的数据对象 data。
如果 data 不是函数,则直接使用 data。如果是
undefined或null,使用空对象{}作为默认值。双重赋值:将处理后的数据对象同时赋值给:局部变量
data和 Vue 实例的_data属性。
<!-- 程序执行到这里,为什么要给 vm 扩展一个_data 属性呢?
* _data 属性,以"_"开始,足以说明,这个属性是人家 Vue 框架底层需要访问的。
* Vue 框架底层它使用 vm._data 这个属性干啥呢?
* vm._data 是啥?
* vm._data 是:{ name : 'jackson', age : 35 }
* vm._data 这个属性直接指向了底层真实的 data 对象。
* 通过_data 访问的 name 和 age 是不会走数据代理机制的。
* 通过 vm._data 方式获取 name 和 age 的时候,是不会走 getter 和 setter 方法的。
* 注意:对于 Vue 实例 vm 来说,不仅有_data 这个属性,还有一个$data 这个属性。
* _data 是框架内部使用的,可以看做私有的。
* $data 这是 Vue 框架对外公开的一个属性,是给我们程序员使用。
-->
<!-- function isReserved(str) {
* var c = (str + '').charCodeAt(0);
* return c === 0x24 || c === 0x5f;
* }
* 这个函数是用来判断字符串是否以 _ 和 $开始的。
* true 表示以_或$开始的。
* false 表示不是以_或$开始的。
-->
<!-- proxy(vm, "_data", key); 通过这行代码直接进入代理机制(数据代理)。
* // target 是目标对象 Vue 实例 (vm)
* // sourceKey: 源数据属性名(如 '_data')
* // key: 要代理的属性名(如 'age')
* function proxy(target, sourceKey, key) {
* sharedPropertyDefinition.get = function proxyGetter() {
* return this["_data"]["age"];
* };
* sharedPropertyDefinition.set = function proxySetter(val) {
* this["_data"]["age"] = val;
* };
* // 定义代理属性;在目标对象(Vue 实例)上定义一个新属性
* Object.defineProperty(vm, 'age', sharedPropertyDefinition);
* }
-->
<!-- 容器 -->
<div>
<h1>姓名:{{name}}</h1>
<h1>年龄:{{age}}岁</h1>
</div>
<!-- vue 代码 -->
<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
}
})
// 如果我们程序员不想走代理的方式读取 data,想直接读取 data 当中的数据,可以通过_data 和$data 属性来访问。
// 建议使用$data 这个属性。
console.log('name = ' + vm.$data.name)
console.log('age = ' + vm.$data.age)
</script>
<body>
<!-- 容器 -->
<div>
<h1>{{msg}}</h1>
</div>
<!-- vue 代码 -->
<script>
const vm = new Vue({
el: '#app',
// data : {
// msg : 'Hello Vue!'
// }
// data functions should return an object:data 函数应该返回一个对象。
// data 也可以是一个函数。
// 如果是函数的话,必须使用 return 语句返回{}对象。
// data 可以是直接的对象,也可以是一个函数,什么时候使用直接的对象?什么时候使用函数呢?
// (等你学到组件的时候自然就明白了。)
// data : function(){
// return {
// msg : 'Hello Vue!'
// }
// }
// 在对象当中,函数的 :function 可以省略
data() {
return {
msg: 'Hello Zhangsan!'
}
}
})
// 关于源码 sharedPropertyDefinition 函数中的配置项:enumerable、configurable
let phone = { name: '苹果 X' }
// 给 phone 对象新增一个 color 属性
Object.defineProperty(phone, 'color', {
value: '奶奶灰',
// true 表示该属性是可以遍历的。(可枚举的,可迭代的。)
// false 表示该属性是不可遍历的。
enumerable: false,
// true 表示该属性是可以被删除的。
// false 表示该属性是不可以被删除的。
configurable: false
})
</script>
</body>
<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>
<h1>{{msg}}</h1>
<!-- 静态写法 -->
<div>{{msg}}</div>
<br><br>
<button @click="changeBig">变大</button>
<button @click="changeSmall">变小</button>
<!-- 动态写法:动静都有 -->
<!-- 适用场景:如果确定动态绑定的样式个数只有 1 个,但是名字不确定。 -->
<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>
<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>
<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>
<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>
<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>
<head>
<title>Style 绑定</title>
<script src="../js/vue.js"></script>
<style>
.static { border: 1px solid black; width: 100px; height: 100px; }
</style>
</head>
<body>
<div>
<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>
<body>
<div>
<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>
<!-- 1. v-for 要写在循环项上。
2. v-for 的语法规则:v-for="(变量名,index) in/of 数组"
变量名 代表了 数组中的每一个元素 -->
<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>
<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. === ) {
arr.( {
a. - b.
})
} (. === ) {
arr.( {
b. - a.
})
}
arr
}
}
})
arr = [, , , , , , ]
arr.( {
b - a
})
.(arr)
</script>
</body>
<body>
<div>
<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>
<!-- v-model.number 用于做类型转换,最后收到的数据没有双引号 -->
年龄:<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>
爱好:
<!-- 注意:对于 checkbox 来说,如果没有手动指定 value,那么会拿这个标签的 checked 属性的值作为 value -->
旅游<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>
简介:
<!-- v-model.lazy 失去焦点再提交-->
<textarea cols="50" rows="15" v-model.lazy="user.introduce"></textarea><br><br>
<input type="checkbox" v-model="user.accept">阅读并接受协议<br><br>
<!-- <button @click.prevent="send">注册</button> -->
<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...!!!!')
// 将数据收集好,发送给服务器。JSON 格式
//console.log(JSON.stringify(this.$data))
console.log(JSON.stringify(this.user))
}
}
})
</script>
</body>
修改 data 后,页面自动改变/刷新。这就是响应式。Vue 的响应式是如何实现的?数据劫持:Vue 底层使用了 Object.defineProperty,配置了 setter 方法,当去修改属性值时 setter 方法则被自动调用,setter 方法中不仅修改了属性值,而且还做了其他的事情,例如:重新渲染页面。setter 方法就像半路劫持一样,所以称为数据劫持。
<body>
<div>
<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
}
}
}
}
})
// 测试:后期给 Vue 实例动态的追加的一些属性,会添加响应式处理吗?
// 目前来看,通过这种方式后期给 vm 追加的属性并没有添加响应式处理。
//vm.$data.a.email = '[email protected]'
// 如果你想给后期追加的属性添加响应式处理的话,调用以下两个方法都可以:
// Vue.set() 、 vm.$set()
//Vue.set(目标对象,属性名,属性值)
//Vue.set(vm.$data.a, 'email', '[email protected]')
//Vue.set(vm.a, 'email', '[email protected]')
vm.$set(vm.a, 'email', '[email protected]')
// 避免在运行时向 Vue 实例或其根$data 添加响应式
// 不能直接给 vm / vm.$data 追加响应式属性。只能在声明时提前定义好。
//Vue.set(vm, 'x', '1')
//Vue.set(vm.$data, 'x', '1')
</script>
</body>
控制台修改,页面实时渲染。
<body>
<!-- 1. 通过数组的下标去修改数组中的元素,默认情况下是没有添加响应式处理的。怎么解决?
* 数组内每个对象中的属性是有响应式处理的
* 2. 第一种方案:vm.$set(数组对象,下标,值) Vue.set(数组对象,下标,值)
* 3. 第二种方案:push() pop() reverse() splice() shift() unshift() sort()
* 在 Vue 当中,通过以上的 7 个方法来给数组添加响应式处理。
-->
<div>
<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>
// ✅ 正确方法:使用 Vue 包装的数组方法
vm.users.push('newUser') // 添加元素
vm.users.pop() // 删除最后一个
vm.users.splice(0, 1) // 删除第一个
vm.users.splice(1, 0, 'inserted') // 在索引 1 处插入
// ✅ 使用 Vue.set
Vue.set(vm.users, 0, 'modified') // 修改索引 0 的值
// ❌ 错误方法(不会触发更新)
vm.users[0] = 'newName' // 直接索引赋值
vm.users.length = 2 // 修改长度
// ✅ 修改对象属性(响应式)
Vue.set(vm.vips[0], 'name', '张三') // 修改第一个 VIP 的名字
vm.vips[0].age = 30 // 添加新属性(需用 Vue.set)
// ✅ 添加新对象
vm.vips.push({ id: '333', name: '王五' })
// ✅ 删除对象
vm.vips.splice(1, 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() }))

<body>
<div>
<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() {
// 销毁 vm
this.$destroy()
},
/* m(){
console.log('m....')
} */
},
watch: {
counter() {
console.log('counter 被监视一次!')
}
},
/* 1.初始阶段
* el 有,template 也有,最终编译 template 模板语句。
* el 有,template 没有,最终编译 el 模板语句。
* el 没有的时候,需要手动调用 vm.$mount(el) 进行手动挂载,然后流程才能继续。
* 此时如果 template 有,最终编译 template 模板语句。
* el 没有的时候,需要手动调用 vm.$mount(el) 进行手动挂载,然后流程才能继续。
* 此时如果没有 template,最终编译 el 模板语句。
* 结论:流程要想继续:el 必须存在。
* el 和 template 同时存在,优先选择 template。如果没有 template,才会选择 el。
*/
beforeCreate() {
// 创建前
// 创建前指的是:数据代理和数据监测的创建前。
// 此时还无法访问 data 当中的数据。包括 methods 也是无法访问的。
console.log('beforeCreate', this.counter)
// 调用 methods 报错了,不存在。
//this.m()
},
created() {
// 创建后
// 创建后表示数据代理和数据监测创建完毕,可以访问 data 中的数据了。
console.log('created', this.counter)
// 可以访问 methods 了。
//this.m()
debugger // 加断点
},
// 2.挂载阶段
beforeMount() {
// 挂载前
console.log('beforeMount')
},
mounted() {
// 挂载后
console.log('mounted')
// 创建 vm.$el 并用其代替"el"
console.log(this.$el)
console.log(this.$el instanceof HTMLElement)
},
// 3.更新阶段
beforeUpdate() {
// 更新前
console.log('beforeUpdate')
},
updated() {
// 更新后
console.log('updated')
},
// 4.销毁阶段
beforeDestroy() {
// 销毁前
console.log('beforeDestroy')
console.log(this)
// 虽然仍然绑定监视器,但是不能使用 this.counter = 1000
},
destroyed() {
// 销毁后
console.log('destroyed')
console.log(this)
},
})
</script>
</body>
beforeCreate:实例刚被创建,数据观测和事件配置之前调用.
data、methods 和 computed 等属性beforeCreate() {
console.log('beforeCreate: 实例刚创建');
console.log('data: ', this.message); // undefined
}
created:实例创建完成,数据观测和计算属性等已配置.
created() {
console.log('created: 实例创建完成');
console.log('data: ', this.message); // 'Hello Vue!'
this.fetchData(); // 发起 API 请求
}
beforeMount:挂载开始之前调用,模板已编译但未渲染到页面.
beforeMount() {
console.log('beforeMount: 挂载之前');
console.log('$el: ', this.$el); // undefined
}
$el 属性尚未生成mounted:实例挂载到 DOM 后调用.
mounted() {
console.log('mounted: 挂载完成');
console.log('$el: ', this.$el); // DOM 元素
this.initChart(); // 初始化图表库
this.timer = setInterval(this.updateData, 1000);
}
beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前.
beforeUpdate() {
console.log('beforeUpdate: 数据更新前');
console.log('当前值:', this.count);
console.log('DOM 值:', this.$refs.counter.textContent); // 更新前的值
}
updated:数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用.
updated() {
console.log('updated: 数据更新完成');
console.log('DOM 值:', this.$refs.counter.textContent); // 更新后的值
}
beforeDestroy:实例销毁之前调用.
beforeDestroy() {
console.log('beforeDestroy: 销毁之前');
clearInterval(this.timer); // 清除定时器
this.chart.destroy(); // 销毁图表实例
window.removeEventListener('resize', this.handleResize);
}
destroyed:实例销毁后调用.
destroyed() {
console.log('destroyed: 销毁完成');
}

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online