接上文:一套代码小程序&Web&Native运行的探索04——数据更新

对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/mvvm

参考:

https://github.com/fastCreator/MVVM(极度参考,十分感谢该作者,直接看Vue会比较吃力的,但是看完这个作者的代码便会轻易很多,可惜这个作者没有对应博客说明,不然就爽了)

https://www.tangshuang.net/3756.html

https://www.cnblogs.com/kidney/p/8018226.html

http://www.cnblogs.com/kidney/p/6052935.html

https://github.com/livoras/blog/issues/13

根据最近的学习,离我们最终的目标还有一段距离,但是对于Vue实现原理却慢慢有了体系化的认识,相信本系列结束后,如果能完成我们跨端代码,哪怕是demo的实现,都会对后续了解Vue或者React这里源码提供深远的帮助,平时工作较忙,这次刚好碰到假期,虽然会耽搁一些时间,我们试试这段时间运气可好,能不能在这个阶段取得不错的进展,好了我们继续完成今天的学习吧

到目前的地步,其中一些代码比较散乱,没有办法粘贴出来做讲解了,我这边尽量写注释,这里文章记录的主要目的还是帮助自己记录思路

昨天,我们完成了最简单的模板到DOM的实现,以及执行setData时候页面重新渲染工作,只不过比较粗暴还没有引入snabbdom进行了重新渲染,今天我们来完成其中的事件绑定部分代码

这里我们先不去管循环标签这些的解析,先完成事件绑定部分代码,这里如果只是想实现click绑定便直接在此处绑定事件即可:

 class Element {
constructor(tagName, props, children, vm) {
this.tagName = tagName;
this.props = props;
this.children = children || [];
this.vm = vm.vm;
}
render() {
//拿着根节点往下面撸
let el = document.createElement(this.tagName);
let props = this.props.props;
let scope = this; let events = this.props.on; for(let name in props) {
el.setAttribute(name, props[name]);
} for(name in events) {
let type = Object.keys(this.props.on);
type = type[0];
el.addEventListener(type, function (e) {
scope.vm.$options.methods[scope.props.on[type]] && scope.vm.$options.methods[scope.props.on[type]].call(scope.vm, e);
})
} let children = this.children; for(let i = 0, l = children.length; i < l; i++) {
let child = children[i];
let childEl;
if(child instanceof Element) {
//递归调用
childEl = child.render();
} else {
childEl = document.createTextNode(child);
}
el.append(childEl);
}
return el;
}
}

显然,这个不是我们要的最终代码,事实上,事件如何绑定dom如何比较差异渲染,我们这块不需要太多关系,我们只需要引入snabbdom即可,这里便来一起了解之

snabbdom

前面我们对snabbdom做了初步介绍,暂时看来MVVM框架就我这边学习的感觉有以下几个难点:

① 第一步的模板解析,这块很容易出错,但如果有志气jQuery源码的功底就会比较轻易

② 虚拟DOM这块,要对比两次dom树的差异再选择如何做

只要突破这两点,其他的就会相对简单一些,而这两块最难也最容易出错的工作,我们全部引用了第三方库HTMLParser和snabbdom,所以我们都碰上了好时代啊......

我们很容易将一个dom结构用js对象来抽象,比如我们之前做的班次列表中的列表排序:

这里出发的因子就有出发时间、耗时、价格,这里表示下就是:

 let trainData = {
sortKet: 'time', //耗时,价格,发车时间等等方式排序
sortType: 1, //1升序,2倒叙
oData: [], //服务器给过来的原生数据
data: [], //当前筛选条件下的数据
}

这个对象有点缺陷就是不能与页面映射起来,我们之前的做法就算映射起来了,也只会跟一个跟节点做绑定关系,一旦数据发生变化便全部重新渲染,这个还是小问题,比较复杂的问题是半年后筛选条件增加,这个页面的代码可能会变得相当难维护,其中最难的点可能就是页面中的dom关系维护,和事件维护

而我们想要的就是数据改变了,DOM自己就发生变化,并且以高效的方式发生变化,这个就是我们snabbdom做的工作了,而之前我们用一段代码说明过这个问题:

