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);  // 7obj.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属性! 10obj.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.prototypeconst 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.jsexport 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>