代码实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<form>
<input type="text" v-model="number">
<button type="button" v-click="increment">add</button>
</form>
<h3 v-bind="number"></h3>
</div>
<script>
// this指向问题
// 添加事件的3种方法
// Object.defineProperty
// hasOwnProperty
// hasAttribute
// js原生node方法
function Vue(option) {
this._init(option) // 初始化
}
Vue.prototype._init = function (option){
// 初始化vue中数据,方法
this.$option = option
this.$el = document.querySelector(option.el)
this.$data = option.data
this.$methods = option.methods
// 为data数据的每一项添加绑定事件
this._binding = {}
// 建立监听, 编译html
this._obsever(this.$data)
this._compile(this.$el)
}
Vue.prototype._obsever = function (obj) {
// 指针缓存
let _this = this
// 循环data数据的每一项key,为每一个key添加指令
Object.keys(obj).forEach((key) => {
// hasOwnProperty 只监听自身的数据 (非原型上的属性)
if(obj.hasOwnProperty(key)){
// 为每一个key添加指令
_this._binding[key] = {
directives: []
}
}
let value = obj[key]
// 如果value为对象,递归循环
if (typeof value === 'Object') {
_this._obsever(value)
}
// 缓存key的指令,下边set用的,可以不写
let binding = _this._binding[key]
// Object.defineProperty vue实现双向绑定的原理
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Object.defineProperty(_this.$data, key, {
enumerable: true,
configurable: true,
get: function () {
console.log('取值')
return value
},
set: function (newValue) {
if (value !== newValue) {
console.log('赋值')
value = newValue
binding.directives.forEach((item) => {
item.update()
})
}
}
})
})
}
Vue.prototype._compile = function (root) {
let _this = this
// 获取根dom的子元素
let nodes = root.children
// 循环子元素
for (var i = 0; i < nodes.length; i++) {
let node = nodes[i]
// 判断子元素有没有子元素,有就递归循环
if (node.children.length > 0) {
_this._compile(node)
}
// 拿到'v-click'的方法名,给dom添加方法
if (node.hasAttribute('v-click')) {
let attrVal = node.getAttribute('v-click')
// bind(_this.$data)更改this指向 (否则现在的this是dom)
node.onclick = _this.$methods[attrVal].bind(_this.$data)
}
// 同上
if (node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {
let attrVal = node.getAttribute('v-model')
_this._binding[attrVal].directives.push(new Watcher(
'input',
node,
_this,
attrVal,
'value'
))
// 添加事件 更新dom
node.addEventListener('input', () => {
_this.$data[attrVal] = node.value
})
}
// 同上
if (node.hasAttribute('v-bind')) {
let attrVal = node.getAttribute('v-bind')
_this._binding[attrVal].directives.push(new Watcher(
'text',
node,
_this,
attrVal,
'innerHTML'
))
}
}
}
function Watcher (name, el, vm, exp, attr) {
this.name= name
this.el = el
this.vm = vm
this.exp = exp
this.attr = attr
// 为数据data赋值,但是dom未更新
this.update()
}
Watcher.prototype.update = function () {
this.el[this.attr] = this.vm.$data[this.exp]
}
window.onload = ()=> {
new Vue({
el: '#app',
data: {
number: 0,
count: 0
},
methods: {
increment () {
this.number++;
},
incre () {
this.count++;
}
}
})
}
</script>
</body>
</html>
数据初始化
init
方法中初始化对象的属性,定义了数据data
,方法methods
,元素 el
,在初始化中调用了 _obsever
和_compile
。
Object.defineProperty
Object.defineProperty
方法是作为双向绑定实现的基础,在Object.defineProperty
方法中有两个属相 set
和get
,详细功能可查看MDN文档。
我们从后台获取数据后,赋值给data后,html会自动更新,就相当于调用了对应数据的
get
方法。
obsever
在 _obsever
方法中首先遍历vue
对象中的data的所有数据,并对数据添加了Object.defineProperty
方法,此方法中设置了get
和set
属性
tips: 在每个数据添加方法 Object.defineProperty
后,get
和 set
属性会在页面中一直缓存,get
和 set
可以理解为闭包
_compile
_compile
方法首先遍历所有的dom节点,将v-click
指令转化为原生事件,编译完成后添加watch
函数
简述一下实现原理
- 数据绑定
- 主要是监听
data
数据的值 设置回调方法set
get
-
get
在数据被调用时 触发 (一般用在 添加观察者
) -
set
在数据赋值时调用 (调用后会判断数是否更改 来 重新编译模板 渲染dom)
- 主要是监听
- 模板编译
- 主要是把 .vue 文件中的 html 编译成
vdom
然后展示在页面上 -
绑定的数据
会触发get
->get
回调中添加了一个观察者
- 用 js 实现 事件监听
- 主要是把 .vue 文件中的 html 编译成
- 在
请求接口后改变 数据
,触发set
->set
回调 比较数据 编译模板(里面有 vdom 的 diff patch 算法) -
事件触发
触发绑定的事件
->事件在改变数据
->改变数据在调set 方法
->比较后更新数据
网友评论