Este não é o código-fonte
do Vue Este não é o código-fonte do Vue Este não é o código-fonte
do Vue,
apenas para nos ajudar a compreender a ideia do Vue
ordem
- Monte el na instância Vue
- A função render gera DOM virtual
- DOM virtual e dados são combinados e renderizados na página
- Uma vez que os dados são alterados, um novo DOM virtual é gerado imediatamente e o novo DOM virtual é comparado com o antigo DOM virtual
- Página de atualização
Crie seu próprio Vue e execute o método de montagem nesta função
function myVue( options ) {
this._data = options.data;
// vue 是字符串, 这里是 DOM
this._template = document.querySelector( options.el );
this.mount(); // 挂载
}
Aquele com função de cache é este render
- Esta função de renderização gera um DOM virtual dentro
- Porque ele precisa ter um cache, então use o texto do criador
- A montagem aqui realmente publica o modo de assinante, passando um observador
- Para facilitar o entendimento, escreva aqui
myVue.prototype.mount = function () {
// 需要提供一个 render 方法: 生成 虚拟 DOM
this.render = this.createRenderFn()
this.mountComponent();
}
Updata é renderizar o DOM virtual na página
myVue.prototype.mountComponent = function () {
// 执行 mountComponent() 函数
let mount = () => {
this.update( this.render() ) // 渲染虚拟DOM
}
mount.call( this ); // 本质应该交给 watcher 来调用
}
Aqui está a função de renderização
- O objetivo é armazenar em cache a árvore de sintaxe abstrata (usamos DOM virtual para simular)
- No código-fonte do Vue, aqui está a árvore de sintaxe abstrata em cache
myVue.prototype.createRenderFn = function () {
let ast = getVNode( this._template );
// Vue: 将 AST + data => VNode
// 我们: 带有插值语法的 VNode + data => 含有数据的 VNode
return function render () {
// 将 带有 插值语法的 VNode 转换为 待数据的 VNode
let _tmp = combine( ast, this._data );
return _tmp;
}
}
Renderize o DOM virtual na página: o algoritmo diff está lá
myVue.prototype.update = function () {
// 简化, 直接生成 HTML DOM replaceChild 到页面中
}
A estrutura de design da submissão secundária é usada no Vue real
- Há uma correspondência um-para-um entre o DOM e o DOM virtual na página
- Primeiro AST e VNode de geração de dados (novo, render)
- Compare o VNode existente com o novo VNode (diff), atualize (atualize)
A razão pela qual você não o substitui diretamente por um novo VNode
- Enquanto os dados mudam, um novo VNode será gerado, que é um VNode com novos dados
- Compare o VNode com os novos dados com o antigo VNode. Esta comparação é o algoritmo diff
- O VNode antigo e as tags de página correspondem entre si
- Se for substituído diretamente, a relação de ligação entre eles terá que ser religada. A religação envolve o percurso recursivo da árvore, o que consome desempenho.
- Ignore as coisas que são iguais e atualize-as se forem diferentes. É equivalente a atualizar a página
- As funções correspondentes às três funções escritas acima também são exibidas
- createRenderFn (árvore de sintaxe abstrata de cache)
- render (gerar DOM virtual)
- updata (atualização)
Construtor de DOM virtual
class VNode {
constructor( tag, data, value, type ) {
this.tag = tag && tag.toLowerCase();
this.data = data;
this.value = value;
this.type = type;
this.children = [];
}
appendChild ( vnode ) {
this.children.push( vnode );
}
}
Gere VNode a partir de HTML DOM: trate esta função como uma função de compilador
function getVNode( node ) {
let nodeType = node.nodeType;
let _vnode = null;
if ( nodeType === 1 ) {
// 元素
let nodeName = node.nodeName;
let attrs = node.attributes;
let _attrObj = {
};
for ( let i = 0; i < attrs.length; i++ ) {
// attrs[ i ] 属性节点 ( nodeType == 2 )
_attrObj[ attrs[ i ].nodeName ] = attrs[ i ].nodeValue;
}
_vnode = new VNode( nodeName, _attrObj, undefined, nodeType );
// 考虑 node 的子元素
let childNodes = node.childNodes;
for ( let i = 0; i < childNodes.length; i++ ) {
_vnode.appendChild( getVNode( childNodes[ i ] ) ); // 递归
}
} else if ( nodeType === 3 ) {
_vnode = new VNode( undefined, undefined, node.nodeValue, nodeType );
}
return _vnode;
}
Combine Vnode com sintaxe de interpolação com dados de dados para preencher o VNode com dados: Simular AST -> VNode, escrevemos no artigo anterior
let rkuohao = /\{\{(.+?)\}\}/g; // 用来匹配插值语法的正则表达式
function combine( vnode, data ) {
let _type = vnode.type; // 带上下划线,避免混肴
let _data = vnode.data;
let _value = vnode.value;
let _tag = vnode.tag;
let _children = vnode.children;
let _vnode = null;
if ( _type === 3 ) {
// 文本节点
// 对文本处理
_value = _value.replace( rkuohao, function ( _, g ) {
return getValueByPath( data, g.trim() );
} );
_vnode = new VNode( _tag, _data, _value, _type )
} else if ( _type === 1 ) {
// 元素节点
_vnode = new VNode( _tag, _data, _value, _type );
_children.forEach( _subvnode => _vnode.appendChild( combine( _subvnode, data ) ) );
}
return _vnode;
}
Código fonte completo
<script>
class VNode {
constructor( tag, data, value, type ) {
this.tag = tag && tag.toLowerCase();
this.data = data;
this.value = value;
this.type = type;
this.children = [];
}
appendChild ( vnode ) {
this.children.push( vnode );
}
}
function getVNode( node ) {
let nodeType = node.nodeType;
let _vnode = null;
if ( nodeType === 1 ) {
// 元素
let nodeName = node.nodeName;
let attrs = node.attributes;
let _attrObj = {
};
for ( let i = 0; i < attrs.length; i++ ) {
_attrObj[ attrs[ i ].nodeName ] = attrs[ i ].nodeValue;
}
_vnode = new VNode( nodeName, _attrObj, undefined, nodeType );
let childNodes = node.childNodes;
for ( let i = 0; i < childNodes.length; i++ ) {
_vnode.appendChild( getVNode( childNodes[ i ] ) );
}
} else if ( nodeType === 3 ) {
_vnode = new VNode( undefined, undefined, node.nodeValue, nodeType );
}
return _vnode;
}
let rkuohao = /\{\{(.+?)\}\}/g;
function getValueByPath( obj, path ) {
let paths = path.split( '.' );
let res = obj;
let prop;
while( prop = paths.shift() ) {
res = res[ prop ];
}
return res;
}
function combine( vnode, data ) {
let _type = vnode.type;
let _data = vnode.data;
let _value = vnode.value;
let _tag = vnode.tag;
let _children = vnode.children;
let _vnode = null;
if ( _type === 3 ) {
_value = _value.replace( rkuohao, function ( _, g ) {
return getValueByPath( data, g.trim() );
} );
_vnode = new VNode( _tag, _data, _value, _type )
} else if ( _type === 1 ) {
_vnode = new VNode( _tag, _data, _value, _type );
_children.forEach( _subvnode => _vnode.appendChild( combine( _subvnode, data ) ) );
}
return _vnode;
}
function myVue( options ) {
this._data = options.data;
this._template = document.querySelector( options.el );
this.mount();
}
myVue.prototype.mount = function () {
this.render = this.createRenderFn()
this.mountComponent();
}
myVue.prototype.mountComponent = function () {
let mount = () => {
this.update( this.render() )
}
mount.call( this );
}
myVue.prototype.createRenderFn = function () {
let ast = getVNode( this._template );
return function render () {
let _tmp = combine( ast, this._data );
return _tmp;
}
}
myVue.prototype.update = function () {
// 简化, 直接生成 HTML DOM replaceChild 到页面中
}
let app = new myVue( {
el: '#root',
data: {
name: '张三', age: 19
}
} );
app.name = '李四'; // 这个赋值已完成, 页面数据就更新
</script>