vue 原理

  • Post author:
  • Post category:vue


(一)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实例



版权声明:本文为Luckyzhoufangbing原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。