vue watch props 不触发_Vue面试汇总

1、vue数据双向绑定原理以及订阅者模式

核心就是利用Object.defineProperty 方法

Object.defineProperty(obj, prop, descriptor) 

descript最核心是get 和 set当我们访问了该属性的时候会触发 getter 方法;当我们对该属性做修改的时候会触发 setter 方法。

一旦对象拥有了 getter 和 setter,我们可以简单地把这个对象称为响应式对象。

给数据添加了 getter 和 setter,目的就是为了getter 做的事情是依赖收集,setter 做的事情是派发更新


在vue里面给这个方法做了很多包装,

new Vue实例的时候,初始化initState方法,这个方法主要做了2件事情,

1:通过对定义的props,data函数遍历,通过proxy代理把每一个值代理到http://vm.xxx的形式。

2:调用observe方法。observe的功能就是用来监测数据的变化,给数据添加了 getter 和 setter,getter 做的事情是依赖收集,setter 做的事情是派发更新。并且如果对象子对象是对象或者数组,会递归调用observe,保证所有子属性变成响应式属性。

搜集依赖

每个对象值的 getter 都持有一个 dep,(Dep是对watch的管理)

在触发 getter 的时候会调用 dep.depend() 方法,通过dep.depend(),就是调用当前渲染watch的addDep(),最终push这个watch到这个数据持有的dep的subs,

当后续数据变化时候,通知订阅的watch做准备。

当收集完了依赖后,通过cleanupDeps 函数的,会首先遍历 deps,移除对 dep.subs 数组中 Wathcer 的订阅

做 deps 订阅的移除呢,在添加 deps 的订阅过程,已经能通过 id 去重避免重复订阅了。

考虑到一种场景,我们的模板会根据 v-if 去渲染不同子模板 a 和 b,当我们满足某种条件的时候渲染 a 的时候,会访问到 a 中的数据,这时候我们对 a 使用的数据添加了 getter,做了依赖收集,那么当我们去修改 a 的数据的时候,理应通知到这些订阅者。那么如果我们一旦改变了条件渲染了 b 模板,又会对 b 使用的数据添加了 getter,如果我们没有依赖移除的过程,那么这时候我去修改 a 模板的数据,会通知 a 数据的订阅的回调,这显然是有浪费的。

派发更新:

当我们在组件中对响应的数据做了修改,就会触发 setter 的逻辑,调用 dep.notify() 方法,遍历所有的 Watcher 的实例数组,然后调用每一个 watcher 的 update 方法,最后走到一个 queueWatcher(this) 的逻辑,

引入了一个队列的概念,每次数据改变并不会触发 watcher 的回调,而是把这些 watcher 先添加到一个队列里,对watch排序遍历,拿到对应的watch,在 nextTick 后执行执行 watcher.run(),执行数据更新

2、谈一谈对vue生命周期的理解

源码中最终执行生命周期的函数都是调用 callHook 方法, callhook 函数的功能就是调用某个生命周期钩子注册的所有回调函数。

beforeCreate & created

beforeCreate 和 created 的钩子调用是在 initState 的前后,

initState 的作用是初始化 props、data、methods、watch、computed 等属性

beforeCreate 的钩子函数中就不能获取到 props、data 中定义的值,也不能调用 methods 中定义的函数。

如果是需要访问 props、data 等数据的话,就需要使用 created 钩子函数。

在这俩个钩子函数执行的时候,并没有渲染 DOM,所以我们也不能

够访问 DOM

如果组件在加载的时候需要和后端有交互,放在这俩个钩子函数执行都可以。

beforeMount & mounted

beforeMount 钩子函数发生DOM 挂载之前,它的调用时机是在 mountComponent

vm._render() 函数渲染 VNode 之前,执行了 beforeMount 钩子函数,在执行完 vm._update() 把 VNode patch 到真实 DOM 后,执行 mounted 钩子。

在 mounted 钩子函数中可以访问到 DOM,

beforeUpdate & updated

beforeUpdate 和 updated 的钩子函数执行时机都应该是在数据更新的时候

beforeUpdate 的执行时机是在渲染 Watcher 的 before 函数中

  new Watcher(vm, updateComponent, noop, { 
    before () {     
  if (vm._isMounted) {       
  callHook(vm, 'beforeUpdate')       }     }   }, true /* isRenderWatcher */) 

注意这里有个判断,也就是在组件已经 mounted 之后,才会去调用这个钩子函数

只有满足当前 watcher 为 vm._watcher 以及组件已经 mounted 这两个条件,才会执行 updated 钩子函数。

beforeDestroy & destroyed

beforeDestroy 和 destroyed 钩子函数的执行时机在组件销毁的阶段,

destroy钩子函数中可以做一些定时器销毁工作

3、vue computed原理、computed和watch的区别;

computed和watch实际上都是watch的订阅者,但是computed 的watch是惰性的,不会立刻去求值,并持有一个dep实例,内部有一个dirty属性,来判断是否需要真正的去求值。只有依赖的属性值发生变化,才会真正的去求值,并且computed里面回调函数是空的

watch里面user为true,回调函数cp里面有值得,每当监听数据发生变化时候,就会调用回调函数去进行后续操作。起到监听的作用。

