一、功能需求
自己写一个vue程序定义为Myvue,功能是实现简单的数据双向绑定
二、效果预览
三、代码实现与解析
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
#app{
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<input type="text" v-model="number" />
<button type="button" v-click="btnhandle">增加计数</button>
<p v-bind="number" style="font-size: 22px;font-weight: 600;"></p>
</div>
<script type="text/javascript">
// 实现流程总结:
// 1.定义Myvue构造函数,构造函数主要功能是执行初始化,
// 2.new一个Myvue实例,相当于vue实例,包含data,methods等等
// 3.原型中定义初始化函数,主要功能是保存属性和定义方法
// 4.定义_obverse函数重写data中的set与get函数,让他变成响应式数据
// 5.定义_complie函数,监听三个指令的操作(如v-click的dom中的点击事件绑定到methods定义的事件中,
// v-model监听输入框的值赋值到data中,使data的值改变,从而触发data的set函数,通过update函数更新dom数据)
// 定义构造函数Myvue
function Myvue(options) {
// option:Myvue实例的所有参数
this._init(options)
}
// 重写data的set和get函数,并且更新值
Myvue.prototype._obverse = function(obj) {
var value
// 遍历对象(监听对象,data中的参数)
for (key in obj) {
// 判断key是否自己的私有属性(非继承来的)
if (obj.hasOwnProperty(key)) {
value = obj[key]
console.log('value', value)
console.log('key', value)
// 定义一个_directives数组保存映射关系
this._binding[key] = {
_directives: []
}
// 如果是对象递归遍历
if (typeof value === 'object') {
this._obverse(value)
}
var binding = this._binding[key]
Object.defineProperty(this.$data, key, {
enumerable: true,
configurable: true,
// 获取值
get: function() {
return value
},
set: function(newVal) {
// 如果新值和旧值不一样,则需要更新
if (value !== newVal) {
// 更新data中的值
value = newVal
// 遍历映射关系,并且更新
binding._directives.forEach(function(item) {
// 遍历三个指令的Watcher实例,并且执行取得Myvue中的data中的参数赋值给dom中的value或者innerHTML的操作
// 把data中值更新到dom
item.update()
})
}
}
})
}
}
}
//添加指令函数,主要目的对每个dom节点的指令的属性值和Myvue中定义的属性进行对应
// 如:v-click指令,找到该dom节点绑定点击事件,并且是methods中的定义的点击事件
// v-model指令:监听输入框的输入值,赋值到Myvue的data中,创建Watcher实例并保存
// v-bind指令:创建Watcher实例并保存
Myvue.prototype._complie = function(root) {
var _this = this
// root:id为app的dom节点
console.log('节点', root)
// root.children:获取节点,[div,button,p]
var nodes = root.children
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i]
// 有子节点再遍历
if (node.children.length) {
this._complie(node)
}
// 判断v-click指令,给包含v-click指令的该节点绑定点击事件,从而实现点击时会执行methods中定义的点击事件
if (node.hasAttribute('v-click')) {
node.onclick = (function() {
// 点击事件是一个methods中和dom节点中key值一致(事件名称一致)的函数
var attrVal = nodes[i].getAttribute(('v-click'))
// bind是使methods的作用域和data中的作用域保持一致
return _this.$methods[attrVal].bind(_this.$data)
})()
}
// 判断v-model指令,而且该指令是写在input和textarea标签中的,从而实现输入宽的值映射到data中,并且保存一个Watcher实例
if (node.hasAttribute('v-model')&&(node.tagName==='INPUT' || node.tagName === 'TEXTAREA')) {
// 给该节点绑定一个事件监听输入框输入事件
node.addEventListener('input',(function(key) {
// number
var attrVal = node.getAttribute('v-model')
console.log('vmodel', attrVal)
// 每个指令(v-bind,v-model,v-click)有自己的_directives数组
// 传入一个Watcher实例,实例中保存了以下参数
_this._binding[attrVal]._directives.push(new Watcher(
'input',
node,
_this,
attrVal,
'value'
))
return function() {
// 监听输入框输入事件,把输入值映射到data中对应的参数中
_this.$data[attrVal] = nodes[key].value
}
})(i))
}
// 判断v-bind,保存一个Watcher实例
if (node.hasAttribute('v-bind')) {
// number
var attrVal = node.getAttribute('v-bind')
_this._binding[attrVal]._directives.push(new Watcher(
'text',
node,
_this,
attrVal,
'innerHTML'
))
}
}
}
// 构造函数原型挂载初始化方法
Myvue.prototype._init = function(options) {
console.log('初始化参数', options)
console.log('作用域Myvue', this)
this.$options = options
// app的dom节点
this.$el = document.querySelector(options.el)
this.$data = options.data
this.$methods = options.methods
//_binding保存着model与view的映射关系,也就是我们前面定义的Watcher的实例。
// 当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
this._binding = {}
this._obverse(this.$data)
this._complie(this.$el)
}
// 定义watcher类,绑定更新函数,实现对dom的更新
function Watcher(name, el, vm, exp, attr) {
// 节点名称(input,)
this.name = name
// 指令对应的dom元素
this.el = el
// 指令所属myvue实例
this.vm = vm
// 指令对应的值,如本列的v-model = number中的number
this.exp = exp
// 绑定的属性值,取到标签的值,p标签按innerHTML取值,input则按value取值
this.attr = attr
}
// 通过监听器把data中的参数值更新到dom中的函数
Watcher.prototype.update = function() {
// 取得Myvue中的data中的参数赋值给dom中的value或者innerHTML
this.el[this.attr] = this.vm.$data[this.exp]
}
// 实例化myvue
window.onload = function() {
var app = new Myvue({
el: '#app',
data: {
number: 0
},
methods: {
btnhandle: function() {
this.number++
console.log('按钮', this.number)
}
}
})
}
</script>
</body>
</html>