本篇主要为阅读源码做一些准备,相关代码参考了网上相关资料。
本篇主要讲述数据的动态动态绑定与更新:
1:数据的动态绑定与更新核心方法是Object.defineProperty(),下面是相关介绍。
Object.definePropety(obj,property,descriptor)
/
* obj 需要操作的对象
* property 需要修改的属性
* descriptor 属性描述符,属性描述符有两种类型的取值 1:数据描述符,2:存取描述符,属性描述符是这两者之一,不能同时存在
*
数据描述符和存取描述符均具有的可选键有:
configurable:(true||false) 默认false, 当且仅当该属性值为true时,属性描述符的内容才能被改变
enumerable: (true||false) 默认false, 当该属性为true时,操作的(property)属性才是枚举类型
数据描述符独有的属性
value: 默认为undefined 值为property属性对应的值。
writable: 默认false property是否可写入,仅当为true时,操作的属性才能进行赋值操作
存取描述符独有的属性
get: 默认值undefined。给property提供geter方法的属性,当访问property时,就会触发get属性,
set: 默认值undefined。 给property提供setter方法的属性,当修改property时会触发该方法。
/
2:这里我们想实现和vue一样的格式:
window.onload = function(){
let app = new Vue ({
el:'#app', //根节点
data:{ //数据
number:1,
count:1
},
methods:{ //方法
addN: function(){
this.number+=2
},
addC: function(){
this.count++
}
}
}) }
首先我们需要先定义函数Vue()
//新建Vue函数
function Vue (options){
//初始化实例
this._init(options);
};
Vue.prototype._init = function (options){
this.$el = document.querySelector(options.el); //根节点
this.$data = options.data; //数据
this.$methods = options.methods; //方法
this._var = {}; //为动态更新数据设置的变量
this._obverse(this.$data); //为数据设置get set 方法
this._directive(this.$el); // 循环遍历文档,找寻v指令进行初始化
};
再看_obverse()方法:
Vue.prototype._obverse = function(data){
//循环数据$data,对每个数据执行Object.defineProperty()
var _this = this;
Object.keys(data).forEach((item)=>{
if(data.hasOwnProperty(item)){
var value = data[item];
_this._var.dire= [];
if(typeof value === 'Object'){
this._obverse(value)
}
Object.defineProperty (data,item,{
configurable: true,
enumerable: true,
get(){
return value
},
set(newValue){
if(newValue!=value){
value = newValue;
//需要明白的是input中的数据对应的是实例data的数据,然后通过v指定标记进行恰当的赋值操作
//如何让一个确定的节点与数据发生关系,方法是给实例设置变量,将节点作为对象添加到变量中,然后
//通过某一标记进行循环判断即可获得节点。
_this._var.dire.forEach(item=>{
item._update();
})
}
}
})
}
})
};
接着看_directive()方法 : 该方法循环遍历整个文档节点,然后找出所有v指令,并对各个指令进行逻辑赋值,v-click增加点击事件,v-model增加input事件, v-bind:进行数据显示。
//对v指令进行逻辑赋值
Vue.prototype._directive = function (el) {
let _this = this;
let root = el.children
//childNodes与children的区别
let node=Array.prototype.slice.call(root);
node.forEach((item)=>{
if(item.children.length>0){
_this._directive(item);
}
if(item.hasAttribute('v-click')){
//v-click触发点击事件
let attrValue = item.getAttribute('v-click');
item.onclick = function(){
//调用实例的methods方法
_this.$methods[attrValue].apply(_this.$data)
}
}
if(item.hasAttribute('v-model')&&(item.nodeName === 'INPUT'||item.nodeName === 'TEXTAREA')){
//v-model触发input事件
let attrValue = item.getAttribute('v-model'); //自执行函数为了初始化input值
item.addEventListener('input',(function(){
let value = item.Value;
_this._var.dire.push(new Node({
content:'value',
name: attrValue,
node: item,
vm: _this
}));
return function(){
_this.$data[attrValue] = this.value==''?0:parseInt(this.value);
}
})(),false)
}
if(item.hasAttribute('v-bind')){
let attrValue = item.getAttribute('v-bind');
//查询实例对象的数据对节点进行赋值
_this._var.dire.push(new Node({
content:'innerHTML',
name: attrValue,
node: item,
vm: _this
}))
// item.innerHTML = _this.$data[attrValue]
}
})
}
这里有一个问题:如何对某一节点进行数据的动态交互呢?
要想解决该问题可以将某一节点设置数据需要的必要条件保存下来,然后供vue数据使用,这也是Node函数存在的理由。
//设置节点对象
function Node(opt){
this.$content = opt.content; //内容输出到哪里 innerHTML 或者 value
this.$name = opt.name; //节点对应的属性值 v-bind= 'name'
this.$node = opt.node; //节点本身
this._this = opt.vm // Vue实例this
this._update();
}
Node.prototype._update = function(){
//通过该函数对节点内容进行赋值,由该句话可知我们所需要的节点信息
this.$node[this.$content] = this._this.$data[this.$name]
}
最后说明一下大致流程:
文档加载完毕 > 执行_init()函数进行初始化>>首先执行_obverse()方法遍历整个data数据,为每个数据添加get与set方法,注意
set方法内循环保存有节点信息的数组来对含有v-bind指令的节点进行数据展示。>>接着执行_directive()方法,该方法遍历整个文档节点,当发现节点存在v-click就绑定onclick事件与vue实例方法相关联,当发现v-model就绑定input事件,注意该句柄是一个
自执行函数,目的为了使该节点初始时就有值,之后又返回一个函数作为input事件该函数是为了触发数据的set方法,
当发现v-bind时,将节点信息保存到数组中同时会运行update()初始化节点的值。
最后附上完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>vue源码一</title>
</head>
<body>
<div id="app">
<form>
<input type="text" v-model="number" class="in">
<button type="button" v-click="addN">增2</button>
</form>
<h3 v-bind="number"></h3>
<form>
<input type="text" v-model="count" class="in">
<button type="button" v-click="addC">增1</button>
</form>
<h3 v-bind="count"></h3>
</div>
<script>
//新建Vue函数
function Vue (options){
this._init(options);
};
Vue.prototype._init = function (options){
this.$el = document.querySelector(options.el);
this.$data = options.data;
this.$methods = options.methods;
//定义观察者函数
this._var = {};
this._obverse(this.$data);
this._directive(this.$el);
};
Vue.prototype._obverse = function(data){
//循环数据$data
var _this = this;
Object.keys(data).forEach((item)=>{
console.log(item)
if(data.hasOwnProperty(item)){
var value = data[item];
_this._var.dire= [];
if(typeof value === 'Object'){
this._obverse(value)
}
Object.defineProperty (data,item,{
configurable: true,
enumerable: true,
get(){
return value
},
set(newValue){
if(newValue!=value){
value = newValue;
//编写代码对h3中的数据进行实时监控
//需要明白的是input中的数据对应的是实例data的数据,然后通过v指定标记进行恰当的赋值操作
//如何让一个确定的节点与数据发生关系,方法是给实例设置变量,将节点作为对象添加到变量中,然后
//通过某一标记进行循环判断即可获得节点。
_this._var.dire.forEach(item=>{
item._update();
})
}
}
})
}
})
};
//对v指令进行逻辑赋值
Vue.prototype._directive = function (el) {
let _this = this;
let root = el.children
//childNodes与children的区别
let node=Array.prototype.slice.call(root); //抓化为真正的数组
node.forEach((item)=>{
if(item.children.length>0){
_this._directive(item);
}
if(item.hasAttribute('v-click')){
//v-click触发点击事件
let attrValue = item.getAttribute('v-click');
item.onclick = function(){
console.log(1111111111)
//调用实例的methods方法
_this.$methods[attrValue].apply(_this.$data)
}
}
if(item.hasAttribute('v-model')&&(item.nodeName === 'INPUT'||item.nodeName === 'TEXTAREA')){
//v-model触发input事件
let attrValue = item.getAttribute('v-model'); //自执行函数为了初始化input值
item.addEventListener('input',(function(){
let value = item.Value;
_this._var.dire.push(new Node({
content:'value',
name: attrValue,
node: item,
vm: _this
}));
return function(){
_this.$data[attrValue] = this.value==''?0:parseInt(this.value);
}
})(),false)
}
if(item.hasAttribute('v-bind')){
let attrValue = item.getAttribute('v-bind');
//查询实例对象的数据对节点进行赋值
_this._var.dire.push(new Node({
content:'innerHTML',
name: attrValue,
node: item,
vm: _this
}))
// item.innerHTML = _this.$data[attrValue]
}
})
}
//设置节点对象
function Node(opt){
this.$content = opt.content; //内容输出到哪里 innerHTML 或者 value
this.$name = opt.name; //节点对应的属性值 v-bind= 'name'
this.$node = opt.node; //节点本身
this._this = opt.vm // Vue实例this
this._update();
}
Node.prototype._update = function(){
//通过该函数对节点内容进行赋值
this.$node[this.$content] = this._this.$data[this.$name]
}
window.onload = function(){
let app = new Vue ({
el:'#app',
data:{
number:1,
count:1
},
methods:{
addN: function(){
this.number+=2
},
addC: function(){
this.count++
}
}
})
}
</script>
</body>
</html>