検索
ホームページウェブフロントエンドVue.jsこの記事では、Vue の双方向バインディングの原理を詳しく分析します (徹底的に理解してください)。

この記事では、vue をカスタマイズし、データの双方向バインディングを段階的に実装します。例を通じて、vue の双方向バインディングの原理を段階的に理解できます。皆さんのお役に立てれば幸いです。

この記事では、Vue の双方向バインディングの原理を詳しく分析します (徹底的に理解してください)。

#カスタム vue クラス

  • Vue には、テンプレートとデータという少なくとも 2 つのパラメーターが必要です。 [関連する推奨事項:

    vue.js ビデオ チュートリアル ]

  • Compiler オブジェクトを作成し、データをテンプレートにレンダリングして、指定したノードにマウントします。

    ##
    class MyVue {
      // 1,接收两个参数:模板(根节点),和数据对象
      constructor(options) {
        // 保存模板,和数据对象
        if (this.isElement(options.el)) {
          this.$el = options.el;
        } else {
          this.$el = document.querySelector(options.el);
        }
        this.$data = options.data;
        // 2.根据模板和数据对象,渲染到根节点
        if (this.$el) {
          // 监听data所有属性的get/set
          new Observer(this.$data);
          new Compiler(this)
        }
      }
      // 判断是否是一个dom元素
      isElement(node) {
        return node.nodeType === 1;
      }
    }
  • #ページへのデータの最初のレンダリングを実現

##コンパイラ

1. node2fragment 関数はテンプレート要素をメモリに抽出するため、データをテンプレートにレンダリングしてすぐにページにマウントできます。2. テンプレートがメモリに抽出された後、buildTemplate 関数を使用してテンプレート要素を走査します

要素ノード

  • buildElement 関数を使用して要素の v- で始まる属性を確認します

    • テキスト ノード
  • buildText 関数を使用して、テキストに {{}} コンテンツがあるかどうかを確認します

    • 3、vue 命令を処理する CompilerUtil クラスを作成し、{{}}、データのレンダリングを完了します
  • 4。これで、最初のデータのレンダリングが完了します。次に、自動的に更新する必要があります。データが変更されたときのビュー。
class Compiler {
  constructor(vm) {
    this.vm = vm;
    // 1.将网页上的元素放到内存中
    let fragment = this.node2fragment(this.vm.$el);
    // 2.利用指定的数据编译内存中的元素
    this.buildTemplate(fragment);
    // 3.将编译好的内容重新渲染会网页上
    this.vm.$el.appendChild(fragment);
  }
  node2fragment(app) {
    // 1.创建一个空的文档碎片对象
    let fragment = document.createDocumentFragment();
    // 2.编译循环取到每一个元素
    let node = app.firstChild;
    while (node) {
      // 注意点: 只要将元素添加到了文档碎片对象中, 那么这个元素就会自动从网页上消失
      fragment.appendChild(node);
      node = app.firstChild;
    }
    // 3.返回存储了所有元素的文档碎片对象
    return fragment;
  }
  buildTemplate(fragment) {
    let nodeList = [...fragment.childNodes];
    nodeList.forEach(node => {
      // 需要判断当前遍历到的节点是一个元素还是一个文本
      if (this.vm.isElement(node)) {
        // 元素节点
        this.buildElement(node);
        // 处理子元素
        this.buildTemplate(node);
      } else {
        // 文本节点
        this.buildText(node);
      }
    })
  }
  buildElement(node) {
    let attrs = [...node.attributes];
    attrs.forEach(attr => {
      // v-model="name" => {name:v-model  value:name}
      let { name, value } = attr;
      // v-model / v-html / v-text / v-xxx
      if (name.startsWith('v-')) {
        // v-model -> [v, model]
        let [_, directive] = name.split('-');
        CompilerUtil[directive](node, value, this.vm);
      }
    })
  }
  buildText(node) {
    let content = node.textContent;
    let reg = /\{\{.+?\}\}/gi;
    if (reg.test(content)) {
      CompilerUtil['content'](node, content, this.vm);
    }
  }
}
let CompilerUtil = {
  getValue(vm, value) {
    // 解析this.data.aaa.bbb.ccc这种属性
    return value.split('.').reduce((data, currentKey) => {
      return data[currentKey.trim()];
    }, vm.$data);
  },
  getContent(vm, value) {
    // 解析{{}}中的变量
    let reg = /\{\{(.+?)\}\}/gi;
    let val = value.replace(reg, (...args) => {
      return this.getValue(vm, args[1]);
    });
    return val;
  },
  // 解析v-model指令
  model: function (node, value, vm) {
    // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值
    new Watcher(vm, value, (newValue, oldValue) => {
      node.value = newValue;
    });
    let val = this.getValue(vm, value);
    node.value = val;
  },
  // 解析v-html指令
  html: function (node, value, vm) {
    // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值
    new Watcher(vm, value, (newValue, oldValue) => {
      node.innerHTML = newValue;
    });
    let val = this.getValue(vm, value);
    node.innerHTML = val;
  },
  // 解析v-text指令
  text: function (node, value, vm) {
    // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值
    new Watcher(vm, value, (newValue, oldValue) => {
      node.innerText = newValue;
    });
    let val = this.getValue(vm, value);
    node.innerText = val;
  },
  // 解析{{}}中的变量
  content: function (node, value, vm) {
    let reg = /\{\{(.+?)\}\}/gi;
    let val = value.replace(reg, (...args) => {
      // 在触发getter之前,为dom创建Wather,并为Watcher.target赋值
      new Watcher(vm, args[1], (newValue, oldValue) => {
        node.textContent = this.getContent(vm, value);
      });
      return this.getValue(vm, args[1]);
    });
    node.textContent = val;
  }
}

