四川企业网站建设平台网站开发的技术难点
Vue:模板 & MVVM
- 模板
 - 插值语法
 - 指令语法
 
- MVVM
 - defineProperty
 - 数据代理
 
模板
Vue实例绑定一个容器,想要向容器中填入动态的值,就需要使用模板语法。模板语法分为插值语法和指令语法。
插值语法
插值语法很简单,使用{{}}包含一个表达式,Vue会把表达式的结果替代这个值渲染到HTML中。
示例:
<div id="root"><h3>hello,{{name}}</h3>
</div>
<script type="text/javascript">new Vue({el:'#root',data:{name:'jack',}})
</script>
 
以上代码,最终会生成hello,jack标题,{{name}}被替换为了data.name。
此外,任意表达式都可以填入{{}}中,比如:
<div id="root"><h3>hello,{{1 + 1}}</h3><h3>hello,{{x === y ? 'a' : 'b'}}</h3>
</div>
 
比如{{1 + 1}}最终会变成2,三元表达式会进行计算,或者说函数调用也可以在{{}}内部执行。
另外的,{{}}内的表达式,可以读取到Vue示例的data下的所有属性。
指令语法
插值语法能处理的情况是非常有限的,比如标签属性。
现在有一个<a>标签,要动态的指定url,如果使用插值语法,可能写出如下内容:
<div id="root"><a href="{{addr.url}}">{{addr.name}}</a>
</div>
<script type="text/javascript">new Vue({el:'#root',data:{addr:{name:'百度',url:'http://www.baidu.com',}}})
</script>
 
但是这是一个错误的语法,{{}}作为标签属性时,不会被解析。
此时就要使用指令语法,v-bind指令可以将一个属性值解析为表达式。
<div id="root"><a v-bind:href="addr.url">{{addr.name}}</a>
</div>
 
以上代码中,herf属性前面加上了v-bind:,那么后面的""内部不再被解析为字符串,而是当作表达式处理,此时addr.url就会被解析了。
一个v-bind只能作用于其后面的那个属性,比如以下情况:
<div id="root"><a v-bind:href="addr.url" id="addr.name">{{addr.name}}</a>
</div>
 
在id="name"中,addr.name不会被当作表达式,而是当作字符串,因为其前面没用v-bind指令。
v-bind指令可以简写为一个冒号:。
<div id="root"><a :href="addr.url">{{addr.name}}</a>
</div>
 
- 数据绑定
 
刚才的v-bind称为单向数据绑定,简单来说就是如果Vue实例中的属性值改变了,那么HTML的内容也会改变。但是如果HTML的内容改变了,Vue实例的内容不会改变。
示例:
<div id="root">单向数据绑定:<input type="text" :value="inner"><br/>
</div>
<script type="text/javascript">new Vue({el:'#root',data:{inner:'hello world'}})
</script>
 
这是一个输入框,输入框的value属性被绑定为了inner的内容。

初始时,inner和输入框的内容相同,尝试修改inner的值:

修改后,输入框的内容也随之改变了。
但是如果修改输入框的内容:

此时inner的内容不会改变,这就是单向数据绑定,Vue实例的值会影响HTML,但是HTML不会影响Vue实例。
指令v-model可以使用双向数据绑定:
<div id="root">双向数据绑定:<input type="text" v-model:value="inner"><br/>
</div>
 
此时修改输入框,也会反过来修改Vue实例的值:

但是不是所有标签都可以使用v-model,只有可以输入元素,类似表单的元素可以使用,其他标签使用会导致报错,并且标签会丢失。
v-model:也可以简写,因为v-model:后面的属性往往是value,表示元素内容,所以可以省略:value,直接写为v-model:
<div id="root">双向数据绑定:<input type="text" v-model="inner"><br/>
</div>
 
MVVM
MVVM是一种软件开发模型,在Vue设计时参考了这个模型,其分为三个组成部分:
Model:模型View:视图ViewModel:视图模型

