补充指令解析器compile

github源码

补充下HTML节点类型的知识:
元素节点   Node.ELEMENT_NODE(1)
属性节点   Node.ATTRIBUTE_NODE(2)
文本节点   Node.TEXT_NODE(3)
CDATA节点 Node.CDATA_SECTION_NODE(4)
实体引用名称节点    Node.ENTRY_REFERENCE_NODE(5)
实体名称节点   Node.ENTITY_NODE(6)
处理指令节点   Node.PROCESSING_INSTRUCTION_NODE(7)
注释节点   Node.COMMENT_NODE(8)
文档节点   Node.DOCUMENT_NODE(9)
文档类型节点   Node.DOCUMENT_TYPE_NODE(10)
文档片段节点   Node.DOCUMENT_FRAGMENT_NODE(11)
DTD声明节点 Node.NOTATION_NODE(12)

Compile指令解析器,解析DOM节点,直接固定某个节点进行替换数据的

解析模板指令,替换模板数据,初始化试图

将模板指令对应的节点绑定对应的更新函数,初始化对应的订阅器

首先需要获取到DOM元素,然后对含有DOM元素上含有指令的节点进行处理,
因此这个环节需要对DOM操作比较频繁,所有可以先建一个fragment片段,
将需要解析的DOM节点存入fragment片段里再进行处理:

