>  기사  >  웹 프론트엔드  >  vuejs의 양방향 바인딩 원칙은 무엇입니까?

vuejs의 양방향 바인딩 원칙은 무엇입니까?

青灯夜游
青灯夜游원래의
2021-09-28 14:06:273598검색

vuejs의 양방향 바인딩 원칙: 데이터 하이재킹 및 게시-구독 모드 사용, "Object.defineProperty()"를 통해 각 속성의 setter 및 getter 하이재킹, 데이터 변경 시 구독자에게 메시지 게시 및 해당 트리거 콜백을 수신한 다음 뷰를 업데이트합니다.

vuejs의 양방향 바인딩 원칙은 무엇입니까?

이 튜토리얼의 운영 환경: Windows 7 시스템, vue 버전 2.9.6, DELL G3 컴퓨터.

Vue 데이터 양방향 바인딩 원칙

Vue는 주로 데이터 하이재킹 및 게시-구독 모드를 통해 양방향 데이터 바인딩을 구현하며 Object.defineProperty() 메서드를 사용하여 데이터 하이재킹을 수행합니다. 게시자(토픽 개체)는 모든 관찰자에게 알림을 받은 후 뷰를 업데이트합니다. Object.defineProperty() 方法进行的数据劫持,然后通知发布者(主题对象)去通知所有观察者,观察者收到通知后,就会对视图进行更新。

https://jsrun.net/RMIKp/embedded/all/light

MVVM 框架主要包含两个方面,数据变化更新视图,视图变化更新数据。

视图变化更新数据,如果是像 input 这种标签,可以使用 oninput 事件..

数据变化更新视图可以使用 Object.definProperty() 的 set 方法可以检测数据变化,当数据改变就会触发这个函数,然后更新视图。

实现过程

我们知道了如何实现双向绑定了,首先要对数据进行劫持监听,所以我们需要设置一个 Observer 函数,用来监听所有属性的变化。

如果属性发生了变化,那就要告诉订阅者 watcher 看是否需要更新数据,如果订阅者有多个,则需要一个 Dep 来收集这些订阅者,然后在监听器 observer 和 watcher 之间进行统一管理。

还需要一个指令解析器 compile,对需要监听的节点和属性进行扫描和解析。

因此,流程大概是这样的:

  • 实现一个监听器 Observer,用来劫持并监听所有属性,如果发生变动,则通知订阅者。

  • 实现一个订阅者 Watcher,当接到属性变化的通知时,执行对应的函数,然后更新视图,使用 Dep 来收集这些 Watcher。

  • 实现一个解析器 Compile,用于扫描和解析的节点的相关指令,并根据初始化模板以及初始化相应的订阅器。

vuejs의 양방향 바인딩 원칙은 무엇입니까?

显示一个 Observer

Observer 是一个数据监听器,核心方法是利用 Object.defineProperty()

https://jsrun.net/RMIKp/embedded/all/light

MVVM 프레임워크에는 주로 두 가지 측면이 포함됩니다. 데이터 변경은 뷰를 업데이트하고, 뷰 변경은 데이터를 업데이트합니다.

View 변경 사항이 입력과 같은 태그인 경우 oninput 이벤트를 사용할 수 있습니다.

Data 변경 사항은 Object.definProperty()의 set 메서드를 사용할 수 있습니다. 데이터 변경을 감지합니다. 이 함수는 데이터가 변경될 때 트리거되고 뷰가 업데이트됩니다.

구현 프로세스

우리는 양방향 바인딩을 구현하는 방법을 알고 있습니다. 먼저 데이터를 하이재킹하고 모니터링해야 하므로 모든 속성의 변경 사항을 모니터링하려면 Observer 기능을 설정해야 합니다.

속성이 변경되면 구독자 감시자에게 데이터 업데이트가 필요한지 확인해야 합니다. 구독자가 여러 명인 경우 이러한 구독자를 수집하기 위한 Dep가 필요하며 관찰자와 감시자 간의 통합 관리를 수행해야 합니다. .

모니터링해야 하는 노드와 속성을 스캔하고 구문 분석하려면 구문 분석기 컴파일 명령도 필요합니다.

따라서 프로세스는 대략 다음과 같습니다.

리스너 관찰자를 구현하여 모든 속성을 하이재킹하고 모니터링하고 변경 사항이 발생하면 구독자에게 알립니다.

구독자 Watcher를 구현합니다. 속성 변경 알림을 받으면 해당 기능을 실행한 후 뷰를 업데이트하고 Dep을 사용하여 이러한 Watcher를 수집합니다.

노드의 관련 명령어를 스캔하고 구문 분석하는 데 사용되는 파서 컴파일을 구현하고 초기화 템플릿에 따라 해당 구독자를 초기화합니다.

1 .png

Observer 표시

Observer는 데이터 리스너입니다. 핵심 방법은 Object.defineProperty()를 사용하여 모니터링을 위해 모든 속성에 setter 및 getter 메서드를 반복적으로 추가하는 것입니다. .