var element = {
tagName: 'ul', // 节点标签名
props: { // DOM的属性,用一个对象存储键值对
id: 'list'
},
children: [ // 该节点的子节点
{tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
]
}
1 <ul id='list'>
2 <li class='item'>Item 1</li>
3 <li class='item'>Item 2</li>
4 <li class='item'>Item 3</li>
5 </ul>

真实的虚拟DOM会翻译为这样:

class Element {
constructor(tagName, props, children) {
this.tagName = tagName;
this.props = props;
this.children = children;
}
} function el(tagName, props, children) {
return new Element(tagName, props, children)
} el('ul', {id: 'list'}, [
el('li', {class: 'item'}, ['Item 1']),
el('li', {class: 'item'}, ['Item 2']),
el('li', {class: 'item'}, ['Item 3'])
])

这里很快就能封装一个可运行的代码出来:

<!doctype html>
<html>
<head>
<title>起步</title>
</head>
<body> <script type="text/javascript">
//***虚拟dom部分代码,后续会换成snabdom
class Element {
constructor(tagName, props, children) {
this.tagName = tagName;
this.props = props;
this.children = children;
}
render() {
//拿着根节点往下面撸
let root = document.createElement(this.tagName);
let props = this.props; for(let name in props) {
root.setAttribute(name, props[name]);
} let children = this.children; for(let i = 0, l = children.length; i < l; i++) {
let child = children[i];
let childEl;
if(child instanceof Element) {
//递归调用
childEl = child.render();
} else {
childEl = document.createTextNode(child);
}
root.append(childEl);
} this.rootNode = root;
return root;
}
} function el(tagName, props, children) {
return new Element(tagName, props, children)
} let vnode = el('ul', {id: 'list'}, [
el('li', {class: 'item'}, ['Item 1']),
el('li', {class: 'item'}, ['Item 2']),
el('li', {class: 'item'}, ['Item 3'])
]) let root = vnode.render(); document.body.appendChild(root); </script> </body>
</html>

我们今天要做的事情,便是把这段代码写的更加完善一点,就要进入第二步,比较两颗虚拟树的差异了,而这块也是snabbdom的核心,当然也比较有难度啦

PS:这里借鉴:https://github.com/livoras/blog/issues/13

实际代码中,会对两棵树进行深度优先遍历,这样会给每个节点一个唯一的标志:

在深度优先遍历的时候,每到一个节点便与新的树进行对比,如果有差异就记录到一个对象中:

 //遍历子树,用来做递归的
function diffChildren(oldNodeChildren, newNodeChildren, index, patches) { let leftNode = null;
let curNodeIndex = index; for(let i = 0, l = oldNodeChildren.length; i < l; i++) {
let child = oldNodeChildren[i];
let newChild = newNodeChildren[i]; //计算节点的标识
curNodeIndex = (leftNode && leftNode.count) ? curNodeIndex + leftNode.count + 1 : curNodeIndex + 1;
dfsWalk(child, newChild)
leftNode = child;
}
} //对两棵树进行深度优先遍历,找出差异
function dfsWalk(oldNode, newNode, index, patches) {
//将两棵树的不同记录之
patches[index] = [];
diffChildren(oldNode.children, newNode.children, index, patches);
} //对比两棵树的差异
function diff(oldTree, newTree) {
//当前节点标志
let index = 0;
//记录每个节点的差异
let patches = {};
//深度优先遍历
return patches;
}
patches[0] = [{difference}, {difference}, ...] // 用数组存储新旧节点的不同

这里已经做好了工具流程遍历节点得出差异,而我们的差异有:

① 替换原来的节点,例如把div换成section

② 移动、删除、新增子节点,例如把p与ul顺序替换

③ 这个比较简单,修改节点属性

④ 这个也比较简单,修改文本内容

这里给这几种类型的定义:

let REPLACE = 0
let REORDER = 1
let PROPS = 2
let TEXT = 3

节点替换首先判断tagname是否一致即可:

patches[0] = [{
type: REPALCE,
node: newNode // el('section', props, children)
}]

如果给div新增属性,便记录之:

patches[0] = [{
type: REPALCE,
node: newNode // el('section', props, children)
}, {
type: PROPS,
props: {
id: "container"
}
}]

如果是文本节点便记录之:

patches[2] = [{
type: TEXT,
content: "Virtual DOM2"
}]

以上都比较常规,不会做太大改变,情况比较多的是REODER(Reorder重新排列),比如将这里div的子节点顺序变成了div-p-ul,这个该如何对比,其实这个情况可能会直接被替换掉,这样DOM开销太大,这里牵扯到了列表对比算法,有点小复杂:

假如现在对英文字母进行排序,久的顺序:

a b c d e f g h i

然后对节点进行了一系列的操作,新增j节点,删除e节点,移动h节点,于是有了:

a b c h d f g i j

知道了新旧顺序,现在需要我们写一个算法计算最小插入、删除操作(移动是删除+插入),这块具体我们不深入,有兴趣移步至,这里代码,我们最终形成的结果是:

patches[0] = [{
type: REORDER,
moves: [{remove or insert}, {remove or insert}, ...]
}]

于是我们将这段寻找差异的代码放入前面的遍历代码:

function patch (node, patches) {
var walker = {index: 0}
dfsWalk(node, walker, patches)
} function dfsWalk (node, walker, patches) {
var currentPatches = patches[walker.index] // 从patches拿出当前节点的差异 var len = node.childNodes
? node.childNodes.length
: 0
for (var i = 0; i < len; i++) { // 深度遍历子节点
var child = node.childNodes[i]
walker.index++
dfsWalk(child, walker, patches)
} if (currentPatches) {
applyPatches(node, currentPatches) // 对当前节点进行DOM操作
}
} function applyPatches (node, currentPatches) {
currentPatches.forEach(function (currentPatch) {
switch (currentPatch.type) {
case REPLACE:
node.parentNode.replaceChild(currentPatch.node.render(), node)
break
case REORDER:
reorderChildren(node, currentPatch.moves)
break
case PROPS:
setProps(node, currentPatch.props)
break
case TEXT:
node.textContent = currentPatch.content
break
default:
throw new Error('Unknown patch type ' + currentPatch.type)
}
})
}

这个就是我们snabbdom中重要的patch.js的实现,而Virtual DOM算法主要就是:
① 虚拟DOM element的定义

② 差异的定义与实现

③ 将差异部分代码补足形成新树的patch部分

// 1. 构建虚拟DOM
var tree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: blue'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li')])
]) // 2. 通过虚拟DOM构建真正的DOM
var root = tree.render()
document.body.appendChild(root) // 3. 生成新的虚拟DOM
var newTree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li'), el('li')])
]) // 4. 比较两棵虚拟DOM树的不同
var patches = diff(tree, newTree) // 5. 在真正的DOM元素上应用变更
patch(root, patches)

