(一)vue 初始化对数据的处理
初始化用户传入data —-> 将数据进行观测 —–> 进行对象的处理 —–> 遍历对象 —–> 绑定监听
(1)将对象进行观测:会对数据进行判断,是【数组】还是【对象】
(观察者模式)
特点:
观察者
要直接订阅
观察目标
,
观察目标
一做出通知,
观察者
就要进行处理(这也是
观察者模式
区别于
发布/订阅模式
的最大区别)
(2)进行对象的处理:对象遍历 第一遍 (遍历Object.keys())
(3)循环对象定义响应式变化:如果对象的value 还是对象 再遍历
(4)使用Object.defineProperty()
get 方法:先收集依赖(data 对象中的数据)watcher
set 方法:当发现新值旧值不一样时候,更新依赖,通知视图
每个组件实例都有相应的
watcher
实例 – 渲染组件的过程,会把属性记录为依赖 – 当我们操纵一个数据时,依赖项的
setter
会被调用,从而通知
watcher
重新计算,从而致使与之相关联的组件得以更新
数组没用 Object.defineProperty() ,数组是更改原型的方法
(二)Vue 异步渲染
如果vue 不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染,所以为了性能考虑,vue 会在本轮数据更新后,再去异步更新视图
把每一次更改放入watcher 队列里,最后统一执行,清空 watcher 队列,渲染节流
nextTick实现原理:
nextTick 方法主要是使用了 宏任务和微任务,多次调用 nextTick 只会执行一次,主要的API,html 5 的 MutationObserver() 【chrome浏览器】
MutationObserver
接口提供了监视对DOM树所做更改的能力。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.qq {
color: green;
}
</style>
</head>
<body>
<div id="test">11</div>
<button>add children</button>
<button>change attributes</button>
<script>
var oTest = document.getElementById('test')
// 添加子节点按钮
var oButton = document.getElementsByTagName('button')[0]
// 更改属性按钮
var oButtonAttributes = document.getElementsByTagName('button')[1]
// 添加子节点
oButton.onclick = function() {
var newNode = document.createElement('div')
newNode.innerHTML = 111
oTest.appendChild(newNode)
}
// 更改属性
oButtonAttributes.onclick = function() {
oTest.setAttribute('class', 'qq')
}
</script>
<script src="./index.js"></script>
</body>
</html>
js 监听:
// 选择需要观察变动的节点
const targetNode = document.getElementById('test');
// 观察器的配置(需要观察什么变动)
// childList 观察目标节点的子节点的新增和删除。(仅仅子节点)
// attributes 观察目标节点的属性节点(新增或删除了某个属性,以及某个属性的属性值发生了变化)。
// subtree 观察目标节点的所有后代节点(观察目标节点所包含的整棵DOM树上的上述三种节点变化)
const config = {
attributes: true,
childList: true,
subtree: true
};
// 当观察到变动时执行的回调函数
const callback = function(mutationsList, observer) {
console.log('mutationsList', mutationsList)
for(let mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.');
}
else if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified.');
}
}
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
observer.observe(targetNode, config);
// 之后,可停止观察
// observer.disconnect();
(三)computed、watch
他们两个都是基于 Vue 的 watcher 方法,到底从不从缓存里面取,watcher 有一个标识位 dirty
如果dirty 是 true, 就不从缓存里面取,如果dirty是false,就从缓存里面取
如果计算属性,dirty 默认就是 true
dirty:true 依赖的变量有更新过 走缓存
dirty:false 依赖的变量没有更新
this.get() 就是从Object.defineProperty 中取
(四)watch: true 是如何使实现的?(深度监听)
当用户指定了 watch 为 deep: true 的时候,
if (this.deep) { // 如果需要深度监控
traverse(value) // 会对对象中的每一项取值,取值时会执行对应的get方法
}
deep的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,但是这样性能开销就会非常大了,任何修改obj里面任何一个属性都会触发这个监听器里的 handler。
当用户指定了
watch
中的deep属性为
true
时,如果当前监控的值是数组类型。会对对象中的每一项进行求值,此时会将当前
watcher
存入到对应属性的依赖中,这样数组中对象发生变化时也会通知数据更新
(五)diff算法是怎么降低复杂度的?
从 n 的三次方降低到 n:层级比较,不跨级比较,这样就是 O(n)
两个层级比较采用【双指针】的方式
比较策略:4种
(1)旧开头和新开头先比较是否相同,相同,两个指针从头开始遍历比较,如果不相同,策略2
(2)旧结尾和新结尾先比较,如果结尾和结尾相同,两个指针从结尾开始向前遍历比较,如果不相同,策略2
(3)旧开头和新结尾比较,看看是否开头的节点移动到了末尾,如果是,两个指针继续(向前)(向后)遍历比较,如果不是策略4
(3)旧结尾和新开头比较,看看是否结尾的节点移动到了开头,如果是,两个指针继续(向前)(向后)遍历比较,如果不是策略5
(4)最后如果四种都不符合,根据key开始定位更新
(六)v-for的key
使用v-for更新已渲染的元素列表时,默认用就地
复用策略
;列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素;
key一样、标签一样就会复用
(七)data 为什么是一个函数?
1. 组件与组件之间变量互不影响
2. 如果一个组件被引入多次,如果是对象的话,那么所有组件都共享了这一个对象,为了保证每个组件的独立性,所以定义为函数
3. 同时每次调用 data,都会重新调用函数,保证函数返回的值是最新的
为什么new Vue({}) 是一个对象,因为new Vue 只能 new 一次
(八)vue中的事件绑定原理
vue中的事件绑定分为两种:
(1)组件的事件绑定:采用$on方法(内部封装的方法、自定义的方法)必须有$emit才可以触发。
(2)DOM 元素的事件绑定:采用的是 addEventListener 方法
// 元素的绑定
<div class="btn" @click="handler(item)">立即领取</div>
// 组件事件的绑定 父组件给子组件传值
<detergent @change="changeCleanAgent" />
// 原生事件的绑定 点击父组件触发事件 必须加.native
<detergent @click.native="changeCleanAgent" />
(九)vue中的 v-html 会导致哪些问题?
(1)xss跨站脚本攻击
(2)替换掉标签内部的子元素
(十)组件如何通信(6种)
(十一)v-model 原理
其核心就是,
一方面modal层通过defineProperty来劫持每个属性,一旦监听到变化通过相关的页面元素更新。
另一方面通过编译模板文件,为控件的v-model绑定input事件,从而页面输入能实时更新相关data属性值。
vue 进行模版编译的时候会根据不同的标签绑定不同的监听函数
一些修饰符的源码
lazy 的时候涉及两个方法:oninput、onchange
oninput事件是在输入框中输入时就会触发
onchange事件是在输入框输入完内容后,输入框失焦后触发
onchange事件兼容性好,主流浏览器都支持
(十二)插槽
(1)普通插槽:
不包括数据的交互,仅仅是占位置用的(具名插槽、匿名插槽)
普通插槽的渲染作用域是在父组件,等到子组件拿到该插槽的时候,已经是渲染完的vnode了,所以是什么就展示什么。
(2)作用域插槽:子组件给父组件传值的插槽(scope-slot / v-slot)
作用域插槽和普通插槽的区别在于,子组件拿到它的时候它还是一个函数,只有你执行该函数,它才会返回要渲染的内容(即vnode),所以就给了你在子组件中传递参数(子组件数据)过去的机会,同时由于该函数也能访问父组件作用域的数据
两者的不同:渲染的作用域不同,普通插槽是在父组件,作用域插槽是在子组件
(十三)Vue3.0有哪些改进
(1)vue3.0 采用了ts来编写
(2)Vue3 中的响应式数据原理改成了Proxy
(3)支持 Composition API
(4)vdom 的对比算法更新只更新vdom绑定了动态数据的部分
(十四)Vuex原理
每一个组件都会有一个render函数
创建一个组件都会 Vue.extend 创建vue实例