var library = {
  book1: {
    name: "",
  },
  book2: "",
};
observe(library);
library.book1.name = "vue权威指南"; // 属性name已经被监听了,现在值为:“vue权威指南”
library.book2 = "没有此书籍"; // 属性book2已经被监听了,现在值为:“没有此书籍”

// 为数据添加检测
function defineReactive(data, key, val) {
  observe(val); // 递归遍历所有子属性
  let dep = new Dep(); // 新建一个dep
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function() {
      if (Dep.target) {
        // 判断是否需要添加订阅者,仅第一次需要添加,之后就不用了,详细看Watcher函数
        dep.addSub(Dep.target); // 添加一个订阅者
      }
      return val;
    },
    set: function(newVal) {
      if (val == newVal) return; // 如果值未发生改变就return
      val = newVal;
      console.log(
        "属性" + key + "已经被监听了,现在值为:“" + newVal.toString() + "”"
      );
      dep.notify(); // 如果数据发生变化,就通知所有的订阅者。
    },
  });
}

// 监听对象的所有属性
function observe(data) {
  if (!data || typeof data !== "object") {
    return; // 如果不是对象就return
  }
  Object.keys(data).forEach(function(key) {
    defineReactive(data, key, data[key]);
  });
}
// Dep 负责收集订阅者,当属性发生变化时,触发更新函数。
function Dep() {
  this.subs = {};
}
Dep.prototype = {
  addSub: function(sub) {
    this.subs.push(sub);
  },
  notify: function() {
    this.subs.forEach((sub) => sub.update());
  },
};

아이디어 분석에는 구독자를 수용할 수 있는 메시지 구독자 Dep이 있어야 하며, 이는 구독자를 수집하고 속성 변경 시 해당 업데이트 기능을 실행하는 데 사용됩니다.

코드 관점에서 보면 Watcher가 초기화될 때 이를 트리거하기 위해 구독자 Dep이 getter에 추가되므로 구독자가 필요한지 여부를 결정해야 합니다.
  • setter에서는 데이터가 변경되면 모든 구독자에게 알림이 전송되며 구독자는 해당 기능을 업데이트합니다.

    이제 비교적 완전한 Observer가 완성되었습니다. 다음으로 Watcher 설계를 시작합니다.🎜🎜Implementing the Watcher🎜🎜구독자 Watcher는 초기화 중에 구독자 Dep에 자신을 추가해야 합니다. Watcher 작업은 가져오기 시 수행되므로 Watcher 초기화 시 해당 구독자 작업을 추가하려면 해당 가져오기 함수만 트리거하면 됩니다. 🎜🎜어떻게 트리거를 받을 수 있나요? Object.defineProperty()를 이미 설정했기 때문에 이를 트리거하려면 해당 속성 값만 가져오면 됩니다. 🎜🎜구독자 Watcher가 초기화될 때만 Dep.target에 구독자를 캐시하고 추가가 성공한 후에 제거하면 됩니다. 🎜
    function Watcher(vm, exp, cb) {
      this.cb = cb;
      this.vm = vm;
      this.exp = exp;
      this.value = this.get(); // 将自己添加到订阅器的操作
    }
    
    Watcher.prototype = {
      update: function() {
        this.run();
      },
      run: function() {
        var value = this.vm.data[this.exp];
        var oldVal = this.value;
        if (value !== oldVal) {
          this.value = value;
          this.cb.call(this.vm, value, oldVal);
        }
      },
      get: function() {
        Dep.target = this; // 缓存自己,用于判断是否添加watcher。
        var value = this.vm.data[this.exp]; // 强制执行监听器里的get函数
        Dep.target = null; // 释放自己
        return value;
      },
    };
    🎜지금까지 간단한 Watcher 디자인이 완성되었으며, 이후 Observer와 Watcher를 연결하면 간단한 양방향 바인딩이 가능해집니다. 🎜🎜파서 Compile이 아직 설계되지 않았기 때문에 템플릿 데이터를 먼저 하드 코딩할 수 있습니다. 🎜🎜코드를 ES6 생성자 작성 방식으로 변환하여 미리 봅니다. 🎜🎜https://jsrun.net/8SIKp/embedded/all/light🎜🎜이 코드는 컴파일러를 구현하지 않고 바인딩된 변수를 직접 전달합니다. 한 노드에 하나의 데이터(이름)만 설정하여 바인딩합니다. 그런 다음 페이지에서 새로운 MyVue를 수행하여 양방향 바인딩을 달성합니다. 🎜🎜그리고 2초 후에 변경하면 페이지도 변경된 것을 볼 수 있습니다. 🎜
    // MyVue
    proxyKeys(key) {
        var self = this;
        Object.defineProperty(this, key, {
            enumerable: false,
            configurable: true,
            get: function proxyGetter() {
                return self.data[key];
            },
            set: function proxySetter(newVal) {
                self.data[key] = newVal;
            }
        });
    }
    🎜위 코드의 기능은 this.data의 키를 이것으로 프록시하여 this.xx를 사용하여 this.data.xx를 쉽게 얻을 수 있도록 하는 것입니다. 🎜🎜컴파일 구현🎜🎜위에서 양방향 데이터 바인딩을 구현했지만 전체 프로세스는 DOM 섹션 저장소를 구문 분석하지 않고 고정적으로 교체하므로 다음 단계는 데이터 구문 분석 및 바인딩 작업을 수행하는 파서를 구현하는 것입니다. 🎜🎜파서 컴파일 구현 단계: 🎜🎜🎜🎜템플릿 지침을 구문 분석하고, 템플릿 데이터를 교체하고, 보기를 초기화합니다. 🎜
  • 将模板指定对应的节点绑定对应的更新函数,初始化相应的订阅器。

