Vueのオブザーバーとパブリッシュ購読
34751 ワード
皆さん、こんにちは、今日はVueの中の観察者と、発表と購読の実現について説明します.
1、まず観察者を作成します.
次に、パブリケーションとサブスクリプションのコンストラクション関数を作成します.
観察者にはこのようなコードがあります
jsは単一スレッドであるため,各観察者が古い値を取得するときに,この観察者にtarget属性を追加することができ,この属性はその観察者自身を指す.その後、このtargetをクリアします.
クリアする前に何があったのでしょうか?
実はデータハイジャックが発生しました.これは前編で述べました.
データハイジャックでは、まず、パブリケーションとサブスクリプションの機能インスタンスをnewし、各プロパティにパブリケーションとサブスクリプションの機能を追加します.
また、このプロパティに値を再割り当てするときに、パブリッシュとサブスクリプションの機能を持つ値にサブスクリプション関数を実行させます.
2、はい、今すべての値にパブリッシュとサブスクリプション機能があります.では、このオブザーバーは誰に追加されますか.私たちのオブザーバーはまだ実行されていません.0.0
まず、v-modelはバインドする必要がある観察者です.数値は変化できるからでしょう.
あと{{}}式も1つ必要でしょうか.1つのラベルに複数の式が存在するので、式を巡ります
例:{{shool.name}{{shool.name}}
v-htmlも1つ追加する必要があるのではないでしょうか.もちろん、まだたくさんありますが、基本的にはこのような形式で、真似して書くことができます.
3、はい、vueのデータの双方向バインドが実現しました.以下は完全なコードです.理解して、実行してみてください.
1、まず観察者を作成します.
/**
*
*/
class Watcher{
constructor(vm,expr,cd){
this.vm = vm;
this.expr = expr;
this.cd = cd;
//
this.oldValue = this.get();
}
get(){ //
Dep.target = this; // this
//
let value = CompileUtil.getVal(this.vm,this.expr);
Dep.target = null; // watcher
return value;
}
updata(){
let newVal = CompileUtil.getVal(this.vm,this.expr); //
if(newVal !== this.oldValue){
this.cd(newVal)
}
}
}
次に、パブリケーションとサブスクリプションのコンストラクション関数を作成します.
/**
* ( )
*/
class Dep{
constructor(){
this.subs = [] // watcher
}
//
addSub(watcher){ // watcher
this.subs.push(watcher)
}
//
notify(){
this.subs.forEach(watcher=>watcher.updata());
}
}
観察者にはこのようなコードがあります
get(){ //
Dep.target = this; // this
//
let value = CompileUtil.getVal(this.vm,this.expr);
Dep.target = null; // watcher
return value;
}
jsは単一スレッドであるため,各観察者が古い値を取得するときに,この観察者にtarget属性を追加することができ,この属性はその観察者自身を指す.その後、このtargetをクリアします.
クリアする前に何があったのでしょうか?
実はデータハイジャックが発生しました.これは前編で述べました.
//
defineReactive(obj,key,value){
this.observer(value); // , ,
let dep = new Dep(); //
Object.defineProperty(obj,key,{
get(){
// watcher , watcher
Dep.target && dep.addSub(Dep.target);
return value;
},
set: (newVal)=>{
if(value !== newVal){
this.observer(newVal); // get set
value = newVal;
dep.notify(); //
}
}
})
}
データハイジャックでは、まず、パブリケーションとサブスクリプションの機能インスタンスをnewし、各プロパティにパブリケーションとサブスクリプションの機能を追加します.
また、このプロパティに値を再割り当てするときに、パブリッシュとサブスクリプションの機能を持つ値にサブスクリプション関数を実行させます.
2、はい、今すべての値にパブリッシュとサブスクリプション機能があります.では、このオブザーバーは誰に追加されますか.私たちのオブザーバーはまだ実行されていません.0.0
まず、v-modelはバインドする必要がある観察者です.数値は変化できるからでしょう.
model(node,expr,vm){ //node expr vm
// console.log(node)
let fn = this.updater['modelUpdater'];
// ,
new Watcher(vm,expr,(newVal)=>{ //newVal ,
fn(node,newVal);
});
node.addEventListener('input',(e)=>{
let value = e.target.value; //
this.setValue(vm,expr,value) // v-model
})
let value = this.getVal(vm,expr);
// console.log(value)
fn(node,value);
},
あと{{}}式も1つ必要でしょうか.1つのラベルに複数の式が存在するので、式を巡ります
例:{{shool.name}{{shool.name}}
getContentValue(vm,expr){
//
return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getVal(vm,args[1]);
});
},
text(node,expr,vm){
let fn = this.updater['textUpdater']
//console.log(expr) :{{ school.name }}
let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
//console.log(args) :["{{ school.name }}", " school.name ", 0, "{{ school.name }}"]
//
new Watcher(vm,args[1],()=>{
fn(node,this.getContentValue(vm,expr)); //
})
return this.getVal(vm,args[1]);
})
// console.log(content) //
fn(node,content);
},
v-htmlも1つ追加する必要があるのではないでしょうか.もちろん、まだたくさんありますが、基本的にはこのような形式で、真似して書くことができます.
html(node,expr,vm,eventName){
let fn = this.updater['htmlUpdater']
// ,
new Watcher(vm,expr,(newVal)=>{ //newVal ,
fn(node,newVal);
});
let value = this.getVal(vm,expr);
fn(node,value);
},
3、はい、vueのデータの双方向バインドが実現しました.以下は完全なコードです.理解して、実行してみてください.
/**
* ( )
*/
class Dep{
constructor(){
this.subs = [] // watcher
}
//
addSub(watcher){ // watcher
this.subs.push(watcher)
}
//
notify(){
this.subs.forEach(watcher=>watcher.updata());
}
}
/**
*
*/
class Watcher{
constructor(vm,expr,cd){
this.vm = vm;
this.expr = expr;
this.cd = cd;
//
this.oldValue = this.get();
}
get(){ //
Dep.target = this; // this
//
let value = CompileUtil.getVal(this.vm,this.expr);
Dep.target = null; // watcher
return value;
}
updata(){
let newVal = CompileUtil.getVal(this.vm,this.expr); //
if(newVal !== this.oldValue){
this.cd(newVal)
}
}
}
/**
*
*/
class Observer{
constructor(data) {
this.observer(data);
}
observer(data){
//
if(data && typeof data === 'object'){
for (let key in data) { // data
this.defineReactive(data,key,data[key]);
}
}
}
//
defineReactive(obj,key,value){
this.observer(value); // , ,
let dep = new Dep(); //
Object.defineProperty(obj,key,{
get(){
// watcher , watcher
Dep.target && dep.addSub(Dep.target);
return value;
},
set: (newVal)=>{
if(value !== newVal){
this.observer(newVal); // get set
value = newVal;
dep.notify(); //
}
}
})
}
}
/**
*
*/
class Compiler{
constructor(el,vm) {
this.vm = vm;
// el
this.el = this.isElementNode(el) ? el : document.querySelector(el);
// console.log(this.el);
//
let fragment = this.node2fragment(this.el);
//
//
this.compile(fragment);
//
this.el.appendChild(fragment);
}
// v-
isDirective(attrName){
return attrName.startsWith('v-');
// return /^v-/.test(attrName)
}
//
compileElement(node){
let attributes = node.attributes; // , node
// console.log(attributes)
attributes = [...attributes]
// console.log(attributes)
attributes.forEach(attr=>{ // attr
let {name, value:expr} = attr; //:expr value expr **school.name
// vue
if(this.isDirective(name)){ // v-model='asdad' v-on:click='dsa'
let [,directive] = name.split('-');
let [directiveName, eventName] = directive.split(':');
// *** v-if v-modle v-show v-else
CompileUtil[directiveName](node,expr,this.vm,eventName);
}
})
}
//
compileText(node){ // {{}}
let content = node.textContent;
if(/\{\{.+?\}\}/.test(content)){
CompileUtil['text'](node,content,this.vm);
}
}
// dom
compile(node){
let childNodes = node.childNodes; // node
[...childNodes].forEach(child=>{
if(this.isElementNode(child)){ //
this.compileElement(child); //
//
this.compile(child);
}else{ //
this.compileText(child); //
}
})
}
// ,
node2fragment(node){
//
let fragment = document.createDocumentFragment();
let firstChild;
// node firstChild node
while(firstChild = node.firstChild){
//appendChild
fragment.appendChild(firstChild);
}
return fragment;
}
//
isElementNode(node){
return node.nodeType === 1;
}
}
CompileUtil = {
//
getVal(vm,expr){
// 7. reduce()
// ( )1. ,2. ,3. 4.
// ( )
// :
expr = expr.trim()
return expr.split('.').reduce((data,current)=>{ //[school,name]
// console.log(data,current)
return data[current];
},vm.$data);
},
setValue(vm,expr,value){
expr.split('.').reduce((data,current,index,arr)=>{ //[school,name]
if(index === arr.length - 1){ //
//console.log(data,current)
// data
data[current] = value; // school.name = xxxx
}
return data[current];
},vm.$data);
},
model(node,expr,vm){ //node expr vm
// console.log(node)
let fn = this.updater['modelUpdater'];
// ,
new Watcher(vm,expr,(newVal)=>{ //newVal ,
fn(node,newVal);
});
node.addEventListener('input',(e)=>{
let value = e.target.value; //
this.setValue(vm,expr,value) // v-model
})
let value = this.getVal(vm,expr);
// console.log(value)
fn(node,value);
},
on(node,expr,vm,eventName){
node.addEventListener(eventName, (e)=>{ // node
vm[expr].call(vm,e)
})
},
html(node,expr,vm,eventName){
let fn = this.updater['htmlUpdater']
// ,
new Watcher(vm,expr,(newVal)=>{ //newVal ,
fn(node,newVal);
});
let value = this.getVal(vm,expr);
fn(node,value);
},
getContentValue(vm,expr){
//
return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
return this.getVal(vm,args[1]);
});
},
text(node,expr,vm){
let fn = this.updater['textUpdater']
//console.log(expr) :{{ school.name }}
let content = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
//console.log(args) :["{{ school.name }}", " school.name ", 0, "{{ school.name }}"]
//
new Watcher(vm,args[1],()=>{
fn(node,this.getContentValue(vm,expr)); //
})
return this.getVal(vm,args[1]);
})
// console.log(content) //
fn(node,content);
},
updater: {
// value
modelUpdater(node,value){
node.value = value;
},
htmlUpdater(node,value){
node.innerHTML = value;
},
//
textUpdater(node,value){
//textContent , 。
node.textContent = value;
}
}
}
class Vue{
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.computed = options.computed;
this.methods = options.methods;
//
if(this.$el){
// this.$data Object.defineProperty
new Observer(this.$data);
// vm vm.$data
this.proxyVm(this.$data);
//
for (let key in this.computed) {
Object.defineProperty(this.$data,key,{ //
get:()=>{
return this.computed[key].call(this);
}
})
};
//
for (let key in this.methods) {
Object.defineProperty(this,key,{
get: ()=>{
return this.methods[key]
}
})
}
//
new Compiler(this.$el,this);
}
}
// vm.$data
proxyVm(data){
for (let key in data) {
// vm.xxx vm.$data.xxx
Object.defineProperty(this,key,{ //this Vue
get:()=>{ // vm vm.$data
// console.log(this)
return data[key]; //
},
set: (newVal)=>{ // vm vm.$data
data[key] = newVal
}
})
}
}
}