>  기사  >  웹 프론트엔드  >  Vue 반응형 구현의 원리에 대해 이야기하는 기사

Vue 반응형 구현의 원리에 대해 이야기하는 기사

青灯夜游
青灯夜游앞으로
2022-09-15 19:36:431556검색

Vue 반응형 구현의 원리에 대해 이야기하는 기사

vue는 다양한 편리한 기능이 통합되어 있는 사용하기 쉬운 프레임워크입니다. 가장 눈에 띄는 특징은 하단에 숨겨진 반응형 시스템입니다. 구성 요소 상태는 모두 반응형 JavaScript 개체입니다. 이를 변경하면 보기가 업데이트되어 상태 관리가 더 쉽고 직관적이 됩니다. 그렇다면 Vue 반응형 시스템은 어떻게 구현되나요? 이 글 역시 Vue 소스코드를 읽어본 후의 이해와 모방 구현을 바탕으로 작성되었으니, 저자의 생각을 따라가며 Vue를 얕은 것부터 깊게 살펴보도록 하겠습니다! [관련 권장 사항: vuejs 비디오 튜토리얼]

이 문서의 Vue 소스 코드 버전: 2.6.14 이해를 돕기 위해 코드를 단순화했습니다.

Vue가 데이터 응답성을 구현하는 방법

일반 JavaScript 객체를 Vue 인스턴스에 데이터 옵션으로 전달하면 Vue는 이 객체의 모든 속성을 탐색하고 Object.defineProperty를 사용하여 getter에 대한 모든 속성을 변환합니다. /setters를 실행한 다음 getter/setters를 실행합니다.

Vue의 반응 시스템을 한 문장으로 요약하려면: Observer 패턴 + Object.defineProperty 차단 getter/setter

MDN ObjdefineProperty

Observer 패턴

Object.defineProperty란 무엇인가요?

Object.defineProperty()Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

简单的说,就是通过此方式定义的 property,执行 obj.xxx 时会触发 get,执行 obj.xxx = xxx会触发 set,这便是响应式的关键。

Object.defineProperty 是 ES5 中一个无法 shim(无法通过polyfill实现) 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

响应式系统基础实现

现在,我们来基于Object.defineProperty 메서드는 객체에 대한 새 속성을 직접 정의하거나 객체의 기존 속성을 수정하고 이 객체를 반환합니다.

간단히 이렇게 정의한 속성은 obj.xxx를 실행할 때 get을 트리거하고, obj.xxx = xxx를 실행할 때 set을 트리거합니다. 이것이 바로 반응성입니다. 핵심입니다.

Object.defineProperty는 shim이 불가능한(폴리필을 통해 구현할 수 없는) ES5의 기능입니다. 이것이 Vue가 IE8 이하 브라우저를 지원하지 않는 이유입니다.

반응형 시스템의 기본 구현

이제 "애피타이저"로 Object.defineProperty를 기반으로 하는 간단한 반응형 업데이트 시스템을 구현해 보겠습니다.

let data = {};
// 使用一个中间变量保存 value
let value = "hello";
// 用一个集合保存数据的响应更新函数
let fnSet = new Set();
// 在 data 上定义 text 属性
Object.defineProperty(data, "text", {
  enumerable: true,
  configurable: true,
  set(newValue) {
    value = newValue;
    // 数据变化
    fnSet.forEach((fn) => fn());
  },
  get() {
    fnSet.add(fn);
    return value;
  },
});

// 将 data.text 渲染到页面上
function fn() {
  document.body.innerText = data.text;
}
// 执行函数,触发读取 get
fn();

// 一秒后改变数据,触发 set 更新
setTimeout(() => {
  data.text = "world";
}, 1000);
다음으로 살펴보겠습니다. 이 코드 실행 서버에서 원하는 효과를 얻을 수 있습니다

