<div id="app" :class="myclass" @click="show">
<p>{{ num }}</p>
</div>
一、处理模板
1、获取 template
var dom = document.querySelector(id);
var template = dom ? dom.outerHTML : '<div></div>'
...
<div id="app" :class="myclass" @click="show">
<p>{{ num }}</p>
</div>
2、template 转 ast
var startRe = /^<(\w+)/
var bindRe = /^(:|v-bind:)/
var eventRe = /^(@|v-on:)/
var textRe = /\s*\{\{\s*([^\{\}]*)\s*\}\}\s*/
function parseHTML(ops){
var html = ops.html
while(html){
var pos = html.indexOf('<')
if(pos == 0){
var start = html.match(startRe)
if(start){
// 开始标签
continue
}
var end = html.match(endRe)
if(end){
// 结束标签
continue
}
}
if(pos >= 0){
// 文本内容
advance(pos)
}
if(pos < 0){
// 处理完毕
html = ''
}
}
function advance(len){
html = html.substr(len)
}
}
...
var ast = {
tag: 'div',
attrs: {
id: 'app'
},
bindClass: 'myclass',
events: {
click: 'show'
},
children: [...ast]
}
3、ast 转 render
function render(ast){
var code = 'with(this){ return '
code += renderCode(ast)
return code +' }'
}
function renderCode(ast){
if(ast.for && !ast.fored){
return renderFor(ast)
} else if(ast.if && !ast.ifed){
return renderIf(ast)
} else {
var code = ''
if(ast.type == 1){
// dom 元素
var data = renderData(ast)
var children = renderChildren(ast.children)
code += '_c("'+ ast.tag +'",'+ data +','+ children +')'
} else if(ast.type == 2){
// {{ ...}} 表达式
code += renderText(ast.exp)
} else {
// 纯文本
code += renderText(ast.text)
}
return code
}
}
...
var render = 'with(this){ return _c("div",{bindClass:myclass,attrs:{"id":"app"},on:{click:show}},[_t("\n\t\t"),_c("p",{},[_t(_s(num))]),_t("\n\t")]) }'
4、render 转 vNode
function bind(fn, ctx){
function bindFn(){
arguments.length ? fn.apply(ctx, arguments) : fn.call(ctx)
}
return bindFn
}
this.$render = new Function(render)
var vNode = this.$render.call(this)
...
var vNode = {
tag: 'div',
attrs: {
id: 'app'
},
bindClass: 'active',
on: {
click: bindFn()
},
elm: div#app.active,
children: [...vNode]
}
二、初始化生命周期
1、构造函数
var did = 0
function Dep(){
this.id = did++
this.subs = [...Watcher]
}
Dep.prototype.notify = function(){
var subs = this.subs
for(var i=0, l=subs.length; i<l; i++){
subs[i].update()
}
}
Dep.target = null
var wid = 0
function Watcher(ctx, exp, cb){
this.id = wid++
this.cb = cb
this.ctx = ctx
this.getter = exp
this.value = this.get()
}
Watcher.prototype.get = function(){
var value
var ctx = this.ctx
var exp = this.getter
Dep.target = this
if(isFun(exp)){
try {
value = this.getter.call(ctx)
} catch(e) {
warn(e)
}
} else {
value = ctx[exp]
}
this.value = value
Dep.target = null
return value
}
Watcher.prototype.update = function(){
var old = this.value
var now = this.get()
if(old != now){
this.cb()
}
}
var oid = 0
function Observer(data){
this.id = oid++
if(isArray(data)){
walkArray(data)
} else {
walkObject(data)
}
data.__ob__ = this
}
function defineReactive(obj, key, val){
var dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function(){
dep.depend()
return val
},
set: function(value){
val = value
dep.notify()
}
})
}
2、初始化
var vid = 0
function V(ops){
this.id = vid++
this.$ops = ops
this.$init()
}
V.prototype.$init = function(){
initHook(this)
initWorth(this)
initRender(this)
}
function initWorth(ctx){
initData(ctx, ops.data)
initMethod(ctx, ops.methods)
initFilter(ctx, ops.filters)
initComputed(ctx, ops.computed)
initWatch(ctx, ops.watch)
callHook(ctx, 'created')
}
function initComputed(v, computed){
for(var k in computed){
defineComputed(v, k, computed[k])
}
}
function defineComputed(obj, key, userDef){
var getter = isFun(userDef) ? userDef : userDef.get
var setter = isFun(userDef) ? noop : userDef.set
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: bind(getter, obj),
set: bind(setter, obj)
})
}
function initWatch(v, watch){
for(var k in watch){
watchHandle(v, k, watch[k])
}
}
function watchHandle(obj, key, userDef){
var fn = obj[userDef]
var cb = isFun(fn) ? fn : noop
new Watcher(obj, key, cb)
}
function initRender(ctx){
ctx.$render = new Function(code)
ctx.$watcher = new Watcher(ctx, ctx.$update, noop)
removeNode(query(ops.el))
}
V.prototype.$update = function(){
var node = this.$render.call(this)
if(this.$node){
patch(this.$node, node)
callHook(this, 'updated')
} else {
initNode(node)
callHook(this, 'mounted')
}
this.$node = node
}
三、渲染视图
1、初次渲染
function initNode(node, parent, ref){
parent = parent || document.body
foundElm(node, parent, ref)
}
function foundElm(node, parent, ref){
var i = null
if(node.tag){
node.elm = makeElm(node.tag)
} else if(node.annotate){
node.elm = makeAnnotate(node.text)
} else if(node.text){
node.elm = makeText(node.text)
}
if(node.data){
foundData(node)
}
// 挂载 dom
makeNode(node.elm, parent, ref)
if(isDef(i = node.data) && isDef(i = i.html)){
node.elm.innerHTML = i
} else if(isDef(i = node.data) && isDef(i = i.text)){
node.elm.innerText = i
} else if(node.children){
for(var i=0, l=node.children.length; i<l; i++){
foundElm(node.children[i], node.elm)
}
}
}
2、更新
function patchNode(a, b){
var c = a.children
var d = b.children
var elm = b.elm = a.elm
if(isUndef(b.text)){
if(isDef(c) && isDef(d)){
// diff 算法
updateChildren(c, d, elm)
} else if(isDef(c)){
removeChildren(c, 0, c.length, elm)
} else if(isDef(d)){
addChildren(d, 0, d.length, elm)
} else if(isDef(c.text)){
elm.nodeValue = ''
}
} else if(a.text != b.text) {
elm.nodeValue = b.text
}
}
网友评论