我们可以看看在genData中都做了什么。上面的parse函数将html字符串转化为ast,而在genData中则将节点的attrs数据进一步处理,例如class -> renderClass style class props attr 分类。在这里可以看到 bind 指令的实现,即通过正则匹配 : 和 bind,如果匹配则把相应的 value值转化为 (value)的形式,而不匹配的则通过JSON.stringify()转化为字符串('value')。最后输出attrs的(key-value),在这里得到的对象是字符串形式的,例如(value)等也仅仅是将变量名,而在generate中通过new Function进一步通过(this.value)得到变量值。
function genData (el, key) { // 没有属性返回空对象 if (!el.attrs.length) { return '{}' } // key let data = key ? `{key:${ key },` : `{` // class处理 if (el.attrsMap[':class'] || el.attrsMap['class']) { data += `class: _renderClass(${ el.attrsMap[':class'] }, "${ el.attrsMap['class'] || '' }"),` } // attrs let attrs = `attrs:{` let props = `props:{` let hasAttrs = false let hasProps = false for (let i = 0, l = el.attrs.length; i < l; i++) { let attr = el.attrs[i] let name = attr.name // bind属性 if (bindRE.test(name)) { name = name.replace(bindRE, '') if (name === 'class') { continue // style处理 } else if (name === 'style') { data += `style: ${ attr.value },` // props属性处理 } else if (mustUsePropsRE.test(name)) { hasProps = true props += `"${ name }": (${ attr.value }),` // 其他属性 } else { hasAttrs = true attrs += `"${ name }": (${ attr.value }),` } // on指令,未实现 } else if (onRE.test(name)) { name = name.replace(onRE, '') // 普通属性 } else if (name !== 'class') { hasAttrs = true attrs += `"${ name }": (${ JSON.stringify(attr.value) }),` } } if (hasAttrs) { data += attrs.slice(0, -1) + '},' } if (hasProps) { data += props.slice(0, -1) + '},' } return data.replace(/,$/, '') + '}' }
而对于genChildren,我们可以猜到就是对ast中的children进行遍历调用genElement,实际上在这里还包括了对文本节点的处理。
// 遍历子节点 -> genNode function genChildren (el) { if (!el.children.length) { return 'undefined' } // 对children扁平化处理 return '__flatten__([' + el.children.map(genNode).join(',') + '])' } function genNode (node) { if (node.tag) { return genElement(node) } else { return genText(node) } } // 解析{{}} function genText (text) { if (text === ' ') { return '" "' } else { const exp = parseText(text) if (exp) { return 'String(' + escapeNewlines(exp) + ')' } else { return escapeNewlines(JSON.stringify(text)) } } }
genText处理了text及换行,在parseText函数中利用正则解析{{}},输出字符串(value)形式的字符串。
现在我们再看看__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })中__h__函数
// h 函数利用上面得到的节点数据得到 vNode对象 => 虚拟dom export default function h (tag, b, c) { var data = {}, children, text, i if (arguments.length === 3) { data = b if (isArray(c)) { children = c } else if (isPrimitive(c)) { text = c } } else if (arguments.length === 2) { if (isArray(b)) { children = b } else if (isPrimitive(b)) { text = b } else { data = b } } if (isArray(children)) { // 子节点递归处理 for (i = 0; i < children.length; ++i) { if (isPrimitive(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i]) } } // svg处理 if (tag === 'svg') { addNS(data, children) } // 子节点为文本节点 return VNode(tag, data, children, text, undefined) }
到此为止,我们分析了const render = compile(getOuterHTML(el)),从el的html字符串到render函数都是怎么处理的。
2. 代理data数据/methods的this绑定
// 实例代理data数据 Object.keys(options.data).forEach(key => this._proxy(key)) // 将method的this指向实例 if (options.methods) { Object.keys(options.methods).forEach(key => { this[key] = options.methods[key].bind(this) }) }
实例代理data数据的实现比较简单,就是利用了对象的setter和getter,读取this数据时返回data数据,在设置this数据时同步设置data数据
_proxy (key) { if (!isReserved(key)) { // need to store ref to self here // because these getter/setters might // be called by child scopes via // prototype inheritance. var self = this Object.defineProperty(self, key, { configurable: true, enumerable: true, get: function proxyGetter () { return self._data[key] }, set: function proxySetter (val) { self._data[key] = val } }) } }
3. Obaerve的实现