与 watch 有什么区别

computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。

watch 侦听器 : 更多的是「观察」的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。

与 watch 运用场景的对比

需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。

3、组件通信

1、在父子组件路面

父亲传给孩子,孩子通过propos接受父亲的数据

孩子emit传给父亲,父亲通过监听孩子定义的事件,接受孩子数据

2、eventbus,事件中心

主要通过自定义一个空的 new Vue()实例,作为中央事件总站台,利用emit,on 来触发和监听数据,来实现兄弟或者父亲,或者跨级通信

3、vuex

4c7c1d288dde15ed7c1da0235466f76a.png

vuex实现单向数据流,全局拥有一个state存放数据,利用mutation去改变数据状态,当有异步操作向ajax请求时候,会走action,action也不能直接修改state,需要通过mutation去改变数据。

如果有计算属性,mapGetters()来更新组件,或者mapState()的方式来更新组件

最后根据state的变化,改变视图。


4、MVM,MVVM

MVC即Model、View、Controller即模型、视图、控制器。

用户操作->View(负责接收用户的输入操作)->Controller(业务逻辑处理)->Model(数据持久化)->View(将结果反馈给View)

MVVM:Model(服务端) View(页面) ViewModel(框架,如vue.js),其中ViewModel和View双向绑定,view改变,viewModel的data相应变化,viewModel的data改变也会映射到view,

ViewModel和data改变,通过ajax与服务器通信,改变Model。

MVM和MVVM区别:

它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然

双向绑定的原理:

viewModel的data,view是页面:

ViewModel和View双向绑定,view改变,viewModel的data相应变化,viewModel的data改变也会映射到view

data改变view是通过Object.defineProper这个API,会监听data的变化。当给data属性属性值修改时,触发set函数,set函数会检查旧值和新值,不一样。会触发回调函数。这个回调函数写明data属性与view的关系。

view通过input事件改变data

7a9ce0d8124b138d77fb6af9dcbd250e.png

5、虚拟DOM

Virtual DOM 其实就是一棵以 JavaScript 对象( VNode 节点)作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上。

虚拟DOM在Vue.js主要做了两件事:

  • 提供与真实DOM节点所对应的虚拟节点vnode
  • 将虚拟节点vnode和旧虚拟节点oldVnode进行对比patch打补丁,然后更新视图

diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。

在采取diff算法比较新旧节点的时候,比较只会在同层级进行, 不会跨层级比较。

当数据发生改变时,set方法会让调用Dep.notify通知所有订阅者Watcher,订阅者就会调用patch给真实的DOM打补丁,更新相应的视图。

a8fbe64708c738ff54877ea95fcdf5a2.png

patch函数接收两个参数oldVnode和Vnode分别代表新的节点和之前的旧节点

function patch (oldVnode, vnode) {
    // some code
    if (sameVnode(oldVnode, vnode)) {
        patchVnode(oldVnode, vnode)
    } else {
        const oEl = oldVnode.el // 当前oldVnode对应的真实元素节点
        let parentEle = api.parentNode(oEl)  // 父元素
        createEle(vnode)  // 根据Vnode生成新元素
        if (parentEle !== null) {
            api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl)) // 将新元素添加进父元素
            api.removeChild(parentEle, oldVnode.el)  // 移除以前的旧元素节点
            oldVnode = null
        }
    }
    // some code 
    return vnode
}

判断两节点是否值得比较,不值得比较则用Vnode替换oldVnode,值得比较则执行patchVnode

patchVnode (oldVnode, vnode) {
    const el = vnode.el = oldVnode.el
    let i, oldCh = oldVnode.children, ch = vnode.children
    if (oldVnode === vnode) return
    if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
        api.setTextContent(el, vnode.text)
    }else {
        updateEle(el, vnode, oldVnode)
        if (oldCh && ch && oldCh !== ch) {
            updateChildren(el, oldCh, ch)
        }else if (ch){
            createEle(vnode) //create el's children dom
        }else if (oldCh){
            api.removeChildren(el)
        }
    }
}

这个函数做了以下事情:

  • 找到对应的真实dom,称为el
  • 判断Vnode和oldVnode是否指向同一个对象,如果是,那么直接return
  • 如果他们都有文本节点并且不相等,那么将el的文本节点设置为Vnode的文本节点。
  • 如果oldVnode有子节点而Vnode没有,则删除el的子节点
  • 如果oldVnode没有子节点而Vnode有,则将Vnode的子节点真实化之后添加到el
  • 如果两者都有子节点,则执行updateChildren函数比较子节点,这一步很重要

updateChildren(src/core/vdom/patch.js)方法,它也是整个diff过程中最重要的环节

给oldCh和newCh分别分配一个startIndex和endIndex来作为遍历的索引,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和vCh至少有一个已经遍历完了,就会结束比较

虚拟dom中,key的作用

如果没有key,每一个节点就没有唯一一个标志,假如在同一层级的某一堆节点中插入一个新节点,那么,那么他会就地复用,一个一个对比,然后新的替换旧的。如果有key,diff算法就可以通过对比找到正确的位置插入新节点,而key值相同的dom节点就不要去比较

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值