说一说Vue双向绑定的原理与缺陷?| 面试题

1,051 阅读4分钟

能说说双向绑定以及它的实现原理吗?

题目分析:

双向绑定是vue的特色之一,开发中必然会用到的知识点,然而此题还问了实现原理,升级为深度考查。

思路分析:3w1h

  1. 给出双绑定义
  2. 双绑带来的好处
  3. 在哪使用双绑
  4. 使用方式
  5. 扩展:使用细节、原理实现描述

回答范例:

  1. vue中双向绑定是一个指令v-model,可以绑定一个动态值到视图,同时视图中变化能改变该值。v-model是语法糖,默认情况下相当于:value和@input。
  2. 使用v-model可以减少大量繁琐的事件处理代码,提高开发效率,代码可读性也更好
  3. 通常在表单项上使用v-model
  4. 原生的表单项可以直接使用v-model,自定义组件上如果要使用它需要在组件内绑定value并处理输入事件
  5. 我做过测试,输出包含v-model模板的组件渲染函数,发现它会被转换为value属性的绑定以及一个事件监听,事件回调函数中会做相应变量更新操作,这说明神奇魔法实际上是vue的编译器完成的。

可能的追问:

  1. v-model和sync修饰符有什么区别
  2. 自定义组件使用v-model如果想要改变事件名或者属性名应该怎么做

知其所以然:测试代码,test.html

观察输出的渲染函数:

// <input type="text" v-model="foo">
_c('input', { 
  directives: [{ name: "model", rawName: "v-model", value: (foo), expression: "foo" }], 
  attrs: { "type": "text" }, 
  domProps: { "value": (foo) }, 
  on: { 
    "input": function ($event) { 
      if ($event.target.composing) return; 
      foo = $event.target.value 
    } 
  } 
})
// <input type="checkbox" v-model="bar">
_c('input', { 
  directives: [{ name: "model", rawName: "v-model", value: (bar), expression: "bar" }], 
  attrs: { "type": "checkbox" }, 
  domProps: { 
    "checked": Array.isArray(bar) ? _i(bar, null) > -1 : (bar) 
  }, 
  on: { 
    "change": function ($event) { 
      var $$a = bar, $$el = $event.target, $$c = $$el.checked ? (true) : (false); 
      if (Array.isArray($$a)) { 
        var $$v = null, $$i = _i($$a, $$v); 
        if ($$el.checked) { $$i < 0 && (bar = $$a.concat([$$v])) } 
        else { 
          $$i > -1 && (bar = $$a.slice(0, $$i).concat($$a.slice($$i + 1))) } 
      } else { 
        bar = $$c 
      } 
    } 
  } 
})
// <select v-model="baz">
//     <option value="vue">vue</option>
//     <option value="react">react</option>
// </select>
_c('select', { 
  directives: [{ name: "model", rawName: "v-model", value: (baz), expression: "baz" }], 
  on: { 
    "change": function ($event) { 
      var $$selectedVal = Array.prototype.filter.call(
        $event.target.options, 
        function (o) { return o.selected }
      ).map(
        function (o) { 
          var val = "_value" in o ? o._value : o.value; 
          return val 
        }
      ); 
      baz = $event.target.multiple ? $$selectedVal : $$selectedVal[0] 
    } 
  } 
}, [
  _c('option', { attrs: { "value": "vue" } }, [_v("vue")]), _v(" "), 
  _c('option', { attrs: { "value": "react" } }, [_v("react")])
])

说一说Vue2.0 双向绑定的原理与缺陷?

要点: Object.defineProperty、getter、setter

答:

vue的双向绑定是采用数据劫持结合发布者订阅者模式的方式来实现响应式,通过object.defineProperty来劫持数据的getter,setter,在数据变化时发送消息给订阅者,订阅者收到消息后进行相应的处理。缺点是不能监听新对象的新增属性和删除属性,不能监听通过下标改变数组对应数据.

原理: Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object.defineProperty来劫持数据的setter,getter,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。通过原生js提供的监听数据的API,当数据发生变化的时候,在回调函数中修改dom

核心API: Object.defineProperty

Object.defineProperty API的作用: 用来定义对象属性

特点: 默认情况下定义的数据的属性不能修改 描述属性和存取属性不能同时使用,使用会报错

响应式原理:

  • 获取属性值会触发getter方法
  • 设置属性值会触发setter方法
  • 在setter方法中调用修改dom的方法

Object.defineProperty的缺点

  1. 一次性递归到底开销很大,如果数据很大,大量的递归导致调用栈溢出
  2. 不能监听对象的新增属性和删除属性
  3. 当监听的下标对应的数据发生改变时,无法正确的监听数组的方法

说一说Vue3.0 实现数据双向绑定的方法 ?

要点: Proxy、数据拦截、劫持整个对象、返回一个新对象、有13种劫持

答: Vue3.0 是通过Proxy实现的数据双向绑定,Proxy是ES6中新增的一个特性,实现的过程是在目标对象之前设置了一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

原生提供 Proxy 构造函数,用来生成 Proxy 实例。

var proxy = new Proxy(target, handler)

target: 是用Proxy包装的被代理对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

handler: 是一个对象,其声明了代理target 的一些操作,其属性是当执行一个操作时定义代理的行为的函数。


Object.defineProperty 的问题:在Vue中,Object.defineProperty无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。目前只针对以上方法做了hack处理,所以数组属性是检测不到的,有局限性Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历,显然如果能劫持一个完整的对象,不管是对操作性还是性能都会有一个很大的提升。 Proxy的两个优点:可以劫持整个对象,并返回一个新对象,有13种劫持。