有了以上知识,我们现在来开始使用snabbdom,相比会得心应手

应用snabbdom

var snabbdom = require("snabbdom");
var patch = snabbdom.init([ // 初始化补丁功能与选定的模块
require("snabbdom/modules/class").default, // 使切换class变得容易
require("snabbdom/modules/props").default, // 用于设置DOM元素的属性(注意区分props,attrs具体看snabbdom文档)
require("snabbdom/modules/style").default, // 处理元素的style,支持动画
require("snabbdom/modules/eventlisteners").default, // 事件监听器
]);
//h是一个生成vnode的包装函数,factory模式?对生成vnode更精细的包装就是使用jsx
//在工程里,我们通常使用webpack或者browserify对jsx编译
var h = require("snabbdom/h").default; // 用于创建vnode,VUE中render(createElement)的原形 var container = document.getElementById("container"); var vnode = h("div#container.two.classes", {on: {click: someFn}}, [
h("span", {style: {fontWeight: "bold"}}, "This is bold"),
" and this is just normal text",
h("a", {props: {href: "/foo"}}, "I\"ll take you places!")
]);
// 第一次打补丁,用于渲染到页面,内部会建立关联关系,减少了创建oldvnode过程
patch(container, vnode);
//创建新节点
var newVnode = h("div#container.two.classes", {on: {click: anotherEventHandler}}, [
h("span", {style: {fontWeight: "normal", fontStyle: "italic"}}, "This is now italic type"),
" and this is still just normal text",
h("a", {props: {href: "/bar"}}, "I\"ll take you places!")
]);
//第二次比较,上一次vnode比较,打补丁到页面
//VUE的patch在nextTick中,开启异步队列,删除了不必要的patch
//nextTick异步队列解析,下面文章中会详解
patch(vnode, newVnode); // Snabbdom efficiently updates the old view to the new state

这里可以看到,我们传入h的要求是什么样的格式,依次有什么属性,这里还是来做一个demo:

 <div id="container">
