第一部分分享
1.vue2和vue3响应式数据的理解
响应式:就是数据改变,对应的视图也会改变
- vue2:是通过Object.defineProperty(),如果是多层就要递归
- vue3:采用proxy,如果是多层次数据,用户不使用,就不会递归
2.vue2中是如何检测数组变化的
- vue2中没有使用defineProperty对这个数组的每一项进行拦截,而是选择重写数组 {push}方法
- 数组中如果是对象的数据类型,也继续递归
- 数组的索引和长度变化是无法监控的
3.vue中如何进行依赖收集?
- 每一个属性都有一个dep,存放我们我们所依赖的watcher,当属性变化后通知自己对应的watcher去更新
- 默认在渲染的时候(获取这个响应式数据),此时就会触发属性收集依赖dep.depend()
- 当属性发生变化时触发watcher通过dep.notify()
- data => dep => ,在页面上使用 => watcher
4.nextTick在哪里使用?原理?
- 使用nextTick中的回调函数 在下一次DOM更新循环结束之后 执行回调
- 用于获取更新后的DOM
- vue中的数据更新是异步的,使用nextTick方法可以保证用户定义的逻辑在更新之后执行
实现
1)作用:将回调延迟到下次DOM更新循环之后执行
2)原因:VUE在更新DOM时是异步的,vue检测到数据变化后,不会立即更新DOM,而是会开启一个事件队列,并缓冲同一时间循环中的所有数据变更,在下一次tick中,执行更新DOM。
3)js的运行机制:js是单线程的,基于事件循环,有宏任务和微任务。
4)内部原理:
- 能力检测:Promise.then(微), MutationObserve(微),setImmediate(微),setTimeout(宏)
- 将回调函数推入回调队列,锁上易步锁,执行回调。
5.vue为什么需要虚拟DOM
- 如果直接操组真实DOM 性能低
- vnode就是一个js对象,可以理解为他是真实DOM的抽象
6.vue的diff算法原理
- vue2:深层递归+双指针 1)判断是不是同一元素,不是同一元素,直接替换
2)是同一元素 => 对比属性 => 对比儿子(
- old有,new没有;
- old没有,new有;
- 都是文本的情况;
- 都有children ) => 双指针: 头头,尾尾,头尾,尾头
- vue3:采用最长递增子序列
7.key的作用和原理
- vue中在patch过程中通过这个key可以判断两个vnode节点是否相同(可以复用老的节点)
- 没有key导致更新的时候出错
8.作用域和作用域链
- 作用域:就是一个规则,用来查找变量
- 作用域链:多层嵌套作用域
9.怎么实现防抖
防抖和节流
- 防抖:高频率触发的事件,在指定的单位时间内,只响应最后一次,如果在指定的时间在触发,则重新计算时间(后面触发的事件执行,替代了前面的事件)
- 节流:高频率触发的事件,在指定的单位时间内,只响应第一次(前面触发的执行前,忽略后面的事件)
区别: 节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而防抖只是在最后一次事件后才触发一次函数。
防抖
- 定时器
- 高阶函数 高阶函数:1. 函数的返回值是一个函数;2.它的参数是一个函数
防抖的🌰
function change(val){
console.log("click",val);
}
function delay(cb,time) {
let timer // 这个变量会保存在内存,只会创建一次,使用闭包哦
return function(){
clearTimeout(timer);
timer = setTimeout(() => {
cb(1);
},time)
}
}
// 防抖
btn.addEventListener('click',delay(change,1000))
10.关于闭包
概念
- 函数中返回一个函数
- 函数声明的作用域和函数使用的作用域不同
用途
- 1.获取私有作用域中的变量
- 2.这些变量可以保存到内存中 关于2 的🌰:
function a() {
let n = 0 // 保存到内存中
function add() {
n++;
return n
}
return add
}
let getN = a();
console.log(getN()) //1
console.log(getN()) //2
来道面试题🌰:
var fnArr = [];
for (var i = 0; i < 10; i++) {
fnArr[i] = function(){
return i
}
}
console.log(i) // 10
console.log(fnArr[3]()) // 10 也是10,i作用域全局
添加闭包保存🌰
var fnArr = [];
for (var i = 0; i < 10; i++) {
fnArr[i] = (function(){
// 闭包
let j = i; // 保存变量
return function(){
return j
}
})()
}
console.log(fnArr[3]()) // 3
第二部分分享
1.vue2 双向绑定
view和model相互实时更新原理:
Object.defineProperty数据劫持+发布者-订阅者(依赖收集)模式
observer,compile,watcher
- observe是一个数据监听器,核心方法是Object.defineProperty
- watcher是一个订阅者,将Observer发来的update消息处理,执行更新
- compile是一个指令解析器,对需要监听的节点和属性进行扫描和解析。
此模式的优点:不需要显式调用,可以直接通知变化,更新视图;劫持了属性setter,不需要额外的diff操作
Object.defineProperty缺点
- 不能监听数组
- 不能监听整个对象,只能监听属性
- 不能监听属性的增删,只能监听变化
3.0版本使用Proxy
- 可以监听数组
- 可直接监听整个对象,不用层层递归属性
- get和set时候直接有参数,不需要单独存储变量
- new Proxy()会返回一个新对象,不会污染源对象。
2.数据不更新问题
1)更新的原理:
在数据读取时收集依赖,在赋值时通知依赖更新。
2)object有defineProperty方法,通过getter,setter只监听了属性的读取和赋值,但是新增属性和删除属性没有检测,所以专门提供了$set和$delete来实现
3)array,没有defineProperty方法,没有setter,通过get和新建数组方法拦截器修改原生方法push,pop,shift,unshift,splice,sort,reserve来实现监听,而通过修改数组下标操作数组的不会被检测,所以专门提供了$set和$delete来实现
4)$set(target, key, value)和$delete(target, propertyName/index)方法原理
- 判断target是否是undefined,null,或者原始类型,或者vue实例,或者vue实例的跟数据对象
- target为数组,则还是通过调用splice操作索引更新数据
- target为对象,且为响应式,则调用defineReactive操作数据
- 更新完数据后通知依赖更新
3.computed和watch和methods
computed
- 设计初衷:为了使模板中的逻辑运算更简单
- 适用于数据被重复使用或者计算复杂费时的场景; 依赖其他数据的场景
- 读取缓存,依赖不变,则不需重新计算。(根据dirty标志判断)
watch是对数据的监听回调
computed和watch的区别
相同点:都会观察页面的数据变化
不同点:
- computed是读取缓存,watch每次都要重新执行;
- watch更适合数据变化时的异步操作和开销较大的操作。
computed和methods的区别
computed依赖缓存,可以定义getter和setter,但是methods不行
4.vue-router的模式区别
1)abstract:非浏览器环境下使用
2)hash:
- 默认。监听hashchange实现。
- 优点,兼容性好,ie8支持
- 缺点:看起来奇怪 3)history:
- h5新增的。允许开发者直接修改前端路由而不重新触发请求页面
- 实现原理:监听popstate事件。能监听到用户点击浏览器的前进后退事件或者手动调用go,back,forward事件;不能监听到pushState和replaceState事件。
- 为了避免浏览器刷新出现的404页面,需要在服务端配置兼容。
- 如果浏览器不支持,会降级到hash模式
通过vue.use插件机制和vue.mixin将store在beforeCreate和destroyed生命周期进行混入。
5.vuex
- vuex解决了vue项目中的数据状态管理问题
- 是组件通信的一种方式。
- 原理:创建了单一的状态树,包含state,mutation,action,getter,module。
- view(dispatch)action(commit)mutation(mutate)state(render)view
- 通过vue的data和computed,让state变成响应式,
- 通过vue.use插件机制和vue.mixin将store在beforeCreate生命周期进行混入。
6.keep-alive内置组件和LRU算法(队列)
1)自身不会渲染成DOM,没有常规的<template>标签,是个函数组件,被他包裹的组件,切换时会被缓存在内存中,而不是销毁。
- 可以有条件的缓存:include(匹配到的缓存),exclude(匹配到的不缓存),max(最多可以缓存多少组件实例)
2)原理:
内部维护了this.cache(缓存的组件对象)和this.keys(this.cache中的key),运用LRU策略。
- 命中了缓存的组件要调整组件key的顺序。
- 缓存的组件数量如果超过this.max时,要删除第一个缓存组件。
- LRU(Least recently used,最近最少使用):根据数据的历史访问记录来进行淘汰数据。“如果数据最近被访问过,那么将来被访问的几率也更高。”
3)生命周期钩子:
activated和deactivated,被keep-alive包括的组件激活和停用时调用。先停用组件的deactivated,再激活组件的activated