データ駆動型ビューの実装

オブザーバー

1、defineRecative 関数を使用して、Object.defineProperty 処理を実行します。 get/set2 でデータ内の各データを監視できるように、データを取得します。 次に、データ値の変化を監視した後、ビューの内容を更新する方法を考えます。 Observer デザイン パターンを使用して、Dep クラスと Water クラスを作成します。

class Observer {
  constructor(data) {
    this.observer(data);
  }
  observer(obj) {
    if (obj && typeof obj === 'object') {
      // 遍历取出传入对象的所有属性, 给遍历到的属性都增加get/set方法
      for (let key in obj) {
        this.defineRecative(obj, key, obj[key])
      }
    }
  }
  // obj: 需要操作的对象
  // attr: 需要新增get/set方法的属性
  // value: 需要新增get/set方法属性的取值
  defineRecative(obj, attr, value) {
    // 如果属性的取值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法
    this.observer(value);
    // 第三步: 将当前属性的所有观察者对象都放到当前属性的发布订阅对象中管理起来
    let dep = new Dep(); // 创建了属于当前属性的发布订阅对象
    Object.defineProperty(obj, attr, {
      get() {
        // 在这里收集依赖
        Dep.target && dep.addSub(Dep.target);
        return value;
      },
      set: (newValue) => {
        if (value !== newValue) {
          // 如果给属性赋值的新值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法
          this.observer(newValue);
          value = newValue;
          dep.notify();
          console.log('监听到数据的变化');
        }
      }
    })
  }
}

オブザーバー デザイン パターンを使用して Dep クラスと Wather クラスを作成する

1. オブザーバー デザイン パターンを使用する目的は次のとおりです。

テンプレートを解析し、データ内の特定のデータがテンプレートで使用されている DOM ノード セットを収集し、データが変更された場合、DOM ノード セットを更新することでデータの更新を実現します。

  • Dep: 特定のデータ属性が依存する dom ノードのセットを収集し、更新メソッドを提供するために使用されます。

  • Watcher: 各 domノードのパッケージ オブジェクト

  • attr: dom によって使用されるデータ属性

    cb: dom 値を変更するコールバック関数は、次の場合に
    • を受け取ります。
    • 2 を作成しました。この時点で、このアイデアは問題ないと感じており、すでに勝利を確信しています。では、Dep と Watcher をどのように使用するのでしょうか?