</div> <script type="module">
"use strict";
import { patch, h, VNode } from './libs/vnode.js'
var container = document.getElementById("container");
function someFn(){ console.log(1)}
function anotherEventHandler(){ console.log(2)} var oldVnode = h("div", {on: {click: someFn}}, [
h("span", {style: {fontWeight: "bold"}}, "This is bold"),
" and this is just normal text",
h("a", {props: {href: "/foo"}}, "I\"ll take you places!")
]); // 第一次打补丁,用于渲染到页面,内部会建立关联关系,减少了创建oldvnode过程
let diff = patch(container, oldVnode);
//创建新节点
var newVnode = h("div", {on: {click: anotherEventHandler}}, [
h("span", {style: {fontWeight: "normal", fontStyle: "italic"}}, "This is now italic type"),
" and this is still just normal text",
h("a", {props: {href: "/bar"}}, "I\"ll take you places!")
]);
//第二次比较,上一次vnode比较,打补丁到页面
//VUE的patch在nextTick中,开启异步队列,删除了不必要的patch
//nextTick异步队列解析,下面文章中会详解
patch(oldVnode, newVnode); // Snabbdom efficiently updates the old view to the new state
function test() {
return {
oldVnode,newVnode,container,diff
}
}
</script>

所以我们现在工作变得相对简单起来就是根据HTML模板封装虚拟DOM结构即可,如果不是我们其中存在指令系统甚至可以不用HTMLParser,所以我们改下之前的代码,将我们自己实现的丑陋vnode变成snabbdom,这里详情还是看github:https://github.com/yexiaochai/wxdemo/tree/master/mvvm。接下来,我们来解决其中的指令

指令系统

这里所谓的指令用的最多的也就是:

① if

② for

对应到小程序中就是:

<block wx:for="{{[1, 2, 3]}}">
<view> {{index}}: </view>
<view> {{item}} </view>
</block>
<block wx:if="{{true}}">
<view> view1 </view>
<view> view2 </view>
</block>

Vue中的语法是:

<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>

大同小异,我们来看看如何处理这种代码,这里也开始进入数组对象的处理,这里便引入了指令系统,我们这里单独说下这块代码

框架里面的for或者if这种指令代码因为要要保证框架性,首先写的很分散,其次用起来也很绕,就很不好理解,所以这里需要单独拎出来说下

之前我们使用的模板一般就是js代码,直接被翻译为了js函数,比如这段代码:

<ul>
<% for(let key in arr) { %>
<li>...</li>
<% } %>
</ul>

会被大概翻译为这个样子:

var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};
with(obj||{}){
__p+='<ul>\n ';
for(let key in arr) {
__p+='\n <li>...</li>\n ';
}
__p+='\n</ul>';
}
return __p;

而MVVM类框架执行的是相同的逻辑,只不过代码实现上面因为要考虑映射关系就复杂的多了:

<ul>
<li m-for="(val, key, index) in arr">索引 {{key + 1}} :{{val}}
</li>
</ul>

翻译后基本就是这个代码:

with (this) {
debugger ;return _h('ul', {}, [_l((arr), function(val, key, index) {
return _h('li', {
attrs: {
"m-for": '(val, key, index) in arr'
}
}, ["索引 " + _s(key + 1) + " :" + _s(val)])
})])
}

所有的这一切都是为了形成虚拟树结构,最终要的是这样的东西

所以指令是其中的工具,一个过程,帮助我们达到目的,为了帮助理解,我们这边单独抽一段代码出来说明这个问题,这里再强调一下指令系统在整体流程中的意义是:

我们最终目标是将模板转换为snabbdom中的vnode,这样他便能自己渲染,而这里的过程是

模板 => HTMLParser解析模板 => 框架element对象 => 解析框架element对象中的属性,这里包括指令 => 将属性包含的指令相关信息同步到element对象上(因为每个标签都会有element对象)=> 生成用于render的函数(其实就是将element转换为snabbdom可识别的对象) => 生成snabbdom树后,调用pacth即可完成渲染

所以指令系统在其中的意义便是:解析element中的指令对应的代码,方便后续生成render匿名函数罢了,这就是为什么指令系统的实现包含了两个方法:

① template2Vnode,这个事实上是将模板中与指令相关的信息放到element对象上方便后续vnode2render时候使用

② vnode2render,便是将之前存到element中与生成最终函数有关的字段拿出来拼接成函数字符串,调用的时候是在mvvm实例对象下,所以可以取到传入的data以及method