위의 코드를 통해 이미 반응형 시스템의 작동 원리를 어느 정도 이해하신 것 같습니다. 이 "애피타이저"를 쉽게 소화할 수 있도록 하기 위해 이 간단한 응답 시스템에는 여전히 많은 단점이 있습니다. 예를 들어 데이터 및 응답 업데이트 기능은 하드 코딩을 통해 강력하게 결합되어 일대일 상황만 달성하며 모듈화되지는 않습니다. 잠깐만요... 그럼 다음은 하나씩 완성해 볼까요?

완전한 반응형 시스템 설계완벽한 반응형 시스템을 설계하려면 먼저 사전 지식인 관찰자 패턴이 무엇인지 이해해야 합니다. 관찰자 패턴이란 무엇인가요?

Vue 반응형 구현의 원리에 대해 이야기하는 기사

객체 이벤트가 발생할 때 객체를 "관찰"하는 여러 다른 객체에 알릴 수 있는 구독 메커니즘을 정의할 수 있는 동작 설계 패턴입니다.

주목할만한 상태를 가진 개체를

대상이라고 부르는 경우가 많습니다.

자체 상태가 변경되면 다른 개체에 알려야 하므로

publisher

라고도 합니다. 게시자 상태의 변경 사항을 따르려는 다른 모든 개체를

구독자

라고 합니다. 또한 게시자는 인터페이스를 통해서만 모든 구독자와 직접 상호 작용하며 모두 동일한 인터페이스를 가져야 합니다.

예:

🎜당신(예: 앱 구독자)이 특정 서점의 주간지에 관심이 있고 상사(예: 앱의 출판사)에게 전화번호를 남깁니다. 소식이 있으면 바로 사장님께 알려주세요. 위클리에서 전화를 드리고, 이번 위클리에 관심이 있는 다른 분들도 사장님께 전화번호를 남겨드립니다. 새로운 주간이 오면 사장님이 한 명씩 전화를 걸어 독자들에게 받아보라고 알린다. 🎜🎜독자가 실수로 전화번호 대신 QQ 번호를 남겨둔 경우, 이전 버전에서는 전화 연결이 불가능하며, 독자는 알림을 받을 수 없습니다. 이것이 위에서 말한 것과 동일한 인터페이스를 가져야 합니다. 🎜🎜관찰자 패턴을 이해한 후 반응형 시스템을 설계하기 시작했습니다. 🎜🎜🎜🎜추상 관찰자(구독자) 클래스 Watcher🎜🎜🎜

在上面的例子中,数据和响应更新函数是通过硬编码强耦合在一起的。而实际开发过程中,更新函数不一定叫fn,更有可能是一个匿名函数。所以我们需要抽像一个观察者(订阅者)类Watcher来保存并执行更新函数,同时向外提供一个update更新接口。

// Watcher 观察者可能有 n 个,我们为了区分它们,保证唯一性,增加一个 uid
let watcherId = 0;
// 当前活跃的 Watcher
let activeWatcher = null;

class Watcher {
  constructor(cb) {
    this.uid = watcherId++;
    // 更新函数
    this.cb = cb;
    // 保存 watcher 订阅的所有数据
    this.deps = [];
    // 初始化时执行更新函数
    this.get();
  }
  // 求值函数
  get() {
    // 调用更新函数时,将 activeWatcher 指向当前 watcher
    activeWatcher = this;
    this.cb();
    // 调用完重置
    activeWatcher = null;
  }
  // 数据更新时,调用该函数重新求值
  update() {
    this.get();
  }
}

抽象被观察者(发布者)类Dep

我们再想一想,实际开发过程中,data 中肯定不止一个数据,而且每个数据,都有不同的订阅者,所以说我们还需要抽象一个被观察者(发布者)Dep类来保存数据对应的观察者(Watcher),以及数据变化时通知观察者更新。

class Dep {
  constructor() {
    // 保存所有该依赖项的订阅者
    this.subs = [];
  }
  addSubs() {
    // 将 activeWatcher 作为订阅者,放到 subs 中
    // 防止重复订阅
    if(this.subs.indexOf(activeWatcher) === -1){
      this.subs.push(activeWatcher);
    }
  }
  notify() {
    // 先保存旧的依赖,便于下面遍历通知更新
    const deps = this.subs.slice()
    // 每次更新前,清除上一次收集的依赖,下次执行时,重新收集
    this.subs.length = 0;
    deps.forEach((watcher) => {
      watcher.update();
    });
  }
}

