来一张原理图:
实现思路:
(1)绑定data 种的数据,为每个数据添加指令。通过Object,defineProperty() 来通知属性是否更改
(2) 找到每个DOM节点的指令。绑定事件。并绑定watcher
(3) 实现DOM事件改变之后, 响应data数据,实现视图更新
<!DocType>
<html>
<title>vue 的双向绑定事件</title>
<body id="app">
<input type="text" v-model="number"/>
<span v-bind="number"></span>
<input type="text" v-model="age"/>
<span v-bind="age"></span>
</body>
<script>
function Vue (options) {
this._init(options);
}
Vue.prototype._init = function (options) {
this.$data = options.data;
this.$methods = options.data.methods;
this.$el = document.querySelector(options.el);
this.$methods = options.methods;
this.$key = '';
this._binding = {};
// 观测数据
this._observer(this.$data);
this._complie(this.$el);
// this._test(this.$data);
}
// 观测数据
Vue.prototype._observer = function (obj) {
var value;
let _this = this;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
this._binding[key] = {
_directives: []
};
value = obj[key];
if (typeof value === 'object') {
this._observer(value);
}
Object.defineProperty(this.$data, key, {
enumerable: true,
configurable: true,
get: function () {
console.log(`获取${value}`, key);
return value;
},
set: function (newVal) {
console.log('key:', key, _this.$key);
if (value !== newVal) {
value = newVal;
_this._binding[_this.$key]._directives.forEach(function (item, index) {
item.update();
})
}
}
})
}
}
}
// 为DOM节点添加指令事件
Vue.prototype._complie = function (root) {
var _this = this;
var nodes = root.children;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.children.length) {
this._complie(node);
}
if (node.hasAttribute('v-click')) {
node.onclick = (function () {
var attrVal = nodes[i].getAttribute('v-click');
return _this.$methods[attrVal].bind(_this.$data);
})();
}
if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
node.addEventListener('input', (function(key) {
var attrVal = node.getAttribute('v-model');
_this._binding[attrVal]._directives.push(new Watcher(
'input',
node,
_this,
attrVal,
'value'
))
return function() {
_this.$key = attrVal
_this.$data[attrVal] = nodes[key].value;
}
})(i));
}
if (node.hasAttribute('v-bind')) {
var 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; //指令名称,例如文本节点,该值设为"text"
this.el = el; //指令对应的DOM元素
this.vm = vm; //指令所属myVue实例
this.exp = exp; //指令对应的值,本例如"number"
this.attr = attr; //绑定的属性值,本例为"innerHTML"
this.update();
}
// 更新数据
Watcher.prototype.update = function () {
this.el[this.attr] = this.vm.$data[this.exp];
}
// 测试
Vue.prototype._test = function($data) {
var a = $data.number;
$data.number = 32;
}
window.onload = function () {
var app = new Vue({
el: '#app',
data: {
number: 12,
age: 444
},
methods: {}
})
}
</script>
</html>
来一张原理图:
实现思路:
(1)绑定data 种的数据,为每个数据添加指令。通过Object,defineProperty() 来通知属性是否更改
(2) 找到每个DOM节点的指令。绑定事件。并绑定watcher
(3) 实现DOM事件改变之后, 响应data数据,实现视图更新
<!DocType>
<html>
<title>vue 的双向绑定事件</title>
<body id="app">
<input type="text" v-model="number"/>
<span v-bind="number"></span>
<input type="text" v-model="age"/>
<span v-bind="age"></span>
</body>
<script>
function Vue (options) {
this._init(options);
}
Vue.prototype._init = function (options) {
this.$data = options.data;
this.$methods = options.data.methods;
this.$el = document.querySelector(options.el);
this.$methods = options.methods;
this.$key = '';
this._binding = {};
// 观测数据
this._observer(this.$data);
this._complie(this.$el);
// this._test(this.$data);
}
// 观测数据
Vue.prototype._observer = function (obj) {
var value;
let _this = this;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
this._binding[key] = {
_directives: []
};
value = obj[key];
if (typeof value === 'object') {
this._observer(value);
}
Object.defineProperty(this.$data, key, {
enumerable: true,
configurable: true,
get: function () {
console.log(`获取${value}`, key);
return value;
},
set: function (newVal) {
console.log('key:', key, _this.$key);
if (value !== newVal) {
value = newVal;
_this._binding[_this.$key]._directives.forEach(function (item, index) {
item.update();
})
}
}
})
}
}
}
// 为DOM节点添加指令事件
Vue.prototype._complie = function (root) {
var _this = this;
var nodes = root.children;
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.children.length) {
this._complie(node);
}
if (node.hasAttribute('v-click')) {
node.onclick = (function () {
var attrVal = nodes[i].getAttribute('v-click');
return _this.$methods[attrVal].bind(_this.$data);
})();
}
if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
node.addEventListener('input', (function(key) {
var attrVal = node.getAttribute('v-model');
_this._binding[attrVal]._directives.push(new Watcher(
'input',
node,
_this,
attrVal,
'value'
))
return function() {
_this.$key = attrVal
_this.$data[attrVal] = nodes[key].value;
}
})(i));
}
if (node.hasAttribute('v-bind')) {
var 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; //指令名称,例如文本节点,该值设为"text"
this.el = el; //指令对应的DOM元素
this.vm = vm; //指令所属myVue实例
this.exp = exp; //指令对应的值,本例如"number"
this.attr = attr; //绑定的属性值,本例为"innerHTML"
this.update();
}
// 更新数据
Watcher.prototype.update = function () {
this.el[this.attr] = this.vm.$data[this.exp];
}
// 测试
Vue.prototype._test = function($data) {
var a = $data.number;
$data.number = 32;
}
window.onload = function () {
var app = new Vue({
el: '#app',
data: {
number: 12,
age: 444
},
methods: {}
})
}
</script>
</html>