之所以设计的比较复杂是为了让大家方便新增自定义指令,这里仍然先上一段简单的说明性代码:

 <!doctype html>
<html>
<head>
<title>指令系统演示</title>
</head>
<body> <script type="module"> //需要处理的模板,我们需要将他转换为虚拟dom vnode
let html = `
<ul>
<li m-for="(val, key, index) in arr">索引 {{key + 1}} :{{val}}</li>
</ul>
`
//这里我们为了降低学习成本将这段模板再做一次简化,变成这样
html = '<div m-for="(val, key, index) in arr">索引 {{key + 1}} :{{val}}</div>'; //处理element元素生成render函数
function genElement(el) {
//这里如果有自定义指令也会被拿出来
if (!el.processed) {
//如果没有这个指令会递归调用
el.processed = true;
let hooks = el.vm.hooks;
for (let hkey in hooks) {
if (el[hkey] && hooks[hkey].vnode2render) {
return hooks[hkey].vnode2render(el, genElement);
}
}
}
//不带hook的情况,这个就是普通的标签
return nodir(el)
} function nodir(el) {
let code //转换子节点
const children = genChildren(el, true);
code = `_h('${el.tag}'${
',{}'
}${
children ? `,${children}` : '' // children
})`
return code
} function genChildren(el, checkSkip) {
const children = el.children
if (children.length) {
const el = children[0]
// 如果是v-for
if (children.length === 1 && el.for) {
return genElement(el)
}
const normalizationType = 0
return `[${children.map(genNode).join(',')}]${
checkSkip
? normalizationType ? `,${normalizationType}` : ''
: ''
}`
}
} //将element转换为render函数
function compileToFunctions(el) {
let vm = el.vm;
let render = genElement(el); render = `with(this){ debugger; return ${render}}`; return new Function(render); } function genNode(node) {
if (node.type === 1) {
return genElement(node)
} else {
return genText(node)
}
} function genText(text) {
return text.type === 2 ? text.expression : JSON.stringify(text.text)
} //我们依旧定义个MVVM的类
class MVVM {
constructor(options) {
this.$data = options.data;
this.template = options.template; //将data中的数据装填到实例上,以便后续函数组装使用
for(let name in this.$data) {
this[name] = this.$data[name];
} this.compile(); } //解析模板生成虚拟dom,这里是重要的一步将模板变成方法
compile() { let element = this.html2Elment();
this.element = element; this.initHooks();
this.setElDrictive(element);
//因为设置属性已经被我们手动做了这里便不需要处理了 let hooks = this.hooks;
//这里,我们需要将有的钩子执行,主要是为了处理指令
for(let hkey in hooks) {
//如果对象上面已经装载了这个指令,并且具有模板到node的函数定义则执行
//这里之所以需要模板上具有,因为对象数据需要在这里取
if(element[hkey] && hooks[hkey].template2Vnode) {
//调用这个钩子,事实上这个钩子要往对象实例上面加东西
//这个会将循环相关的指令,比如要循环的对象放到for字段,将值放到alias,将迭代器属性关键词放到iterator
hooks[hkey].template2Vnode(element, element[hkey], this);
}
} //上面做了指令系统第一步,将模板中的属性存到element对应对象上,这里开始调用之
this.$render = compileToFunctions(element) //执行渲染
let vnode = this.$render(); console.log(html, element, vnode)
debugger; } initHooks() {
//需要处理的指令钩子,本来该放到prototype上
this.hooks = {
'for': {
template2Vnode: function (el, dir) {
//(val, key, index) in arr
let exp = dir.expression //for in 或者 for of 这种循环
const forAliasRE = /(.*?)\s+(?:in|of)\s+(.*)/
//取出迭代器关键词
const forIteratorRE = /\((\{[^}]*\}|[^,]*),([^,]*)(?:,([^,]*))?\)/ //获取数组
//(key ,index) in arr
//[0] (key ,index) in arr,[1] (key ,index),[2] arr
const inMatch = exp.match(forAliasRE)
if (!inMatch) {
warn(`Invalid v-for expression: ${exp}`)
return
} //上面的正则其实是为了取出迭代器中的字符串,后面好组装函数
//这里开始重新组装对象上的for指令,这里把循环的对象指向了数组关键词
el.for = inMatch[2].trim()
//(val, key, index)
let alias = inMatch[1].trim() //关键词拿出来
const iteratorMatch = alias.match(forIteratorRE)
if (iteratorMatch) {
el.alias = iteratorMatch[1].trim();
el.iterator1 = iteratorMatch[2].trim()
if (iteratorMatch[3]) {
el.iterator2 = iteratorMatch[3].trim()
}
} else {
el.alias = alias
} },
//将node对象转换为函数
//因为之前已经用上面的函数
//将循环相关的指令,比如要循环的对象放到for字段,将值放到alias,将迭代器属性关键词放到iterator
//所以这里直接取出关键词使用即可
vnode2render: function (el, genElement) {
//一个状态机
if(el.forProcessed) return null; //取出相关属性
let exp = el.for;
let alias = el.alias; //注意这个字符串里面的代码会执行,最新js语法
let iterator1 = el.iterator1 ? `,${el.iterator1}` : '';
let iterator2 = el.iterator2 ? `,${el.iterator2}` : ''; /*
输出
_l((arr), function(val,key,index) {
console.log(arguments);
})
*/
let _render = ` _l((${exp}), function(${alias}${iterator1}${iterator2}) {
console.log(arguments);
return ${genElement(el)}
})
`
console.log('render', _render); return _render }
}
};
} //渲染for时,返回多个render
//因为_l调用的时候是处在mvvm实例作用域,所以这里传入的时候是一个数组
_l(val, render) {
let ret, i, l, keys, key
if (Array.isArray(val) || typeof val === 'string') {
ret = new Array(val.length)
for (i = 0, l = val.length; i < l; i++) {
ret[i] = render(val[i], i)
}
} else if (typeof val === 'number') {
ret = new Array(val)
for (i = 0; i < val; i++) {
ret[i] = render(i + 1, i)
}
} else if (isObject(val)) {
keys = Object.keys(val)
ret = new Array(keys.length)
for (i = 0, l = keys.length; i < l; i++) {
key = keys[i]
ret[i] = render(val[key], key, i)
}
}
return ret
} _s(val) {
return val == null
? ''
: typeof val === 'object'
? JSON.stringify(val, null, 2)
: String(val)
} _h(sel, data, children) { debugger; return
} //解析指令
setElDrictive(el) {
//解析指令,这里主要是解析for与if
let attrs = el.attrs; //判断m-xxx这种类型的正则
const drictiveRE = /^m\-(\w+)(\:[^\.]+)?\.?([^\:]+)?/ for(let name in attrs) {
let darr = name.match(drictiveRE);
if(darr){ //没有什么其他目的,就是将属性中的指令挪到对象上
el[darr[1]] = {
name: darr[1],
expression: attrs[name],
arg: darr[2] && darr[2].slice(1)
} }
} } //将模板转换为js对象,这里要调用HTMLParser
html2Elment() {
//我们这里简化代码,直接返回解析后的结果即可
//...一大段调用htmlParser,包括递归调用生成js对象的过程,略
return {
vm: this,
tag: 'div',
attrs: {
'm-for': '(val, key, index) in arr'
},
children: [
{
type: 2,
text: '索引 {{key + 1}} :{{val}}',
expression: '"索引 "+_s(key + 1)+" :"+_s(val)'
}
]
} } } //然后我们在这里实例化即可
new MVVM({
template: html,
data: {
arr: [
'叶小钗', '素还真', '一页书'
]
}
}) </script>
</body>
</html>