抽象 Observer

现在,WatcherDep只是两个独立的模块,我们怎么把它们关联起来呢?

答案就是Object.defineProperty,在数据被读取,触发get方法,Dep 将当前触发 get 的 Watcher 当做订阅者放到 subs中,Watcher 就与 Dep建立关系;在数据被修改,触发set方法,Dep就遍历 subs 中的订阅者,通知Watcher更新。

下面我们就来完善将数据转换为getter/setter的处理。

上面基础的响应式系统实现中,我们只定义了一个响应式数据,当 data 中有其他property时我们就处理不了了。所以,我们需要抽象一个 Observer类来完成对 data数据的遍历,并调用defineReactive转换为 getter/setter,最终完成响应式绑定。

为了简化,我们只处理data中单层数据。

class Observer {
  constructor(value) {
    this.value = value;
    this.walk(value);
  }
  // 遍历 keys,转换为 getter/setter
  walk(obj) {
    const keys = Object.keys(obj);
    for (let i = 0; i <p>这里我们通过参数 value 的闭包,来保存最新的数据,避免新增其他变量</p><pre class="brush:php;toolbar:false">function defineReactive(target, key, value) {
  // 每一个数据都是一个被观察者
  const dep = new Dep();
  Object.defineProperty(target, key, {
    enumerable: true,
    configurable: true,
    // 执行 data.xxx 时 get 触发,进行依赖收集,watcher 订阅 dep
    get() {
      if (activeWatcher) {
        // 订阅
        dep.addSubs(activeWatcher);
      }
      return value;
    },
    // 执行 data.xxx = xxx 时 set 触发,遍历订阅了该 dep 的 watchers,
    // 调用 watcher.updata 更新
    set(newValue) {
      // 如果前后值相等,没必要跟新
      if (value === newVal) {
        return;
      }
      value = newValue;
      // 派发更新
      dep.notify();
    },
  });
}

至此,响应式系统就大功告成了!!

测试

我们通过下面代码测试一下:

let data = {
  name: "张三",
  age: 18,
  address: "成都",
};
// 模拟 render
const render1 = () => {
  console.warn("-------------watcher1--------------");
  console.log("The name value is", data.name);
  console.log("The age value is", data.age);
  console.log("The address value is", data.address);
};
const render2 = () => {
  console.warn("-------------watcher2--------------");
  console.log("The name value is", data.name);
  console.log("The age value is", data.age);
};
// 先将 data 转换成响应式
new Observer(data);
// 实例观察者
new Watcher(render1);
new Watcher(render2);

在浏览器中运行这段代码,和我们期望的一样,两个render都执行了,并且在控制台上打印了结果。

Vue 반응형 구현의 원리에 대해 이야기하는 기사

我们尝试修改 data.name = '李四 23333333',测试两个 render 都会重新执行:

Vue 반응형 구현의 원리에 대해 이야기하는 기사

我们只修改 data.address = '北京',测试一下是否只有render 1回调都会重新执行:

Vue 반응형 구현의 원리에 대해 이야기하는 기사

都完美通过测试!!?

总结

Vue 반응형 구현의 원리에 대해 이야기하는 기사

Vue响应式原理的核心就是ObserverDepWatcher,三者共同构成 MVVM 中的 VM

Observer中进行数据响应式处理以及最终的WatcherDep关系绑定,在数据被读的时候,触发get方法,将 Watcher收集到 Dep中作为依赖;在数据被修改的时候,触发set方法,Dep就遍历 subs 中的订阅者,通知Watcher更新。

本篇文章属于入门篇,并非源码实现,在源码的基础上简化了很多内容,能够便于理解ObserverDepWatcher三者的作用和关系。

本文的源码,以及作者学习 Vue 源码完整的逐行注释源码地址:github.com/yue1123/vue…

(学习视频分享:web前端开发编程基础视频

위 내용은 Vue 반응형 구현의 원리에 대해 이야기하는 기사의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제