# 1、谈一下你对MVVM原理的理解

MVC:用户操作视图会请求服务端路由路由会调对应的控制器来处理,控制器会获取数据,将结果返回给前端,页面重新渲染(是一个单向的过程)

MVVM:传统的前端会将Controller(控制器层返回的数据)手动渲染到页面上,MVVM模式不需要用户操作DOM元素,将数据绑定到viewModle层,会自动将数据渲染到页面中,视图View层变化会通知viewModle层更新数据。同理viewModle层变化也会通知Model层更新(是一个双向的过程)

# 2、请说一下响应式数据的原理?

# 理解:

  • 1、核心点:Object. defineProperty
  • 2、默认vue在初始化数据时,会给data中的属性使用Object. defineProperty重新定义所有属性,当页面取到对应属性时,会进行依赖收集(收集当前组件的watcher)如果属性发生变化会通知相关依赖进行更新操作。

# 拓展:为什么要使用Object. defineProperty?

  • 因为这个方法可以使数据的获取和设置都增加一个拦截的功能,在获取数据或更新数据时增加一些自己的逻辑,这样的逻辑称之为依赖收集;当数据变化时,我们可以告诉这些依赖(自定义的逻辑)去更新,收集的依赖叫做watcher
  • 例如:页面初次渲染时,会对数据进行取值,取值的时候会有一个渲染watcher,把其存起来,当数据更新的时候,告诉对应的数据watcher更新,这样就实现了数据的响应式原理。

# 原理:

响应式数据的原理

Object. defineProperty(obj, key, {
  enumerable: true, // 是否为可枚举属性
  configurable: true, // 属性是否可重新定义配置
  get: function reactiveGetter () { 

    const value = getter ? getter.call(obj) : val 
    if (Dep.target) { 
      dep.depend() // ** 收集依赖 ** / 
      if (childOb) { 
        childOb.dep.depend() 
        if (Array.isArray(value)) {
           dependArray(value) 
        } 
      } 
    }
    return value 

  }, 
  set: function reactiveSetter (newVal) { 

    const value = getter ? getter.call(obj) : val 
    if (newVal === value || (newVal !== newVal && value !== value)) { 
      return 
    }
    if (process.env.NODE_ENV !== 'production' && customSetter) { 
      customSetter() 
    }
    val = newVal
    childob = !shallow && observe(newVal)
    dep.notify() /**通知相关依赖进行更新**/

  }
})

# 3、Vue中是如何检测数组变化?

注:: 数据并没有使用Object. definedProperty重新定义

# 理解:

  • 核心点考察的是:数组考虑性能原因没有用defineProperty对数组的每一项进行拦截,而是选择重写数组(push, shift, pop, splice, unshift, sort, reverse)方法进行重写。
  • 核心点答出来了也可以在进行补充回答,在Vue中修改数组的索引长度是无法监控到的。需要通过以上7种变异方法修改数组才会触发数组对应的watcher进行更新。数组中如果是对象数据类型也会进行递归劫持。
  • 引发出的问题,那如果想更改索引更新数据怎么办?可以通过Vue. $set()来进行处理 =》 核心内部用的是splice方法

# 原理:

检测数组更新

vue数据劫持的缺点 ? / 什么样的数组不会被检测 ?

1、只有数组里的对象才能响应式的数据变化
2、不能通过索引值vm. arr[index]直接改变数组元素
3、[1, 2, 3]. length-- 因为数组的长度变化 没有检测

// 拿到数组原型上的方法(旧方法)
let oldArrayProtoMethods = Array. prototype; 
// 继承一下: arrayMethods. __proto__ = oldArrayProtoMethods
export let arrayMethods = Object. create(oldArrayProtoMethods)
const methods = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]

