文章目录
- Vue基础速通
- 前言
- 1、Vue概述
- 2、快速入门
- 3、模板语法
- 4、数据绑定
- 5、el和data的两种写法
- 6、数据代理
- 7、事件处理
- 7.1 快速入门
- 7.2 事件修饰
- 7.3 键盘事件
- 8、计算属性
- 9、监视属性
- 9.2 快速入门
- 9.2 深度监视
- 9.3 知识拓展
- 10、动态绑定样式
- 11、条件渲染
- 12、列表渲染
- 13、key的作用和原理
- 14、过滤
- 14.1 列表过滤
- 14.2 过滤器
- 15、数据更新时的底层原理
- 16、内置指令和自定义指令
- 16.1 内置指令
- 16.2 自定义指令
- 17、Vue实例化和生命周期
Vue基础速通
前言
欢迎来到知识汲取者的个人博客!在这篇文章中,我将为你介绍Vue.js的基础知识,帮助你快速入门并掌握Vue开发的基本技巧。Vue.js是一个流行的JavaScript框架,被广泛用于构建现代化、交互式的Web应用程序。它采用了MVVM(Model-View-ViewModel)架构模式,通过数据驱动视图的方式实现了高效的前端开发。在这篇文章中,我们将从Vue.js的核心概念开始,包括Vue实例、组件、指令和响应式数据等。我会详细解释每个概念的作用和用法,并通过实际的代码示例帮助你理解和运用这些概念。如果你是初学者,相信通过本文的学习,一定可以让你熟练掌握Vue的基础语法,理解Vue中的一些常见概念。
PS:对于文章一些描述不当、存在错误的地方,还请大家指出,笔者不胜感激相关推荐
- Vue2官方文档
- Vue3官方文档
1、Vue概述
-
Vue是什么?
Vue (发音为 /vjuː/,类似 view) 是一款基于MVVM思想用于构建用户界面的渐进式 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。
Vue官网:
- Vue2:Vue.js (vuejs.org)
- Vue3:Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org)
Vue的亲爹:尤雨溪
没想到吧😃,Vue亲爹是如此的年轻帅气,和我们以往对于哪些某一领域的开创者的印象是很不一样的,不像其他的大部分创始人都是大胡子老头,这个Vue的创始人看着好亲切(没错它是一个华人),而且它本科是计算机,硕士读的是美术设计(没想到吧)!难怪Vue是如此的优美,堪称一件精致的艺术品😆,同样的Vue也如同它本人一样,也是如此的年轻
题外话:他之前是任职于Google,之后又加盟了阿里,现在是全职维护Vue,它的目标是让Vue成为最强的JavaScripted框架
-
什么是MVVM?
MVVM(Model-View-ViewModel)是一种架构模式,是MVC模式的改进版。MVVM实现了数据的双向绑定1,解决了MVC模式只能实现模型到视图的单向展示。
- Mode:数据模型,泛指后端进行的各种业务逻辑处理和数据操控
- View:视图,是用户界面(可以理解为平时浏览的网页),用于展示数据
- ViewModel:视图模型,是由前端开发人员组织生成和维护的视图数据层,用于Model和View的数据交换。在这一层,前端开发者从后端获取得到Model数据进行转换出来,做二次封装,以生成符合View层使用预期的视图数据模型
个人理解:其实MVVM就是在MVC的MV之间添加了一个中间层VM,VM实时监听DOM,一旦网页中的数据发生了改变,VM就会立马反馈给Model,然后Model也发生改变。这让我想起来一个大佬说过的一句话”计算机中任何问题,都可以通过添加一个中间层来进行解决“
-
什么是渐进式?
渐进式就是可以自底向上逐层地应用,即:既可以简单应用(只需要引入一个简单的核心库),又可以复杂应用(引入各种各样的Vue插件),也就是说Vue的应用是具有层次的
-
Vue的特点
- 轻量、灵活、易上手。Vue只注重视图层,核心库只有5Kb,提供 MVVM 数据绑定和一个可组合的组件系统,具有简单、灵活的 API,使开发者更加容易理解,能够更快上手
- 组件化。Vue采用组件化模式2进行编码,提高代码的复用率,且代码具有更好的可维护性
- 声明式。Vue采用声明式编码3,让开发人员无需直接操作DOM,提高开发效率
- 响应性。Vue会自动跟踪 JavaScript 状态变化并在改变发生时响应式地更新 DOM,并且面对数据的动态变化时,Vue使用了虚拟DOM+Diff,尽可能复用DOM结点
-
Vue和其它JS框架的关系:
- 借鉴Angular的模板和数据绑定技术
- 借鉴React的组件化和虚拟DOM技术
-
Vue周边库:
vue-resource、axios、vue-router(路由)、vue-cli(vue脚手架)、vueX(状态管理)、element-ui(基于vue的PC端UI组件库)
-
Vue的发展历程
详情请参考:Vue.js的发展历程
2、快速入门
-
Step1:下载并引入Vue.js
- vue.js:是开发版。它包含完整的警告和调试模式
- vue.min.js:是生产版。它删除了警告,是vue.js的压缩版,大小只有37.36KB
具体下载可以去官网
-
Step2:关闭提示
温馨提示:这一步并不是必须的
1)Google安装Vue插件:解决上面①的警告,同时让Vue的开发更加丝滑
2)配置productionTip属性:productionTip是Vue的全局属性之一,用于判断是否需要进行警告提示,它在Vue2中是默认取值true,开启提示,所以需要将他设置成false(在Vue3中已经默认取值为false了)
3)设置网页Log:
注意:如果
favicon.ico
图片不在项目的根目录下,则Liver Server无法识别,这时候log无法显示,此时如果任然想要log效果,可以通过<link rel="shortcut icon" href="" type="image/x-icon">
标签进行引入 -
Step3:编写Vue代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>初识Vue</title> <!-- 引入开发版Vue --> <script src="./js/vue.js"></script> </head> <body> <!-- Step0: 创建容器 --> <div id="root"> <h1>Hello,{{name}}!</h1> </div> <script> //Step1: 设置Vue全局配置,并创建Vue对象 Vue.config.productionTip = false; //关闭Vue的警告提示 new Vue({ //Step1: 让容器和Vue建立联系。使用el指定当前Vue示例为哪个容器服务,值通常是CSS选择器字符串 /* 写法一:你找到容器给Vue对象用 el: document.getElementById('root') */ //写法二:让Vue对象自己去找容器,并使用(推荐使用) el: '#root', //Step2: 进行数据绑定。data中存储的数据提供给建立了联系的容器进行使用(暂时写成对象的形式) data: { name: 'Vue' } }); </script> </body> </html>
总结:
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象;
- root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法;
- root容器里的代码被称为【Vue模板】;
- Vue实例和容器是 一 一 对应的;
- 真实开发中只有一个Vue实例,并且会配合着组件一起使用;
- {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性;
- 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新;
注意区分:js表达式 和 js代码(语句)
-
js表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
- a
- a+b
- f()
- x === y ? ‘a’ : ‘b’
-
js代码(语句)
- if(){}
- for(){}
js表达式是js代码的子集,它属于特殊的js代码
3、模板语法
Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上
Vue的模板语法分为插值语法和指令语法
-
插值语法:
{{JS表达式}}
作用:用于解析标签体内容
<div id="root"> <h1>hello,{{name}}</h1> </div> <!-- 引入开发板版Vue.js --> <script src="./js/vue.js"></script> <script> new Vue({ el: '#root', data: { name: 'Vue' } }); </script>
-
指令语法:
v-xxx:
作用:用于解析标签(解析标签属性、解析标签体内容、绑定事件……)
<div id="root"> <a v-bind:href="url">点击跳转至百度1</a> <!-- 简写方式(不是所有的指令都能简写) --> <a :href="url">点击跳转至百度2</a> </div> <!-- 引入开发板版Vue.js --> <script src="./js/vue.js"></script> <script> new Vue({ el: '#root', data: { url: 'http://www.baidu.com' } }); </script>
推荐阅读:
-
v-bind详细用法
-
Vue指令大全
-
总结:
Vue模板语法有2大类:
1.插值语法:
功能:用于解析标签体内容。
写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。
2.指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)。
举例:v-bind:href=“xxx” 或 简写为 :href=“xxx”,xxx同样要写js表达式,
且可以直接读取到data中的所有属性。
备注:Vue中有很多的指令,且形式都是:v-???,此处我们只是拿v-bind举个例子。
4、数据绑定
示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据绑定</title>
<!-- 引入开发板版Vue.js -->
<script src="./js/vue.js"></script>
</head>
<body>
<div id="root">
单向绑定数据: <input type="text" v-bind:value="value1"> <br>
双向绑定数据: <input type="text" v-model:value="value2">
<!--
v:model的简写形式:
<input type="text" v-model="value2">
-->
</div>
<script>
new Vue({
el: '#root',
data: {
value1: '单向数据绑定',
value2: '双向数据绑定'
}
});
</script>
</body>
</html>
-
单向数据绑定:改变data中的数据会影响页面中的数据,反之不会
v-bind
就是单向数据绑定,示例如下: -
双向数据绑定:改变data中的数据会影响页面中的数据,同时如果改变页面中的数据也会影响data中的数据
v-model
就是双向数据绑定,示例如下:注意:
v-model
只能用于表单类(能进行输入,拥有value值)标签上,比如:input、复选框等,而h1~6、p等标签使用就会直接报错!
总结:
Vue中有2种数据绑定的方式:
1.单向绑定(v-bind):数据只能从data流向页面。
2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。
备注:
1.双向绑定一般都应用在表单类元素上(如:input、select等)
2.v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。
5、el和data的两种写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>el和data的两种写法</title>
<!-- 引入开发板版Vue.js -->
<script src="./js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>Hello,{{name}}!</h1>
</div>
<script>
const vue = new Vue({
/*
el的第一种写法:
el: '#root',
*/
/*
data的第一种写法:对象式
data: {
name: 'Vue'
}
*/
//data的第二种写法:函数式
/*
data: function() {
//注意这里一定要使用普通函数进行声明,不能使用箭头函数
return {
name: 'Vue'
}
}
*/
//简写:
data() {
console.log(this); //这个函数是由Vue对象进行调用的
return {
name: 'Vue'
}
}
});
//el的第二种写法:通过Vue的对象内置的mount函数关联容器(这种方式十分灵活)
setTimeout(() => {
vue.$mount('#root');
}, 1000);
</script>
</body>
</html>
总结
data与el的2种写法
1.el有2种写法
(1).new Vue时候配置el属性。
(2).先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值。
2.data有2种写法
(1).对象式
(2).函数式
如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。
3.一个重要的原则:
由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。拓展:MVVM
MVVM模型
1. M:模型(Model) :data中的数据
2. V:视图(View) :模板代码
3. VM:视图模型(ViewModel):Vue实例观察发现:
- data中所有的属性,最后都出现在了vm身上。
- 2.vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用。
推荐阅读:箭头函数与普通函数的区别详解
6、数据代理
所谓数据代理(也叫数据劫持),通过一个对象代理对另一个对象属性的操作。指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。比较典型的是
Object.defineProperty()
和 ES2015 中新增的Proxy
对象。另外还有已经被废弃的Object.observe()
,他被废弃的原因就是由于Proxy的出现PS:这个思想类似设计模式中的代理模式,而Vue中的双向数据绑定就是用到了数据代理,这和Spring中AOP使用到动态代理是一致的,都是一种代理
示例:
问题描述:通过对num操作来改变Person对象中的age属性
当使用Object的
defineProperty
方法对Person的age属性进行数据代理时,age属性默认将不会被枚举、修改、删除
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据代理</title>
</head>
<body>
<script>
let number = 18;
let person = {
name: '张三',
gender: '男',
};
Object.defineProperty(person, 'age', {
// value: 18,
// enumerable:true, //控制属性是否可以枚举,默认值是false
// writable: true, //控制属性是否可以被修改,默认值是false
// configurable:true //控制属性是否可以被删除,默认值是false
//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get() {
console.log('有人读取age属性了')
return number
},
//当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(value) {
console.log('有人修改了age属性,且值是', value)
number = value
}
});
console.log(person);
//遍历person对象所有的属性名
console.log(Object.keys(person));
</script>
</body>
</html>
备注:当我这样写getter和setter时,writable
和configurable
属性无效了,因为当我们此时修改age,他会执行number = value,value没有修改成功,但是却修改了number;而获取age时,返回的是number,此时相当于间接改变了age
知识拓展:Vue中的数据代理
在Vue中,data底层是利用了数据代理与View中的数据进行绑定。data中的数据是存储在Vue对象的
_data
属性中的,但是如果只是这样,就会导致想访问Vue中的某个data的值,就需要Vue对象._data.属性名
来获取,这样显得很繁琐,所以Vue直接对_data进行数据代理,直接可以通过Vue对象.属性名
来获取data的属性1.Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
2.Vue中数据代理的好处:
更加方便的操作data中的数据,简化对属性的操作
3.基本原理:
通过Object.defineProperty()
把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
7、事件处理
事件处理需要用到
v-on
指令,同时还需要搭配methos
一起使用
7.1 快速入门
通过点击事件修改h1中的值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件处理1</title>
<!-- 引入开发版的Vue -->
<script src="./js/vue.js"></script>
</head>
<div id="root">
<h1>{{name}}</h1>
<button v-on:click="changeName()">点我更换名字</button>
</div>
<body>
<script>
Vue.config.productionTip = false; //关闭Vue的警告提示
const vm = new Vue({
el: '#root',
data: {
name: '张三'
},
methods: {
changeName(e) {
console.log(this); //此处的this代表vm
console.log(e.target); //<button>点我更换名字</button>
vm.name = "李四";
}
}
});
</script>
</body>
</html>
7.2 事件修饰
prevent
:阻止事件的默认行为,相当于event.preventDefault()
(常用)stop
:阻止事件冒泡 ,相当于event.stopPropagation()
(常用)once
:事件只触发一次 (常用)capture
:开启事件的捕获模式self
:只有event.target是当前操作元素时,才触发事件(可用来阻止事件冒泡)passive
:事件的默认行为立即执行,无需等待事件回调再执行native
:vue中如果你想在某个组件的根元素上绑定事件,需要使用native修饰符示例:
测试前四个事件修饰关键字
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件修饰符</title>
<!-- 引入开发版的Vue -->
<script src="./js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>测试prevent</h1>
<a v-on:click.prevent="jump()" href="http://www.baidu.com">点击跳转至{{name}}</a>
<!--简写: <a @click.prevent="jump()" href="http://www.baidu.com">点击跳转至{{name}}</a> -->
<h1>测试stop</h1>
<div @click="showMsg(1)">
<button>div1</button>
<div @click="showMsg(2)">
<button>div2</button>
<div @click.stop="showMsg(3)">
<button>div3</button>
</div>
</div>
</div>
<h1>测试once</h1>
<a @click.prevent.once="jump()" href="http://www.baidu.com">点击跳转至{{name}}</a>
<h1>测试capture</h1>
<!-- 事件冒泡由内往外,事件捕获由外往内,先事件捕获,再事件冒泡 -->
<div @click.capture="showMsg(1)">
<button>div1</button>
<div @click="showMsg(2)">
<button>div2</button>
<div @click="showMsg(3)">
<button>div3</button>
</div>
</div>
</div>
<h1>测试self</h1>
<div @click="showMsg(1)">
<button>div1</button>
<div @click.self="showMsg(2)">
<button>div2</button>
<div @click="showMsg(3)">
<button>div3</button>
</div>
</div>
</div>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '#root',
data: {
name: '百度'
},
methods: {
jump() {
alert("将要跳转至" + this.name);
},
showMsg(number) {
console.log(number);
}
}
})
</script>
</body>
</html>
测试一:点击超链接不跳转
测试二:点击div3按钮,再控制台只输出div3;点击div2仍然冒泡
测试三:第一个点击超链接,不跳转;第二次点击,事件不生效,直接跳转到百度
测试四:点击div3,开启了事件捕获的再事件捕获阶段事件就执行了,所以顺序是div1、div3、div2
7.3 键盘事件
keyup
:用于绑定事件,用于按下并释放某个键时触发绑定事件keydown
:用于绑定事件,用于按下某个键时触发绑定事件key
:获取按键的名称keyCode
:获取按键的编码keyName
:获取按键的名称(少部分)
Vue常用按键别名:回车:enter 、删除:delete、退出:esc、空格:space、换行:tab、上\下\左\右:up\down\left\right
需要注意的是:除上述Vue自定义按键,其它的按键,①如果是一个单词就直接是按键名,需要首字母大写,比如:Alt、Enter、Shift……
②如果是多个单词,测试采用小写字母加-
连接,比如:caps-lock(大小写转换键)。同时③对于一些特殊的按键是无法绑定事件的,比如:调整音量的按键(F1,F2)、调整亮度的按键(F5,F6)……④当使用tab
键时,需要搭配keydown使用,因为tab键具有特殊功能,使用tab键会切换焦点,所以@keyup.tab
是用于无法触发事件的!⑤系统修饰键:Ctry
、Alt
、Shift
、Meta
(Win)这四个键,搭配@keyup,需要在按下系统修饰键的同时,按下其他非系统修饰键,然后再释放,事件才会被触发;搭配@keydown使用,正常触发事件
//给按键起别名(这里给回车键Enter起别名)
Vue.config.keyCodes.huiche = 13;
//系统修饰符还能接一个其它键
@keyup.ctry.y//只有当按下Ctry+y后释放y才能触发事件
示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>键盘事件</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="root">
<input @keyup="showCode" type="text" placeholder="请输入">
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '#root',
methods: {
showCode(e) {
//当按下回车(出车的编码是13),就打印输出input中的内容
// if (e.keyCode != 13) return;//可以使用@keyup.enter替代
console.log(e.target.value);
console.log(e.key, e.keyCode);
}
}
});
</script>
</body>
</html>
备注:e.target.value
的值不等于e.keyCode
,e.target.value只会展示按下字母和数字的按键的值,而e.keyCode会展示几乎所有的按键编码(好像只有一些特殊的按键,如:Fn等不会展示编码)
8、计算属性
计算属性是什么?
计算属性是Vue对插值表达式的一种优化,通过
computed
关键字将一个属性进行计算,然后再赋值给Vue对象,让Vue对象直接使用{{}}
进行引用为什么要使用计算属性?
在使用插值表达式进行取值时,有时候我们想要对表达式进行各种各样的操作(使用链式编程),可能会导致
{{}}
中的值十分复杂,虽然不会报错,但是会严重影响代码的可读性!这就需要使用计算属性来解决这种问题了。
-
方式一:直接使用插件表达式实现
备注:这里的例子过于简单,其实直接使用插件表达式也是没毛病的,但如果在实际开发中遇到需要在插件表达式中进行大量函数调用时,建议使用计算属性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>计算属性1</title> <!-- 引入开发版Vue --> <script src="./js/vue.js"></script> </head> <body> <div id="root"> 姓: <input type="text" v-model="firstName"> <br> 名: <input type="text" v-model="secondName"> <br> <!-- slice函数截取firstName前三个字符 --> 全名:<span>{{firstName.slice(0,3)}}-{{secondName}}</span> </div> </body> <script> const vm = new Vue({ el:'#root', data:{ firstName:'张', secondName:'三' } }) </script> </html>
效果展示:
-
方式二:使用methods进行实现
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>计算属性2</title> <!-- 引入开发版Vue --> <script src="./js/vue.js"></script> </head> <body> <div id="root"> 姓: <input type="text" v-model="firstName"> <br> 名: <input type="text" v-model="secondName"> <br> <!-- 注意函数的调用一定不能省略小括号 --> 全名:<span>{{fullName()}}</span> </div> </body> <script> const vm = new Vue({ el: '#root', data: { firstName: '张', secondName: '三' }, methods: { //当firstName或secondName发生改变时,fullName函数就会被调用 fullName() { return this.firstName.slice(0, 3) + '-' + this.secondName; } } }) </script> </html>
-
方式三:使用计算属性实现
备注:计算属性底层也是使用数据代理实现的,需要注意的是计算属性属于Vue对象(可以直接通过Vue调用),但不属于_data
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>计算属性3</title> <!-- 引入开发版Vue --> <script src="./js/vue.js"></script> </head> <body> <div id="root"> 姓: <input type="text" v-model="firstName"> <br> 名: <input type="text" v-model="secondName"> <br> 全名:<span>{{fullName}}</span> </div> </body> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data: { firstName: '张', secondName: '三' }, computed: { fullName: { //get的调用时机:1)初次调用,get被调用 2)所依赖的data数据发生改变时get被调用(不改变就直接使用缓存) get() { // console.log(this);//此处的this是Vue return this.firstName + '-' + this.secondName; }, //set方法,用于对fullName这个计算属性进行修改 set(value) { console.log("set方法被调用了"); const arr = value.split("-"); this.firstName = arr[0]; this.secondName = arr[1]; } } } }); </script> </html>
知识拓展
-
methods实现和计算属性实现的比较:使用计算属性实现性能更高,因为计算属性使用了缓存技术,当同一时间内调用同一个计算属性,且这段时间内该计算不发生该改变,不需要执行get方法,而是直接去缓存中取数据,避免了重复计算;而methods就需要重复调用方法
-
计算属性的简写形式:
computed: { /*fullName: { get() { return this.firstName + '-' + this.secondName; } }*/ //简写:只针对只读不写的计算属性,也就是只有get函数的计算属性 /*fullName: function() { return this.firstName + '-' + this.secondName; }*/ //进一步简写: fullName() { return this.firstName + '-' + this.secondName; } }
-
总结
计算属性:
1.定义:要用的属性不存在,要通过已有属性计算得来。
2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
3.get函数什么时候执行?
(1).初次读取时会执行一次。
(2).当依赖的数据发生改变时会被再次调用。
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5.备注:
1.计算属性最终会出现在vm上,直接读取使用即可。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。
9、监视属性
什么是监视属性?
也称侦听属性,是指使用
watch
对于属性进行监控,类似于Java中的监听器监视属性的作用?
通过检测属性的改变,触发相应的事件
示例:
实现应该天气切换效果
使用监视属性:点击跳转
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>天气切换</title>
<!-- 引入开发版Vue -->
<script src="./js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>天气很{{info}}</h2>
<button @click="changeWeather()">切换天气</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '#root',
data: {
isHot: true
},
computed: {
info() {
return this.isHot ? "热" : "冷";
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
});
</script>
</body>
</html>
效果展示:
注意事项:
这里可以直接将方法代码直接写在
@click=""
中,但是需要注意的是直接写在Vue的指令中,用到的变量或方法只会从Vue对象中寻早,如果Vue中没有找到就会到Vue原型中找;如果是写在方法methods
中则会默认在Window
对象中寻找,当使用this
时才会到Vue对象中寻找,示例:对于第二个按钮,效果和第一个按钮是一致的;但是对于第三个按钮,运行不会报错,但在触发点击事件后就会报错
alert方法不存在
!原因是由于alert
函数是属于Window对象中的,并不是Vue对象中的,在这里使用this
、window
关键字也无效最终建议:如果是一些简单是语句,且只用到Vue对象或Vue原型中的属性,可以直接在使用第二个按钮的写法,对于复杂的语句最好使用第一个按钮的写法。所有的Vue函数都必须使用普通函数的形式
9.2 快速入门
示例:
前面的案例我们学到了函数的两种写法,现在就让我们看看使用监视属性后的效果吧
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>监视属性2</title>
<!-- 引入开发版Vue -->
<script src="./js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>天气很{{info}}</h2>
<button @click="changeWeather()">切换天气</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '#root',
data: {
isHot: true
},
computed: {
info() {
return this.isHot ? '热' : '冷';
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
//方式一:
/* watch: {
isHot: {
//程序初识时就执行handler(换句话说,就是当我们加上这个属性,保存后就执行handler函数)
immediate: true,
//当isHot属性发生改变时,handler函数就被调用
handler(newValue, oldValue) {
console.log('isHot未修改前:' + oldValue + ';' + 'isHot修改后:' + newValue);
}
}
} */
});
//方式二:(方式一中的isHot其实也是需要加引号的,只是简写了)
vm.$watch('isHot', {
immediate: true,
handler(newValue, oldValue) {
console.log('isHot未修改前:' + oldValue + ';' + 'isHot修改后:' + newValue);
}
});
</script>
</body>
</html>
效果展示:
9.2 深度监视
什么是深度监视?
深度监视是指对于多层次结构的属性,其中某一个属性发生改变时,就能得知该属性发生了改变。
比如:numbers={a:1,b:{c:2,e:3}},numbers中的b属性中的c属性发生了改变时,我们能立刻得知numbers发生了改变的这一过程
深度监视有什么用?
监控属性的改变,可以做出相应的事件
示例:
使用深度监视检测多层次结构中属性的变化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>监视属性2</title>
<!-- 引入开发版Vue -->
<script src="./js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>天气很{{info}}</h2>
<button @click="changeWeather()">切换天气</button>
<hr>
<h2>a的值是:{{numbers.a}}</h2>
<button @click="numbers.a++">点我让a值加1</button>
<button @click="numbers={a:666,b:888}">点我改变numbers的地址</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: '#root',
data: {
isHot: true,
numbers: {
a: 1,
b: 2
}
},
computed: {
info() {
return this.isHot ? '热' : '冷';
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
watch: {
isHot: {
//程序初识时就执行handler(换句话说,就是当我们加上这个属性,保存后就执行handler函数)
immediate: true,
//当isHot属性发生改变时,handler函数就被调用
handler(newValue, oldValue) {
console.log('isHot未修改前:' + oldValue + ';' + 'isHot修改后:' + newValue);
}
},
//监视多级结构中某个属性的变化
'numbers.a': {
handler(newValue, oldValue) {
console.log('a未修改前:' + oldValue + ';' + 'a修改后:' + newValue);
}
},
//不开启深度监视,只有当numbers的地址发生改变时,才会调用handler函数
/* numbers: {
handler() {
console.log('numbers的地址发生了改变');
}
}, */
//Vue默认能够检测多层次数据结构的改变,但是watch函数默认不能检测
numbers: {
//开启watch函数的深度检测,当numbers中的属性或地址发生改变时,调用handler函数
deep: true,
handler() {
console.log('numbers中的属性发生了改变');
}
}
}
});
</script>
</body>
</html>
效果展示:
9.3 知识拓展
主要拓展三个点:
- 深度监视的简写形式
- 使用深度监视实现前面计算属性的案例
- 监视属性和计算属性的比较
-
深度监视的简写形式:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>监视属性4</title> <!-- 引入开发版Vue --> <script src="./js/vue.js"></script> </head> <body> <div id="root"> <h2>天气很{{info}}</h2> <button @click="changeWeather()">切换天气</button> </div> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data: { isHot: true }, computed: { info() { return this.isHot ? '热' : '冷'; } }, methods: { changeWeather() { this.isHot = !this.isHot; } }, watch: { //完整写法: /* isHot: { //程序初识时就执行handler(换句话说,就是当我们加上这个属性,保存后就执行handler函数) // immediate: true, //开启watch的深度监视 // deep: true, //当isHot属性发生改变时,handler函数就被调用 handler(newValue, oldValue) { console.log('isHot未修改前:' + oldValue + ';' + 'isHot修改后:' + newValue); } } */ //简写:(只需要handler时才能使用!) isHot(newValue, oldValue) { console.log('isHot未修改前:' + oldValue + ';' + 'isHot修改后:' + newValue); } } }); //完整写法 /* vm.$watch('isHot', { //程序初识时就执行handler(换句话说,就是当我们加上这个属性,保存后就执行handler函数) // immediate: true, //开启watch的深度监视 // deep: true, //当isHot属性发生改变时,handler函数就被调用 handler(newValue, oldValue) { console.log('isHot未修改前:' + oldValue + ';' + 'isHot修改后:' + newValue); } }); */ //简写:(只需要handler时才能使用!) vm.$watch('isHot', function(newValue, oldValue) { console.log('isHot未修改前:' + oldValue + ';' + 'isHot修改后:' + newValue); }); </script> </body> </html>
备注:使用vm配置监听属性和直接在watch中配置监听属性,是等价的。简写的代价是只能调用handler函数,无法配置其它属性
-
使用深度监视实现前面计算属性的案例:
也就是实现效果联动
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>监视属性4</title> <!-- 引入开发版Vue --> <script src="./js/vue.js"></script> </head> <body> <div id="root"> <h2>天气很{{info}}</h2> <button @click="changeWeather()">切换天气</button> </div> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data: { isHot: true }, computed: { info() { return this.isHot ? '热' : '冷'; } }, methods: { changeWeather() { this.isHot = !this.isHot; } }, watch: { //完整写法: /* isHot: { //程序初识时就执行handler(换句话说,就是当我们加上这个属性,保存后就执行handler函数) // immediate: true, //开启watch的深度监视 // deep: true, //当isHot属性发生改变时,handler函数就被调用 handler(newValue, oldValue) { console.log('isHot未修改前:' + oldValue + ';' + 'isHot修改后:' + newValue); } } */ //简写:(只需要handler时才能使用!) isHot(newValue, oldValue) { console.log('isHot未修改前:' + oldValue + ';' + 'isHot修改后:' + newValue); } } }); //完整写法 /* vm.$watch('isHot', { //程序初识时就执行handler(换句话说,就是当我们加上这个属性,保存后就执行handler函数) // immediate: true, //开启watch的深度监视 // deep: true, //当isHot属性发生改变时,handler函数就被调用 handler(newValue, oldValue) { console.log('isHot未修改前:' + oldValue + ';' + 'isHot修改后:' + newValue); } }); */ //简写:(只需要handler时才能使用!) vm.$watch('isHot', function(newValue, oldValue) { console.log('isHot未修改前:' + oldValue + ';' + 'isHot修改后:' + newValue); }); </script> </body> </html>
效果展示:
-
监视属性和计算属性的比较
从上面那个案例,可以发现使用监视属性
watch
实现联动效果好像比计算属性computed
要复杂的多,这么说是不是如果实现联动效果,是不是可以全部使用计算属性来实现?答案是否定的!在一些特殊的地方还是需要使用监视属性来实现,比如延迟动态改变属性值,具体见下面代码👇
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>计算属性5</title> <!-- 引入开发版Vue --> <script src="./js/vue.js"></script> </head> <body> <div id="root"> 姓: <input type="text" v-model="firstName"> <br> 名: <input type="text" v-model="secondName"> <br> 全名: <span>{{fullName}}</span> </div> </body> <script> Vue.config.productionTip = false; const vm = new Vue({ el: '#root', data: { firstName: '张', secondName: '三', fullName: '张-三' } /* , computed: { fullName: function() { //直接无效,因为fullName的Getter没有返回值,这个返回值给了setTimeout函数 setTimeout(() => { return this.firstName.slice(0, 3) + '-' + this.secondName; }) } } */ , watch: { firstName(newValue) { setTimeout(() => { // console.log(this);//指向Vue this.fullName = newValue.slice(0, 3) + '-' + this.secondName; }, 1000) //这里一定要使用箭头函数,否则this是指向window的(这和methods中的函数是相反的!) /* setTimeout(function() { console.log(this);//指向window this.fullName = newValue.slice(0, 3) + '-' + this.secondName; }) */ }, secondName(newValue) { this.fullName = this.firstName + '-' + newValue; } } }); </script> </html>
知识点:普通函数有自己的this,箭头函数没有自己的this,当使用箭头函数式,函数内部的this会从上一级依次寻找
总结
- computed能完成的功能,watch都可以完成
- watch能完成的功能,compute不一定能够完成,例如:watch可以进行异步操作(上面的延迟功能)
- 所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm对象或组件实例对象
- 所有不被Vue管理的函数(例如:定时器的回调函数、Ajax的回调函数、Promise的回调函数……),最好写成箭头函数,这样this的指向才能是vm对象或组件的实例化对象
10、动态绑定样式
本小节主要学习:
- 动态修改
class
属性- 动态修改
style
属性
-
动态修改
class
属性:实现方式:
:class=""
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>样式绑定</title> <!-- 引入开发版Vue --> <script src="./js/vue.js"></script> <style> .zero { color: aqua; } .first { width: 100px; height: 100px; background-color: pink; } .second { width: 100px; height: 100px; border: 2px solid greenyellow; border-radius: 10px; } .third { color: red; } </style> </head> <body> <div id="app"> <div class="zero" :class="style"> 你好,Vue </div> <button @click="changeStyle">随机切换一种样式</button> <button @click="addStyle">添加一种样式</button> <button @click="removeStyle">移除一种样式</button> <button @click="addAll">添加多个样式</button> {{i}} </div> <script> const vm = new Vue({ el: '#app', data: { i: 2, arr: ['first', 'second', 'third'], style: [] }, methods: { changeStyle() { //只有style只有一种样式时,才能进行随机切换 if (this.i == this.arr.length - 1) { const index = Math.floor(Math.random() * 3); //index取值范围:0~1 this.style.splice(0, this.style.length); //清空数组 this.style.push(this.arr[index]); } }, addStyle() { if (this.i > -1) { this.style.push(this.arr[this.i]); this.i--; } }, removeStyle() { //从最后数组最后一个元素开始删 if (this.i < this.arr.length - 1) { this.style.shift(); this.i++; } else if (this.style.length != 0) { this.style.splice(0, this.style.length); //清空数组 } }, addAll() { //注意,不能去掉{{i}},必须是一个动态的数据,使用{{}}符号 for (let j = 0; j < this.arr.length; j++) { this.style[j] = this.arr[j]; } this.i = -1; } } }); </script> </body> </html>
效果展示:
-
动态修改
style
属性:实现方式:
:style
注意事项:对于两个名字的style内置属性,需要切换成小驼峰的方式
<div id="app"> <!-- 不使用Vue,硬编码 --> <h1 style="font-size: 16px;">张三</h1> <!-- 方式一 --> <h1 :style="{fontSize: fSize+'px'}">张三</h1> <!-- 方式二 --> <h1 :style="fontSizeObj">张三</h1> </div> <script> let vm = new Vue({ el: '#app', data: { fSize: 36, fontSizeObj: { // 注意这里的fontSize不能随便命名,必须有对应的CSS属性 fontSize: '36px' } } }); </script>
效果展示:
11、条件渲染
-
v-if="表达式"
:当表达式为false时,不显示,前端无法查看到DOM结构。可以搭配v-else-if="表达式"
和v-else
使用注意事项:当我们搭配后面两个指令时,中间一定不要被其它标签打断
<div v-if="n==1">1</div> <div v-else-if="m==2">2</div> <!-- 后面无法生效,当n变成3时还会报错 --> <div>@</div> <div v-else="n==3">3</div>
可以搭配
template
标签使用,不会影响DOM结构 -
v-show="表达式"
:当表达式为false时,不显示(等价于display:none),DOM结构仍然存在,前端可以查看到DOM结构
显示、隐藏切换频率高的用v-show
,切换频率低的用v-if
。因为添加和删除DOM结构需要消耗时间
12、列表渲染
测试使用
v-if
指令,它可以遍历数组、JSON对象、数字、字符串
<div id="app">
<ul>
<!-- 遍历数组(这里的in可以用of代替) -->
<li v-for="p in persons" :key="p.id">
{{p.name}}-{{p.age}}
</li>
<!-- 遍历数组 -->
<li v-for="(p,index) in persons" :key="index">
{{p}}-{{index}}
</li>
<!-- 遍历JSON对象 -->
<li v-for="(val,key) in student" :key="key">
{{val}}-{{key}}
</li>
<!-- 遍历指定次数 -->
<li v-for="(number,index) in 3" :key="index">
{{number}}-{{index}}
</li>
<!-- 遍历字符串 -->
<li v-for="(str,index) in strs" :key="index">
{{str}}-{{index}}
</li>
</ul>
</div>
</body>
<script>
new Vue({
el: '#app',
data: {
persons: [{
id: '1',
name: '张三',
age: 18
}, {
id: '2',
name: '李四',
age: 19
}, {
id: '3',
name: '王五',
age: 20
}],
student: {
name: '张三',
age: 18,
gender: '男'
},
strs: 'abc'
}
});
</script>
效果展示:
13、key的作用和原理
Vue中有虚拟DOM,能够实现DOM的复用,复用的核心算法是diff算法。
首先,使用了Vue的标签,会先将你写的标签转成虚拟DOM,暂存在内存中,然后再转成真实DOM(能够再浏览器中查看到的DOM);当你的代码(也就是你写的标签)发生改变时,会重新转成虚拟DOM,然后从内存中使用diff算法进行虚拟DOM对比,当发现两个虚拟DOM的id一致时,他就会直接使用之前的虚拟DOM,这就避免了DOM的转换所消耗的时间,大大提高代码效率。
显然由于这个原因,所以要保障key
值的唯一性,否则Vue就无法进行有效的虚拟DOM对比
示例:
<div id="app">
<button @click.once="add">点击添加一个输入框</button>
<ul>
<!-- 遍历数组 -->
<li v-for="(p,index) in persons" :key="index">
{{p.id}}--{{p.name}}--{{index}}
<input type="text">
</li>
</ul>
</div>
</body>
<script>
new Vue({
el: '#app',
data: {
persons: [{
id: '1',
name: '张三',
age: 18
}, {
id: '2',
name: '李四',
age: 19
}, {
id: '3',
name: '王五',
age: 20
}]
},
methods: {
add() {
let person = {
id: '4',
name: '赵六',
age: '19'
};
//将这个JSON对象添加再数组的前面
this.persons.unshift(person);
}
}
});
</script>
结果展示:
这是由于key的顺序发生了改变,我再添加一个新的输入框是,是将新的输入框添加再最前面的,而key是index,导致旧的虚拟DOM往后移了一位,产生了错位
总结:
面试题:react、vue中的key有什么作用?(key的内部原理)
1. 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
2.对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。
3. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
4. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。
14、过滤
14.1 列表过滤
本小节主要实现:模糊查询、以及降序和升序排序
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>过滤器</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<h2>人员列表</h2>
<input type="text" placeholder="请输入要搜索的名字" v-model="keywords">
<button @click="sortType=0">原始排序</button>
<button @click="sortType=1">升序排序</button>
<button @click="sortType=2">降序排序</button>
<h2>使用监视属性实现模糊查询</h2>
<ul>
<li v-for="(p,index) in searchPersons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
<hr>
<h2>使用计算属性实现模式查询</h2>
<ul>
<li v-for="(p,index) in computedPersons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script>
new Vue({
el: '#app',
data: {
keywords: '',
/* 0:原始排序;1:升序;2:降序 */
sortType: 0,
persons: [{
id: 1,
name: '马冬梅',
age: 23,
sex: '女'
}, {
id: 2,
name: '周冬雨',
age: 18,
sex: '女'
}, {
id: 3,
name: '周杰伦',
age: 33,
sex: '男'
}, {
id: 4,
name: '周润发',
age: 31,
sex: '男'
}],
searchPersons: []
},
// 使用监视属性实现模糊查询
watch: {
keywords: {
// 初始化时,就调用handler方法(当keyWords为null时,返回0)
immediate: true,
handler(val) {
// 当val非null,且val不存在p.name中,就会返回-1
this.searchPersons = this.persons.filter((p) => {
return p.name.indexOf(val) != -1
});
//由于监视属性,必须要keywords发生变化,才会执行handler函数,所以无法实现排序功能
/* const arr = this.persons.filter((p) => {
return p.name.indexOf(val) != -1
})
if (this.sortType) {
//sortType!=0,进行排序
arr.sort((p1, p2) => {
return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
})
}
this.searchPersons = arr */
}
}
},
// 使用计算属性实现模式查询
computed: {
computedPersons() {
/* return this.persons.filter((p) => {
return p.name.indexOf(this.keywords) != -1
}) */
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keywords) != -1
});
if (this.sortType) {
//sortType!=0,进行排序
arr.sort((p1, p2) => {
return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age
})
}
return arr
}
}
});
</script>
</body>
</html>
效果展示:
备注:就实现方式而言,两相比较,使用计算属性更优
14.2 过滤器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.6/dayjs.min.js"></script>
</head>
<div id="app">
当前时间戳:<input type="text" size="100" v-model="nowTime"><br/> 计算属性实现时间戳转换:
<input type="text" v-model="computedTime" size="100"><br/> methods实现时间戳转换:
<input type="text" v-model="methodsTime" @click="getMethodsTime" size="100"><br/> 过滤器实现时间戳转换(无参数):
<span>{{nowTime | getFilterTime}}</span><br/> 过滤器实现时间戳转换(有参数):
<span>{{nowTime | getFilterTime('YYYY-MM-DD HH:mm:ss')}}</span><br/> 多个过滤器:
<span>{{nowTime | getFilterTime('YYYY-MM-DD HH:mm:ss') | secondFilter}}</span><br/> 搭配v-bind使用:
<input type="text" :value="msg | secondFilter"><br>
</div>
<script>
Vue.config.productionTip = false;
// 全局过滤器
Vue.filter('myFilter', function(val) {
return dayjs(val).format(str)
});
const vm = new Vue({
el: '#app',
data: {
nowTime: new Date,
methodsTime: '',
msg: '搭配v-bind使用过滤器'
},
computed: {
computedTime() {
return dayjs(this.nowTime).format('YYYY-MM-DD HH:mm:ss')
}
},
mounted() {
this.getMethodsTime();
},
methods: {
getMethodsTime() {
this.methodsTime = dayjs(this.nowTime).format()
}
},
filters: {
getFilterTime(val, str) {
//当str为null时,format会使用dayjs默认的格式
return dayjs(val).format(str)
},
secondFilter(val) {
//先执行上面那个过滤器,执行后的结果通过val传入这个过滤器
return val.slice(0, 4)
}
}
});
</script>
<body>
</body>
</html>
效果展示:
15、数据更新时的底层原理
本小节
-
给对象新增一个属性失效,Vue无法探测到直接添加的属性的(这是因为Vue底层实现数据监视,是使用数据代理,而数据代理需要属性具有getter和setter,如果直接给一个数据新增一个属性,不会进行数据加工,也就是转到_data中,从而不会有getter和setter,所以也就是会逃避vue的检测),而想要之后添加属性,可以使用以下两个API
-
vue.set(target,attribute,val)
-
vm.$set(target,attribute,val)
-
注意:target不能是vm,也不能是data,必须是data中的一个属性
<div id="app">
<button @click="addSex">给学生添加一个性别属性</button>
<h3>学生姓名:{{student.name}}</h3>
<h3>学生年龄:{{student.age}}</h3>
<h3 v-if="student.sex">学生性别:{{student.sex}}</h3>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
student: {
name: '张三',
age: 18,
teacher: {
name: '李四',
age: 32
}
}
},
methods: {
addSex() {
//错误示范:
// this.student.sex = '男';
//方式一:使用Vue的API
Vue.set(this.student, 'sex', '男');
//方式二:使用vm的API
// this.$set(this.student, 'sex', '男');
}
}
});
</script>
效果展示:略……
-
修改数组的值失效,对于数组而言,只有调用数组自身方法时,Vue才能够监测到(因为数组的修改,无法自动新增getter和setter进行实现,所以Vue就写死规定,只有通过数组自带的七个方法进行数据修改,才能监测到),直接使用赋值号
=
修改数组,Vue并不能够进行监测。数组自身拥有的七个方法:
push
(向数组的末尾添加一个或多个元素,并返回新的长度)\pop
(删除数组最后一个元素,并返回值)\shift
(把数组的第一个元素从其中删除,并返回第一个元素的值)\unshift
(向数组的开头添加一个或更多元素,并返回新的长度)\splice
\(替换数组指定位置的元素)\sort
\(排序数组)\reverse
(反转数组)<div id="app"> <button @click="addSex">给学生添加一个性别属性</button> <h3>学生姓名:{{student.name}}</h3> <h3>学生年龄:{{student.age}}</h3> <h3 v-if="student.sex">学生性别:{{student.sex}}</h3> <button @click="updateHobby">点击修改学生爱好</button> <h3>学生爱好:</h3> <ul> <li v-for="(h, index) in hobby" :key="index">{{h}}</li> </ul> </div> <script> const vm = new Vue({ el: '#app', data: { student: { name: '张三', age: 18, teacher: { name: '李四', age: 32 } }, hobby: ['抽烟', '喝酒', '烫头'] }, methods: { updateHobby() { //错误示范,直接报错:Cannot set properties of undefined (setting '0')" //this.student.hobby[0] = "学习"; //方式一: this.student.hobby.splice(0, 1, '学习'); // 方式二: Vue.set(this.student.hobby, 1, '学习'); //方式三: // this.$set(this.student.hobby, 1, '学习'); } } }); </script>
效果展示:
16、内置指令和自定义指令
拓展知识:
对于表单数据的收集:
- 对于复选框(checkbox),如果不设置vlaue,Vue默认是获取它的checked属性的值
- 对于单选框(radio),如果设置vlaue,Vue默认是无法得到单选框的值的,需要设置value
- 数据转成JSON字符串,使用API:
JSON.stringify()
- 表单提交时想执行函数,可以直接再form标签上使用
@submit="函数名"
,如果不想要表单提交,可以加后缀.prevent
- v-model修饰符:
v-model.number
:自动将前端输入的字符转成数字,可以搭配HTML的type="number"
一起使用v-model.trim
:去掉输入框前后的空格,中间的空格无法去除v-model.lazy
:失去焦点,才收集数据(默认是实时收集数据的)
16.1 内置指令
内置指令介绍:
我们学过的指令:
v-bind : 单向绑定解析表达式, 可简写为 :xxx
v-model : 双向数据绑定
v-for : 遍历数组/对象/字符串
v-on : 绑定事件监听, 可简写为@
v-if : 条件渲染(动态控制节点是否存存在)
v-else : 条件渲染(动态控制节点是否存存在)
v-show : 条件渲染 (动态控制节点是否展示)
v-text指令:
1.作用:向其所在的节点中渲染文本内容。
2.与插值语法的区别:v-text会替换掉节点中的内容,{{xx}}则不会。
v-html指令:
1.作用:向指定节点中渲染包含html结构的内容。
2.与插值语法的区别:
(1).v-html会替换掉节点中所有的内容,{{xx}}则不会。
(2).v-html可以识别html结构。
3.严重注意:v-html有安全性问题!!!!
(1).在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
(2).一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
v-cloak指令(没有值):
1.本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
2.使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题。
v-once指令:
1.v-once所在节点在初次动态渲染后,就视为静态内容了。
2.以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。
v-pre指令:
1.跳过其所在节点的编译过程。
2.可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
16.2 自定义指令
Vue中的指令本质就是一个函数,对于自定义指令,我们可以使用
directives
进行配置
实现方式一:以函数的形式实现
示例,定义一个v-big指令,和v-text功能类似,但会把绑定的值放大
实现方式二:以对象的形式实现
示例,定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
如果想要拿到父元素并修改父元素的样式、让指令所在DOM获取焦点,只能通过方式二来实现。总的来讲:方式二的使用范围大于方式一,但方式一更加简便,且适合大部分常规操作,方式一使用的更加普遍
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
</head>
<div id="app">
<!-- 原生v-text -->
<h2>当前n的值是:<span v-text="n"></span></h2>
<!-- 自定义v-big -->
<h2>将n放大十倍后的值是:<span v-big="n"></span></h2>
<button @click="n++">点我n值加1</button><br/>
<!-- 原生v-bind -->
<input type="text" :value="n"><br>
<!-- 自定义v-fbind -->
<input type="text" v-fbind="n"><br>
<!-- 自定义的全局指令 -->
<input type="text" v-big2="n"><br>
<input type="text" v-f-bind2="n">
</div>
<body>
<script>
Vue.config.productionTip = false
//全局指令
Vue.directive('big2', function(element, binding) {
element.value = binding.value * 10
});
Vue.directive('f-bind2', {
bind(element, binding) {
element.value = binding.value
},
inserted(element, binding) {
element.focus()
},
update(element, binding) {
element.value = binding.value
}
})
const vm = new Vue({
el: '#app',
data: {
n: 1
},
directives: {
//方式一:使用函数。这种方式更加精简,但是对于一些细节无法处理
big(element, binding) {
console.log(this) //注意,此处的this是window并不是vm
//element是指令所在的DOM(即span标签),binding是绑定对象,含有指令所在的DOM的所有属性
element.innerText = binding.value * 10
//备注:一旦模板发生更新(也就是id=app这个区域的DOM发生更新),big函数就会被执行
},
//错误演示,使用方式一无法使input标签默认获取焦点(这是由于指令的执行时机决定的)
/* fbind(element, binding) {
element.innerText = binding.value
element.focus()
} */
//方式二:使用对象的形式实现
fbind: {
//指令与元素成功绑定时执行(一上来就执行)。第一执行
bind(element, binding) {
element.value = binding.value
},
//指令所在DOM被展示到页面时执行,第二执行
inserted(element, binding) {
element.focus()
},
//模板发生更新时执行,第三执行
update(element, binding) {
element.value = binding.value
}
}
}
})
</script>
</body>
</html>
备注:
- 指令定义时,不要加v-,指令使用时,需要加v-
- 指令命名时,使用kebab-case方式,不要使用cameCase方式命名,使用kebab-case命名的指令,在定义时记得使用引号包裹
效果展示:
17、Vue实例化和生命周期
共8个四队钩子方法,对应Vue的8个生命周期
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="app">
<h2 :style="{opacity}">欢迎学习Vue</h2>
<button @click="stop">停止渐变</button>
</div>
<script>
const vm = new Vue({
el: '#app',
data: {
opacity: 1
},
//Vue还未实例化,还未进行数据代理,无法通过vm访问到data和methods
beforeCreate() {
console.log("beforeCreate" + this);
// debugger;
},
//Vue实例化完成,进行了数据代理,可以通过vm访问到data和methods,但还未生成虚拟DOM
created() {
console.log("created" + this);
// debugger;
},
//页面呈现的都是未经Vue编译的DOM,此时虚拟DOM已生成,但未加载到页面上
beforeMount() {
//注意:此处的DOM操作最终都会无效,可以看前面的图加以理解
console.log("beforeMount" + this);
document.querySelector('h2').innerHTML = "你好Vue";
// debugger;
},
//Vue完成模板的==初次==解析并把真实的DOM元素放入页面后(即挂载完毕)调用mounted函数
mounted() {
console.log("mounted" + this)
this.change();
},
//模板更新前(页面和数据没有保持同步,内存中的数据已发生变化,但是页面中的数据没有发生变化)
beforeUpdate() {
console.log("beforeUpdate" + this);
// debugger;
},
//模板更新就调用updated钩子方法,页面和数据同步
updated() {
console.log("updated" + this);
//https://v2.cn.vuejs.org/v2/api/#vm-destroy
// this.$destroy()
// debugger;
},
methods: {
change() {
this.timer = setInterval(() => {
this.opacity -= 0.01
if (this.opacity <= 0) this.opacity = 1
})
},
stop() {
clearInterval(this.timer);
}
},
beforeDestroy() {
console.log("beforeDestroy" + this);
// debugger;
},
destroyed() {
console.log("destroyed" + this);
// debugger;
},
});
//外部调用定时器实现(定时器直接会被浏览器的JS引擎调用)
/* setInterval(() => {
vm.opacity -= 0.01
if (vm.opacity <= 0) vm.opacity = 1
}) */
</script>
</body>
</html>
测试beforeCreate钩子方法的作用:
测试created钩子方法的作用:
测试beforeMount钩子方法的作用:
各阶段:
备注:mounted
:发送ajax请求,启动定时器等异步任务;beforeDestory
:做收尾工作,如:清除定时器
参考资料:
- 尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通:B站学习视频
- 介绍 — Vue.js (vuejs.org):Vue2官方文档
- 简介 | Vue.js (vuejs.org):Vue3官方文档
- Vue.js 模板语法 | 菜鸟教程 (runoob.com)
- Vue.js是什么?它有什么特点?:C语言网Vue教程
- 编程风格之声明式与命令式的区别
- 什么是MVVM?
- vue中的挂载是什么意思?
- (8条消息) 箭头函数与普通函数的区别详解
- vue中data是干嘛的? 有几种写法 ? 有什么区别?
在此致谢(^_^ゞ
数据的双向绑定,指当View的数据发生改变时,Model的数据也会发生改变,反之亦然 ↩︎
组件化模式,就是将各个不同的部分进行封装,从而降低给部分的耦合度,这样每个部分成为一个组件,具有更高地移植性 ↩︎
原生的JS是命令式编码,它关注编程的实现步骤,需要一步一步地编写代码向程序下达命令;而声明式编码是告诉程序,你该干嘛,具体实现由程序自己实现 ↩︎