//直接上代码:(先判断"{{}}")
function Compile(elm){// el->"#name" ,vm->{el:;data:;}
this.vm = elm;
this.el = document.querySelector(elm.el);
this.fragment = null;
this.init();
}
Compile.prototype = {
init:function(){
if(this.el) {
//将需要解析的DOM节点存入fragment片段里再进行处理
this.fragment = this.nodeToFragment(this.el); //接下来遍历各个节点,对含有指定的节点特殊处理,先处理指令“{{}}”:
this.compileElement(this.fragment); //绑定到el上
this.el.appendChild(this.fragment);
}else{
console.log('DOM元素不存在');
}
},
//创建代码片段
nodeToFragment:function(el){
var fragment = document.createDocumentFragment();
var child = el.firstChild;
while(child){//将DOM元素移入fragment
fragment.appendChild(child);
child = el.firstChild;
}
return fragment;
},
//对所有子节点进行判断,1.初始化视图数据,2.绑定更新函数的订阅器
compileElement:function(el){
var childNodes = el.childNodes;
var self = this;
[].slice.call(childNodes).forEach(function(node){
var reg = /\{\{(.*)\}\}/;//匹配" {{}} "
var text = node.textContent; if(self.isTextNode(node) && reg.test(text)) {//判断" {{}} "
self.compileText(node,reg.exec(text)[1]);
} if(node.childNodes && node.childNodes.length){
self.compileElement(node);//// 继续递归遍历子节点
}
});
},
//初始化视图updateText和生成订阅器:
compileText:function(node,exp){
var self = this;
var initText = this.vm[exp]; //代理访问self_vue.data.name1 -> self_vue.name1
this.updateText(node,initText);//将初始化的数据初始化到视图中
new Watcher(this.vm,exp,function(value){//{},name, // 生成订阅器并绑定更新函数
self.updateText(node,value);
})
},
updateText: function (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
isTextNode:function(node){
return node.nodeType == 3;//文本节点
}
};

为了将解析器Compile与监听器Observer和订阅者Watcher关联起来,我们需要再修改一下类SelfVue函数:

 // function SelfVue(data,el,exp){  //first
function SelfVue(options){
var self = this; // this.data = data; //first
this.data = options.data;
this.el = options.el; this.vm = this; //second
console.log(this) Object.keys(this.data).forEach(function (key) {
self.proxyKeys(key);//绑定代理属性
}); //监听数据:
observers(this.data); //first:
/*el.innerHTML = this.data[exp];//初始化模板数据的值
new Watcher(this,exp,function(value){//绑定订阅器
el.innerHTML = value;
});*/ //初始化视图updateText和生成订阅器
new Compile(this); return this;
}

到这里,大括号"{{}}"类型的双向数据绑定完成;

补充上v-model和事件指令:

在compileElement函数加上对其他指令节点进行判断,然后遍历其所有属性

添加事件指令

添加一个v-model指令

  Compile.prototype = {
init:function(){
if(this.el) {
//将需要解析的DOM节点存入fragment片段里再进行处理
this.fragment = this.nodeToFragment(this.el); //接下来遍历各个节点,对含有指定的节点特殊处理,先处理指令“{{}}”:
this.compileElement(this.fragment); //绑定到el上
this.el.appendChild(this.fragment);
}else{
console.log('DOM元素不存在');
}
},
//创建代码片段
nodeToFragment:function(el){
var fragment = document.createDocumentFragment();
var child = el.firstChild;
while(child){//将DOM元素移入fragment
fragment.appendChild(child);
child = el.firstChild;
}
return fragment;
},
//对所有子节点进行判断,1.初始化视图数据,2.绑定更新函数的订阅器
compileElement:function(el){
var childNodes = el.childNodes;
var self = this;
[].slice.call(childNodes).forEach(function(node){
var reg = /\{\{(.*)\}\}/;//匹配" {{}} "
var text = node.textContent;
/* 补充判断: */
if(self.isElementNode(node)){//元素节点判断
self.compile(node);
}else if(self.isTextNode(node) && reg.test(text)) {//文本节点判断 ,判断" {{}} "
self.compileText(node,reg.exec(text)[1]);
} if(node.childNodes && node.childNodes.length){
self.compileElement(node);//// 继续递归遍历子节点
}
});
},
//初始化视图updateText和生成订阅器:
compileText:function(node,exp){
var self = this;
var initText = this.vm[exp]; //代理访问self_vue.data.name1 -> self_vue.name1
this.updateText(node,initText);//将初始化的数据初始化到视图中
new Watcher(this.vm,exp,function(value){//{},name, // 生成订阅器并绑定更新函数
self.updateText(node,value);
})
},
updateText: function (node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
compile:function(node){
var nodeAttrs = node.attributes;
var self = this;
Array.prototype.forEach.call(nodeAttrs,function(attr){
var attrName = attr.name;
if(self.isDirective(attrName)){//查到" v- "
var exp = attr.value;
var dir = attrName.substring(2);//" v-on/v-model " if(self.isEventDirective(dir)){ // 事件指令
self.compileEvent(node,self.vm,exp,dir);
}else{
self.compileModel(node,self.vm,exp,dir);
}
node.removeAttribute(attrName);
}
})
},
compileEvent:function(node,vm,exp,dir) {//代码片段<><>,{data:;vm:;el:;},v-on="add",on:
var eventType = dir.split(':')[1];//on
var cb = vm.methods && vm.methods[exp]; if(eventType && cb){
node.addEventListener(eventType,cb.bind(vm),false);
}
},
compileModel:function(node,vm,exp,dir){//代码片段<><>,{data:;vm:;el:;},v-on="addCounts",model:
var self = this;
var val = this.vm[exp];
this.modelUpdater(node,val);
new Watcher(this.vm,exp,function(value){
self.modelUpdater(node,value);
}); node.addEventListener('input',function(e){
var newValue = e.target.value;
if(val === newValue){
return;
}
self.vm[exp] = newValue;
val = newValue;
})
},
modelUpdater:function(node,value){
node.value = typeof value == 'undefined' ? '' : value;
},
isTextNode:function(node){
return node.nodeType == 3;//文本节点
},
isElementNode:function(node){
return node.nodeType == 1;//元素节点<p></p>
},
isDirective:function(attr){//查找自定义属性为:v- 的属性
return attr.indexOf('v-') == 0;
},
isEventDirective:function(dir){ // 事件指令
return dir.indexOf('on:') === 0
}
};

再改造下类SelfVue,使它更像Vue的用法:

    function SelfVue(options){
var self = this;
this.data = options.data;
this.el = options.el;
this.methods = options.methods; this.vm = this; //second Object.keys(this.data).forEach(function (key) {
self.proxyKeys(key);//绑定代理属性
}); //监听数据:
observers(this.data); //初始化视图updateText和生成订阅器
new Compile(this);
options.mounted.call(this); return this;
}

测试:

<body>
<div id="app">
<h2>{{name1}}</h2>
<h2>{{name2}}</h2> <input type="text" v-model="title">
<h3>{{title}}</h3> <button v-on:click="clickMe">v-on事件</button>
<h3>{{event}}</h3>
</div> <script src="js-second/observer.js"></script>
<script src="js-second/watcher.js"></script>
<script src="js-second/compile.js"></script>
<script src="js-second/selfVue.js"></script>
<script> var self_vue = new SelfVue({
el:"#app",
data:{
name1:'我是name1',
name2:'我是name2',
event:'event',
title:'title初始值'
},
methods:{
clickMe:function(){
this.event = '事件重新赋值'
}
},
mounted:function(){
console.log('mounted')
}
}); /* window.setTimeout(function(){
self_vue.name1 = 'name1再次赋值'
},2000);
window.setTimeout(function(){
self_vue.name2 = 'name2再次赋值'
},3000)*/
</script>
</body>

系列文章的目录:

Vue双向绑定的实现原理系列(一):Object.defineproperty
Vue双向绑定的实现原理系列(二):设计模式
Vue双向绑定的实现原理系列(三):监听器Observer和订阅者Watcher
Vue双向绑定的实现原理系列(四):补充指令解析器compile