这一大坨代码,是可运行的代码,其中打了很多断点写了很多注释,剔除了很多无用的代码,想要了解指令系统的朋友可以看看,这里如何自定义指令,大家也可以思考下是怎么实现的,今天的学习暂时到这里,我们明天来看看组件一块的实现

一套代码小程序&Web&Native运行的探索05——snabbdom的更多相关文章

  1. 一套代码小程序&Web&Native运行的探索06——组件系统

    接上文:一套代码小程序&Web&Native运行的探索05——snabbdom 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tre ...

  2. 一套代码小程序&Web&Native运行的探索04——数据更新

    接上文:一套代码小程序&Web&Native运行的探索03 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/m ...

  3. 一套代码小程序&Web&Native运行的探索03——处理模板及属性

    接上文:一套代码小程序&Web&Native运行的探索02 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/m ...

  4. 一套代码小程序&Web&Native运行的探索02

    接上文:一套代码小程序&Web&Native运行的探索01,本文都是一些探索性为目的的研究学习,在最终版输出前,内中的内容可能会有点乱 参考: https://github.com/f ...

  5. 一套代码小程序&Web&Native运行的探索07——mpvue简单调研

    前言 接上文:[一套代码小程序&Native&Web阶段总结篇]可以这样阅读Vue源码 最近工作比较忙,加之上个月生了小孩,小情人是各种折腾他爸妈,我们可以使用的独立时间片不多,虽然这 ...

  6. 一套代码小程序&Web&Native运行的探索01

    前言 前面我们对微信小程序进行了研究:[微信小程序项目实践总结]30分钟从陌生到熟悉 并且用小程序翻写了之前一个demo:[组件化开发]前端进阶篇之如何编写可维护可升级的代码 之前一直在跟业务方打交道 ...

  7. 【一套代码小程序&Native&Web阶段总结篇】可以这样阅读Vue源码

    前言 前面我们对微信小程序进行了研究:[微信小程序项目实践总结]30分钟从陌生到熟悉 在实际代码过程中我们发现,我们可能又要做H5站又要做小程序同时还要做个APP,这里会造成很大的资源浪费,如果设定一 ...

  8. 小程序 web 端实时运行工具

    微信小程序 web 端实时运行工具 https://chemzqm.github.io/wept/

  9. 小程序web开发框架-weweb介绍

    weweb是一个兼容小程序语法的前端框架,你可以用小程序的写法,来写web单面应用.如果你已经有小程序了,通过它你可以将你的小程序运行在浏览器中.在小程序大行其道的今天,它可以让你的小程序代码得到最大 ...

