Vue入门到精通:从零开始学Vue
目录
1. Vue2简介
// 数据驱动视图 // 双向数据绑定1.1 数据驱动视图
1.1.1 单向数据绑定
单向的数据绑定,当页面数据发生变化时,页面会自动重新渲染。

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

1.1.3 MVVM分层思想

// 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> 姓名:<input type="text" v-model="name"> </div> <!-- vue程序 --> <script> // ViewModel VM const vm = new Vue({ el: '#app', // Model M data: { name: 'zhangsan' } }) </script> </body>2. 第一个Vue程序
首先,使用 script 标签引入vue.js文件
<script src="../js/vue.js"></script><body> <!-- 指定VUE实例的挂载位置 --> <div></div> <script> const myVue = new Vue({ template: '<h1>Hello World!</h1>' }) myVue.$mount('#app') // Vue实例的挂载代码 </script> </body>当使用 script 引入 vue.js 之后,Vue 会被注册为一个全局变量。首先必须要new一个Vue实例。
2.1 Vue构造函数的参数:options
options翻译为多个选项,Vue框架要求options参数必须是一个纯粹的JS对象{}
在当前对象中编写大量的键值对key:value;每个键值对都是配置项。
2.2 Vue实例挂载
将Vue实例挂载到'id=app'的元素位置
1. Vue实例都有一个$mount()方法,这个方法的作用是什么?
将Vue实例挂载到指定位置,将 Vue 实例挂载到指定位置。也就是说将 Vue 编译后的 HTML
代码渲染到页面的指定位置。注意:指定位置的元素被替换。
2. #app显然是ID选择器
2.3 Template语句的数据来源data
<body> <div></div> <script> /* 模板语句的数据来源: 1.谁可以给模板语句提供数据支持呢?data选项。 2.data选项的类型是什么?0bject|Function(对象或者函数) 3.data配置项的专业叫法:Vue 实例的数据对象. (data实际上是给整个Vue实例提供数据来源的。) 4.如果data是对象的话,对象必须是纯粹的对象(含有零个或多个的 key/value 对) 5.data数据如何插入到模板语句当中? {{}} 这是Vue框架自己搞的一套语法,别的框架看不懂的, 浏览器也是不能够识别的。 vue框架自己是能够看懂的。 这种语法在vue框架中被称为;模板语法中的插值语法。(有的人把他叫做胡子语法。) 怎么用? {{data的key}} 插值语法: {不能有其他字符包括空格{}不能有其他字符包括空格} */ new Vue({ template: `<h1>{{ name }}{{releasename}}开始学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> <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: "明天不tm学了!" }, el: '#app' // el: document.getElementById('app') }) </script> </body>2.5 Vue实例和容器
Vue实例和容器的关系是:一夫一妻制。
<body> <!-- 准备容器 --> <div> <h1>{{msg}}</h1> </div> <div> <h1>{{msg}}</h1> // {{msg}} </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>3. Vue核心语法
3.1 插值{{ }}语法
<!-- 主要研究:{{这里可以写什么}} 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', // 为了方便沟通,以后我们把msg叫做变量。(这行代码就可以看做是变量的声明。) sayHello: function () { console.log('hello vue!'); } } }) </script><!-- 准备容器 --> <div> <!-- 在data中声明的 --> <!-- 这里就可以看做在使用msg变量。 --> <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> <!-- 没有加''是变量,所以取得是abcdef,之后字符串拼接 --> <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>3.2 Vue 指令
1. 什么是指令?作用是什么?
指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
2. Vue框架中的所有指令的名字都以“v-”开始。
3. Vue框架中所有的指令都是以HTML标签的 属性形式 存在的。
4. 指令的语法规则:
<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> <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>3.2.2.2 v-show指令
<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的优点:页面加载速度快,提高了页面的渲染效率。 -->3.2.3 v-bind属性绑定指令
可以让HTML标签的某个属性的值产生动态的效果。
<HTML v-bind:参数="表达式"></HTML> <!--简写方式--> <HTML :参数="表达式"></HTML>编译后:
<HTML 参数="表达式的执行结果"></HTML>第一:在编译的时候v-bind后面的“参数名”会被编译为HTML标签的“属性名”
第二:表达式会关联 data ,当data发生改变之后,表达式的执行结果就会发生变化。
所以,连带的就会产生动态效果。
什么时候使用插值语法?什么时候使用指令?
1. 凡是标签体当中的内容要想动态,需要使用插值语法。
2. 只要想让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>3.2.4 v-model指令
<!-- 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>3.2.4.1 v-model指令修饰符

3.2.5 事件绑定指令 v-on
<!-- 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>3.2.5.1 事件修饰符
<!-- 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> 3.2.5.2 按键修饰符
<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> 3.2.6 v-for列表渲染指令
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>3.2.7 虚拟DOM与diff算法 (:key属性)
<!-- 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>3.2.7.1 列表插入删除方法
// 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)3.2.8 v-text v-html指令
<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>3.2.9 v-cloak指令
<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>3.2.10 v-pre指令
<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>3.2.11 自定义指令
在每个 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) } })4. 过滤器
过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。
过滤器可以用在两个地方:插值表达式和 v-bind 属性绑定,过滤器应该被添加在 JavaScript 表达式的尾部。
<!-- 需求: 从服务器端返回了一个商品的价格price,这个price的值可能是这几种情况: ''、null、undefined、60.5 要求: 如果是''、null、undefined ,页面上统一显示为 - 如果不是 ''、null、undefined,则页面上显示真实的数字即可。 在Vue3当中,已经将过滤器语法废弃了。 -->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){ // 确保传递过来的数据val,保留两位小数。 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> <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>5. 计算属性computed
虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性;
使用Vue的原有属性,经过一系列的运算/计算,最终得到了一个全新的属性,叫做计算属性。
Vue的原有属性: data对象当中的属性可以叫做Vue的原有属性。
全新的属性: 表示生成了一个新的属性,和data中的属性无关了,新的属性也有自己的属性名和属性值。
5.1 反转字符串methods实现
<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>5.2 反转字符串计算属性实现
<!--语法格式:需要一个新的配置项 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>6. 监视属性
1. 监视属性:监视哪个属性,就把属性放入watch中即可。
2. 可以监视Vue的原有属性。
<!-- watch: { 1. 打开页面初始化时,会调用一次handler方法。 2. handler方法的调用时间: 当被监视的属性发生变化的时候,handler就会自动调用一次。 3. handler方法上有两个参数:第一个参数newValue,第二个参数是oldValue。 newValue是属性值改变之后的新值,oldValue是属性值改变之前的旧值。 } -->6.1 监视属性的变化
<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>6.2 后期添加监视
// 如何后期添加监视?调用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) })6.3 immediate 选项
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。
如果想让 watch 侦听器立即被调用,则需要使用 immediate 选项。
immediate : true 页面首次加载完毕就触发handler:0, undefined
6.4 deep选项
如果 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。
此时需要使用 deep 选项。
7. 数据代理
7.1 VM(View Model)
<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>7.2 Object.defineProperty()方法
给对象新增属性,或者设置对象原有的属性
<!-- 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>7.3 数据代理机制
1. 什么是数据代理机制?
通过访问 代理对象的属性 来间接访问 目标对象的属性。
数据代理机制的实现需要依靠:Object.defineProperty()方法。
2. ES6新特性:
在对象中的函数/方法 :function 是可以省略的。
<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>7.3.1 Vue数据代理机制对属性名的要求
<!-- 1. Vue实例不会给以_和$开始的属性名做数据代理。 2. 为什么? 如果允许给_或$开始的属性名做数据代理的话。 vm这个Vue实例上可能会出现_xxx或$xxx属性, 而这个属性名可能会和Vue框架自身的属性名冲突。 3. 在Vue当中,给data对象的属性名命名的时候,不能以_或$开始。 -->7.3.2 手写Vue框架数据代理的实现
// 定义一个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>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选项是否是函数类型
<!--Vue 允许两种形式的 data: 对象形式:data: { message: 'Hello' } 函数形式:data() { return { message: 'Hello' } } -->如果data是函数,则调用getData(data, vm)来获取真正的数据对象data。
如果data不是函数,则直接使用data。如果是undefined或null,使用空对象{}作为默认值。
双重赋值:将处理后的数据对象同时赋值给:局部变量data和 Vue 实例的_data属性。
8.1 为什么要给vm扩展_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框架对外公开的一个属性,是给我们程序员使用。 -->8.2 重点函数
<!-- 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>8.3 data(函数形式)
<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>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> <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>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> <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> <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> <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> <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.type === 1) { arr.sort((a, b) => { return a.power - b.power }) } else if (this.type === 2) { arr.sort((a, b) => { return b.power - a.power }) } // 返回 return arr } } }) // 回顾sort方法 let arr = [8, 9, 5, 4, 1, 2, 3] // sort方法排序之后,不会生成一个新的数组,是在原数组的基础之上进行排序,会影响原数组的结构。 arr.sort((a, b) => { return b - a }) console.log(arr) </script> </body>12. 表单数据的收集
<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>13. 响应式与数据劫持
修改 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>13.1 数组的响应式处理
控制台修改,页面实时渲染。
<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>13.1.1 响应式处理代码
// ✅ 正确方法:使用 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() }))14. Vue的生命周期

<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>14.1 创建阶段
beforeCreate:实例刚被创建,数据观测和事件配置之前调用.
- 使用场景:插件初始化、全局事件总线设置
- 特点:无法访问
data、methods和computed等属性
beforeCreate() { console.log('beforeCreate: 实例刚创建'); console.log('data: ', this.message); // undefined }created:实例创建完成,数据观测和计算属性等已配置.
- 使用场景:API请求、初始化数据、访问响应式数据
- 特点:可以访问数据,但DOM尚未生成
created() { console.log('created: 实例创建完成'); console.log('data: ', this.message); // 'Hello Vue!' this.fetchData(); // 发起API请求 }14.2 挂载阶段
beforeMount:挂载开始之前调用,模板已编译但未渲染到页面.
beforeMount() { console.log('beforeMount: 挂载之前'); console.log('$el: ', this.$el); // undefined }- 使用场景:操作DOM前的准备工作
- 特点:
$el属性尚未生成
mounted:实例挂载到DOM后调用.
- 使用场景:操作DOM、集成第三方库、启动定时器
- 特点:可以访问渲染后的DOM元素
mounted() { console.log('mounted: 挂载完成'); console.log('$el: ', this.$el); // DOM元素 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:实例销毁之前调用.
- 使用场景:清理工作(清除定时器、取消事件监听、销毁第三方库实例)
- 特点:实例仍然完全可用
beforeDestroy() { console.log('beforeDestroy: 销毁之前'); clearInterval(this.timer); // 清除定时器 this.chart.destroy(); // 销毁图表实例 window.removeEventListener('resize', this.handleResize); }destroyed:实例销毁后调用.
- 使用场景:最后的清理工作
- 特点:所有绑定已解除,事件监听器已移除
destroyed() { console.log('destroyed: 销毁完成'); }