当面试官问你Vue响应式原理,你可以这么回答他

简介:

看过vue官方文档的同学,对这张图应该已然相当熟悉了。

vue的响应式是如何实现的?

听过太多回答,通过Object.defineProperty,可是再详细的问时,对方浑然不知。

先撸为敬

const Observer = function(data) {
 // 循环修改为每个属性添加get set for (let key in data) {
 defineReactive(data, key);
 }
}

const defineReactive = function(obj, key) {
 // 局部变量dep,用于get set内部调用 const dep = new Dep();
 // 获取当前值 let val = obj[key];
 Object.defineProperty(obj, key, {
 // 设置当前描述属性为可被循环
 enumerable: true,
 // 设置当前描述属性可被修改
 configurable: true,
 get() {
 console.log('in get');
 // 调用依赖收集器中的addSub,用于收集当前属性与Watcher中的依赖关系
 dep.depend();
 return val;
 },
 set(newVal) {
 if (newVal === val) {
 return;
 }
 val = newVal;
 // 当值发生变更时,通知依赖收集器,更新每个需要更新的Watcher, // 这里每个需要更新通过什么断定?dep.subs
 dep.notify();
 }
 });
}

const observe = function(data) {
 return new Observer(data);
}

const Vue = function(options) {
 const self = this;
 // 将data赋值给this._data,源码这部分用的Proxy所以我们用最简单的方式临时实现 if (options && typeof options.data === 'function') {
 this._data = options.data.apply(this);
 }
 // 挂载函数 this.mount = function() {
 new Watcher(self, self.render);
 }
 // 渲染函数 this.render = function() {
 with(self) {
 _data.text;
 }
 }
 // 监听this._data
 observe(this._data); 
}

const Watcher = function(vm, fn) {
 const self = this;
 this.vm = vm;
 // 将当前Dep.target指向自己
 Dep.target = this;
 // 向Dep方法添加当前Wathcer this.addDep = function(dep) {
 dep.addSub(self);
 }
 // 更新方法,用于触发vm._render this.update = function() {
 console.log('in watcher update');
 fn();
 }
 // 这里会首次调用vm._render,从而触发text的get // 从而将当前的Wathcer与Dep关联起来 this.value = fn();
 // 这里清空了Dep.target,为了防止notify触发时,不停的绑定Watcher与Dep, // 造成代码死循环
 Dep.target = null;
}

const Dep = function() {
 const self = this;
 // 收集目标 this.target = null;
 // 存储收集器中需要通知的Watcher this.subs = [];
 // 当有目标时,绑定Dep与Wathcer的关系 this.depend = function() {
 if (Dep.target) {
 // 这里其实可以直接写self.addSub(Dep.target), // 没有这么写因为想还原源码的过程。
 Dep.target.addDep(self);
 }
 }
 // 为当前收集器添加Watcher this.addSub = function(watcher) {
 self.subs.push(watcher);
 }
 // 通知收集器中所的所有Wathcer,调用其update方法 this.notify = function() {
 for (let i = 0; i < self.subs.length; i += 1) {
 self.subs[i].update();
 }
 }
}

const vue = new Vue({
 data() {
 return {
 text: 'hello world'
 };
 }
})

vue.mount(); // in get
vue._data.text = '123'; // in watcher update /n in get 复制代码

这里我们用不到100行的代码,实现了一个简易的vue响应式。当然,这里如果不考虑期间的过程,我相信,40行代码之内可以搞定。但是我这里不想省略,为什么呢?我怕你把其中的过程自动忽略掉,怕别人问你相关东西的时候,明明自己看过了,却被怼的哑口无言。总之,我是为了你好,多喝热水。

Dep的作用是什么?

依赖收集器,这不是官方的名字蛤,我自己起的,为了好记。

