一、Vue指令
vue框架特点:双向数据绑定与组件化开发
1.1、(重点) 深入双向数据绑定原理
Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。Vue里面是怎么做到的的呢?其实就是使用了
Object.defineProperty
把Vue内的属性全部转成getter/setter
。Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
Object.defineProperty
实现了对象劫持这个功能
vue双向数据绑定原理:
借助Object.defineProperty()对数据进行劫持,并结合发布-订阅者模式,来实现双向数据绑定
语法:
Object.defineProperty(obj, prop, desc)
obj
需要定义属性的当前对象prop
当前需要定义的属性名desc
属性描述符
数据属性:
通过Object.defineProperty()为对象定义属性,有两种形式,分别为数据描述符,存取描述符,下面分别描述两者的区别:
value
表示它的默认值writable
如果为true标识可以被修改,如果为false标识不能被修改(默认值为false)configurable
描述属性是否配置,以及可否删除,可以认为是总开关 默认值 false(不可删除)enumerable
描述属性是否出现在for in 或者 Object.keys()的遍历中 默认值false(不能遍历)
let obj = {};
Object.defineProperty(obj, 'name', {
value: '张三'
})
obj.name = '李四'
console.log(obj.name) // 张三
let obj = {};
Object.defineProperty(obj, 'name', {
value: '张三',
writable: true
})
obj.name = '李四'
console.log(obj.name)
let obj = {};
Object.defineProperty(obj, 'name', {
value: '张三',
writable: true,
configurable: true,
enumerable: true
})
obj.name = '李四'
// delete obj.name
console.log(obj.name) // 李四
console.log(Object.keys(obj)) // ['name']
存取属性:
let obj = {};
let temp = null;
Object.defineProperty(obj, 'name', {
get() {
return temp
},
set(val) {
temp = val
}
})
obj.name = '李四'
console.log(obj.name)
面试题回答:
vue的双向数据绑定原理是什么?
vue数据双向绑定是通过数据劫持结合“发布者-订阅者模式”的方式来实现的。
vue是通过Object.defineProperty()来实现数据劫持,其中会有getter()和setter方法;当读取属性值时,就会触发getter()方法,在view中如果数据发生了变化,就会通过Object.defineProperty()对属性设置一个setter函数,当数据改变了就会来触发这个函数;参考:https://segmentfault.com/a/1190000014274840
参考:https://zhuanlan.zhihu.com/p/51357583
1.2、自定义指令(仅做了解,不常用)
除了一些内置的制定(v-model和v-show…),Vue也允许注册自定义指令。
全局自定义指令格式:
// 注册一个全局自定义指令 v-demo
Vue.directive('demo', {
inserted: function (el, binding) {
console.log(el, binding);
},
update(el, binding){}
})
局部自定义指令格式:
// 组件中注册局部指令
new Vue({
el: '#app',
data: {},
directives: {
demo: {
inserted: function (el, binding) {
cosnole.log(el, binding);
}
}
}
})
自定义指令的使用:
// 在模板中使用自定义指令
<div v-demo>
</div>
函数:
inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。
参数:
el
:指令所绑定的元素,可以用来直接操作 DOM 。binding
:一个对象,包含以下属性:name
:指令名,不包括v-
前缀。value
:指令的绑定值,例如:v-demo="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值expression
:字符串形式的指令表达式。例如v--demo="1 + 1"
中,表达式为"1 + 1"
。modifiers
:一个包含修饰符的对象。例如:v-demo.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
实现类似v-show的自定义指令
<input type="text" id="oipt">
<p id="op"></p>
<script>
let obj = {};
let val = "默认值"
Object.defineProperty(obj, "iptVal", {
get() {
return val
},
// 第三方
set(newVal) {
// val = newVal
oipt.value = newVal;
op.innerHTML = newVal;
}
})
// 订阅者
oipt.value = obj.iptVal;
op.innerHTML = obj.iptVal;
oipt.addEventListener("keyup", function(e) {
// 我们希望修改obj.iptVal的值,达到修改oipt.value和op.innerHTML的值
// 发布者
obj.iptVal = e.target.value // 触发了set
})
// 借助Object.defineProperty()对数据进行劫持,并结合发布-订阅者模式,来实现双向数据绑定
</object>
</script>
</html>
二、(重点)Vue组件化开发
2.1、什么是组件化?
面对复杂问题的处理方式,把问题拆解成很多个能处理的小问题,再将其放在整体中,会发现大的问题也会迎刃而解。
而组件化的思想也类似:
- 如果我们实现一个页面结构和逻辑非常复杂的页面时,如果全部一起实现会变得非常复杂,而且也不利于后续的维护和迭代功能。
- 但如果我们这时候把页面分成一个个小的功能块,每个功能块能完成属于自己这部分独立的功能,那么整个页面之后的维护和迭代也会变得非常容易。
- 组件化开发的优势:可维护性高 可复用性高
2.2、Vue组件化思想
组件化是Vue重要的思想
- 它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
- 任何的应用都会被抽象成一颗组件树。
组件化思想的应用开发:
- 有了组件化的思想,我们在之后的开发中就要充分的利用它。
- 尽可能的将页面拆分成一个个小的、可复用的组件。
- 这样让我们的代码更加方便组织和管理,并且扩展性也更强。
2.3、全局组件
通过
Vue.component('组件名称', {})
,通过这个方法注册的都是全局组件,也就是他们再注册之后可以用在任何新创建的Vue
实例挂载的区域内。
<body>
<div id="app">
<my-con></my-con>
<div>
<my-con></my-con>
</div>
</div>
<my-con></my-con>
</body>
<script>
Vue.component('my-con', {
template: '<section><h3>组件标题</h3><p>组件内容</p></section>'
})
const vm = new Vue({
el: '#app'
})
</script>
上面案例中,在
<div id="app">...</div>
外的组件my-con
没有替换成组件真正的页面结构,是因为new Vue()
挂载在id=app
的节点上,不在这个节点上标签,不会受到Vue的影响。
2.4、局部组件
通过
Vue.component
方式注册的组件,称之为全局组件。任何地方都可以使用。全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
注册局部组件
<body>
<div id="app">
<my-con></my-con>
<div>
<my-con></my-con>
</div>
</div>
<div id="app1">
<my-con1></my-con1>
</div>
</body>
<template id="template1">
<section>
<h3>组件标题</h3>
<p>组件内容</p>
</section>
</template>
<template id="template2">
<section>
<h3>组件标题B</h3>
<p>组件内容B</p>
</section>
</template>
<script>
var componentA = {
template: '#template1'
}
var componentB = {
template: '#template2'
}
const vm = new Vue({
el: '#app',
components: {
'my-con': componentA
}
})
const vm1 = new Vue({
el: '#app1',
components: {
'my-con1': componentB
}
})
</script>
父组件和子组件
组件和组件之间存在层级关系,而其中一种最重要的关系就是父子组件关系。
2.5、组件可以访问Vue实例数据吗?
那组件如果要使用data定义自己属性保存数据要怎么做呢?
- 组件对象也有一个data的属性(也有methods等属性,下面我们有用到)
- 只是这个data属性必须是一个函数,而且函数返回一个对象 ,对象保存着数据
<body>
<div id="app">
<my-con></my-con>
<div>
<my-con></my-con>
</div>
</div>
<div id="app1">
<my-con1></my-con1>
</div>
</body>
<template id="template1">
<section>
<h3>{{title}}</h3>
<p>组件内容</p>
</section>
</template>
<template id="template2">
<section>
<h3>{{title}}B</h3>
<p>组件内容B</p>
<aa></aa>
</section>
</template>
<script>
var componentA = {
template: '#template1',
data() {
return {
title: 'zujianbiaoti'
}
}
}
var componentB = {
template: '#template2',
data() {
return {
title: 'zj'
}
},
components: {
'aa': {
template: '<div>aa</div>'
}
}
}
const vm = new Vue({
el: '#app',
data: {title: '组件标题'},
components: {
'my-con': componentA
}
})
const vm1 = new Vue({
el: '#app1',
components: {
'my-con1': componentB
}
})
</script>
为什么data在组件中必须是一个函数呢?
原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
2.6、父子组件间的通讯
父级向子级传递
在组件中,使用选项props来声明需要从父级接收到的数据。
props的值有两种方式:
- 字符串数组,数组中的字符串就是传递时的名称。
- 对象,对象可以设置传递时的类型(String,Number,Boolean,Array, Object,Date,Function,Symbol),也可以设置默认值等。
<body>
<div id="app1">
<my-con1></my-con1>
</div>
</body>
<template id="template2">
<section>
<h3>{{title}}B</h3>
<p>组件内容B</p>
<!-- my-con1组件内的aa组件 -->
<aa v-bind:parent-txt="childtxt"></aa>
</section>
</template>
<script>
var componentB = {
template: '#template2',
data() {
return {
title: 'zj',
childtxt: 'child text'
}
},
components: {
'aa': {
template: '<div>{{parentTxt}}</div>',
props: ['parentTxt']
}
}
}
const vm1 = new Vue({
el: '#app1',
components: {
'my-con1': componentB
}
})
</script>
子级向父级传递
父组件向子组件传递数据,通过自定义事件
<body>
<div id="app1">
<my-con1></my-con1>
</div>
</body>
<template id="template2">
<section>
<h3>{{title}}B</h3>
<p>组件内容B</p>
<aa v-bind:parent-txt="childtxt" v-on:changetitle="changeTitle"></aa>
</section>
</template>
<script>
var componentB = {
template: '#template2',
data() {
return {
title: 'zj',
childtxt: 'child text'
}
},
components: {
'aa': {
template: '<div v-on:click="change">{{parentTxt}}</div>',
props: ['parentTxt'],
methods: {
change() {
this.$emit('changetitle', {
a: 1
})
}
}
}
},
methods: {
changeTitle(obj) {
console.log(obj)
this.title = obj.a
}
}
}
const vm1 = new Vue({
el: '#app1',
components: {
'my-con1': componentB
}
})
</script>
案例分析:
- 现在的需求是点击子组件
aa
然后把父组件my-con1
上的标题给改变;- 首先,在父组件的具体页面结构找到子组件
aa
,在子组件aa
上使用v-on:changetitle="changeTitle"
,changetitle
是子组件的自定义事件名称,changeTitle
是父组件my-con1
里的methods
属性定义的方法;- 其次,在子组件
aa
里为div绑定点击事件v-on:click="change"
, 在子组件aa
里的methods定义change方法,change方法里使用this.$emit('changetitle')
,使用 e m i t 方 法 来 触 发 绑 定 在 子 组 件 上 的 自 定 义 事 件 , emit方法来触发绑定在子组件上的自定义事件, emit方法来触发绑定在子组件上的自定义事件,emit第一个参数就是上一步定义的自定义事件changetitle
,第二个参数就是传递到父组件的参数,可以不传。