Vue深入学习3—数据响应式原理

1、数据响应式原理

1.1、MVVM是什么?
简单来说,就是数据变了,视图也会跟着变,首先你得定义一个带有{{ }}的模板Model,当数据中的值变化了,视图View就会跟着变化;视图模型View-model是模板Model和视图View之间的桥梁,Vue属于非侵入式,React和小程序就是侵入式(数据变化的时候需要调用提前写好的API)

// Vue数据变化,非侵入式
this.a ++
// React、小程序数据变化,侵入式
this.setState({
    a: this.state.a + 1
});

1.2、数据响应式的中心思想?
通过重写数据的get和set属性方法,让数据在被渲染时,把所有用到自己的订阅者,存放在自己的订阅者列表中;当数据发生改变时,该方法通知所有订阅了自己的订阅者,达到重新渲染的目的。

官方提供深入响应式原理图

是不是有点懵了?没关系,举个简单的栗子🌰:

《西游记》中的妖怪(Watcher)时刻惦记(订阅)着唐僧(Data),想吃唐僧肉,孙悟空(Component)在听到(get搜集依赖)唐僧被抓的消息后,做出反应(set触发依赖),准备救出师傅。于是来到了妖怪(Watcher)的老巢,跟它大战几个回合后,成功救出唐僧(Data),达到重新踏上(渲染)西天取经(Vittual DOM Tree)的目的!

2、尤大找到了一把”上帝的钥匙🔑“ Object.defineProperty()方法:

数据劫持、数据代理,MDN这样描述的:直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

Object.defineProperty(obj(定义那个对象),'a(定义这个对象的什么属性)',{
  //属性值定义为多少
    value : 3
})
//-----------------------------------🌰栗子🌰-------------------------------------------
var obj = {};
Object.defineProperty(obj, 'a', {
    get(){
        console.log('访问obj的a属性!');
        return 7;
    },
    set(){
        console.log('改变obj的a属性', newValue);
        temp = newValue;
    }
})
console.log(obj.a);  // 7
obj.a = 9;           //修改a的值
console.log(obj.a);  // 7

3、弥补这把钥匙🔑的不足 defineReactive函数:
为了解决defineProperty()方法存在的问题,get中并不能返回set刚刚修改过的值,再次调用会显示修改前的值,怎么解决这个问题?在外面定义一个全局变量,用来周转变量值。

// 解决defineProperty存在到的问题
defineProperty(data(数据对象),key(键名),val(值)){
    
}
//-----------------------------------🌰栗子🌰-------------------------------------------
var obj = {};
var temp;   //在外面定义一个全局变量,用来周转变量值。
function defineProperty(data, key, val) {
    Object.defineProperty(data, key, {
        // 可枚举
        enumerable: true,
        // 可被配置,比如被delete
        configurable: true,
        get(){
            console.log('访问obj的'+ key +'属性!');
            return temp;
        },
        set(){
            console.log('改变obj的'+ key +'属性!', newValue);
            if(val == newValue){
                return;
            }
            temp = newValue;
        }
    });
}
defineReactive(obj, 'a',10)
 
console.log(obj.a); // 访问obj的a属性! 10
obj.a = 69;         //修改a的值
obj.a ++;
console.log(obj.a); // 修改obj的a属性!70
// definReactive 实现
    // 简化后的版本 
    function defineReactive( target, key, value, enumerable ) {
      // 折中处理后, this 就是 Vue 实例
      let that = this;

      // 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )
      if ( typeof value === 'object' && value != null && !Array.isArray( value ) ) {
        // 是非数组的引用类型
        reactify( value ); // 递归
      }

      Object.defineProperty( target, key, {
        configurable: true,
        enumerable: !!enumerable,

        get () {
          console.log( `读取 ${key} 属性` ); // 额外
          return value;
        },
        set ( newVal ) {
          console.log( `设置 ${key} 属性为: ${newVal}` ); // 额外

          value = reactify( newVal );

        }
      } );
    }

4、数组的响应式处理
改写了7个属性,push(数组尾部推入)、pop(数组尾部移除)、shift(数组头部插入)、unshift(数组尾部移出)、splice(切割)、sort(就地排序)、reverse(排序位置颠倒);