各属性の dep を追加して、依存する dom を収集します

  • データ データは、ページが最初にレンダリングされるときに読み取られるためです。このとき、データのゲッターがトリガーされるので、dom

  • を具体的に収集します。CompilerUtil クラスが v-model、{{}} およびその他のコマンドを解析するとき、 getter がトリガーされます。トリガーする前に Water を作成し、dom を指す static 属性を Watcher に追加してから、getter 関数で static 変数を取得し、依存関係に追加して、コレクションを完成させます。 getter がトリガーされるたびに静的変数に値が割り当てられるため、間違った依存関係が収集されることはありません。

  • class Dep {
      constructor() {
        // 这个数组就是专门用于管理某个属性所有的观察者对象的
        this.subs = [];
      }
      // 订阅观察的方法
      addSub(watcher) {
        this.subs.push(watcher);
      }
      // 发布订阅的方法
      notify() {
        this.subs.forEach(watcher => watcher.update());
      }
    }
    class Watcher {
      constructor(vm, attr, cb) {
        this.vm = vm;
        this.attr = attr;
        this.cb = cb;
        // 在创建观察者对象的时候就去获取当前的旧值
        this.oldValue = this.getOldValue();
      }
      getOldValue() {
        Dep.target = this;
        let oldValue = CompilerUtil.getValue(this.vm, this.attr);
        Dep.target = null;
        return oldValue;
      }
      // 定义一个更新的方法, 用于判断新值和旧值是否相同
      update() {
        let newValue = CompilerUtil.getValue(this.vm, this.attr);
        if (this.oldValue !== newValue) {
          this.cb(newValue, this.oldValue);
        }
      }
    }

    3. この時点では、データがバインドされるとビューが自動的に更新されます。当初はコードを段階的に実装したかったのですが、扱いが難しいと感じたので、投稿しました完全なクラス。

  • ビュー駆動型データの実装

実際には、入力ボックスの入力と変更イベントを監視するためのものです。 CompilerUtilのモデルメソッドを変更します。具体的なコードは次のとおりです。

model: function (node, value, vm) {
    new Watcher(vm, value, (newValue, oldValue)=>{
        node.value = newValue;
    });
    let val = this.getValue(vm, value);
    node.value = val;
	// 看这里
    node.addEventListener('input', (e)=>{
        let newValue = e.target.value;
        this.setValue(vm, value, newValue);
    })
},

要約

vue 双方向バインディングの原則

vue はテンプレートとデータを受け取りますパラメーター。 1. まず、data 内のデータを再帰的に走査し、各プロパティに対して Object.defineProperty を実行し、get 関数と set 関数を定義します。そして、プロパティごとに dep 配列を追加します。 get が実行されると、呼び出された DOM ノードに対してウォッチャーが作成され、配列に格納されます。 set が実行されると、値が再割り当てされ、dep 配列の notify メソッドが呼び出されて、この属性を使用するすべてのウォッチャーに通知され、対応する dom コンテンツが更新されます。 2. テンプレートをメモリにロードし、テンプレート内の要素を再帰し、要素に v- で始まるコマンドまたは二重中括弧命令があることを検出すると、対応する値がデータから取得されてテンプレートの内容が変更されます。今回はテンプレートの内容が変更され、属性の dep 配列に dom 要素が追加されます。これにより、データ駆動型ビューが実装されます。 v-model命令を処理する際に、domに入力イベント(または変更)を追加し、入力時に対応する属性の値を変更することでページ駆動型データを実現します。 3. テンプレートをデータにバインドした後、テンプレートを実際の DOM ツリーに追加します。

ウォッチャーをdep配列に配置するにはどうすればよいですか?

テンプレートを解析すると、v 命令に従って対応するデータ属性値が取得されます。このとき、属性の get メソッドが呼び出されます。最初に Watcher インスタンスを作成し、内部の属性値を取得します。古い値としてウォッチャー内に保存されています。値を取得する前に、Watcher プロトタイプ オブジェクトに属性 Watcher.target = this を追加してから、Watcher.target = となる値を取得します。 null; get が呼び出されたときに Watcher.target に従ってウォッチャー インスタンス オブジェクトを取得するようにします。

メソッドの原理

vue インスタンスを作成するとき、メソッド パラメータを受け取ります

テンプレート命令を解析するときに v-on に遭遇します。対応するイベントのリスナーが DOM 要素に追加され、call メソッドを使用して vue をこのメソッドにバインドします: vm.$methods[value].call(vm, e);

計算の原理

vue インスタンスを作成するときに、計算されたパラメータを受け取ります。

vue インスタンスを初期化するときに、次のことを実行します。計算されたキーのオブジェクト .defineProperty の処理と取得プロパティの追加。

(学習ビデオ共有: Web フロントエンド )

