Vue.js是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
简单应用:只需要引入一个轻量小巧的核心库即可实现功能
复杂应用:可以引入各式各样的Vue插件来实现不同的功能
采用组件化模式,提高了代码的复用率且代码更好维护,在Vue中新添加了一个后缀名.vue。在该文件中包含了HTML,JS,CSS的功能代码。每一个vue文件之间代码都是相互隔离的互不相关。所以代码的维护性更好。
声明式代码,让编程人员无需直接操作DOM,提高开发效率。
命令式代码和声明式代码
使用了虚拟DOM+优秀的Diff算法,尽量复用DOM节点
vue提供了两种不同的安装方法
我们选择引入script标签的方法安装vue,其中开发版本用于学习的时候使用,在项目上线的使用生产版本,可以删除警告的功能代码,减小体积。
但是直接引入vue文件打开浏览器会有提示信息。第一个是提示安装对应的工具即可,第二个需要设置一个vue的全局变量,将生成提示信息取消掉。
先在vue的官方文档中找到链接进入选择安装
其次根据官方文档找到全局配置下config对象下的productionTip属性,该属性的功能是生成提示。默认为true,在我们的vue文代码 中只需修改为false就不会一直浏览器提示了。
先直接在HTML代码中写一个标签显示内容,但是这么写的会出现一个问题。就是一个favicon图标的资源获取失败的报错提示。
需要注意的是,这个图标报错提示需要强制刷新浏览器才会出现,普通的刷新可能不会出现。
http://127.0.0.1:5500/favicon.ico这个路径是由打开网页使用的插件引起的,打开的一瞬间创建一个服务器。请求的所有资源都是在这个文件夹中存放。由于我们没有favicon的图标,所以就会报错。所以我们可以简单的引入一个favicon的图标即可。
首先想使用Vue的语法,需要先new 一个Vue构造函数,其次需要往构造函数中传入配置对象信息。
在HTML的容器中依旧符合HTML的语法规范,只不过有添加了一些Vue的语法。
HTML容器中代码被称为Vue模板。
所谓Vue模板就是先写好HTML代码和Vue的配置信息,当代码执行的时候Vue构造函数中的配置信息el读取到页面中的容器,就知道这个容器可能是Vue需要操作的部分。然后会根据Vue的语法{{}}去查看这个容器中是否使用了Vue的规范语法,如果有就替换数据。并生成一个全新的容器(box)去替换掉原有的旧容器,并渲染到页面中。
el:element元素的缩写,通常用于书写css选择器来操控html容器,可以直接按照图片中的格式书写,也可以使用document.querySelector('')的格式去获取选择器,但是这种写法过去臃肿。
data:可以传入多种数据格式,目前传入对象,方便操作修改多个数据。在data中存放修改的数据信息变量名。
在Vue实例化配置接管的容器中,如果Vue语法出现在容器中则可以正常使用。如果出现在容器外外面,则容器会按照正常的HTML解析。
Vue实例和容器是一 一对应的关系,不存在一对多或者多对的的关系。
如下,Vue解析获取容器的时候虽然两个容器都能够被获取到,但是执行的时候只会对第一个被解析的容器进行操作。
当只有一个容器的时候,而Vue实例却有两个,那么Vue的第一个实例才会操作容器,而第二个Vue实例无法对容器执行操作。之所以第二个图片会报错是因为第一个Vue实例才是操作容器的主体,而该实例中无对address的定义声明,所以在容器中的Vue模板解析中找不到会报错。
因此想要对两个容器进行操作,需要设置两个不同的容器名称并设置两个不同的Vue实例进行操作。
在容器中的模板里,可以使用JS的表达式去写,并且{{x}}中的x会自动读取到Vue实例中的data中的属性。
可以很简单的在Vue模板中去调用JS的相关方法去实现相应的步骤。如下图将小写字母转换为大写字母
以下是Vue在浏览器中打开Vue的工具,可以修改data中的值,同时会反射到页面(V模板)中用到data中属性的地方并自动更新。
插值语法
这种以{{}}包住的Vue语法就是插值语法。
指令语法
以v-bind:或者:开头的就是指令语法。以下是指令语法的使用即注意事项
注意第一个图片的这种是想以插值语法的写法来替换href的链接地址,但是发现无法实现地址跳转,插值语法被当做一个url地址解析了。
在href属性前添加v-bind:并且在href的属性使用url,即当Vue执行到这里的时候语句v-bind:,就会知道后面的属性值需要当做一个js表达式的变量去处理,于是去找到data中的url属性,并将值绑定到href中。
v-bind:可以简写为一个:,需要知道的是并不是所以的v-开头的vue写法都能简写。
插值语法
主要用于解析标签体内容
指令语法
用于解析标签(包括解析标签的属性,标签的内容,绑定事件等等都能够实现)
在v-bind:或:中可以写JS表达式,如Date.now()这种。
有的时候会采用这种多级对象结构来设置data的值。
代码如下,使用v-bind:来绑定数据的方式为单相绑定
何为单向绑定?简单来说就是v-bind只能实现修改vue中的data反应到页面中,无法实现页面中的数据修改反应到vue中
单向数据绑定:
使用v-model:value实现双向绑定
什么是双向绑定?即vue中的data数据改变可以影响到页面中的数据,页面中的数据修改也能影响到vue中的数据。
单向数据绑定:
双向数据绑定:
单向绑定:v-bind:或: 只能实现vue的data数据影响页面
双向绑定:v-model:value 数据能够双向影响
双向绑定一般只能绑定在表单元素上,即可以获取到value属性。
v-model:value可以简写为v-model,这是因为该指定语法默认收集的就是value的值。
第一种也就是在vue的配置对象中使用el来获取css选择器实现与容器之间连接交互数据
第二种,使用new出来的实例对象来配置, v.$mount('')。首先在new出来的实例对象上,带$的都是vue提供给程序员可以使用的属性或者方法。而$mount这个方法在构造函数的原型身上。
使用v.$mound()的 好处是在vue和容器之间绑定关系的时间可以选择,可以将v.$mound()放入一个定时器中,等待具体时间后去修改容器中的值。
可以直接写成对象式
可以直接写成函数式,如下,必须在函数中返回一个return对象。在使用组件的时候,必须使用到函数式的写法
this的指向了Vue构造函数,不能使用箭头函数,箭头函数无this指向
M 模型Model:对应代码中的data数据
V 视图View:Vue模板
VM 视图模型ViewModel:Vue实例对象
data中的数据在经过一些列处理之后,会绑定在Vue实例对象身上。
总结
data中的所有属性都会出现在vm实例对象身上。
vm身上的所有属性还有Vue原型身上的所有属性,Vue模板都可以使用
在一个对象身上新添加一个属性,可以直接在对象上设置,对应的打印如下
也可以使用ES6新语法给一个对象添加一个新的属性
需要注意的是,两种方法添加一个属性在控制台显示的颜色是不同的。使用defineProperty方法设置的属性是默认是不可遍历的。
使用defineProperty的时候需要传入三个参数,设置的对象,哪个属性,配置项,在配置项中可以设置相关的参数
除此之外,配置对象中还可以传入一个get函数和一个set函数。
数据代理:委托一个对象对另一个对象进行操作
在vue2中使用Object.defineProperty()方法进行数据代理
信息:{{desc}}
new出来的实例对象vm身上进行了一个简单的数据处理,将原先配置对象中的data对象中的desc进行了简单的处理,直接给到了vm身上可以直接使用
在控制台对vm.desc进行重新赋值的时候会发现页面中的数据也会跟着改变。这是因为调用了set函数。
在每一个vm实例对象身上都有一个get和set函数对数据进行处理
当调用vm.desc的时候实际上去调用了get函数返回了data.desc的数据
当进行vm.desc='新数据'的时候,实际上调用了set函数data.desc=vm.desc,进行了一次赋值操作.
这时候先引入vm身上的_data属性。该属性就是配置项中的data属性的值。在new的时候传入的配置项不可能是无用的,所以肯定进行了处理挂载到了vm身上。下面会跟着写端代码验证相等。
现在我们知道了vm._data中的数据就是data属性中的数据可以在控制台打印显示。
可以对_data和data之间进行验证相等
即修改vm.desc的时候调用了set函数去修改了data中的数据,只要vue中data数据一修改则页面中的对应的数据部分也会修改。
修改vm._data.desc的值会调用set函数修改data.desc的值,又因为在vue中修改任何data中的数据都会影响到页面中使用到该数据的地方。这就是一个数据代理
基于Object.defineProperty()方法实现数据代理,只不过在vm中又对_data中的数据进行了处理
如果vue不进行数据代理,只处理到_data将data中的数据取出就完事。那么在Vue模板中完全可以使用_data.desc来进行操作,因为vue中实例对象身上的所有属性和方法,vue模板中都可以直接使用。但是过于麻烦,于是vue将_data中的数据又一次进行处理即数据代理操作,将_data中的数据又一次的取出,于是可以直接在vue模板中直接写desc。这么做的好处就是更加方便程序员去编码。
Vue中的数据代理
通过vm实例对象代理配置项中data对象的属性操作(即修改vm中的某些数据会影响到data中的数据)
Vue中的数据代理好处
方便了操作data中的数据
基本原理
基于Object.defineProperty()中的get和set函数实现,将data对象的所有值添加到了vm对象身上
每一个添加到vm对象身上的属性都会添加一个set和get方法
在赋值和调用vm的属性的时候都会影响到data中的数据
在vue中,给一个元素绑定一个事件采用指令语法v-on:click=回调函数名。同时在Vue配置项中建立methods属性来存储回调函数。
在回调函数中的this指向了vm实例对象
同时v-on:click可以直接简写为@click
在默认情况下,绑定事件调用的回调函数默认自动传递一个参数即事件对象event。
@click="demo"等价于@click="demo($event)",这里的$event会被Vue模板解析为关键字传递。
传递普通参数:直接在回调函数后面紧跟小括号调用。但是这种传参会丢失默认传递的事件对象。因此vue为我们提供了$event当做参数传递。
同时在methods中的参数不会提供数据代理的操作,但是一旦将这些参数写在data中,vue就会提供数据代理。显然这是不对的,因为这些函数只是用来调用的,而非用在数据响应上。
总结
使用v-on:事件或@事件 来绑定事件
事件回调需要配置在methods对象中,最终会在vm上,不会对methods中的一切回调函数进行数据代理(data中的一数据切都会进行数据代理)
methods中的回调函数不要使用箭头函数,会修改this的指向
methods中的回调函数,其this指向了vm实例对象或者组件实例对象
事件的回调函数默认传递事件对象作为参数,即@click="demo"===@click="demo($event)",可以使用后者传递想要的参数,需要注意可能会丢失事件对象,所以尽量将$event添加到参数中一起传递。
阻止一个标签的默认行为在vue中可以借助于传统的JS事件对象的e.preventDefault()。但是在vue中为我们提供了事件的修饰符去实现。
@click.prevent=回调函数,即为点击事件添加一个修饰符,修饰符的作用是阻止标签的默认行为
当出现如下结构的时候,点击事件是同一个,当点击里面的盒子的时候,会先触发内部的点击事件,其次再次触发外部的点击事件。这种时候可以使用JS事件对象的处理方式:e.stopPropagation()阻止冒泡行为。
当前在vue中,我们可以直接使用事件修饰符stop来阻止冒泡行为,注意需要添加到内部的事件身上
once修饰符的主要作用就是让事件只触发一次
提示一下:{{name}}
capture修饰符的主要作用就是使用事件的捕获行为
在默认的情况下由外向里进行捕获,但是不触发事件,在冒泡阶段由里向外依次触发事件,使用了capture修饰符就可以在捕获阶段就触发事件。注意写在外部的事件对象身上
点击内部的事件,先从外开始捕获触发事件
只有当event.target是当前操作的元素的时候才会触发事件。作用和阻止冒泡差不多。
如下代码中,为外层的点击事件添加了@click.self="info(1)",即只有当e.target为当前元素才会触发,那么为什么点击内部的时候,e.target不应该冒泡到外层的元素吗?事件对象为什么不是外层元素,可以控制台打印冒泡的时候的e.target
这个时候就清除了点击内部对象的时候,e.target操作的对象在冒泡阶段始终都是内部元素,而非外部元素,当为外部元素添加了self修饰符的时候会因为e.target不是自身被操作而不去执行回调函数
事件的默认行为会立即执行,无需等待回调函数执行完毕
这里以wheel鼠标滚轮事件和scroll滚动条事件为例来说明。
scroll滚动条事件,一旦滚动条滚动到底部了就不会再次触发事件
whell鼠标滚动事件:哪怕滚动条滚动到底部,事件依旧会触发
这个时候为wheel事件写一个同步的for循环,会发现滚动了滚轮但是没有立即触发事件,等待回调函数中的任务做完了才会执行默认移动滚动条事件
添加passive修饰符后,移动滚轮优先去执行滚动条事件的移动,类似于异步操作
多个事件修饰符之间可以连用,如a连接需要阻止冒泡和默认行为
回车 enter
删除 delete
退出 esc
空格 space
换行 tab(会切换光标,建议配合keydown使用)
上 up
下down
左 left
右rught
举例:给定一个输入框,只有当输入完按回车的时候才将输入框的内容显示。
可以使用JS基础的知识通过判断键盘码来区分按回车
{{name}} :
但是在Vue中为我们提供了常用的键盘别名去操作
注意:vue未提供别名的按键,可以使用JS中的e.key 去获取键盘名然后在去绑定,但是需要注意转换(短横线小写命名)
在这个事件后面的键盘别名都为小写,除非是vue默认为我们设置的,如enter可以写为Enter,如切换大小写的CapsLock,像这种vue就没有帮助我们设置,所以我们不能直接在键盘事件后面直接写,否则会没效果
正确写法是将两个单词之间的大写字母都转换为小写,并且在两个单词之间添加一个-
系统修饰键(具有特殊用法)
ctrl
alt
shift
win键
可以配合keyup使用,但是按下该键的同时,需要再次按其他键,并再次释放,才能触发事件。如先按ctrl+b,随后释放b,才会触发事件
配合keydown使用,正常触发事件
如果指定只有ctrl+y才能触发事件可以按如下代码写法
可以使用KeyCode去绑定键盘码,但是这种做法不推荐,因为不同的键盘的编码可能不同于,尽量使用Key去绑定键盘名
可以自定义一个键盘别名:Vue.config.KeyCodes.名字=键码
如下代码先使用插值语法去实现,为了实现输入框输入会影响vue中的data,所以采用了v-model。
姓:
名:
姓名:{{firstName}} - {{secondName}}
但是如果需要对最终结果进行处理,可以在{{}}中进行JS代码处理,但是这样子会显得整个代码很长,不推荐,于是这里采用了在插值语法中写一个函数并返回处理的结果
如下代码,需要注意的是在插值语法值使用函数的时候需要添加小括号去调用,而指令语法中的函数小括号可写可不写
在vue中,每当data中的数据发送改变,那么vue模板就会重新解析一次,于是就会重新去调用函数去获取最新的data数据返回显示
计算属性:在{{}}插值语法中,凡是设计到复杂的逻辑,都可以使用计算属性实现
区分属性和计算属性
属性
vue中,在data中的为属性,firstName为属性名,后面跟着属性值。
计算属性
在Vue配置对象中添加computed,该属性就为计算属性,如果不写get函数则控制台会报错。需要注意,在这里底层也是基于Object.defineProperty()。
如下代码,在插值语法中写showName,该属性就是计算属性,调用的时候会默认去执行get函数并返回值。
需要注意的是,showName不存在与vm._data对象中。而是直接出现在vm实例对象身上,所以在插值语法中可以直接写showName
在计算属性computed中,会计算get函数的返回值并绑定在vm实例对象的某一个属性身上,而这个属性就是showName,而不是showName对象,所以可vm.showName直接调用
在computed属性中showName对象中已经将this的指向修改了指向了vm实例对象(受vue管理的函数,this基本上都指向了vm实例对象)
所以可以使用vm身上的属性
在计算属性中利用到了缓存,将vue模板中第一次用到showName的时候会将结果缓存一下,如果下面继续使用到就会从缓存中取。但是这会出现一个问题,如果姓和名改变了那么缓存中的数据如何改变。
这就需要理解get什么时候才会被调用。
初次读取showName的时候
所依赖的数据发送改变的时候,即data中的数据改变
输入框的数据改变影响到了data中的数据,而showName中的get中的this.firstName依赖于data数据,一旦改变就会重新计算并将结果存入缓存。
在methods方法中,无缓存一说,每次调用就是重新去执行一次函数
如果想修改showName的值则必须使用set函数去修改,如果不写set函数而直接区修改showName的值则会报错。
为了能够区分姓和名,所以添加-方便字符串截取操作。在赋值的时候,会调用set函数,而set函数将data中的firstName数据修改了,而data中的数据修改了会影响到页面中的相关数据修改。同时最重要的一点是,get函数依赖的data数据被修改了,则get函数会自动去重新执行一次计算新的值存入缓存。
属性必须是受Vue管理的
如下代码a不收Vue管理,即a变量数据改变的时候,Vue无法检测出值的改变不能作出响应
底层依旧是Object.defineProperty方法中的get和set函数实现
get函数的执行时机
初次被执行的时候会执行一次将结果存入缓存
依赖的数据发送改变的时候会再次执行一次
与methods方法相比,提供了缓存机制效率高,调试方便
只有当这个程序只会用到get函数而不会用到set函数到时候,才能写计算属性的简写形式
如图简写一个普通的函数,但是这个函数等价于get函数
还可以继续简写为如下
可以将点击事件再次简写,因为isHot在vm实例对象身上,所以点击的时候会往实例对象身上找到并修改,同时会影响get再次调用
需要注意的是在指令语法中,后面尽量写一个简单的表达式,不要写大量复杂的运算表达式,会显得语句很长。
在watch配置项中需要设置监听的对象即isHot并为其设置配置对象(这里的isHot等价于'isHot'),在该配置对象中有一个handler函数,可以接收值修改前和修改后的值。并且在值修改的时候会修改。
在该配置项中还可以设置一个immediate属性,默认为false,该属性的作用是是否初始化的时候立即调用handler函数
同时在watch中监听的对象不仅可也是data中的属性,也可以是methods等中的函数
另一种方式去设置监视属性
通过实例对象去实现监听属性:vm.$watch(监听的属性,配置项)
如下才是正确写法
需要注意的是,使用watch配置项去监视一个不存咋的属性是不对的,控制台并不会报错提示
当要监视的对象是这种多级结构如何监视
在watch中配置numbers下的a属性,于是按如下写法,可是会报错,numbers.a不是一个合格的键名。所以需要恢复成原始写法添加引号。平常不常写,需要重点记忆
正确写法
所以目前监听numbers中的属性变回可以如下写。但是这种写法有缺陷,一旦numbers中的属性多起来需要全部监听就很麻烦,有没有一种方式能够一次性监听全部?
在watch配置对象中添加一个deep属性,它接收一个布尔值,默认false(即不写)不进行深度遍历。
默认不开启deep属性是因为考虑到效率问题。
只有当监测的对象不需要配置immediate或者deep等其他属性的时候,单单只需要一个handler函数的时候才可也使用简写模式。(多级结构需要监视的时候不能简写)
第一种简写:当watch配置在Vue配置项中的时候,整个isHot函数作用相当于handler函数
第二种,当watch配置在vm实例对象身上的简写方式,在第二个参数中不需要配置{},直接简化为一个函数,该函数即为handler函数
深度监视
vue中的watch默认不监测多级对象内部值的改变
对watch中的监测对象设置deep:true即可开启多级监测
在watch中的this也是指向vue实例的
注意:vue自身可以监测到多级对象内部值的改变,并作出数据代理,响应页面。但是watch默认是不可以
下面同个案例的两种写法发现:都可以实现只不过是代码的多少
姓:
名:
姓名:{{showName}}
姓:
名:
姓名:{{fullName}}
为两个程序添加一个异步程序进行对比
在watch中,添加一个定时器实现异步。由于函数中修改的是data中的数据,并返回到页面中。
watch: {
// 监视某个属性,不需要进行其他设置采用简写
firstName(val) {
// 姓改变的时候,名不变
setTimeout(() => {
this.fullName = val + '-' + this.secondName
}, 1000);
},
secondName(val) {
// 名改变的时候,姓不变
setTimeout(() => {
this.fullName = this.firstName + '-' + val
}, 1000);
}
}
在computed中添加一个定时器,会发现页面中的数据无法改变。定时器的中的箭头函数返回值非外围showName的值,并且定时器只会返回编号数字,由于get函数没有返回值,showName的值为underfined
注意this指向
在watch中对定时器的回调函数写成箭头函数,方便内部去使用this。因为定时器不是受vue管理的函数,其this指向了window对象,而使用了function函数的话内部的this指向了window就错了,所以在这里写成了箭头函数,this指向了watch的this,而watch的this指向的是vm实例对象
computed能实现的事情,watch都可以完成
一旦涉及到异步,则computed不一定可以实现,而watch可以实现
所有被vue管理的函数,最好写成普通函数,不修改this的指向。所有不被vue管理的函数。如定时器,ajax最好写成箭头函数,这样子this指向外围
在vue中给一个HTML标签绑定一个css样式可以通过动态指定class样式来绑定
这种方式适用于动态绑定样式,确定只需要一个样式但是样式名不确定,属于字符串写法。当vue解析到指令语法等待时候就会介入并按照JS表达式的使用方式去处理。最终会将box和mood的值拼成一个样式属性
如果需要绑定的css样式个数不确定,且名字也不确定,可以使用数组的方式存储样式
同时也可以将数组直接丢入:class后,但是这种形式就脱离了vue的管控不推荐使用
同时这种形式也需要注意数组后的值一定需要添加引号,否则vue解析到这里会误以为是变量名,转为从data中去寻找
也可以在绑定样式的时候写一个对象,对象中将需要的样式通过布尔值确定是否需要
通过对象绑定样式适用于样式个数确定,样式名字也确定,就是不确定需不需要使用
在vue中绑定style样式的时候,其中的属性和值类似对象的键值对。所以可以修改为对象格式
但是如果直接写为如下的指令语句写法,vue则无法正确识别对象中的JS表达式。需要将font-size该为小驼峰fontSize的格式
需要记住的是像这种css的样式名转换为js的小驼峰的名字不能自定义
将对象从指令语法中简化到data中,这就是style指令语法后面第一种可以跟一个对象的形式
第二种可以在style后面写一个数组的格式,在数组中存放对象样式
v-show指令语法底层是基于display实现的,并不会真正的删除DOM节点,在v-show后面虚跟一个能转换为布尔值的JS表达式
使用v-if语句后面也需要跟着一个能转换为布尔值的JS表达式。但是v-if和v-show的区别是,v-if完全删除页面中的DOM节点。
需要注意的是如果有多个v-if,则vue会解析每一个语句,效率低
v-else指令语法后面通常不跟JS表达式。和if-else一致,判断条件写在if后面,在这里一样遵守
vue会根据if-else进行判断,条件符合则vue就会解析到对应的位置,则不会往下解析
v-else-if后面需要跟着JS表达式进行判断。
如果vue判断条件到上海就结束,则后面的南京广州等语句就不会被vue解析判断,提高了效率
需要注意的是在v-if,v-else,v-else-if之间不能出现其他语句,会打断if-else的格式,并且控制台会给出报错
扩展一个template
给定三个h1标签,需要为1的时候一起出现,这里选择添加一个div进v-if判断,比每一个h1语句添加一个v-if进行判断代码看着舒服。但是这需要额外添加一个节点
为了可以使用template模板标签该标签在解析的时候不会生成到页面中,template并且只能搭配v-if使用
在使用v-if的时候如果删除一个节点,那么这个节点想要再次获取到可能无法实现,因为完全从DOM树中删除了。
v-for指令语法,后面写的东西类似js中的for in循环。
v-for遍历数组
但是这里需要注意的是后面的p是对应的单个数据,而nameArr是对应的data中的数据源。
但是这么写不规范,vue建议我们写的时候绑定一个动态的key区分每一组属性,注意用在虚拟DOM的对比上,在真实页面中不会生成key属性在标签中。
同时可以在in之前绑定两个参数,p依旧对应单个数据源,而index依旧对应下标,从0开始,而key就可以动态绑定编号,但是需要注意的是:key不能写成key,否则就写死了
对应的in也可以写成of
v-for也可以遍历对象
当遍历的数据源为对象的时候,第一个参数为属性值,第二个参数为属性名
v-for也可以遍历字符串
当遍历的是字符串时候,第一个参数接收字符串的每一个字符,第二个参数接收对应字符的下标索引
v-for也可以遍历次数
第一个参数接收从1开始计数的值,第二个参数接收对应的位置
:key是为每一个标签绑定一个标识,方便vue内部去使用这些带有key的标签,但是这些标签vue处理完毕之后不会出现在最终页面的标签上。所以我们尽量不去使用带key关键字。
:key之后的取的唯一标识符有多种写法,可也写index也可以写data中给每一个数据存储的唯一表示符。但是多余不同的取值,key最终可能会出现不同的效果。
采用index作为key的唯一标识,目前代码如下,且显示正常,对于新数据采用头插法
往输入框中插入内容后再次插入新数据查看效果,会发现本该对其的数据却串位了,这就是问题的出现
但是如果将key的标识符换成data中的id则没有该问题
key在vue中的作用解析
当采用index作为key值的时候解析
在不添加数据之前,这些数据在页面中是正常的,对于输入框中输入的数据是在真实DOM中,所以这些输入的内容不会出现在虚拟DOM中,新添加一个数据后,会生成一个新的虚拟DOM,这时候vue会将旧的虚拟dom和新的虚拟dom进行对比。先从新的dom中将唯一表示key取出,去旧的虚拟dom中寻找一样的,当找到后会往后进行比对其文本节点和input节点,对比文本节点的时候发现文本的内容不一样,所以vue就将新的文本数据渲染到页面中,在往后对比input节点,发现两个节点一模一样,于是vue就复用旧dom中的input节点,并将其输入框和节点一起渲染到新页面中。
当vue执行到key为3的时候,对比旧的虚拟dom,发现没有key为3的旧虚拟dom,于是vue直接将新的虚拟dom直接生成到页面中。
这种index头插法打断了原本的顺序所以才会报错
当data中的id作为key的值的时候解析。key为004找不到所以直接渲染新数据,后面三个虚拟dom能在旧的虚拟dom中找到,比起对比后发现相等,所以直接复用旧的虚拟DOM。提高了效率。
vue中绑定key的作用(原理)
虚拟DOM中key的作用
key是虚拟DOM的标识,当数据发生变化的时候,vue会根据生成新key找到对应的旧key,并将新数据并生成新的虚拟dom,并和旧的虚拟dom进行比较
对比规则
新虚拟dom找到旧虚拟dom中的key
若内容没有变化,vue直接复用旧的虚拟dom,即之前的真实数据
若内容发生改变,vue会生成新的DOM,并替换之前的数据
新虚拟dom没有找到和旧虚拟dom一样的key,则直接创建新的数据渲染到页面中
用index作为key可能会出现的问题
对数据进行逆序添加删除会破坏原有的顺序,会产生没有必要的DOM更新,降低了效率,页面效果没问题
如果有输入文字之内的DOM,则页面中效果会出现问题,产生错误的dom更新
如何使用key
最好使用唯一标识符来确定key,就像身份证号一样
过滤数据代码初步如下,将过滤完成的数据放入到data数据中,但是这种方式会不断的修改原始数据,让数据越来越少,如果重新查询其他数据就查不到了,所以需要用一个中间变量存储并显示到页面中。
在一个字符串中包含空字符串,位于字符串的起始位置
模糊查询--姓名案例
利用computed计算属性处理该案例,在get函数中过滤属性并返回一个数组对象给vm实例对象身上的filPerson属性上,所以在vue模板中可以直接使用。
new Vue({
el: '.box',
data: {
title: '列表渲染',
person: [
{ id: '01', name: '马冬梅', age: 12, sex: '女' },
{ id: '02', name: '周冬雨', age: 21, sex: '女' },
{ id: '03', name: '周杰伦', age: 32, sex: '男' },
{ id: '04', name: '文绍周', age: 42, sex: '男' },
],
keyWord: ''//一开始没有输入内容为空
},
computed: {
// 利用get函数调用返回
filPerson() {
return this.person.filter(item => item.name.includes(this.keyWord))
}
}
})
在该案例的基础上添加排序按钮的功能
扩展数组中的sort方法排序,sort排序接收一个回调函数回调函数中接收两个参数,并且回调函数的返回值影响到原数组。
口诀:升序前减后,降序后减前
{{title}}
-
{{p.name}}-{{p.age}}-{{p.sex}}
计算属性中依赖的keyWord和sortType改变的时候就会调用get函数。
new Vue({
el: '.box',
data: {
title: '列表渲染',
person: [
{ id: '01', name: '马冬梅', age: 52, sex: '女' },
{ id: '02', name: '周冬雨', age: 21, sex: '女' },
{ id: '03', name: '周杰伦', age: 14, sex: '男' },
{ id: '04', name: '文绍周', age: 53, sex: '男' },
],
keyWord: '',//一开始没有输入内容为空
sortType: 0 //默认数据不排序为0,1升序,2降序
},
computed: {
// 利用get函数调用返回
filPerson() {
// 先过滤 ,提升效率
let arr = this.person.filter(item => item.name.includes(this.keyWord))
// 处理排序,只要计算属性中依赖的值改变,就会调用一次
if (this.sortType) {
// 非0则处理
arr.sort((x, y) => {
// 根据按钮的值返回
// x,y均为每行对象,需要对其中的年龄进行比较
return this.sortType === 1 ? x.age - y.age : y.age - x.age
})
}
return arr //将get函数返回值映射到filPerson上
}
}
})
注意第一段代码的update方法更新的方式
{{title}}
-
{{p.name}}-{{p.age}}-{{p.sex}}
一定要先打开vue开发者工具,发现vue中data的数据可以更改并影响到页面中
在使用如下方式修改内容,就会出现问题
methods: {
update() {
上 // this.person[0].name = '马老师'
// this.person[0].age = 33
// this.person[0].sex = '男'
this.personp[0] = { id: '01', name: '马老师', age: 55, sex: '男' }
}
},
那么到底数据有没有成功更新,vue有没有监视到数据修改?
依旧需要先打开vue开发者工具,再次点击更新按钮。主要用于先开启vue监视数据更新,否则先点击按钮后,数据已更新,在打开vue开发者工具就能检测到。只不过不会影响到页面
我们借助vm实例对象可以查看到数据起始已经修改了,只是vue没有监视到
可以通过vm实例对象检测vue帮我们对data数据进行了处理才转换为最终的_data,如果不进行中间处理vue完全可以将data中的属性直接搬到_data上即可。添加这一步的主要作用就是为了实现响应式。
通常查看vue实例对象的_data属性可以发现里面有一个observer函数,接下来写代码模拟
我们的这种vue模拟数据代理和vue的数据代理有很大的区别,在vue中能为复杂的data数据,为其中的每一个属性绑定get和set函数
vue底层做了个递归操作遍历绑定get和set函数
为什么需要需要该方法,是因为vue不会为后添加的属性设置响应式get和set,接下来我们就来验证。
以下是基本代码,后期如果说想添加一个属性年龄进去,不能直接在data中修改,而是需要用户添加才能实现可以查看效果
如下代码想输出年龄,但是页面中的_data属性身上只有title和student两个属性,而sex不存在student中,访问一个对象中不存在的属性返回undefined。在vue中对于模板中处理undefined不会进行报错处理,也不会显示。
但是不能直接在模板中写sex属性,这样子就会直接报错,在data中找不到该属性
在控制台为student.sex添加一个属性,发现vue没有给其绑定,所以无法实现响应式。
也就是说,如果想要实现响应式的属性,需要在new实例化的时候就配置完事。以后添加的属性如果不进行特殊处理无法实现响应式。
因此vue为我们提供了Vue.set()将一些后期新添加的属性设置具有get和set函数
Vue.set(设置的对象,设置的属性,属性对应的值)
又因为在vm实例对象身上的属性都是vm._data中的属性处理得来的,所以可以继续简化
尝试使用Vue.set()直接给data的最近一级添加一个属性
两中写法都无法插入数据,这就反映了Vue.set()的局限性
Vue.set()和vm.$set()操作的对象不能是实例对象或者Vue实例的根数据对象(data或_data)
以下是模板解析的是数组的时候,查看vm实例对象是如何处理数组中的属性
因为没有为数组中的元素匹配get和set,所以当修改数组中的某个数据的时候vue无法检测到数据改变从而重新布局。
在vue中,监测数组数据更新实现响应式是通过以下几个方法实现的,这些方法都有一个共同的特点,就是会影响原数组,并且vue对这些方法进行了包裹,所以它们会触发视图更新。
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
以下代码可以证明,调用这些方法的时候页面成功显示数据
那么vue对这些方法进行包裹处理是什么意思?先理解下面的代码
vue中使用数组的一些方法的时候进行了包装处理,第一步依旧是去调用Array原型身上的方法,其次将处理完的数据直接响应到页面中。
所以可以解决之前的更新数据无法生效的问题了
当然可以采用如下这种方式去更新数组不过不常用。使用Vue.set()并通过索引去修改对应位置的内容
vue会监视data中所有层次的数据
vue如何监测对象中的数据
通过setter监视数据变化,需要在new实例化的时候设置好配置项属性
对象中如果在new完后添加新的属性,则vue默认不设置响应式的处理
如果需要对后添加的对象属性设置响应式,需要使用Vue.set()和vm.$set()
如何监测数组中的数据
vue通过包裹数组中的方法实现数组响应式的操作
调用原生的数组方法(会影响到原数组的,非返回数组)
重新解析模板,进行数据替换
vue做过包裹的数组方法:pop,push,shift,unshift,sort,splice,reverse
Vue.set()和vm.$set()无法给vm实例对象或者vue的根数据对象添加的属性设置get和set
当为单选按钮绑定双向数据绑定的时候v-model会出现一个问题,点击的单选按钮无法正确获取表单值而是返回空,那是因为v-model默认取value的值,而单选框无value所以需要设置
当为多选按钮绑定v-model,由于多选框没有value值,所以v-model直接绑定的时候,vue默认会接收多选框中的checked属性的值,由于点击了一个按钮,该按钮的checked属性会直接影响到data中的数据,同时data数据改变会影响到页面中用到的地方改变。
注意如果直接绑定value值,还是不能解决该问题,因为data中的hobbies中的默认给的值会影响到最终的结果,每次点击无法像hobbies中添加数据
如果直接将hobbies赋值为数组则不会出现,每次点击都会添加数据进去
在处理同意按钮的时候需要注意,只需要获取布尔值即可来绑定按钮显示就行
但是在form表单中点击button按钮的时候会出现一个问题,就是默认表单提交行为需要阻止,所以只需要在form表单后跟着一个事件修饰符阻止行为即可
在demo函数中后期使用ajax技术发送数据到后台处理
很多时候对于表单输入的数据默认收集value中的是字符串的格式,对于像年龄这种只会输入数字的,可以使用原生JS限制只允许输入数字,但是最终value的数字还是字符串类型,对于最终传输到后台还是不便于理解处理
所以需要在处理的时候将收集的value数据转换为number类型存入data中。因为value的数据是通过v-model处理的,可以为v-model添加一个修饰符进行数据类型转换
但是如果在type中写的是text,而v-model绑定的修饰符是number,那么输入如果输入框中的数据为123ab,当离开光标的时候会自动清空ab,vue会收集到数字123
所以如果需要将前端输入的是数字类型,并且需要转换为number型,通过type值number和v-model修饰符为number都是配合使用的。
当输入的内容不希望被vue立即收集到的时候可以使用lazy修饰符,可以减少工作量
当为v-model添加完lazy修饰符的时候,只有当光标离开区域的时候,vue才会收集到数据
可以将用户输入的文本前后的空格去除
当给v-model添加完trim修饰符后,可以除去首位空格
如果收集文本框之类的,具有value属性的,则v-model收集的就是默认value的值,用户输入的就是value
如果收集的是radio单选框,那么v-model默认收集的是value需要配置,否则就会收集到null
如果收集的是checkbox复选框,如果没有设置value属性,则v-model默认收集的值是checked的值,即布尔值。如果设置了value属性,那么也需要根据v-model的初始值来决定。如果初始值是非数组,那么收集的依旧是checked的布尔值。如果初始值是数组,那么收集的value就会被vue组成数组
如下给定一个时间戳,如何将时间戳转换为人可以识别的时间。
这里借助一个网址BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务,在网址中搜索monentjs或者dayjs,两者都是处理时间的js库。
这里借助dayjs库,可以根据连接查看基本使用语法,需要使用dayjs()方法,如果不传参数则使用当前的时间,可以传入指定时间,调用format方法处理时间戳并返回。
时间是:{{time}}
计算属性
{{comTime}}
methods
{{getTime()}}
以上两种使用计算属性和methods去将时间戳转换为正常显示
下来使用过滤器实现,过滤器本质就是一个函数去处理,不过是vue自动为我们去调用,vue为我们提供了新的配置项filters,里面存放过滤器函数
需要注意vue中使用过滤器在模板中第一个需要写过滤的数据,然后写一个 | 符号,在后面写一个过滤函数,vue解析这里的模板的时候会收到time,拿着time自动的去调用filTIme过滤器函数,在过滤器函数参数中,第一个参数永远是vue接收|符号之前的参数。第二个参数子自定义的参数
在过滤器中函数中传递参数,使用同一个函数修饰不同的时间显示
{{time | filTime}}
{{time | filTime('YYYY_MM_DD')}}
过滤器也可以串联使用,直接在上一个过滤器函数后面跟着继续写一个 | 符号,后面继续跟着一个过滤器函数即可。就实现了过滤器的串联。
vue执行的时候会拿着time的值进入第一个过滤器函数中处理,然后用返回值进入第二个过滤器函数,最终将这个返回值重新渲染模板显示最终结果。
但是以上写的过滤器函数都是局部的,一旦有第二个容器想使用同样的过滤器函数,就无法实现,因此可以将过滤器函数配置为全局模式
在Vue全局对象中的filter过滤器属性中配置过滤器函数,在Vue中配置的过滤器函数为全局的
过滤器函数不仅可以在插值语法中使用也可以在指令语法v-bind中使用,但是不能在v-model里面使用过滤器
会将内容完全替换掉标签中的内容。都不会解析字符串中的标签
v-html能够解析带标签的内容,但是动态渲染html标签具有安全隐含,如cookie
用户的cookie可以被非法用户使用代码取出然后在跳转的地址中将cookie带走,这样子别人就能获取cookie。
如下代码用户因为并不知道后台代码,当点击的时候,后端代码就会自动的取出cookie拼在网址后面一起发送过去,这样子安全性就成了隐患。
有的时候html标签中的内容配合vue操作来实现的,如果引入的vue.js是通过服务器获取,需要一定的等待时间,那么当浏览器解析的时候,从上往下依次解析,解析到html的时候,由于vue.js位于html的下面引入,所以html先直接解析内容显示{{}},只有当vue被解析完成的时候,才会接管html重新解析。
而v-cloak就是解决这种情况的,v-cloak不需要填写值,该属性在vue没有解析完成之前会出现在标签上作为一个属性,可以使用css让所以具有该属性的全部隐藏,也就是vue没有解析之前全部隐藏标签,一旦vue解析成功接管html后,就会将v-cloak全部去除,不在标签内显示,所以元素又能全部显示了。
如果有一个变量需要在两个地方使用到,同时一个记录初始时候的值,之后这个值改变了也不需要进行更新,另一个记录每次更新的值。v-ocne指令就是用来处理这种场景的
v-once也不需要接收值和v-cloak一样只需要调用即可
v-once所在的节点只有在页面一打开会进行动态渲染,之后就视为静态内容,vue不会进行解析处理,优化了性能
主要用于那些不需要进行vue解析的地方(插值语法,指令语法等),让vue直接跳过所在节点的编译过程,提高了效率
内置指令调用的时候,vue解析到命令,就会自己去操作DOM,而不需要我们去操作。而自定义指令就是自己写一个vue能解析的命令,如何给定执行的DOM操作,通过vue去帮助我们实现操作。
在控制台给出没有该指令的错误提示,所以需要我们在vue配置项中配置该指令
因此,vue为我们提供了新的配置项,directives用来配置指令
同时配置的指令也存在两种写法
在配置指令的函数同时,vue默认会为我们接收两个参数,其中一个是真实的DOM节点,另一个则是组成的对象信息
那么DOM元素有了就可以通过vue去操作DOM节点了
指令和元素初始绑定成功的时候
指令所在的模板被重写解析的时候(而非指令所依赖的数据),因为vue中data中的某个数据被修改的时候,会重新解析整个模板,所以自定义的指令也会被重写解析一次
接下来设计一个自定义指令,并且该自定义指令只能通过完整的对象式完成
如下,想在调用指令的时候,页面初始的时候绑定并获取焦点,但是发现如下情况,只有点击按钮第二次的时候,才能主动获取焦点。这是因为focus()的执行时机和位置引起了,用一段原生JS代码来确定。
原因:该HTML代码并不是直接放到页面中,而由vue接管,由vue先进行解析模板,当执行到自定义指令的时候先进行绑定,这个期间页面中并没有html代码,所以绑定的时候进行吃focus操作就失败了。之后由于页面中存在html代码,vue针对模板重新解析的时候存在input框,所以第二次才会聚焦
在下面的JS代码中,先将生成的input框添加到页面中,才可以对input框进行获取焦点设置,一旦将该代码的位置移到别处就无法实现该功能
当时页面中无法获取该input标签,因为还没有插入到页面中,所以就无法设置获取焦点操作
并不是所以的代码都对位置和执行时机有严格的限制
自定义属性对象式的完整写法
完整写法:配置三个函数名,分别为bind函数,inserted函数,update函数,名字必须一模一样,对应的三个函数对应不同的执行时机。
修改之后,需求实现
所以可以发现,简写形式的自定义属性写法其实就是集合了bind函数和update函数,唯独去除了inserted函数
这么做的是因为指令语法操作的基本都是DOM元素,所以在设计this指向的时候基本都指向了window
在Vue提供的directive身上配置全局自定义属性
下面这种代码写法又会出现vue又出现js,看起来是自上而下同步解决,但实际上是相互交互,在js中依赖了vue的代码部分。
生命周期回调函数,生命周期函数,生命周期钩子(本质就是一个函数)
Vue在关键的时刻帮我们调用一些特殊名称的函数
生命周期函数的名字不可更改,但函数的具体实现由具体代码实现
生命周期函数中的this指向了vm或者组件实例对象
Vue完成模板的解析,生成虚拟DOM,之后才生成真实的DOM,vue将初始的真实DOM元素放入页面后(挂载完毕)才调用mounted生命周期函数。
vue为我们配置了mounted函数,来实现该功能,只有在页面模板解析完成后,调用一次(只会调用一次)
图中所有红色框的均为声明周期函数,在对应的时候处理对应的操作
该阶段vm实例暂时没有进行数据代理,只是指定了相关的规则,所以这个时候vm身上没有data中的数据和methods中的方法。this也没有指向vm
进行打断点进行测试,让程序到这里就停止往下执行
这个阶段还不能操作真实DOM
进入虚拟DOM,判断有没有el配置项,之后往下判断有没有template配置项,像我们平常写的那种就没有。通常配置了el选项,我们不配置template配置项,所以我们直接将如下代码作为模板解析
如果没有配置el配置项,就需要手动使用$mount配置
如果我们配置了template选项,那么是如何解析的,会完全覆盖之前的容器,注意,必须在template中套一个div,否则vue就会认为有两个根节点,就会报错
这个阶段vue将虚拟DOM生成为真实DOM插入页面
数据更新时进行。数据已修改,但尚未和页面保持同步
数据更新时进行。在beforeUpdate和updated生命周期函数之间,因为数据进行更新了,所以vue会进行虚拟DOM的比较。 数据已修改,与页面保持同步
当调用了$destroy销毁vm实例对象的时候,清理了它与其他实例的连接,并解绑了它的全部指令和自定义的事件监听器
此阶段可以调用vm身上的data和methods方法,可以修改其中的数据,但是这些数据最终都不会影响到页面中
像下面这种通过按钮将vm实例销毁的方法属于自我销毁,但是大部分情况不会使用,通常都是因为某些情况被他人销毁。
销毁前希望清除定时器,但是定时器的编号timer是在vm实例上,如果不销毁vm,直接清除定时器,那么可以使用vue开发者工具修改data中的opacity,也会再次实现透明度更新.但是一旦销毁了vm实例,则vue开发者工具就不会帮助我们实现数据代理。vue为此给我们提供了生命周期函数,beforeDestroy正好可以适用于这种情景。将清除等结尾性的工作放在这里执行
mounted生命周期函数:在这一步生成了真实的DOM节点插入到页面中,在这里发生ajax请求,启动定时器,绑定自定义事件,订阅消息等初始化的操作
beforeDestroy生命周期函数:这销毁实例的时候,进行结尾性工作,清除定时器,解绑自定义事件,取消订阅消息等。
组件定义:实现应用中的局部功能代码和资源的集合。
一个组件中包含了完整的功能的:html,css,js,如下图所示,一个组件将对应的部分代码都提取出来进行了封装,以便于复用。当其他页面需要该组件的时候只需要引入即可
为什么需要引入组件呢,这是因为传统的编写代码方式存在问题:文件之间存在依赖关系,不容易维护。当文件数量增多的时候,维护性困难就显而易见。同时代码之间的复用率不高,虽然存在复用,但是html结构并没有进行复用,如两个顶部区域一模一样,则css代码进行引入即可即复用,但是html结构需要在另一个文件中进行复制,复制和复用则是不同的概念。
所以我们引入了组件,组件可以更好的帮助我们细化
需要注意的是在传统的编码中大部分js代码采用模块化开发的思想,即将一个复杂的js拆分成多个js文件,采用模块化语法
而组件化的的概念如下图,即将一个复杂的组件功能可以再次细化为一个一个小的组件。组件可以嵌套
即一个文件中包含多个组件
下面以非单文件组件展开
为两个功能创建两个不同的组件,vue提供了extend方法,该方法和new一个vue一样需要传入配置项,两个配置项基本上是一样的,但还是有一些差别。
如果直接将new的时候创建的配置项原封不动的写入组件的配置项中,则会报错
首先,在组件中,不能使用el配置项,这是因为所以的组件都是由vm分配管理的
在组件中,data配置项必须写成一个函数式,且返回值为一个对象。这是因为如果按照new的时候配置data的写法,那么那种对象式的data存在引用地址的问题,即所有使用到该组件的时候,data以对象式的写法,则均占一个地址,当一个地方修改的时候,所有用到该data的地方都会被影响到。
第一步创建一个组件并设置template模板的解析内容,一个完整的组件基本包含了:css,html,js,因此这里需要将html结构放入template中
第二步注册一个组件
第三部:在HTML结构中使用组件
以下是完整代码
并且多个同名组件之间解决了复用性的问题,只需要调用组件标签即可,标签中包含了结构样式等。
在vue开发者工具中查看,虽然是两个同名的组件,但是组件内部互不干扰
修改一个组件的内容不会影响到其他组件,这是归功于data使用函数式的功劳
如果想在对应的组件中添加按钮则直接在template中添加即可
以上在new配置项中配置components的写法属于局部写法,别的地方无法使用,所以Vue提供了全局配置组件的写法
组件使用的步骤
定义创建组件
注册组件
使用组件(调用组件标签)
如何定义组件
使用Vue.extend({配置项})创建一个组件
在创建组件的时候不需要写el配置项,该配置项由根节点统一管理,根节点决定为哪一个容器服务
data在组件中必须写成函数式,并且返回值为对象。这是为了解决对象存在的引用问题
使用template配置组件的结构
注册组件
局部注册:在new Vue的时候传入components配置项
全局配置:使用Vue.component()配置
组件名为一个字母组成,vue开发者工具会自动帮助我们转换字母开头为大写
首字母小写 school
首字母大写 School
组件名由多个单词组成
多个单词之间以 - 连接, 并且首字母均为小写 my-school
多个单词之间首字母均为大写,MySchool,但是需要借助vue脚手架
组件名应该尽量回避HTML元素中已经存在的元素名
可以在定义组件的同时指定name配置项,该配置项只会指定组件在开发者工具中呈现的名字,如果没有name配置项,则开发者工具使用组件名,如果有name配置项就使用name的值
组件标签
可以使用完整指定
可以简写定义组件,不过vue底层依旧会帮助我们去调用Vue.extend()
在日常开发中,通常Vue的Root根节点只会管理一个名为app的节点,由该节点管理其子组件,从而形参嵌套。
对于定义完毕的组件打印输出发现,它是一个函数,并且是一个名为VueComponent的构造函数。既然为构造函数,但是我们使用的时候却没有使用new关键字。这是vue又一次帮助我们简化了,我们只需要使用Vue.extend即使用了new关键字。
vue底层源码中写了这么一段代码实现。功能全部集合在_init中。
每一次使用组件标签的时候,都会生成一个school组件的实例对象,下面代码中使用了两次组件标签,会发现构造函数被调用了两次,也就是说实例对象创建了两次。
每次定义一个全新的组件调用了Vue.extend的时候都会返回一个全新的VueComponent构造函数,只不过构造函数的名字和功能是一模一样的。下面是使用三种方式的证明
先理解第一个图的代码
如果两个构造函数是一样的,那么对象的地址因是一致的,修改一个会影响另一个
在底层代码中,每次的构造函数都会使用一个全新的变量Sub去接收并返回,所以每次构造函数都不一样
VueComponent构造函数中的this指向
通过打印可以看出,this的指向为VueComponent。并且实现的功能和vm实例对象是一样的,都为data中的每一个数据进行了数据代理操作。
所有的组件最终都会出现在vm实例对象身上,受vm管理
定义的组件school本质是一个名为VueComponent的构造函数,并且不需要手动定义,是使用Vue.extend()自动生成的
只需要使用组件标签
每次调用Vue.extend(),返回的都是一个全新的VueComponent
在VueComponent中的this指向
组件配置中:data函数,methods函数,watch监视中的函数,compluted函数,他们的this均是VueComponet实例对象
new Vue()配置中:data函数,methods函数,watch监视中的函数,compluted函数,他们的this均是vm实例对象
VueComponent检测vc,它与vm的实现几乎一样,但是两者还是有些许差别的。如在组件中就不允许配置el选项,这在vm中是需要配置的。所以不要简单的将vm和vc画等价。他们是不同的
在Vue原型和组件实例对象之间存在:VueComponent.prototype.__proto__ ===Vue.prototype
这么做是为了让组件对象vc可以访问到Vue原型上的属性和方法
在一个构造函数身上才具有prototype属性,即显示原型属性
在一个实例对象身上才具有__prototype属性,即隐式原型属性
即一个文件中只包含一个组件
vue为我们提供了全新的后缀名.vue,该后缀名只有单文件组件才能使用
建立类似左边这种节点,在右边创建对应文件,其中main.js是赋值创建vm实例对象的文件。容器是存放在HTML中。
只不过上面的文件代码创建完成后无法运行在浏览器,浏览器不认识vue文件,所以需要借助脚手架
开发者工具需要全局安装
在哪里需要创建脚手架就在哪里使用命令创建项目,创建成功会有success提示,并且下面会有提示进入cd进入项目名字的提示和运行服务器的命令
vue create 项目名
cd进入项目前,会进行Vue的版本确认,最顶上蓝色为脚手架的版本
在项目中使用命令运行服务器:npm run serve,一旦服务器启动成功就会有提示网址信息,下面的网址是供其他人想使用的服务器,通常我们使用第一个
进入网址只要是下面的页面就代码创建成功
如下为脚手架创建的文件夹,其中所有的子组件全部由App组件管理并统一存放在components文件中,App组件再由mian中的vm管理。所以我们只需要将为我们之前写的代码替换目录中的即可
需要在vue.config文件中配置lintOnSave为false选项,用于关闭语法检测
导入vue的时候如果出错就修改为如下代码You are using the runtime-only build of Vue where the template compiler is not available.Either pre-compiler the templates into render functions, or use the compiler-included build.这是因为我们默认使用import进入的vue是精简版的的,没有模板解析器,就无法去解析配置项中的template选项。
如下就是就是默认导入vue使用的文件,凡是带有runtime的都是缺少模板解析功能的vue,都需要使用render函数去解析。
如果我们引入的是如下的完整版的vue,那么我们就可以使用template配置项解析模板
当我们使用的是精简版的vue的时候需要使用render函数来解析,并且render函数由Vue自动调用,会自动接收一个参数,并且该参数的类型为函数,用来创建元素和内容。
createElement(创建的html元素,创建的内容)
下面是完整版的写法可以继续精简
如果render函数接收的是一个组件,那么就直接写即可,在组件中已经有创建元素的过程,接收参数的名字可以任意
一个完整版的vue包含了基本的两大模块:核心功能+模板解析器。
完整的vue.js和vue.runtime的区别是,精简版的vue去除了模板解析器,在使用的时候需要使用render函数进行解析。
vue提供精简版的原因:模板解析器只有在开发的时候用到,并切这个项目的代码都能跑起来无错误,但是模板解析器的体积大,当需要使用webpack打包文件的时候,vue中会多需要打包一个模板解析器,但是这个模板解析器在打包后就几乎用不到了,反而占体积,因此vue才为我们提供了精简版的vue。
如果将main.js文件修改为index.js.那么脚手架默认去寻找main.js入口地址的时候就会报错
我们可以使用vue inspect > show.js命令将脚手架的默认配置给我们显示出来,并全部放在show文件中。
在导出的脚手架配置文件中可以查看到入口地址,需要注意的是,这个文件中的内容只是将默认配置信息导出给我们看的,我们修改里面的数据无法影响到默认配置
因此如果我们需要修改默认的脚手架配置,需要将配置的信息全部放在vue.config.js文件中,所以的配置必须放在该文件中,并且在高版本中,创建脚手架的同时会自动创建vue.config.js文件,在低版本中需要自己创建。
在vue.config.js文件中,采用的是commonjs的导出方式,这是因为vue最终会把该文件给webpack打包,而webapck是基于node的。所以这里只能使用commonjs语法。
以下是vue官方脚手架中给出的配置项,只有在这里提供的配置项才能修改,并且每次修改vue.config.js文件的时候都需要重新启动服务器
其中pages是修改默认的入口地址的,修改后如下图显示
如果在一个组件中想获取template结构中html的元素,该如何获取,前提条件,不允许使用原生js
在一个组件中this均指向了该组件,并且vue将ref配置为替换id来实现获取元素。并且将所有配置了ref的html元素,都保存在组件实例对象的$refs中
所以可以使用:this.$refs.xxx获取
ref不仅可以直接写在html标签上也可以写在组件标签上,当ref出现在组件标签上,那么在$refs中保存的就是子组件实例对象,简单来说,就是ref出现在那个组件标签上,那么就可以获取到该组件实例。
如果id出现在组件上,那么使用原生js获取的是该组件的完整结构代码
ref被用来给元素或者组件注册引用信息
应用在html元素上就获取真实DOM元素,应用在组件上,就获取该组件的实例对象
当一个组件被多次复用的时候会创建多个不同的组件实例对象,但是复用的时候没有修改传入值,所以每一个组件之间的值都是一样的。如果想在复用的时候每一个组件标签中的内容都不一样,就需要在使用组件标签的时候传入值,同时在组件中配置props配置项
第一种将模板中需要解析的动态数据全部让如props配置项的数组中
props中的所有属性最终都会出现在该组件实例对象身上,所以模板才可以直接使用
props:['name','age']
当把年龄增加的时候,这种静态绑定age会出现问题:年龄并没有增加为19岁,而是进行字符串拼接
这个时候需要在动态传入数据的时候使用指令绑定,当使用vue的指令语法的时候,就会将后面的18当做JS表达式读取,而不是一个普通属性age="18"将18当做字符串。
同时,在props中不能随意配置属性,如果没有动态传入一个属性phone,在props中却自己多配置了一个phone,会导致phone的接收的值为undefined,应该尽量避免
第二种props配置写法,在声明变量的同时,声明允许接收的类型,这种方法接收的参数,如果不是正确要求的类型,控制台会给出错误提示,但是程序会正常执行
第三种配置props的写法,即完整写法,为每一个属性单独配置一个对象检测
其中可以为每一个属性单独配置的参数有:type决定属性的类型,required决定参数为必传项,default决定参数的默认值,通常default和required不会同时出现
props: {
name: {
type: String, //类型
required:true//必传项
},
age: {
type: Number,
default:18 //默认值
},
sex: {
type: String,
default:'男'
}
}
即只有姓名为必须传递的参数,如果不传递控制台给出报错提示,后面两个参数可以选择性传递
props中的属性优先被组件实例处理,其次才会处理组件中自己的data配置项中的数据,并且props中的同名属性优先级高于data中的同名属性,页面优先展示props中的属性。如果出现同名的情况,控制台会给出报错提示。
props中的属性是只读的,一旦修改vue检测到会给出报错提示 ,但是页面会变化。
需要重点注意props中如果是一个对象那么vue只会检测该对象在内存中地址是否发生改变,不关注内部的值是否改变。
即let o = {a:1} 如果修改了o.a = 2那么vue不会给出报错提示,但是不建议这么做
如果直接修改 o ={b:1} 那么vue会直接报错提示
因此我们可以使用一个中间变量存储props中的某个属性值并放在data中保存,修改的时候就修改data中的值即可
在多个组件之间,如果用到的配置项一模一样,那么就可以把相同的部分抽取出来,然后在分别引入,这个就是mixin混入。混合简单来说就是复用配置
单独将抽取出的代码封装为一个js文件用于引入
同时在需要使用的组件中引入文件并且配置选项,vue提供了mixins配置项来配置属性,值必须为一个数组
同时在配置项里的参数如data,methods,生命周期函数等都可以设置为mixin混入
但是如果data,生命周期函数等混入的时候就存在同名问题
当生命周期函数出现同名的时候,那么两个生命周期函数都会被执行,不过优先执行混入中的生命周期函数,其次在执行组件中的生命周期函数
当data中的某些属性也进行混入的时候,如果组件中data没有的属性就会添加,如果出现同名的情况,就优先使用data中的属性
即在vm实例对象和组件实例对象身上都会出现该属性
全局配置混入:Vue.mixin()
下面两种写法都能实现,在vm文件中引入混入的参数
插件的功能是加强Vue的功能
插件的本质是一个对象,这个对象必须包含install函数,且函数的第一个参数为Vue构造函数,在函数内部,可以使用各种Vue的全局配置。
定义完一个插件的功能后需要去使用插件,在实例化vm之前,使用Vue.use(插件名)去使用
在插件中因为接收的参数是Vue构造函数,所以可以进行任意的全局配置,下面是几个常用的全局配置在插件中定义的方式
完整的插件代码
// 导出插件接口
export default {
install(Vue) {
// 可以配置全局过滤器
Vue.filter('useFilter', function (value) {
return value.slice(0, 2)
})
// 配置全局自定义指令
Vue.directive('sn', {
bind(element, binding) {
element.innerHTML = binding.value
},
isnerted(element, binding) {
element.innerHTML = binding.value
},
updated(element, binding) {
element.innerHTML = binding.value
}
})
// 配置全局混入
Vue.mixin({
methods: {
showInfo() {
alert(this.name);
},
},
mounted() {
console.log('mixin生命周期函数!')
}
})
// 配置全局方法
Vue.prototype.hello = () => { console.log('hello') }
}
}
定义完插件只需要在适当的位置使用即可
插件可以使用多个Vue.use(),就想express和koa中中间件一个思路,可以使用多个插件区帮助我们做事情
组件之间设置的样式最终都会汇总到一起,即渲染树。多个组件之间可能出现同名的样式如下,如果出现同名的样式如下,那么哪一个样式最终会被使用是由谁后引入决定的,即后者css样式层叠之前的css样式
那么我们就想使用同名的css名字那么该如何解决此问题
在书写style标签的时候添加scoped属性,限制当前css样式作用域
限制当前css样式只服务于当前组件实例对象的结构,这样子就解决了同名样式的冲突问题了。vue将具有scoped的style标签添加一个随机属性,并配合属性选择器实现
当不添加scoped限制css属性的作用域的时候,一个组件中所定义的样式可以被另一个组件使用。因为所有的样式都会汇总到一起
style标签可以指定写css或者less等,但是需要设置lang属性,当style属性不写lang属性的时候,则默认lang="css"
可以指定lang="less",但vue脚手架不支持解析less语法,所以需要安装插件,vue推荐安装less-loader。在老版本的vue中,less-loader默认更新最新版本,与webpack5搭配。而vue中默认使用的webpack可能会是5以前的版本,就会造成版本不兼容。所以需要给less-loadar降版本才能使用
npm view 库 versions 即可查看所有版本
全局安装:npm i -g less-loader
一个项目拿到首先需要规划那些功能需要查分为一个组件
用于生成自己案例的唯一id可以借助于uuid库,但是这个可以很大因此可以借助该库的简略版nanoid库,帮助我们生成唯一标识符的id
首先对于一个案例需要将功能细化为组件,不同的组件之间对应实现的功能。并且对应的组件关系需要处理好。如下图,header组件用于处理输入数据,list组件用于显示数据和添加的数据,footer组件用于删除和计数
对应的组件关系如下图:Header,List,Footer组件受App组件统一管理,对于List组件中的数据是动态生成的,item组件受List组件管理。
先待见脚手架结构,并分别导入对应的组件作为子组件
在App组件中导入子组件接口,并配置components选项
在List组件中导入Item组件接口
最终Vue结构如下
首先先搭建静态页面和样式,可以先将规则和样式都写在App组件中,然后在依次分离到对应的组件中配置
然后处理头部添加数据,但是以目前的知识点无法处理两个平级组件之间传递数据,因此我们可以将原先存放在Header组件中的数据,保存数据的容器放在App父组件上,将所有的添加的新数据都放在App上。这样子可以在父组件中暴露方法传递给子组件使用,子组件就可以调用方法传递数据从而修改数据
首先将App中的数据以todosData参数的形式传递给List组件使用生成Item组件。并将自身的changeTodo方法提供给子组件使用,该方法用于修改复选框状态
在List子组件中接收传递的参数
List组件中遍历传递来的数组对象生成几个Item组件标签,其中使用v-for指令语法根据数组元素长度生成Item组件。同时该语法需要绑定一个key,以便于虚拟DOM的比对。
其中todoObj用于将当前数组中的对象传递给下一个Item组件,在Item组件中可以用该对象的参数用来绑定一些复选框的状态。cahngeTodo函数接收一个当前id参数,进行点击时候复选框的判断
在Item组件中,复选框的状态由todoObj.done值决定,并且添加一个点击事件来修改该状态,但是不能直接修改props值中的状态。因此我们才在App组件中暴露方法来实现。
内置的事件如click,keyup等都是给HTML元素使用的,而自定义事件都是给组件用的
在School组件中采用父组件传递方法,子组件去调用的方式传递数据给父组件使用
在Student组件标签中采用自定义的组件去实现
哪一个组件需要触发自定义事件就给一个组件标签添加事件。每次调用组件标签的时候,就会new Component创建一个组件实例对象。所以这个点击事件就在该组件的实例对象上
在组件中触发该自定义使用需要使用到自身的组件身上找,并且使用$emit方法触发自定义事件
自定义事件不需要配置props配置项属性
通过父组件给子组件传递函数类型参数,子组件使用props接收。
子组件在调用传递的函数给父组件传递参数
通过父组件给子组件绑定一个自定义事件,子组件触发该事件,就会调用回调,从而实现子组件传递数据
自定义事件除了写一个组件标签在组件标签中绑定事件,还可以如下写法
通过给组件标签添加ref属性获取。如果给一个组件标签添加ref属性,则会获取该组件的实例对象。本质和组件标签一样,获取组件实例对象
完整写法,父组件给需要使用自定义事件的组件实例添加该事件
但是第二种这种使用ref的方法比第一种给组件标签添加的方法更加灵活。比如需要开启一个定时器延迟几秒钟后才能触发该事件,第一种方法就不能实现,因为第一种方法在页面一加载的时候就解析完成了,无法在结构中书写JS代码去控制。
但是第一种方法就能实现
两种添加的事件的方法都可以使用事件修饰符
先定义两个自定义事件在App组件中
子组件中点击按钮就调用回调函数去执行函数内部代码,通过$emit去触发自定义事件,于是两个事件的回调函数都会被执行。
在创建一个新的按钮实现解绑事件功能。调用该实例对象身上的自定义事件,并通过$off方法解除绑定
点击触发事件后再次店家解绑事件,只有为解绑的事件会触发
解绑多个自定义事件,在$off内部使用数组接收需要解绑的自定义事件
当然还有更加暴力的方法,一次性将所有的自定义事件全部解除绑定
第一种: 只解除指定的自定义事件
this.$off("getStudentName");
第二种:解除多个自定义事件,使用数组接收
this.$off(["getStudentName", "demo"]);
第三章:暴力解除所有的自定义事件
this.$off();
在一个vm实例对象或者组件实例对象销毁的时候,其身上的自定义事件也会跟着销毁,但是内置的事件在老版本中依旧可以触发。但是在新版本中,使用$destroy方法的同时,自定义事件和内置事件都会被清除
当使用这种事件绑定去处理值传递的时候,this的指向不会出现问题。this就指向当前组件实例对象即App
但是使用如下代码的写法就会出现问题
当正常使用ref属性添加自定义事件的时候,直接在事件监听后面指定回调函数的时候使用this.getStudentName()的时候,因为使用的是ref属性,所以获取的时候student就是Student组件实例对象,this执行了它,但是在执行的时候getStudentName方法又存在于App组件的methods中,所以vue又帮我们进行了this指向的修改,最后this指向了App
如果直接在第二个参数中写一个函数,则this指向的问题就出现了,页面并没有进行数据更新,这是因为在function中this的执行并不是App组件实例对象,而是Student组件实例对象
vue中,对于自定义事件,谁需要触发该事件,vue就帮助我们将this指向谁。如下代码。在$refs中的student中存放的是Student组件实例对象,最终答案是true,所以这里的this不是App。所以最终在这里需要使用箭头函数
自定义事件是给组件使用的,那么原生的事件是否可以给组件使用
如果直接将原生事件给组件标签使用,那么组件标签会默认认为这是一个自定义事件,需要使用自定义事件去触发
即在使用$emit方法去触发
使用native修饰符会告诉vue,这需要使用原生的事件去处理
作用:实现任意组件之间的通信
设计一个中间人,能够让所以的组件都可以访问,并且还需要能够使用Vue的语法。
这时候可以使用Vue构造函数的原型对象。该原型对象可以供所以的组件实例对象访问。
这里不挂载到组件实例对象是因为每次组件实例对象都是创建的新的VueComponent,所以不能使用
在上面的解释中,已经知道了需要组件之间实现任意通信,需要在Vue的构造函数身上去绑定数据。接下来就是简单的测试
如下代码中,给Vue的原型对象绑定了一个属性x并赋予一个对象。于是在组件student中通过this访问的时候,由于当前组件没有x属性,于是就会去调用隐式原型对象即__proto__,去找原型对象prototype。一直往上找该属性,最后再Vue构造函数身上找到了该属性。
那么我们尝试绑定一个自定义事件,但是这个时候控制台会给出报错提示告诉我们无法这样子直接绑定
原因是如:$emit(),$off(),$on()这些方法只有Vue的构造函数身上的原型对象才有,而vm实例对象或者组件实例对象是通过访问原型链的方式才获取到这些方法的。
于是继续修改代码,但是还是会出现问题:原因是当执行橙色区域代码的时候,需要使用到自定义事件的代码,而那会在Vue的构造函数身上还有将x属性挂载上去,更别提使用$on,$emit等方法
修改代码如下,实现了一个简单的全局事件总线,其中this指向了当前vm实例对象,于是就可以使用$on等方法
由于使用的是全局事件总线,当一个自定义事件被组件占用的时候,其他组件不能占用该自定义事件,只能去触发,并且一个组件销毁的时候,只需要解除绑定即可规范该自定义事件给其他人使用。
所以需要借助生命周期函数beforeDestroy方法去实现,并且解绑的时候需要指定解绑的自定义事件,否则默认解除了全部的自定义事件
通常将事件总线的名字命名为:$bus