// 得到Array.prototype
const arrayPrototype = Array.prototype
// 以Array.prototype为原型,创建arrayMethods对象,定义__proto方法
const arrayMethods = Object.create(arrayPrototype);
// 要被改写的7个数组方法
const methodsNeedChange = [
    'push','pop','shift','unshift','splice','sort','reverse'
];
//遍历
methdsNeedChange.forEach(methodName => {
    // 备份原来的方法
    const original = arrayPrototype[methodName];
    // 把数组身上的__obj__取出来,
    const ob = this.__obj__;
    // 有三种方法push/unshift/splice能够插入新项,把插入的新项变为observe
    let inserted = [];
    switch(methodName){
        case 'push':
        case 'unshift':
            inserted = arguments;
            break;
        case 'splice':
            // splice格式是splice(下标,数量,插入的新项) 
            inserted = arguments.slice(2);
            brack;
    }
    // 判断有没有要插入的新项,让新项也变为响应的
    if(inserted){
        ob.obsetveArray(inserted);
    }
    // 定义新的方法
    def(arrayMethods, methodName, function(){
        original.apply(this, arguments);
    },false);
});

面试题:数组中的响应式是怎么实现的?

答:以Array.prototype为原型,创建了一个arrayMethods的对象,用一个非常强硬的手段,Object.setPrototypeOf()让数组的_ proto _强制指向arrayMethods,这样就可以调用新的改写的7个方法。

5、什么是依赖?
需要用到数据的递归就是依赖,在getter中收集依赖,在setter中触发依赖;收集依赖的代码封装成Dep类,每个Observer的实例都有一个Dep的实例;Watcher是一个中介,数据发生变化时通过watcher中转,通知组件。

再拿《西游记》说,妖怪(Watcher)是怎么知道唐僧(Data)途径此地的呢?那自然是派出去巡山(depend方法)的小妖精(Dep-订阅器)发现(搜集)的;这个小妖精(Dep)巡山有三个目的(属性):目标(target)、id、subs(所有巡山的信息),当唐僧(Data)经过某个提前布置好的陷阱(生命周期的hook)时,就会被抓,压入巢穴(targetStack栈顶),交给妖怪(Watcher)。

6、什么时候能够把Wather放入到Dep当中?

Dep类:封装搜集的代码,管理依赖。
Wather类:①将属性值更新;②执行watch中的回调函数handler(newVal, oldVal)

先把wather设置到全局指定位置,然后读取数据;getter函数当中,会从全局唯一的地方,读取正在读取数据的wather,并把wather再搜集到Dep当中。

//wather.js
export default class Dep{
    constructor(){
        // 用数组存储自己的订阅者,subs是subscribes订阅者的意思。
        // 数组里面存放的是wather的实例。
        this.subs = [];
    }
    // 添加订阅
    addSub(sub){
        this.shbs.push(sub);
    }
    // 添加依赖
    dpend(){
        // 指定全局的位置
        if(Dep.target){
            // 如果Dep.target存下,则推入到subs里面
            this.addSub(Dep.target);
            // 
            
        }
    }
    // 通知更新
    notify(){
        // 浅克隆一份
        
    }
}

7、Vue中怎么识别 a.b.c 的?
利用高阶函数,逐层取出里面的值。

循环查找目标属性流程图
<script>
    // 深层套娃
    var o = {
        a: {
            b: {
                c: {
                    d: 68
                }
            }
        }
    }
    var str = 'a.b.c.d';
    function parsePath(str){
        // 根据 . 来进行拆分
        var segments = str.split('.');
        // 返回接收对象的函数
        return(obj) => {
            // 遍历接收的函数
            for(let i = 0; i < segments.length; i++){
                // 判断obj存不存在
                if(!obj) return;
                // 一层一层的剥开 o 的心
                obj = obj[segments[i]]
            }
            // 高阶函数,函数内部返回一个函数
            return obj;
        }
    }
    // 调用一下
    var fn = parsePath(str);
    var v = fn(o);
    console.log(v);
</script>

参考:https://www.infoq.cn/article/we3l33h5zgyyg6gc9hri

https://juejin.cn/post/6921911974611664903

https://www.bilibili.com/video/BV1G54y1s7xV?p=5&spm_id_from=pageDriver

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容