随机推荐

  1. 【prufer编码】BZOJ1430 小猴打架

    Description 一开始森林里面有N只互不相识的小猴子,它们经常打架,但打架的双方都必须不是好朋友.每次打完架后,打架的双方以及它们的好朋友就会互相认识,成为好朋友.经过N-1次打架之后,整个森 ...

  2. 配置(迁移)Laravel的注意事项

    1.如果Laravel是在Linux下运行,如果权限不足,会报错 2.如果是从git上clone下来的项目,需要安装composer,切到项目根目录下 composer install compose ...

  3. Java进阶篇设计模式之十 ---- 访问者模式和中介者模式

    前言 在上一篇中我们学习了行为型模式的解释器模式(Interpreter Pattern)和迭代器模式(Iterator Pattern).本篇则来学习下行为型模式的两个模式,访问者模式(Visito ...

  4. 使用kibana可视化报表实时监控你的应用程序,从日志中找出问题,解决问题

    先结果导向,来看我在kibana dashborad中制作的几张监控图. 一:先睹为快 dashboard1:监控几个维度的日志,这么点日志量是因为把无用的清理掉了,而且只接入了部分应用. <1 ...

  5. 游戏服务器h2engine架构优化和跨平台设计

    H2engine的GitHub星星不知不觉已经破百了,也没有特意推广过,但是慢慢的关注的人越来越多.因为事情多,好久没有写东西了,前一段时间有了一些想法,把h2engine又更新了一下,感觉h2eng ...

  6. 关于json对象的深拷贝

    前两天写程序,有一个是对后台返回的json数据进行整理然后再使用,用到了关于json 的拷贝.我在我的一篇博客中提到过对数组的拷贝.分为深度拷贝,和浅拷贝.这里附上链接 其实对于数组的拷贝是比较简单的 ...

  7. 5G 时代,可能是什么样呢?

        (摄于上海陆家嘴) 众所周知,5g时代即将到来,其相关的区块链技术也将在更多的领域以及方面发挥越来越多的作用. 与新模式与新领域这种软性变化不同,新技术的产生,有着足够的想象空间.仅从内容创作 ...

  8. 开源ERP Odoo仓存功能模块深度应用(一)

    基本功能 库位 库位是一个逻辑存货区,可以是一个物理库区,可以是一个货架.货架上的一个货位.库位可以有子库位 库位有虚拟库位和实际库位,实际库位是实际存放货物的库位,虚拟库位是因复式库存记账而虚构的库 ...

  9. Windows 下安装RabbitMQ服务器及基本配置

    RabbitMQ是一个在AMQP协议标准基础上完整的,可复用的企业消息系统.它遵循Mozilla Public License开源协议,采用 Erlang 实现的工业级的消息队列(MQ)服务器,Rab ...

  10. cesium 之加载地形图 Terrain 篇(附源码下载)

    前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. 内 ...