以上がこの記事では、Vue の双方向バインディングの原理を詳しく分析します (徹底的に理解してください)。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明
この記事は掘金社区で複製されています。侵害がある場合は、admin@php.cn までご連絡ください。
Vue常见面试题汇总(附答案解析)Vue常见面试题汇总(附答案解析)Apr 08, 2021 pm 07:54 PM

本篇文章给大家分享一些Vue面试题(附答案解析)。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

5 款适合国内使用的 Vue 移动端 UI 组件库5 款适合国内使用的 Vue 移动端 UI 组件库May 05, 2022 pm 09:11 PM

本篇文章给大家分享5 款适合国内使用的 Vue 移动端 UI 组件库,希望对大家有所帮助!

vue中props可以传递函数吗vue中props可以传递函数吗Jun 16, 2022 am 10:39 AM

vue中props可以传递函数;vue中可以将字符串、数组、数字和对象作为props传递,props主要用于组件的传值,目的为了接收外面传过来的数据,语法为“export default {methods: {myFunction() {// ...}}};”。

手把手带你利用vue3.x绘制流程图手把手带你利用vue3.x绘制流程图Jun 08, 2022 am 11:57 AM

利用vue3.x怎么绘制流程图?下面本篇文章给大家分享基于 vue3.x 的流程图绘制方法,希望对大家有所帮助!

如何覆盖组件库样式?React和Vue项目的解决方法浅析如何覆盖组件库样式?React和Vue项目的解决方法浅析May 16, 2022 am 11:15 AM

如何覆盖组件库样式?下面本篇文章给大家介绍一下React和Vue项目中优雅地覆盖组件库样式的方法,希望对大家有所帮助!

聊聊vue指令中的修饰符,常用事件修饰符总结聊聊vue指令中的修饰符,常用事件修饰符总结May 09, 2022 am 11:07 AM

本篇文章带大家聊聊vue指令中的修饰符,对比一下vue中的指令修饰符和dom事件中的event对象,介绍一下常用的事件修饰符,希望对大家有所帮助!

通过9个Vue3 组件库,看看聊前端的流行趋势!通过9个Vue3 组件库,看看聊前端的流行趋势!May 07, 2022 am 11:31 AM

本篇文章给大家分享9个开源的 Vue3 组件库,通过它们聊聊发现的前端的流行趋势,希望对大家有所帮助!

react与vue的虚拟dom有什么区别react与vue的虚拟dom有什么区别Apr 22, 2022 am 11:11 AM

react与vue的虚拟dom没有区别;react和vue的虚拟dom都是用js对象来模拟真实DOM,用虚拟DOM的diff来最小化更新真实DOM,可以减小不必要的性能损耗,按颗粒度分为不同的类型比较同层级dom节点,进行增、删、移的操作。

See all articles

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

DVWA

DVWA

Damn Vulnerable Web App (DVWA) は、非常に脆弱な PHP/MySQL Web アプリケーションです。その主な目的は、セキュリティ専門家が法的環境でスキルとツールをテストするのに役立ち、Web 開発者が Web アプリケーションを保護するプロセスをより深く理解できるようにし、教師/生徒が教室環境で Web アプリケーションを教え/学習できるようにすることです。安全。 DVWA の目標は、シンプルでわかりやすいインターフェイスを通じて、さまざまな難易度で最も一般的な Web 脆弱性のいくつかを実践することです。このソフトウェアは、

AtomエディタMac版ダウンロード

AtomエディタMac版ダウンロード

最も人気のあるオープンソースエディター

Dreamweaver Mac版

Dreamweaver Mac版

ビジュアル Web 開発ツール

PhpStorm Mac バージョン

PhpStorm Mac バージョン

最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール

SecLists

SecLists

SecLists は、セキュリティ テスターの究極の相棒です。これは、セキュリティ評価中に頻繁に使用されるさまざまな種類のリストを 1 か所にまとめたものです。 SecLists は、セキュリティ テスターが必要とする可能性のあるすべてのリストを便利に提供することで、セキュリティ テストをより効率的かつ生産的にするのに役立ちます。リストの種類には、ユーザー名、パスワード、URL、ファジング ペイロード、機密データ パターン、Web シェルなどが含まれます。テスターはこのリポジトリを新しいテスト マシンにプルするだけで、必要なあらゆる種類のリストにアクセスできるようになります。