用两个例子来看看依赖收集器的作用吧。

  • 例子1,毫无意义的渲染是不是没必要?

    const vm = new Vue({
     data() {
     return {
     text: 'hello world',
     text2: 'hey',
     }
     }
    })
    复制代码

    vm.text2的值发生变化时,会再次调用render,而template中却没有使用text2,所以这里处理render是不是毫无意义?

    针对这个例子还记得我们上面模拟实现的没,在Vuerender函数中,我们调用了本次渲染相关的值,所以,与渲染无关的值,并不会触发get,也就不会在依赖收集器中添加到监听(addSub方法不会触发),即使调用set赋值,notify中的subs也是空的。OK,继续回归demo,来一小波测试去印证下我说的吧。

    const vue = new Vue({
     data() {
     return {
     text: 'hello world',
     text2: 'hey'
     };
     }
    })
    
    vue.mount(); // in get
    vue._data.text = '456'; // in watcher update /n in get
    vue._data.text2 = '123'; // nothing 复制代码
  • 例子2,多个Vue实例引用同一个data时,通知谁?是不是应该俩都通知?

     let commonData = {
     text: 'hello world'
    };
    
    const vm1 = new Vue({
     data() {
     return commonData;
     }
    })
    
    const vm2 = new Vue({
     data() {
     return commonData;
     }
    })
    
    vm1.mount(); // in get
    vm2.mount(); // in get
    commonData.text = 'hey' // 输出了两次 in watcher update /n in get 复制代码

希望通过这两个例子,你已经大概清楚了Dep的作用,有没有原来就那么回事的感觉?有就对了。总结一下吧(以下依赖收集器实为Dep):

  • vuedata初始化为一个Observer并对对象中的每个值,重写了其中的getsetdata中的每个key,都有一个独立的依赖收集器。
  • get中,向依赖收集器添加了监听
  • 在mount时,实例了一个Watcher,将收集器的目标指向了当前Watcher
  • data值发生变更时,触发set,触发了依赖收集器中的所有监听的更新,来触发Watcher.update

原文发布时间:2018-04-24

原文作者:JserWang

本文来源掘金如需转载请紧急联系作者

相关文章
|
2月前
|
消息中间件 存储 缓存
大厂面试高频:Kafka 工作原理 ( 详细图解 )
本文详细解析了 Kafka 的核心架构和实现原理,消息中间件是亿级互联网架构的基石,大厂面试高频,非常重要,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:Kafka 工作原理 ( 详细图解 )
|
19天前
|
存储 SQL 关系型数据库
MySQL进阶突击系列(03) MySQL架构原理solo九魂17环连问 | 给大厂面试官的一封信
本文介绍了MySQL架构原理、存储引擎和索引的相关知识点,涵盖查询和更新SQL的执行过程、MySQL各组件的作用、存储引擎的类型及特性、索引的建立和使用原则,以及二叉树、平衡二叉树和B树的区别。通过这些内容,帮助读者深入了解MySQL的工作机制,提高数据库管理和优化能力。
|
11天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
2月前
|
JavaScript 前端开发 开发者
Vue是如何劫持响应式对象的
Vue是如何劫持响应式对象的
35 1
|
2月前
|
JavaScript 前端开发 API
介绍一下Vue中的响应式原理
介绍一下Vue中的响应式原理
36 1
|
2月前
|
监控 JavaScript 算法
深度剖析 Vue.js 响应式原理:从数据劫持到视图更新的全流程详解
本文深入解析Vue.js的响应式机制,从数据劫持到视图更新的全过程,详细讲解了其实现原理和运作流程。
|
2月前
|
JavaScript
Vue 双向数据绑定原理
Vue的双向数据绑定通过其核心的响应式系统实现,主要由Observer、Compiler和Watcher三个部分组成。Observer负责观察数据对象的所有属性,将其转换为getter和setter;Compiler解析模板指令,初始化视图并订阅数据变化;Watcher作为连接Observer和Compiler的桥梁,当数据变化时触发相应的更新操作。这种机制确保了数据模型与视图之间的自动同步。
|
2月前
|
安全 算法 网络协议
网易面试:说说 HTTPS 原理?HTTPS 如何保证 数据安全?
45岁老架构师尼恩在其读者交流群中分享了关于HTTP与HTTPS的深入解析,特别针对近期面试中常问的HTTPS相关问题进行了详细解答。文章首先回顾了HTTP的工作原理,指出了HTTP明文传输带来的三大风险:窃听、篡改和冒充。随后介绍了HTTPS如何通过结合非对称加密和对称加密来解决这些问题,确保数据传输的安全性。尼恩还详细解释了HTTPS的握手过程,包括如何通过CA数字证书验证服务器身份,防止中间人攻击。最后,尼恩强调了掌握这些核心技术的重要性,并推荐了自己的技术资料,帮助读者更好地准备面试,提高技术水平。
|
2月前
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
67 0
|
6天前
|
JavaScript
vue使用iconfont图标
vue使用iconfont图标
48 1