数据劫持
数据劫持:指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。比较典型的是Object.defineProperty()和ES2016中新增的Proxy对象。数据劫持最著名的应该是双向绑定。
Vue的双向绑定就是利用Object.defineProperty(),并且把内部解耦为Observer,Dep,并使用Watcher相连。
数组的这些方法不会触发set:push,pop,shift,unshift,splice,sort,reverse。Vue把这些方法定义为变异方法,指的是会修改原来数组的方法。与之对应的是非变异方法,例如filter,concat,slice等,它们都不会修改原始数组,而会返回一个新的数组。
收集表单数据
若:<input type=“text”/>,则v-model收集的是value值,用户输入的就是value值。
若:<input type=“radio”/>,则v-model收集的是value值,且要给标签配置value值。
若:<input type=“checkbox”/>
1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
2.配置input的value属性:
(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
(2)v-model的初始值是数组,那么收集的的就是value组成的数组
v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤
<div id="root">
<form @submit.prevent="demo">
账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
密码:<input type="password" v-model="userInfo.password"> <br/><br/>
年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
性别:
男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
爱好:
学习<input type="checkbox" v-model="userInfo.hobby" value="study">
打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
<br/><br/>
所属校区
<select v-model="userInfo.city">
<option value="">请选择校区</option>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
<option value="wuhan">武汉</option>
</select>
<br/><br/>
其他信息:
<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.baidu.com">《用户协议》</a>
<button>提交</button>
</form>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{
userInfo:{
account:'',
password:'',
age:18,
sex:'female',
hobby:[],
city:'beijing',
other:'',
agree:''
}
},
methods: {
demo(){
console.log(JSON.stringify(this.userInfo))
}
}
})
</script>
过滤器
-
过滤器·:对要显示的数据进行特定格式化再显示(适用于一些简单逻辑的处理)。
-
注册过滤器: Vue.filter(name,callback) 或 new Vue{filters:{}}
-
使用过滤器:{{xxx | 过滤器名}} 或 v-bind:属性=“xxx|过滤名”
-
过滤器可以接收额外参数,多个过滤器可以串联
-
并没有改变原本的数据,只是产生新的对应数据
<body>
<!-- 容器 -->
<div id="root">
<h2>显示格式化后的时间</h2>
<h3>计算属性实现</h3>
<h3>现在是:{{fmtTime}}</h3>
<h3>methods实现</h3>
<h3>现在是:{{getFmTime}}</h3>
<h3>过滤器实现</h3>
<h3>现在是:{{time | timeFormater}}</h3>
<h3>过滤器实现(传参)</h3>
<h3>现在是:{{time | timeFormater('YYYY_MM_DD')}}</h3>
<h3 :x="msg | mySlice">尚硅谷</h3>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4);
});
//创建vue实例
const vm = new Vue({
el: '#root',
data: {
time:1683555650134,
},
//计算属性
computed: {
fmtTime:() {
//dayjs库
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss');
}
},
//methods
methods:{
getFmTime() {
return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss');
}
},
//局部过滤器
filters:{
timeFormater(value,str='YYYY年MM月DD日 HH:mm:ss'){
return dayjs(value).format(str);
}
}
});
new Vue({
el:'#root2',
data:{
msg:'hello,atguigu!'
}
});
</script>
内置指令
v-bind: 单向绑定解析表达式,可简写为 :xxx
v-model:双向数据绑定
v-for: 遍历数组、对象、字符串
v-if:条件渲染(动态控制节点是否存在)
v-else:条件渲染(动态控制节点是否存在)
v-show: 条件渲染(动态机制节点是否展示)
v-text指令:向其所在的节点中渲染文本内容
v-html指令: 向指定节点中渲染包含html结构的内容
v-once指令:节点初次动态渲染后,就视为静态内容
c-cloak指令:解决网速慢时页面展示出{{xxx}}的问题
v-pre指令:跳过其所在节点的编译过程
v-text
- 向其所在节点渲染文本内容
- 与插值语法的区别:v-text会替换掉节点中的内容,{{xxx}}不会
<body>
<!-- 容器 -->
<div id="root">
<div>你好,{{name}}</div>
<div v-text="name"></div>
<div v-text="str"></div>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//创建vue实例
const vm = new Vue({
el: '#root',
data: {
name: '007',
str:'<h3>你好!!!</h3>'
},
});
</script>
v-html
- 向指定节点中渲染包含html结构的内容
- 与插值语法的区别:
- v-html会替换掉节点中所有的内容,{{xxx}}不会
- v-html可以识别html结构
- v-html有安全性问题
- 在网站上动态渲染任意HTML是非常危险的,任意导致XSS攻击
- 一定要在可信的内容上使用v-html,永远不要在用户提交的内容上
<body>
<!-- 容器 -->
<div id="root">
<div>你好,{{name}}</div>
<div v-html="str"></div>
<div v-html="str2"></div>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//创建vue实例
const vm = new Vue({
el: '#root',
data: {
name: '007',
str:'<h3>你好!!!</h3>',
str2:'<p>hello world</p>'
},
});
</script>
v-cloak
- 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
- 使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}的问题
<!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>
<style>
[v-cloak] {
display: none;
}
</style>
<!-- 引入Vue -->
<script src="../js/vue.js"></script>
</head>
<body>
<!-- 容器 -->
<div id="root">
<h2 v-cloak>{{name}}</h2>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//创建vue实例
const vm = new Vue({
el: '#root',
data: {
name: '007',
}
});
</script>
</html>
v-once
- v-once所在节点在初次动态渲染后,就视为静态内容了
- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
<body>
<!-- 容器 -->
<div id="root">
<h2 v-once>初始化的值为:{{n}}</h2>
<h3>当前值为:{{n}}</h3>
<button @click="n++">点我加一</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//创建vue实例
const vm = new Vue({
el: '#root',
data: {
n:1
}
});
</script>
v-pre
- 跳过其所在节点的编译过程
- 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
<body>
<!-- 容器 -->
<div id="root">
<h2 v-pre>初始化的值</h2>
<h3>当前值为:{{n}}</h3>
<button @click="n++">点我加一</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//创建vue实例
const vm = new Vue({
el: '#root',
data: {
n:1
}
});
</script>
自定义一个指令
回顾一个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>初始Vue</title>
<style>
.demo{
background-color: orange;
}
</style>
</head>
<body>
<button id="btn">点我增加一个输入框</button>
<script type="text/javascript">
const btn = document.getElementById("btn");
btn.onclick = () => {
const input = document.createElement('input');
input.className = 'demo';
input.value = 007;
input.onclick = () => {alert("Please enter")};
document.body.appendChild(input);
input.focus()
};
</script>
</body>
</html>
自定义指令操作
自定义指令总结
定义语法:
-
局部指令
new Vue({ directives:{指令名:配置对象} }); //或 new Vue({ diectives:{指令名:回调函数} });
-
全局指令
Vue.direvtive(指令名, 配置对象); //或 Vue.directive(指令名, 回调函数);
配置对象中常用的3个回调:
- bind:指令与元素绑定成功时调用
- inserted:指令所在元素被插入页面时调用
- update:指令所在模板结构被重新解析时调用
注意:
- 指令定义时不加v-,但使用时要加v-
- 指令名如果是多个单词,要使用kebab-case命名方式,不要使用camelCase命名
具体案例
<body>
<!-- 需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。
需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点。
-->
<!-- 容器 -->
<div id="root">
<h2>{{name}}</h2>
<h2>当前n的值是:<span v-text="n"></span></h2>
<h3>放大十倍后的值为:<span v-big="n"></span></h3>
<button @click="n++">单击我n+1</button></hr>
<input type="text" v-fbind:value="n">
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//定义全局指令
// Vue.directive('fbind',{
// //指令与元素成功绑定时
// bind(element, binding) {
// element.value = binding.value;
// },
// //指令所在元素被插入页面时
// inserted(element,binding) {
// element.focus();
// },
// //指令所在元素模板被重新解析时
// updated(element,binding) {
// element.value = binding.value;
// }
// });
new Vue({
el:'#root',
data:{
name:'007',
n:1
},
//定义局部指令
directives:{
//首先要清除这个指令何时被调用 即:指令与元素成功绑定时;指令所在模板被重新解析时
big(element,binding) {//指令与元素成功绑定时
//此时的this是window
//console.log('big', this);
element.innerText = binding.value * 10;
},
fbind:{//指令所在模板被重新解析
bind(element,binding){
element.value = binding.value;
},
inserted(element,binding){
element.focus();
},
update(element,binding){
element.value = binding.value;
}
}
}
});
</script>
生命周期
引入生命周期
- 生命周期:Vue在关键时刻帮我们调用的一些特殊名称的函数,又名生命周期回调函数、生命周期函数、生命周期钩子。
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写
- 生命周期函数中的this指向是vm或组件实例对象
- 第一次放入页面叫挂载,后面叫更新
挂载: Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
案例:
<body>
<!-- 容器 -->
<div id="root">
<h2 v-if="a">你好啊!!!</h2>
<h2 :style="{opacity}">欢迎学习Vue</h2>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
a:false,
opacity:1
},
mounted() {
console.log('mounted',this);
setInterval(() => {
vm.opacity -= 0.01;
if(vm.opacity <= 0) vm.opacity = 1;
},16);
}
});
//通过外部定时器 不推荐
// setInterval(() => {
// vm.opacity -= 0.01;
// if(vm.opacity <= 0) vm.opacity = 1;
// },16);
</script>
分析生命周期
vm的生命周期:
- 将要创建 ===> 调用beforeCreate函数
- 创建完毕 ===> 调用created函数
- 将要挂载 ===> 调用beforeMounted函数
- 挂载完毕 ===> 调用mounted函数
- 将要更新 ===> 调用beforeUpdate函数
- 更新完毕 ===> 调用updated函数
- 将要销毁 ===> 调用beforeDestroy函数
- 销毁完毕 ===> 调用destoryed函数
<body>
<!-- 容器 -->
<div id="root">
<h2 v-text="n"></h2>
<h2>当前n的值是:{{n}}</h2>
<button @click="add">点我n+1</button>
<button @click="bye">点我销毁vm</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el:'#root',
data:{
n:1
},
methods:{
add() {
console.log('add');
this.n++;
},
bye() {
console.log('bye');
//完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。
this.$destroy();
}
},
watch:{
n(){
console.log('n变了');
}
},
//将要创建生命周期 调用beforeCreate函数
beforeCreate() {
console.log('beforeCreate');
//此处并没有vm中的_data、methods中的配置方法
},
//创建完毕,调用created函数
created() {
console.log('created');
//此处开始有vm和_data、methods中的配置方法以及数据监测和数据代理
},
//将要挂载,调用beforeMount函数
beforeMount() {
console.log('beforeMount');
//此处呈现的是未经Vue编译过的DOM结构,所有对DOM的操作时无效的
},
//挂载完毕,调用mounted函数
mounted() {
console.log('mounted');
//页面中呈现的是经过Vue编译的DOM结构,至此初始化过程结束
//在此处可以进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作
},
//将要更新,调用beforeUpdate函数
beforeUpdate() {
console.log('beforeUpdate');
//此处数据是新的,但页面时旧的
},
//更新完毕,调用update函数
updated() {
console.log('updated');
},
//将要销毁,调用beforeDestroy
beforeDestroy() {
console.log('beforeDestroy');
//此时vm中的data、methods、指令等均可使用,此时方法修改不会触发更新
//在此阶段,一般进行关闭定时器、取消订阅消息等
},
//销毁完毕,调用destroyed函数
destroyed() {
console.log('destroyed');
//此时已经移除监听、子组件、(自定义)事件监听器
}
});
</script>
总结生命周期
常用的生命周期钩子:
- mounted:发送ajax请求、启动定时器、绑定自定义事件、订阅消息等(初始化操作)。
- beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等(收尾工作)。
关于销毁Vue实例:
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但原生DOM操作依然有效
- 一般不会在beforeDestroy操作数据,因为即使操作数据,也不会触发更新流程
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2 :style="{opacity}">欢迎学习Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el:'#root',
data:{
opacity:1
},
methods: {
stop(){
this.$destroy()
}
},
//Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted(){
console.log('mounted',this)
this.timer = setInterval(() => {
console.log('setInterval')
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
},16)
},
beforeDestroy() {
clearInterval(this.timer)
console.log('vm即将驾鹤西游了')
},
})
</script>
组件化编程
模块与组件、模块化与组件化
模块
- 向外提供特定功能的js程序,一般就是一个js文件
- 为什么使用模块:因为js文件太多太复杂,使用模块能够实现js的复用,简化js的编写,提高js的运行效率。
组件
- 用来实现局部(特定功能)效果的代码集合(html\css\js\image…)
- 为什么使用组件:一个界面的功能很复杂,通过使用组件复用编码,简化项目编码,提高运行效率。
模块化与组件化
- 当应用中的js都以模块来编写,那么这个应用就是一个模块化的应用
- 当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
非单文件组件
基本使用
一个文件包含有多个组件。
Vue中使用组件的三大步骤:
-
定义组件
-
注册组件
-
使用组件(写组件标签)
-
定义组件
使用Vue.extend(option)创建,其中options和new Vue(option)时传入的那个options几乎一样,但也有区别。
区别:
- el不写,因为所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
- data必须写成函数,避免组件被复用时,数据存在引用关系
使用template可以配置组件结构。
-
注册组件
-
局部注册
new Vue时传入components选项
-
全局注册
Vue.component(‘组件名’, 组件)
-
-
使用组件
即在html里面编写组件标签,如
注意点:
- 组件名
- 一个单词 首字母小写或大写 school 或School
- 多个单词 kebab-case命名:my-school CamelCase命名MySchool(需要Vue脚手架支持)
- 组件标签
- 第一种写法:
- 第二种写法: 这种需要Vue脚手架支持
- 简写方式:const school = Vue.extend(options) 可简写为:const school = options
<body>
<!-- 容器root -->
<div id="root">
<!-- 使用组件 编写组件标签 -->
<school></school>
<!-- 使用组件 编写组件标签 -->
<student></student>
</div>
<!-- 容器root2 -->
<div id="root2">
<!-- 使用组件 编写组件标签 -->
<hello></hello>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//创建组件
const school = Vue.extend({
//通过template配置组件结构
template:`
<div class="demo">
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名称</button>
</div>
`,
data(){
return {
schoolName:'尚硅谷',
address:'北京昌平'
}
},
methods:{
showName(){
alert(this.schoolName);
}
},
});
//创建组件
const student = Vue.extend({
template: `
<div>
<h2>学生名称:{{studentName}}</h2>
</div>`,
data(){
return {studentName:'007'};
}
});
//创建组件
const hello = Vue.extend({
template: `
<div>
<h1>我是:{{studentName}}</h1>
<h1>{{str}}</h1>
</div>`,
data(){
return {
studentName:'007',
str:'你们好!'
};
}
});
//全局注册组件
Vue.component('student',student);
Vue.component('hello',hello);
new Vue({
el:'#root',
data:{
msg:'你好啊',
},
//注册组件 局部注册
components:{
school,
//student
}
});
new Vue({
el:'#root2',
})
</script>
组件的嵌套
<body>
<!-- 容器root -->
<div id="root">
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//创建组件
const student = Vue.extend({
template: `
<div>
<h2>学生名称:{{studentName}}</h2>
</div>`,
data(){
return {studentName:'007'};
}
});
//创建组件
const school = Vue.extend({
//通过template配置组件结构
template:`
<div class="demo">
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名称</button>
</div>
`,
data(){
return {
schoolName:'尚硅谷',
address:'北京昌平'
}
},
methods:{
showName(){
alert(this.schoolName);
}
},
components:{
student
}
});
//定义hello组件
const hello = Vue.extend({
template:`<h1>{{msg}}</h1>`,
data(){
return {
msg:'欢迎来到尚硅谷学习!'
}
}
});
//定义app组件
const app = Vue.extend({
template:`
<div>
<hello></hello>
<school></school>
</div>
`,
components:{
school,
hello
}
});
new Vue({
el:'#root',
template:'<app></app>',
//注册组件 局部注册
components:{app}
});
</script>
VueComponent
- 一个组件(如school组件)本质上是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
- 我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行 new VueComponent(options)
- 每次调用Vue.extend,返回的是一个全新的VueComponent
- 在组件配置中,data函数、methods中的函数、watch中的函数、computed中的函数,它们的this均是VueComponent。
- 在new Vue(options)配置中,data函数、methods中的函数、watch中的函数、computed中的函数,它们的this指向均是Vue对象实例
- VueComponent的实例对象简称vc(组件实例对象)
<body>
<!-- 容器root -->
<div id="root">
<school></school>
<hello></hello>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
//创建组件
const school = Vue.extend({
//通过template配置组件结构
template:`
<div class="demo">
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名称</button>
</div>
`,
data(){
return {
schoolName:'尚硅谷',
address:'北京昌平'
}
},
methods:{
showName(){
alert(this.schoolName);
}
}
});
const test = Vue.extend({
template:`<span>atguigu</span>`
})
//定义hello组件
const hello = Vue.extend({
template:`
<div>
<h1>{{msg}}</h1>
<test></test>
</div>
`,
data(){
return {
msg:'欢迎来到尚硅谷学习!'
}
},
components: {
test
}
});
new Vue({
el:'#root',
//注册组件 局部注册
components:{school,hello}
});
</script>
一个重要的内置关系
Vue.component.prototype.__proto__ === Vue.prototype
Demo.prototype //显式原型属性
Demo.__proto__ //隐式原型属性
//实例的隐式原型属性,永远指向自己缔造者的原型对象
//通过隐式原型链获取原型的属性,从自身沿着原型链一直找到window的原型为空
//通过显式原型链给原型或原型对原型添加属性
通过这个关系让组件实例对象可以访问到Vue原型上的属性、方法.
相关文章:
单文件组件
一个文件只包含一个组件。
App.vue组件
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
//引入组件
import School from './School.vue'
import Student from './Student.vue'
export default {
name:'App',
components:{
School,
Student
}
}
</script>
Student.vue组件
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
name:'张三',
age:18
}
}
}
</script>
School.vue
<template>
<div class="demo">
<h2>学校名称:{{name}} </h2>
<h2>学校地址:{{address}} </h2>
<button @click="showName">点我提示校名!</button>
</div>
</template>
<script>
//组件交互相关的代码(数据、方法等)
//直接暴露组件的配置对象
// export default {}
const school = Vue.extend({
data() {
return{
name:'尚硅谷',
address:'北京'
}
},
methods:{
showName() {
alert(this.name)
}
}
})
//统一暴露
//export {school}
//默认暴露
export default school
</script>
<style>
.demo {
background-color: red;
}
</style>
main.js
import App from './App.vue'
new Vue({
el:'#root',
template:`<App></App>`,
components:{App},
})
index.html
<!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 -->
</head>
<body>
<!-- 容器root -->
<div id="root"></div>
<script src="../js/vue.js"></script>
<script src="./main.js"></script>
</body>
</html>
但这一案例实际运行不了,需要引入脚手架解决引入问题。