Vue双向绑定的实现原理系列(四):补充指令解析器compile的更多相关文章

  1. Vue双向绑定的实现原理系列(一):Object.defineproperty

    了解Object.defineProperty() github源码 Object.defineProperty()方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象. ...

  2. Vue双向绑定的实现原理系列(三):监听器Observer和订阅者Watcher

    监听器Observer和订阅者Watcher 实现简单版Vue的过程,主要实现{{}}.v-model和事件指令的功能 主要分为三个部分 github源码 1.数据监听器Observer,能够对数据对 ...

  3. 梳理vue双向绑定的实现原理

    Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后 ...

  4. Vue双向绑定的实现原理及简单实现

    vue数据双向绑定原理   vue数据双向绑定是通过(数据劫持)+(发布者-订阅者模式)的方式来实现的,而所谓的数据劫持就是通过Object.defineProperty() 来实现的,所谓的Obje ...

  5. vue双向绑定的原理及实现双向绑定MVVM源码分析

    vue双向绑定的原理及实现双向绑定MVVM源码分析 双向数据绑定的原理是:可以将对象的属性绑定到UI,具体的说,我们有一个对象,该对象有一个name属性,当我们给这个对象name属性赋新值的时候,新值 ...

  6. vue双向绑定原理及实现

    vue双向绑定原理及实现 一.总结 一句话总结:vue中的双向绑定主要是通过发布者-订阅者模式来实现的 发布 订阅 1.单向绑定和双向绑定的区别是什么? model view 更新 单向绑定:mode ...

  7. [Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅

    有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫 ...

  8. vue双向绑定的原理

    什么是双向数据绑定?Vue是一个MVVM框架,数据绑定简单来说,就是当数据发生变化时,相应的视图会进行更新,当视图更新时,数据也会跟着变化. 实现数据绑定的方式大致有以下几种: - 1.发布者-订阅者 ...

  9. vue双向绑定(数据劫持+发布者-订阅者模式)

    参考文献:https://www.cnblogs.com/libin-1/p/6893712.html 实现mvvm主要包含两个方面,数据变化更新视图,视图变化更新数据. 关键点在于data如何更新v ...

随机推荐

  1. Node原生demo

    1.=>创建配置模块,作用是先判断是开发环境还是生产环境,并将开发或生产环境的数据库信息和http信息分别筛开,便于选择 2.=>创建数据库模块,作用是连接数据库 3.=>创建路由模 ...

  2. 插入排序的Python代码实现

    是打扑克的时候了 那种按大小排序的扑克游戏 def insert_sort(arr): for i in range(1,len(arr)): for j in range(i,0,-1): #与左边 ...

  3. Spring学习笔记(一)

    Spring学习笔记(一) 这是一个沉淀的过程,大概第一次接触Spring是在去年的这个时候,当初在实训,初次接触Java web,直接学习SSM框架(当是Servlet都没有学),于是,养成了一个很 ...

  4. NOIP模拟赛 打铁的匠 题解

    [问题描述] Mark Douglas是一名优秀的锻造师.与他优秀的锻造水平不相符,他非常穷,以至于很多好刀都因为缺少素材缺少资金无法打造. Mark把他有能力锻造的所有n种刀建成了一棵锻造树,除了第 ...

  5. 超级实用的 Java 工具类

    超级实用的 Java 工具类 在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.以下工具类.方法按使用流行度排名,参考数据来源于Github上随机选取 ...

  6. 运用加密技术保护Java源代码(转)

    出处:运用加密技术保护Java源代码 为什么要加密? 对于传统的C或C++之类的语言来说,要在Web上保护源代码是很容易的,只要不发布它就可以.遗憾的是,Java程序的源代码很容易被别人偷看.只要有一 ...

  7. Office/Visio/Project 2019 专业版iso

    一.Microsoft Office 2019专业增强版1.简体中文版Office Professional Plus 2019 (x86 and x64) – DVD (Chinese-Simpli ...

  8. js变量浅拷贝 深拷贝

    js的变量分为简单数据类型和复杂数据类型(即引用类型). 简单数据类型在内存中占据着固定大小的空间,被保存在栈内存中,在简单数据类型中,当一个变量指向另一个变量时,只是创建了值的副本,两个变量只是占用 ...

  9. centos配置发送邮件

    邮件已经可以接收到了 CentOS下发送邮件有很多种方法,这里采用比较简单的mail客户端,配置第三方邮件服务商,例如:163.com 1.安装所用软件 yum install mailx sendm ...

  10. C#中操作单个cookie和cookie字典

    单个cookie和cookie字典在浏览器中的存储格式如下:可以看到,单个cookie是以单一键值对的方式存储的,而cookie字典的值包含多个键值对,这些键值对之间以&符号拼接.cookie ...