1.数据劫持
vue的双向数据绑定原理:vue数据双向绑定是通过数据劫持结合“发布者-订阅者模式”的方式来实现的
Object.defineProperty() ---实现数据劫持
其中有getter()和setter方法,当读取属性值时,就会触发getter()方法
在view中如果数据发生了变化,就会通过Object.defineProperty()对属性设置一个setter函数,当数据改变了就会来触发这个函数;
示例:
<body>
<button id="btn">修改msg</button>
<script>
let obj={}
let txt="你好世界"
Object.defineProperty(obj,"msg",{
get(){
//返回msg的数据
return txt
},
set(val){
txt = val
}
})
console.log(obj.msg);
//点击修改msg的值
btn.onclick = function(){
obj.msg = "hello world!" //一旦修改msg的值,就会自动触发setter,并且获取到一个新的值
console.log(obj.msg);
}
</script>
</body>
</html>
2.对象属性的配置
writable:true, //设置某个key值可被修改
configurable:true, //设置某个key值可被删除
enumerable:true //设置当前对象能否枚举所有属性
示例:
let obj = {
msg:"你好世界"
}
console.log(obj.msg);
obj.msg="hello world"
console.log(obj.msg);
//obj.属性值能获取value值,也能直接修改,删除数据
原理:
let obj = {}
Object.defineProperty(obj,"msg",{
value:"你好世界" , //设置value值---与get(){ return "你好世界"}相同,但get,set方法同时出现
writable:true, //设置某个key值可被修改
configurable:true, //设置某个key值可被删除
enumerable:true //设置当前对象能否枚举所有属性
})
console.log(obj.msg);
//修改value值
obj.msg = "hello world"
console.log(obj.msg);
* //删除
delete obj.msg
console.log(obj.msg); //undefined */
//枚举(Object.keys(),for in)一个对象的属性
console.log(Object.keys(obj)); //['msg']
3.自定义指令
Vue.directive("自定义属性",{})
函数:
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
bind: 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
update:更新
函数对应的参数:
el:指令所绑定的元素,可以用来直接操作 DOM 。
binding:当前元素的属性
3.1全局自定义属性
示例:
<!DOCTYPE html>
<html lang="zh_cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style>
.box {
width: 100px;
height: 100px;
background-color: antiquewhite;
}
</style>
</head>
<body>
<div id="app">
<button id="btn" @click="flag=!flag">切换</button>
<div class="box" v-myshow="flag"></div>
</div>
</body>
</html>
<script src="./lib/vue.js"></script>
<script>
//全局自定义属性
Vue.directive("myshow",{
inserted(el,binding){
// el:当前元素
// binding:当前元素的属性
console.log(el,binding);
/* if(binding.value){
el.style.display = "block"
}else{
el.style.display = "none"
} */
el.style.display = binding.value ? "block" : "none"
},
//更新
update(el,binding){
el.style.display = binding.value ? "block" : "none"
}
})
new Vue({
el: "#app",
data: {
flag: false
}
})
</script>
3.2局部自定义指令
示例:
new Vue({
el: "#app",
data: {
flag: false
},
directives: {
myshow: {
inserted(el, binding) {
el.style.display = binding.value ? "block" : "none"
},
//更新
update(el, binding) {
el.style.display = binding.value ? "block" : "none"
}
}
}
})
3.3 案例-自动聚焦
<!DOCTYPE html>
<html lang="zh_cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style>
/*修改自动聚焦时,聚焦的外边框*/
input:focus{
outline: 3px solid pink;
}
</style>
</head>
<body>
<div id="app">
<input type="text" v-focus>
</div>
</body>
</html>
<script src="./lib/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
},
directives:{
focus:{
inserted(el){
//focus()用于为元素设置焦点
el.focus()
}
}
}
})
</script>
4.组件
4.1全局组件
component:组件 template:模板
示例:
<body>
<div id="app">
<my-list></my-list>
<my-list />
</div>
</body>
</html>
<script src="./lib/vue.js"></script>
<script>
//全局组件
Vue.component('my-list',{
template:`<ul>
<li>
<span>百度一下你就知道</span>
<a href="http://baidu.com">链接</a>
</li>
</ul>`
})
new Vue({
el: "#app",
data: {
}
})
</script>
4.1.1模板抽离
写在()标签中--全局任意地方
示例:
<body>
<div id="app">
<my-list></my-list>
<my-list />
</div>
</body>
<template id="list">
<ul>
<li>
<span>百度一下你就知道</span>
<a href="http://baidu.com">链接</a>
</li>
</ul>
</template>
</html>
<script src="./lib/vue.js"></script>
<script>
Vue.component('my-list', {
template: "#list"
})
new Vue({
el: "#app",
data: {
}
})
</script>
4.1.2组件无法调用实例中的data
示例:
<body>
<div id="app">
<my-list></my-list>
<my-list />
</div>
</body>
<template id="list">
<ul>
<li>
<span>{{msg}}</span>
<a href="http://baidu.com">链接</a>
</li>
</ul>
</template>
</html>
<script src="./lib/vue.js"></script>
<script>
Vue.component('my-list', {
template: "#list"
})
new Vue({
el: "#app",
data: {
msg:"你好"
}
})
//报错--"msg" is not defined
</script>
解决方法
组件中data必须是函数
多次调用同一个组件,我们就需要对这个组件的数据进行维护,相当于我们希望给这个组件的数据套上一个作用域,防止被外界的数据影响
ES5中只有函数才有作用域
<body>
<div id="app">
<my-list></my-list>
<my-list />
</div>
</body>
<template id="list">
<ul>
<li>
<span>{{msg}}</span>
<a href="http://baidu.com">链接</a>
</li>
</ul>
</template>
</html>
<script src="./lib/vue.js"></script>
<script>
Vue.component('my-list', {
template: "#list",
data() { //解决方法:组件中data必须是函数
return {
msg: "你好"
}
}
})
new Vue({
el: "#app",
data:{
}
})
</script>
4.1.3组件中的方法
<body>
<div id="app">
<my-list></my-list>
<my-list />
</div>
</body>
<template id="list">
<ul>
<li>
<span>{{msg}}</span>
<a href="http://baidu.com">链接</a>
<button @click="btnClick">按钮</button>
</li>
</ul>
</template>
</html>
<script src="./lib/vue.js"></script>
<script>
Vue.component('my-list', {
template: "#list",
data() {
return {
msg: "你好"
}
},
methods:{
btnClick(){
console.log(123);
}
}
})
new Vue({
el: "#app",
data:{
}
})
</script>
4.2局部组件
new Vue({
el: "#app",
data: {
},
components: {
'my-list': {
template: "#list",
data() {
return {
msg: "你好"
}
},
}
}
})
5.父子组件间的通讯
5.1父传子
在组件中,使用选项props来声明需要从父级接收到的数据。
props的值有两种方式:
- 字符串数组,数组中的字符串就是传递时的名称。
- 对象,对象可以设置传递时的类型(String,Number,Boolean,Array, Object,Date,Function,Symbol),也可以设置默认值等。
示例:
<!DOCTYPE html>
<html lang="zh_cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
</head>
<body>
<div id="app">
<!-- 双标签 -->
<my-list :con="content" :myherf="link"></my-list>
<!-- 单标签-- -->
<my-list />
</div>
</body>
<template id="list">
<ul>
<li>
<span>{{msg}}</span>
<a :href="myherf">{{con}}</a>
<button @click="btnClick">按钮</button>
</li>
</ul>
</template>
</html>
<script src="./lib/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
content:'跳转到百度',
link:"http://baidu.com"
},
components: {
'my-list': {
//props以数组的形式接收时,是简写的形式 ---单标签无法接收
// props:["con","myherf"],
props:{
con:{
type:String,
default:"跳转到新浪"
},
myherf:{
type:String,
default:"http://sina.com.cn"
}
},
template: "#list",
data() {
return {
msg: "你好"
}
},
methods: {
btnClick() {
this.msg="呦呵"
}
}
}
}
})
</script>
5.2子传父
父组件向子组件传递数据,通过自定义事件
示例:
<!DOCTYPE html>
<html lang="zh_cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
</head>
<body>
<div id="app">
<!-- 双标签 -->
<my-list :con="content" :myherf="link" v-on:changecontent="changeContent" @changelink="changeLink"></my-list>
</div>
</body>
<template id="list">
<ul>
<li>
<span>{{msg}}</span>
<a :href="myherf">{{con}}</a>
<button @click="btnClick">按钮</button>
</li>
</ul>
</template>
</html>
<script src="./lib/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
content: '跳转到百度',
link: "http://baidu.com"
},
methods: {
changeContent(val) {
this.content = val
},
changeLink(val){
this.link = val
}
},
components: {
'my-list': {
props: ["con", "myherf"],
template: "#list",
data() {
return {
msg: "你好"
}
},
methods: {
btnClick() {
// 把myherf和con的值改了
/* this.con="跳转到叩丁狼"
this.myherf = "http://wolfcode.cn" //错误 */
//虽然修改成功,但是vue中对数据的维护要求非常严格,原始数组在哪个组件中,就必须在那一个组件里修改
//子组件中不能直接修改props,因为vue是单向数据流
//解决方法
//this.$emit(事件名称,传递的参数) --事件名称不能使用驼峰命名法
this.$emit("changecontent", "跳转到叩丁狼")
this.$emit("changelink", "http://wolfcode.cn")
}
}
}
}
})
</script>
5.3组件对象的抽离
<!DOCTYPE html>
<html lang="zh_cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
</head>
<body>
<div id="app">
<!-- 双标签 -->
<my-list :con="content" :myherf="link" v-on:changecontent="changeContent" @changelink="changeLink"></my-list>
</div>
</body>
<template id="list">
<ul>
<li>
<span>{{msg}}</span>
<a :href="myherf">{{con}}</a>
<button @click="btnClick">按钮</button>
</li>
</ul>
</template>
</html>
<script src="./lib/vue.js"></script>
<script>
var MyList = {
props: ["con", "myherf"],
template: "#list",
data() {
return {
msg: "你好"
}
},
methods: {
btnClick() {
this.$emit("changecontent", "跳转到叩丁狼")
this.$emit("changelink", "http://wolfcode.cn")
}
}
}
new Vue({
el: "#app",
data: {
content: '跳转到百度',
link: "http://baidu.com"
},
methods: {
changeContent(val) {
this.content = val
},
changeLink(val){
this.link = val
}
},
components: {
// 'my-list': MyList
MyList:{}
}
})
</script>
5.4案例
点击登录,弹出登录框
点击 X或者其他灰色界面,登录框消失
<!DOCTYPE html>
<html lang="zh_cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style>
* {
margin: 0;
padding: 0;
list-style: none;
}
.model {
width: 100%;
height: 100%;
/* background-color: rgba(0, 0, 0, .3); */
position: fixed;
top: 0;
left: 0;
}
.content {
width: 600px;
height: 400px;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
background-color: #fff;
}
.bg{
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .3);
}
.del {
/* float: right; */
width: 30px;
height: 30px;
cursor: pointer;
box-sizing: border-box;
font-size: 20px;
color: #999;
/* border: 1px solid #ccc; */
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: 0;
}
</style>
</head>
<body>
<div id="app">
<button @click="flag=true">登录</button>
<my-model :flag="flag" @closemodel="flag=false"></my-model>
</div>
</body>
<template id="model">
<div class="model" v-show='flag'>
<div class="bg" @click="closeModel"><6/div>
<div class="content">
<div class="del" id="del" @click="closeModel">X</div>
</div>
</div>
</template>
</html>
<script src="./lib/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
flag: false
},
//component:组件 template:模板
components: {
MyModel: {
template: "#model",
props: ['flag'],
methods: {
closeModel() {
// 让父组件关闭静态框
this.$emit("closemodel")
}
}
},
},
methods: {}
})
</script>
6.插槽
slot --封装组件
抽取共性,保留不同
- 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
- 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
- 是搜索框,还是文字,还是菜单。由调用者自己来决定。
6.1匿名插槽
<!DOCTYPE html>
<html lang="zh_cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style>
.nav{
width: 400px;
height: 40px;
background-color: aquamarine;
display: flex;
justify-content: space-between;
line-height: 40px;
}
section{
flex: 1;
background-color: pink;
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<my-nav>123123</my-nav>
</div>
</body>
</html>
<template id="mynav">
<div class="nav">
<span><</span>
<section>
<slot></slot> <!--根据自己的需求,决定插槽中插入什么内容-->
</section>
<span>...</span>
</div>
</template>
<script src="./lib/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
},
components:{
MyNav:{
template:"#mynav"
}
}
})
</script>
6.2具名插槽
想让谁显示出来,修改后面slot的name属性
<body>
<div id="app">
<my-nav :slotname="slotname">
<input type="text" slot='ipt'>
<h3 slot="title">标题</h3>
<div slot="list">
<a href="">百度</a>
<a href="">新浪</a>
<a href="">微博</a>
</div>
</my-nav>
</div>
</body>
</html>
<template id="mynav">
<div class="nav">
<span><</span>
<section>
<slot :name="slotname"></slot>
</section>
<span>...</span>
</div>
</template>
<script src="./lib/vue.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
slotname:'ipt'
},
components: {
MyNav: {
template: "#mynav",
props:["slotname"]
}
}
})
</script>
6.3作用域插槽
默认情况下,父组件使用子组件,插槽数据默认是拿父组件的数据,而不是子组件拿数据。
作用域插槽在父组件使用我们的子组件时, 插槽的数据从子组件中拿到数据,而不是从父组件拿到。
6.3.1作用域插槽的多种写法
<div id="app">
<my-nav>
<!-- 方法一 -->
<!-- <h3 slot="title" slot-scope="scope">{{scope.msg}}</h3> -->
<!-- 方法二 -->
<!-- <template slot="title" slot-scope="scope">
<h3>{{scope.msg}}</h3> -->
</template>
<!-- 方法三 -->
<template v-slot:title="scope">
<h3>{{scope.msg}}</h3>
</template>
</my-nav>
</div>