为了解析模板,首先需要解析 DOM 数据,然后对含有 DOM 元素上的对应指令进行处理,因此整个 DOM 操作较为频繁,可以新建一个 fragment 片段,将需要的解析的 DOM 存入 fragment 片段中在进行处理。

function nodeToFragment(el) {
  var fragment = document.createDocumentFragment();
  var child = el.firstChild;
  while (child) {
    // 将Dom元素移入fragment中
    fragment.appendChild(child);
    child = el.firstChild;
  }
  return fragment;
}

接下来需要遍历各个节点,对含有相关指令和模板语法的节点进行特殊处理,先进行最简单模板语法处理,使用正则解析“{{变量}}”这种形式的语法。

function compileElement (el) {
    var childNodes = el.childNodes;
    var self = this;
    [].slice.call(childNodes).forEach(function(node) {
        var reg = /\{\{(.*)\}\}/; // 匹配{{xx}}
        var text = node.textContent;
        if (self.isTextNode(node) && reg.test(text)) {  // 判断是否是符合这种形式{{}}的指令
            self.compileText(node, reg.exec(text)[1]);
        }
        if (node.childNodes && node.childNodes.length) {
            self.compileElement(node);  // 继续递归遍历子节点
        }
    });
},
function compileText (node, exp) {
    var self = this;
    var initText = this.vm[exp];
    updateText(node, initText);  // 将初始化的数据初始化到视图中
    new Watcher(this.vm, exp, function (value) {  // 生成订阅器并绑定更新函数
        self.updateText(node, value);
    });
},
function updateText (node, value) {
    node.textContent = typeof value == 'undefined' ? '' : value;
}

获取到最外层的节点后,调用 compileElement 函数,对所有的子节点进行判断,如果节点是文本节点切匹配{{}}这种形式的指令,则进行编译处理,初始化对应的参数。

然后需要对当前参数生成一个对应的更新函数订阅器,在数据发生变化时更新对应的 DOM。

这样就完成了解析、初始化、编译三个过程了。

接下来改造一个 myVue 就可以使用模板变量进行双向数据绑定了。

https://jsrun.net/K4IKp/embedded/all/light

添加解析事件

添加完 compile 之后,一个数据双向绑定就基本完成了,接下来就是在 Compile 中添加更多指令的解析编译,比如 v-model、v-on、v-bind 等。

添加一个 v-model 和 v-on 解析:

function compile(node) {
  var nodeAttrs = node.attributes;
  var self = this;
  Array.prototype.forEach.call(nodeAttrs, function(attr) {
    var attrName = attr.name;
    if (isDirective(attrName)) {
      var exp = attr.value;
      var dir = attrName.substring(2);
      if (isEventDirective(dir)) {
        // 事件指令
        self.compileEvent(node, self.vm, exp, dir);
      } else {
        // v-model 指令
        self.compileModel(node, self.vm, exp, dir);
      }
      node.removeAttribute(attrName); // 解析完毕,移除属性
    }
  });
}
// v-指令解析
function isDirective(attr) {
  return attr.indexOf("v-") == 0;
}
// on: 指令解析
function isEventDirective(dir) {
  return dir.indexOf("on:") === 0;
}

上面的 compile 函数是用于遍历当前 dom 的所有节点属性,然后判断属性是否是指令属性,如果是在做对应的处理(事件就去监听事件、数据就去监听数据..)

完整版 myVue

在 MyVue 中添加 mounted 方法,在所有操作都做完时执行。

class MyVue {
  constructor(options) {
    var self = this;
    this.data = options.data;
    this.methods = options.methods;
    Object.keys(this.data).forEach(function(key) {
      self.proxyKeys(key);
    });
    observe(this.data);
    new Compile(options.el, this);
    options.mounted.call(this); // 所有事情处理好后执行mounted函数
  }
  proxyKeys(key) {
    // 将this.data属性代理到this上
    var self = this;
    Object.defineProperty(this, key, {
      enumerable: false,
      configurable: true,
      get: function getter() {
        return self.data[key];
      },
      set: function setter(newVal) {
        self.data[key] = newVal;
      },
    });
  }
}

然后就可以测试使用了。

https://jsrun.net/Y4IKp/embedded/all/light

总结一下流程,回头在哪看一遍这个图,是不是清楚很多了。

vuejs의 양방향 바인딩 원칙은 무엇입니까?

可以查看的代码地址:Vue2.x 的双向绑定原理及实现

相关推荐:《vue.js教程

위 내용은 vuejs의 양방향 바인딩 원칙은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.