还是否记得第一次使用Vue时的感觉?双向数据绑定,竟然那么神奇,可以让代码变的如此简单。
一、Vue核心思想
在了解Vue的双向绑定之前,你先要回忆一下Vue的核心思想。还记得getter/setter方法、还记得Watcher?
关于getter/setter方法
1.当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项 (图中就是 a 对象中的 b 属性,即 a.b),Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter 方法。
2. 用户看不到 getter/setter 方法,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
3. 同时 Vue 会对模板进行编译,解析之后会生成指令对象,也例如 v-text, v-hide 等。当指令中 v-text='a.b’时,其实就是触发 getter 方法,获取对应的数据。
另外每个属性都对应有一个 Watcher,它有什么作用?
1.当 data 选项中 a.b 值发生改变时,就会触发 setter 方法,会通知到对应的 watcher。
2. 之后再通知指令去调用 update 方法,由于指令是 DOM 的封装,所在在 update 时其实就是调用了原生 javascript 的 DOM 方法来更新界面。
二、关于Object.defineProperty
在讲解具体原理前,必须先要了解到的方法,可能你在看到这个方法之后,就有点感觉了。
Object.defineProperty
该方法允许精确添加或修改对象的属性。
语法 Object.defineProperty(obj, prop, descriptor)
obj: 要在其上定义属性的对象。
prop: 要定义或修改的属性的名称。
descriptor: 将被定义或修改的属性描述符。
configurable
当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
enumerable: 当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为 false。
get: 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。当访问该属性时,该方法会被执行,方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)。
set: 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。当属性值修改时,触发执行该方法。该方法将接受唯一参数,即该属性新的参数值。
在Vue中,其实是使用Object.defineProperty
进行了数据劫持,当获取数据或者修改数据时,都能通过自定义的getter/setter截取到。
<script type="text/javascript">
document.onclick = function(){
var user = {name: 'atom'}
key = 'name'
function defineProperty(obj, key, val){
Object.defineProperty(obj, key, {
enumerable: true,
get(){ // getter方法
console.log('getter方法获取到的值:' + val)
return val
},
set(newVal){ // setter方法
if (val !== newVal){
console.log(val + ' (setter方法)修改为: ' + newVal)
val = newVal
}
}
})
}
// 调用(即添加getter和setter方法)
defineProperty(user, key, user[key])
console.log(user.name) // 获取值
user.name = '阿童木' // 设置值
}
</script>
三、DOM元素和数据的对应
从上面简单的例子可以看到,在获取值的时候触发get
方法,在设置值的时候触发set
方法。
即此时我们就可以在get/set
方法中,截取到对应的数据。
接下来就要思考一下,在set
方法中 对应的修改视图的函数,就能在data发生变化时,从而达到视图同步更新。相关问题:
1、如何获取到视图中的DOM元素?
最简单,最暴力的方法 ---- 遍历
2、多个属性时,如何解决属性和对应DOM保持一致性问题?
每个属性对应一个回调函数。
当属性发生改变之后,直接调用自身的回调函数。
类似: 一个公司中有100个员工,每个员工的名字不同、技能不一样,如何保证老板不弄错他们的姓名以及技能?这需要老板记住每个员工的相貌特征、对应的名字以及技能吗?
解决: 只要每个员工挂一个工牌,添加上姓名、技能。当老板想知道你的时,只需要拿你的工牌看一下就可以了。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<h3>名字: {{name}}</h3>
<h3>技能: {{skill}}</h3>
</div>
<div>
请输入你的名字:<input type="text" class="text" value="" />
<input type="button" class="bt" value="确定" />
</div>
<script type="text/javascript">
function Vue(options){ // 动态原型方式 创建名字为Vue的类
this.el = options.el // 元素
this.data = options.data // 数据
this.watcher = {} // 属性、数据、元素 的关联
// 给每个属性添加get/set方法
var self = this
Object.keys(this.data).forEach(function(key){ // 遍历所有key
// 通过原型属性添加的方法
self.defineProperty(self.data, key, self.data[key])
})
// 通过原型属性添加的方法-解析DOM
this.compile()
}
Vue.prototype = { // 添加原型属性
defineProperty(obj, key, val){ // 属性添加get/set方法
var self = this // 后续this指向问题的处理
Object.defineProperty(obj, key, {
enumerable: true,
get(){
return val
},
set(newVal){
if (val !== newVal){
val = newVal
self.watcher[key](newVal) // 调用watcher中key对应的函数 进行更新
}
}
})
},
compile(){ // 解析DOM即更新数据
// 获取到对象绑定的元素
var ele = document.querySelector(this.el);
// 所有子元素
var childEls = ele.childNodes;
// 遍历所有子元素
// Array.slice.call(arguments),目的是将arguments对象的数组提出来转化为数组,arguments本身并不是数组而是对象
[].slice.call(childEls).forEach(el => { // ES6写法,这就可以避免this指向问题(上面是没使用ES6写法)
// 定义正则表达式,即两个花括号 括住的
var reg = /\{\{(.*)\}\}/;
// 文本内容
var text = el.textContent;
if(reg.test(text)){ // 正则匹配
var key = reg.exec(text)[1]; // 获取到key
el.textContent = this.data[key]; // data中存储的key值渲染到视图中
// 更新的方法操作 每个key都对应有更新的方法
// 当数据发生改变时,就有对应的方法进行更新
this.watcher[key] = function(newVal){
el.textContent = newVal
}
}
})
}
}
// 创建Vue对象
var myVue = new Vue({
el: '#app',
data: {
name: '阿童木',
skill: 'web前端开发',
}
})
// 修改名字
document.querySelector('.bt').onclick = function(){
myVue.data.name = document.querySelector('.text').value
}
</script>
</body>
</html>
代码大致执行过程:
1、实例化对象
2、实例化过程中,代码中重要的点是遍历data选项中的每个key
3、在defineProperty中添加上get方法
4、在set方法中添加 界面更新的函数调用 【注意: set方法触发后才是调用,此时只是添加set方法】
5、解析DOM 【即将data中的值,先更新到界面中】
6、并给每个key对应上 更新方法 存储在 watcher 中
之后,只要数据发生改变,此时就是触发了set方法,再从watcher中找到key对应的方法进行更新页面。