Model就是一个JavaScript的表达式,简单来说就是数据,比如在{{}}中包含一个表达式,在v-bind后面的" "内部的表达式,这些表达式都会产生一个值,变成具体的数据。
View对应上图中最左侧的DOM,它表示一个可以被用户看到的页面,这个页面通过Vue解析产生。
ViewModel是上图的中间部分,它表示Vue实例对象,这也是整个MVVM的核心,其分为两个区域。
首先是Data Bindings数据绑定,它接收Model产生的值,并把它填入到View视图中,这样就产生了最终的页面。这对应了之前的v-bind指令,或者{{}}插值,他们都是接收JavaScript表达式产生的值,并填入到元素中。
另一个是DOM Listeners元素监听,它监听页面中的元素,当元素发生变化时,返过来影响Model内部的值。这对应的了v-model双向数据绑定,当DOM元素发生更改时,修改Vue.data的内容。
在Vue中,JavaScript表达式可以看到Vue实例的所有属性和方法,包括原型链。
输出一下Vue实例:
const vm = new Vue({el:'#root',data:{name:'hello world',num: 123}
})
console.log(vm)
 
由于Vue实例在MVVM中对应VM,所以接收时常把变量命名为vm。
输出结果:

可以看到,name、num这两个属性最后都进入到了Vue实例中,构造时会把data下面的所有属性都添加到Vue实例中,这就是为什么在{{}}内部可以看到data下面的数据,
仔细看name、num的属性值是(...),这涉及到数据代理的问题。
defineProperty
Object.defineProperty可以给一个对象添加指定的值,并且可以进行属性的复杂配置。
语法:
Object.defineProperty(对象, 属性, {配置对象})
 
对象:被修改的对象属性:要添加的属性{配置对象}:添加属性相关的配置
例如给一个对象"张三"添加一个age属性:
let person = {name:'张三',sex:'男',
}Object.defineProperty(person,'age',{value:18,enumerable:true,  //控制属性是否可以枚举,默认值是falsewritable:true,    //控制属性是否可以被修改,默认值是falseconfigurable:true //控制属性是否可以被删除,默认值是false
})
 
在配置对象中,value就是配置的属性值,剩下三个属性用于对元素做一些限制。
比如说enumerable控制元素是否可以被遍历,如果为false,那么for in语法就无法获取到这个值,或者通过keys方法无法获得属性名。
后两个比较好理解,就是能否修改与删除。
这些都不是重点,在配置对象中还可以配置函数,其中set和get方法非常重要。
get方法在该属性被读取时调用,并且读取元素值时,返回指定值。
Object.defineProperty(person,'age',{get(){console.log('有人读取age属性了')return 123}
})
 
输出结果:

此时可以看到,age属性的值变成了(...),表示暂时未知。当访问person.age,调用了get函数并拿到了返回值123。
注意:get方法和value属性不能同时设置。
set方法在该属性被设置时调用,接收一个参数value,这就是要设置的值。
实例:
Object.defineProperty(person,'age',{set(value){console.log('有人修改了age属性,值是',value)number = value}
})
 
输出结果:

在修改值时,调用了对应的set函数。
注意:set不能和writable属性一起指定。
基于这两个特性,它可以完成一个功能:让一个对象外部的变量与该属性完全同步。
let number = 18
let person = {name:'张三',sex:'男',
}Object.defineProperty(person,'age',{get(){return number},set(value){number = value}
})
 
以上代码,实现了person.age与变量number的完全同步。当每次读取age时,返回number的值,每次设置age时,把number一起修改。
基于以上操作,就可以完成数据代理。
数据代理
数据代理:通过一个对象代理对另一个对象中属性的操作
示例:
let A = {x:100}
let B = {}Object.defineProperty(B,'x',{get(){return A.x},set(value){A.x = value}
})
 
以上就是一个数据代理,A.x与B.x是完全同步的,操作B.x对应的A.x也会修改,任何时候A.x === B.x。
现在再回看之前Vue实例中name和num的变量值为(...),这其实就是一个数据代理。
在Vue实例的底部,可以看到四个方法:

这四个方法分别就是name和num的get和set,此处将vm.name和vm.num进行了数据代理,代理到data.name和data.num,这样可以保证vm和构造时传入的data完全同步。
而传入data时,有可能传了个匿名对象,data本身也要进行保存,其实vm把data保存为了一个vm._data的对象。

真正被代理的其实是vm._data,如果不做这层代理,那么用户访问数据时就要写{{ _data.name }}、{{ _data.num }},这样有点麻烦,因此做了依次数据代理,将他直接放到vm下,这样就可以直接访问了。
另外的,可以发现在_data中,这两个属性的值也是(...),看着很想数据代理,但这并不是。这是数据劫持,当_data的数据发生改变,就要渲染到HTML页面中,这个过程是由数据劫持完成的。