methods. forEach(method => {
  // 重写数组方法之后, 先走自己的函数逻辑
  arrayMethods[method] = function (... args) {  // todo this就是observer函数里的value
  // 再走原来函数的逻辑

    const result = oldArrayProtoMethods[method].apply(this, args);
    
    let ob = this.__ob__; // __ob__ 属性是当前Oberserve的实例,作用检测是否被观测过
    let inserted;

    switch (method) {
      case 'push': // arr.push({a:1}, {b:2})
      case 'unshift': // 这两个方法都是追加元素 追加的内容可能是对象类型,应该被再次劫持
        inserted = args;
        break;
      case 'splice': // vue.$set
        inserted = args.splice(2) // arr.splice(0, 1, {a:1}) 索引 删除的的个数, 新增的元素
      default:
        break;
    }

 

    if(inserted) {
      // 对数组内新增的元素 再次观测
      ob.observeArray(inserted);
    }
    return result;

  }
})

# 4、Vue的双向绑定的原理是什么?

vue. js是采用数据劫持结合发布者-订阅者模式的方式,通过ES5提供的Object. defineProperty()方法来劫持(监视)各个属性的setter, getter,在数据变动的时发布消息给订阅者,触发相应的监听回调。并且,由于是在不同的数据上触发同步,可以精确的将变更发送发送给绑定的视图,而不是对所有的数据都执行一次检测。

具体的步骤:

  1. 需要observer的数据对象进行递归遍历,包括子属性对象的属性,都加上getter和setter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

  2. compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

  3. Watcher订阅者是Observer和compile之间通信桥梁,主要做的事情是:
  • 在自身实例化时往属性订阅器(dep)里面添加自己
  • 自身必须有一个update()的方法
  • 待属性变动dep. notice()通知的时候,能调用自身的update()方法,并触发compile中绑定的回调,则功成身退

  1. MVVM作为数据绑定的入口,整合observer、compile和watcher三者,通过observer来监听自己的model数据变化,通过compile来编译模板指令,最终利用watcher搭起的observer和compile之间的通信桥梁,达到数据变化---试图更新; 视图交互变化(input)-->数据model变更的双向绑定效果

版本比较:

vue是基于依赖收集的双向绑定; 3. 0版本之前使用Object. definePropetry, 3. 0新版使用Proxy。

  1. 基于数据劫持/依赖收集 的双向绑定的优点
  • 不需要显示的调用,Vue利用数据劫持+发布订阅,可以直接通知变化并且驱动视图
  • 直接得到精确的变化数据,劫持了属性setter,当属性值改变,我们可以精确的获取变化的内容newValue,不需要额外的diff操作
  1. Object. defineProperty的缺点
  • 不能监听数组:因为数组没有getter和setter,因为数组长度不确定,如果太长性能负担太大
  • 只能监听属性,而不是整个对象,需要遍历循环属性
  • 只能监听属性变化,不能监听属性的删减
  1. proxy的好处
  • 可以监听数组
  • 监听整个对象不是属性
  • 13种拦截方法,强大很多
  • 返回新对象而不是直接修改原对象,更符合immutable;
  1. proxy的缺点
  • 兼容性不好,而且无法用polyfill磨平;

# 5、Vue中模板编译原理?

(参考答案)

  • 核心点考察的是:如何将template转换成render函数(这里要注意的是我们在开发时尽量不要使用template,因为将template转化成render方法需要在运行时进行编译操作会有性能损耗,同时引用带有compiler包的vue体积也会变大。默认. vue文件中的template处理是通过vue-loader来进行处理的并不是通过运行时的编译 - 后面我们会说到默认vue项目中引入的vue. js是不带有compiler模块的)。
      1. 将template模板转换成ast语法树 - parserHTML
      1. 对静态语法做静态标记 - markUp
      1. . 重新生成代码 - codeGen



  • 核心点答出来了也可以在进行补充回答 (模板引擎的实现原理就是new Function + with来进行实现的)

  • vue-loader中处理template属性主要靠的是vue-template-compiler模块
const VueTemplateCompiler = require('vue-template-compiler'); 
const {render} = VueTemplateCompiler. compile("<div id="hello">{{msg}}</div>"); 
console. log(render. toString())

# 6、生命周期钩子是如何实现的?

(参考答案)

  • 核心点考察的是: Vue的生命周期钩子就是回调函数而已,当创建组件实例的过程中会调用对应的钩子方法
  • 核心点答出来了也可以在进行补充回答:内部主要是使用callHook方法来调用对应的方法。核心是一个发布订阅模式,将钩子订阅好(内部采用数组的方式存储),在对应的阶段进行发布!