前端面试总结--Vue(原理)

理解Vue的MVVM

在这里插入图片描述

在这里插入图片描述

MVVM和MVC的区别

MVC(Model-View-Controller)

  • Model:真正的逻辑处理,操作DOM,例如jslib库中的form、popup、drag等功能组件都属于model模块
  • View:只管页面的显示和样式展示
  • Control:一些逻辑控制,进行页面节点事件的注册和控制,以及页面加载性能的实现

MVVM(Model-View-ViewModel)

  • Model: 所有的数据
  • View:视图
  • ViewModel: View和Model的桥梁

参考:前端web开发的MVC模式 - 从一个简单实例讲起

Vue双向绑定原理

实现mvvm的双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

数据监听器Observer,主要是给每个vue的属性用Object.defineProperty()

 function observe(obj, vm) {
     Object.keys(obj).forEach(function(key) {
         defineReactive(vm, key, obj[key]);
     })
 }
 function defineReactive (obj, key, val) {
    var dep = new Dep(); // 为每个属性创建一个订阅者集合
    Object.defineProperty(obj, key, { // 数据劫持
	    get: function() {
			dep.addSub(Dep.target); // 添加订阅者watcher到订阅者集合dep
	    },
	    set: function (newVal) {
			dep.notify();// 作为发布者发出通知,通知后dep会循环调用各自的update方法更新视图
	    }
   })
}

指令解析器Compile,解析各种指令成真正的html

function Compile(node, vm) {
}
Compile.prototype.compileElement = function(node, vm) {
    if(node.nodeType === 1) { // input元素
        var attr = node.attributes;
        // 解析属性
        for(var i = 0; i < attr.length; i++ ) {
            if(attr[i].nodeName == 'v-model') {//遍历属性节点找到v-model的属性
                var name = attr[i].nodeValue; // 获取v-model绑定的属性名
                //监听input事件
                node.addEventListener('input', function(e) {
                    // 给相应的data属性赋值,进而触发该属性的set方法,在set方法中发出通知
                    vm[name]= e.target.value;
                });
                // 创建新的watcher,会触发该属性的get方法,在get方法中向对应属性的订阅者集合dep添加订阅者watcher,
                new Watcher(vm, node, name, 'value');
            }
        };
    }
}

首先我们为每个vue属性用Object.defineProperty()实现数据劫持,为每个属性分配一个订阅者集合的管理数组dep;然后在编译的时候在该属性的数组dep中添加订阅者,v-model会添加一个订阅者,{{}}也会,v-bind也会,只要用到该属性的指令理论上都会,接着为input会添加监听事件,修改值就会为该属性赋值,触发该属性的set方法,在set方法内通知订阅者数组dep,订阅者数组循环调用各订阅者的update方法更新视图。

限制:无法监听属性的添加和删除、数组索引和长度的变更

参考网址:vue双向绑定原理分析vue依赖收集代码分析

vue怎么操作DOM

渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,有没有可能我们只更新我们修改的那一小块dom而不要更新整个dom呢?diff算法能够帮助我们。

我们先根据真实DOM生成一棵virtual DOM,当virtual DOM某个节点的数据改变后会生成一个新的Vnode,然后Vnode和oldVnode作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode的值为Vnode。

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

参考网址:https://www.cnblogs.com/wind-lanyan/p/9061684.html

虚拟DOM(virtual DOM)

在这里插入图片描述
参考网址:https://www.cnblogs.com/wind-lanyan/p/9061684.html

diff算法

在这里插入图片描述

updateChildren方法做了什么:


四个指针:oldStart,oldEnd,newStart,newEnd
每次对比都会按照以下步骤依次判断:

  1. oldStart和newStart指向同类节点:更新DOM,oldStart++,newStart++
  2. oldEnd和newEnd指向同类节点:更新DOM,oldEnd–,newEnd–
  3. oldStart和newEnd指向同类节点:oldStart移到oldEnd指向的节点后面,oldStart++,newEnd–
  4. oldEnd和newStart指向同类节点:oldEnd移到oldStart指向的节点前面,oldEnd–,newStart++
  5. 新增节点:在oldVNode中找不到newStart指向节点,说明该节点是新增的,直接插入到oldStart指向的节点前面
  6. 更新的节点:在oldVNode中能找到newStart指向节点,说明该节点的位置移动了,直接移到oldStart指向的节点前面
  7. 如果oldStart>oldEnd或者newStart>newEnd,说明对比完了
    (如果newStart和newEnd先相遇了,说明有需要删除的节点,直接删除oldStart和oldEnd之间的节点就可以了;如果oldStart和oldEnd先相遇了,说明有新增的节点,直接插入到对应位置即可)

参考网址:详解vue的diff算法深入 Vue2.x 的虚拟 DOM diff 原理VUE更新使用的PATCH算法(DIFF算法)图文详解 vue diff 核心方法 updateChildren

key的作用

在这里插入图片描述

key 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。
个人理解:
不设置key:updateChildren里每次判断的时候都认为他们是不用的节点,直接创建新的节点并插入,没有复用DOM
设置key:updateChildren判断的时候能找到相同的节点,只操作新增、修改、或删除的节点即可,实现了DOM的最大程度复用,减少了回流
因为插入节点以后,index会改变,会导致同一个节点前后index不一样而影响判断,所以将key设置为index是没用的
参考:关于Vue v-for中的:key作用

为什么data在组件中必须是一个函数

Vue每复用一次组件都返回一个新的对象,data是定义在组件的原型对象上的,如果data是对象的话,各个组件对象修改data会相互影响, 如果是用函数的话,每次返回的data都是新的,拥有独立的作用域

参考网址:https://blog.csdn.net/lareinalove/article/details/94019594

$nextTick

API:Vue.nextTick( [callback, context] )
用法:
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。(当数据更新了,在dom中渲染后,自动执行该函数)

// 传入回调函数
this.$nextTick(() => {
  this.msg2 = this.$refs.msgDiv.innerHTML
})
// 不传回调函数返回的是一个promise
this.nextTick()
  .then(function () {
    // DOM 更新了
  })

源码解析:
1.生成异步函数timerFunc,在函数中调用flushCallbacks
2.调用nextTick:将监听事件加入到callbacks数组中;判断是否pending中,是的话等待本轮操作完毕,不是的话将pending更新为true,然后执行异步函数timerFunc
3.flushCallbacks中执行callbacks数组中得回调函数

原理:
1.观察到数据变化
2.开启一个队列,把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。
3.在下一个事件循环中,Vue会清空队列,并进行必要的DOM更新

参考网址:https://juejin.im/post/6844904120625659911
https://segmentfault.com/a/1190000012861862
https://blog.csdn.net/zhouzuoluo/article/details/84752280

Vue 不能检测数组和对象的变化

其实说白了就是vue的监听机制都是通过defineproperty来实现的,但是不能检测对象新添加的属性,对象可以初始化的时候就设置好属性,不加新属性,数组也是对象,然而如果要求数组初始化的时候就设置好所有属性(索引),不能新增索性(改变长度),显然是不可能的,那么要数组也就没用了。

v-for与v-if不能同时使用

原因:
vue 处理指令时,v-for 比 v-if 具有更高的优先级 ,因此哪怕我们只渲染出一小部分用户的元素,也得在每次重新渲染的时候遍历整个列表,不论活跃用户是否发生了变化
解决办法:
将遍历的对象替换为一个计算属性

参考网址:https://blog.csdn.net/betterliumm/article/details/93472616

vue-router原理分析

参考链接:
https://www.cnblogs.com/yanze/p/7644631.html
https://www.cnblogs.com/hity-tt/p/7059192.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值