vueを真似て自分で応答式フレームワークを書く(五)終章-v-for命令実現
概要
下一篇では、vueオブジェクトの構築を実現し、変数のバインドとイベントバインドを初歩的に実現しました.今、
難点
v-forを実現するには以下のいくつかの難点がある.式の解析、v-forには2つの構文 を解析する必要がある. v-for内の要素をコンパイルして、すでにcompile関数がありますが、v-forループ内のコンテキストとvueは一致していません.どういう意味ですか.compile内のバインド値と変数はvueで、vueはグローバルですが、v-for内のバインドされた変数はループ内で、毎回異なります.
コンパイル
compileでは、v-forに遭遇した場合、v-for内のノードをすべて生成してから、サブノードappendとして親ノードに渡すので、最初のステップはv-forコマンドが含まれているかどうかを判断することです.
解析
コンパイルの第一歩は解析であり,3つの部分の内容を解析する必要がある. サイクルの配列変数 サイクル中の変数名 サイクル中の要素の下付き
要素の生成
解析が完了すると要素の生成を開始できますは変数directiveを定義し、v-forのいくつかの文法も保存し、次回は直接使用することができ、 を再解析する必要はありません. cloneで生成されるため、v-forラベルを削除する必要があります.そうしないと、デッドサイクル に入ります.再帰呼び出しcompileは新しい要素を生成し、ループごとに現在の変数と下付きラベルをvueに配置し、コンパイル時にプログラムが変数 を見つけることを保証する.結果をloopElementに保存する目的は、バインドされた配列が変化すると、現在の関連ノードを削除して新しいノード を再生成する必要があることである.
インストラクション現在の要素を除去する .上の論理と同様に、新しい要素 を生成する以前に保存したparentによりappend を行う. addSubscriberサブスクライバを作成サブスクライバにコマンドを登録する 完全なcompileLoopコードは以下の通りです.
下一篇では、私たちのイベント応答はこのように書かれています. evaluteExpression まず変数を解析する ループ変数値を取得し、getVariableValue を呼び出す.サイクル置換 getVariableValue は、 である.未定義が空白文字列に設定されている場合 効果
以下は実現した効果図で、ここをクリックして表示することもできます
完全なjsコードはここをクリックして見ます
リファレンス
次のリンクをクリックして、このシリーズの他の記事を表示します. vueを真似て自分で応答式フレームワークを書く(一)-Vueはtodo応用を実現する vueを真似て応答フレームワークを自分で書きます(二)-Vueオブジェクト作成用 vueを真似て自分で応答式フレームワークを書く(三)-dom解析 vueを真似て応答フレームワークを自分で書きます(4)-Vueオブジェクト構築 vueを真似て自分で応答式フレームワークを書く(五)終章–v-for命令実現
下一篇では、vueオブジェクトの構築を実現し、変数のバインドとイベントバインドを初歩的に実現しました.今、
v-for
命令の実装という問題が残っています.これも本シリーズで最も難しい部分です.難点
v-forを実現するには以下のいくつかの難点がある.
item in items
と(item,index) in items
があり、2つ目はシーケンス番号を取得することができ、プログラムはこの2つの構文コンパイル
compileでは、v-forに遭遇した場合、v-for内のノードをすべて生成してから、サブノードappendとして親ノードに渡すので、最初のステップはv-forコマンドが含まれているかどうかを判断することです.
function isLoop(element) {
return element.attributes && element.attributes['v-for'];
}
compile関数再帰コンパイルサブノードfor (let i = 0; i < node.childNodes.length; ++i) {
element.appendChild(compile(node.childNodes[i]));
}
次のように変更for (let i = 0; i < node.childNodes.length; ++i) {
let child = node.childNodes[i];
if (isLoop(child)) {
let ns = compileLoop(child, element);
for (let j = 0; j < ns.length; ++j) {
element.appendChild(ns[j]);
}
} else {
element.appendChild(compile(child));
}
}
compileLoop
はv-forノードをコンパイルし、ノード配列を返し、親ノードは戻ったノードをappendする.解析
コンパイルの第一歩は解析であり,3つの部分の内容を解析する必要がある.
let vfor = element.attributes['v-for'].value;
let itemName;
let indexName;
let varName;
let loopExp1 = /\(([^,]+),([^\)]+)\)\s+in\s+(.*)/g;
let loopExp2 = /(\w+)\s+in\s+(.*)/g;
let m;
if (m = loopExp1.exec(vfor)) {
itemName = m[1];
indexName = m[2]
varName = m[3];
} else if (m = loopExp2.exec(vfor)) {
itemName = m[1];
varName = m[2];
}
直接正則で解析します.loopExp 1とloopExp 2はそれぞれ2つの構文に対応します.varName:配列名、itemName:ループ変数名、indexName:ループ下付き要素の生成
解析が完了すると要素の生成を開始できます
var directive = {
origin: element.cloneNode(true),
attr: 'v-for',
exp: {
varName: varName,
indexName: indexName,
itemName: itemName
}
}
element.attributes.removeNamedItem('v-for');
let arrays = vue[varName];
let elements = [];
for (let i = 0; i < arrays.length; ++i) {
vue[itemName] = arrays[i];
vue[indexName] = i;
elements.push(compile(element.cloneNode(true), false));
}
if (!loopElement[varName]) {
let loop = {};
loop.elements = elements;
loop.parent = parent;
loopElement[varName] = loop;
}
for (let i = 0; i < arrays.length; ++i) {
vue[itemName] = arrays[i];
vue[indexName] = i;
elements.push(compile(element.cloneNode(true), false));
}
インストラクション
directive.change = function (name, value) {
let ele = loopElement[name];
for (let i = 0; i < ele.elements.length; ++i) {
ele.elements[i].remove();
}
let newEles = [];
let arrays = vue[this.exp.varName];
for (let i = 0; i < arrays.length; ++i) {
vue[this.exp.itemName] = arrays[i];
vue[this.exp.indexName] = i;
let node = compile(this.origin.cloneNode(true));
newEles.push(node);
}
loopElement[name].elements = newEles;
for (let j = 0; j < newEles.length; ++j) {
ele.parent.appendChild(newEles[j]);
}
}
addSubscriber(varName, directive);
function compileLoop(element, parent) {
let vfor = element.attributes['v-for'].value;
let itemName;
let indexName;
let varName;
let loopExp1 = /\(([^,]+),([^\)]+)\)\s+in\s+(.*)/g;
let loopExp2 = /(\w+)\s+in\s+(.*)/g;
let m;
if (m = loopExp1.exec(vfor)) {
itemName = m[1];
indexName = m[2]
varName = m[3];
} else if (m = loopExp2.exec(vfor)) {
itemName = m[1];
varName = m[2];
}
var directive = {
origin: element.cloneNode(true),
attr: 'v-for',
exp: {
varName: varName,
indexName: indexName,
itemName: itemName
}
}
element.attributes.removeNamedItem('v-for');
let arrays = vue[varName];
let elements = [];
for (let i = 0; i < arrays.length; ++i) {
vue[itemName] = arrays[i];
vue[indexName] = i;
elements.push(compile(element.cloneNode(true), false));
}
if (!loopElement[varName]) {
let loop = {};
loop.elements = elements;
loop.parent = parent;
loopElement[varName] = loop;
}
directive.change = function (name, value) {
let ele = loopElement[name];
for (let i = 0; i < ele.elements.length; ++i) {
ele.elements[i].remove();
}
let newEles = [];
let arrays = vue[this.exp.varName];
for (let i = 0; i < arrays.length; ++i) {
vue[this.exp.itemName] = arrays[i];
vue[this.exp.indexName] = i;
let node = compile(this.origin.cloneNode(true));
newEles.push(node);
}
loopElement[name].elements = newEles;
for (let j = 0; j < newEles.length; ++j) {
ele.parent.appendChild(newEles[j]);
}
}
addSubscriber(varName, directive);
return elements;
}
イベントレスポンス下一篇では、私たちのイベント応答はこのように書かれています.
function addEvent(element, event, method) {
element.addEventListener(event, function(e) {
let params = [];
let paramNames = method.params;
if (paramNames) {
for (let i = 0; i < paramNames.length; ++i) {
params.push(vue[paramNames[i]]);
}
}
vue[method.name].apply(vue, params);
})
}
このように書くと、ループのたびに下付きおよびループ変数がリセットされるため、下付きおよびループ変数はvueオブジェクトに保存されるため、イベントがトリガーされるとparams.push(vue[paramNames[i]]);
行のコードはコンテキストが変化したため、値を取得できません.この問題を解決する方法は,パケットを閉じて当時の環境情報を保存することで,実行時に失われることなく,取得データを外に移動するだけでよい.function addEvent(element, event, method) {
let params = [];
let paramNames = method.params;
if (paramNames) {
for (let i = 0; i < paramNames.length; ++i) {
params.push(vue[paramNames[i]]);
}
}
element.addEventListener(event, function (e) {
vue[method.name].apply(vue, params);
})
}
ここでv-for命令を実装することができますが、以前のいくつかの残留物はまだ修復されていません.dom解析では、テキストのノード値が変化したのは、以下のように簡単なテキスト置換にすぎません.if (node.nodeType == 3) {
directive.change = function(name, value) {
this.node.textContent = this.origin.replace("\{\{" + name + "\}\}", value);
}
}
複数の変数やtodo.text
のようなマルチレベル変数の結果がエラーになる場合は、表現を解析するための関数を書きます.if (node.nodeType == 3) {
directive.change = function (name, value) {
this.node.textContent = evaluteExpression(this.origin);
}
}
function evaluteExpression(text) {
let vars = parseVariable(text);
for (let i = 0; i < vars.length; ++i) {
let value = getVariableValue(vars[i]);
text = text.replace("\{\{" + vars[i] + "\}\}", value);
}
return text;
}
function getVariableValue(name) {
let value;
if (name.indexOf(".")) {
let ss = name.split(".");
value = vue[ss[0]];
if (value) {
for (let i = 1; i < ss.length; ++i) {
value = value[ss[i]];
if (value == undefined) {
break;
}
}
}
} else {
value = vue[name];
}
if (value == undefined || value == null) {
value = "";
}
return value;
}
item.text
のような多段変数を循環取得する以下は実現した効果図で、ここをクリックして表示することもできます
完全なjsコードはここをクリックして見ます
リファレンス
次のリンクをクリックして、このシリーズの他の記事を表示します.