函数式组件劣势和原理
函数组件的特点
- 函数式组件须要在申明组件是指定
functional:true
- 不须要实例化,所以没有
this
,this
通过render
函数的第二个参数context
来代替 - 没有生命周期钩子函数,不能应用计算属性,
watch
- 不能通过
$emit
对外裸露事件,调用事件只能通过context.listeners.click
的形式调用内部传入的事件 - 因为函数式组件是没有实例化的,所以在内部通过
ref
去援用组件时,理论援用的是HTMLElement
- 函数式组件的
props
能够不必显示申明,所以没有在props
外面申明的属性都会被主动隐式解析为prop
, 而一般组件所有未声明的属性都解析到$attrs
外面,并主动挂载到组件根元素下面 (能够通过inheritAttrs
属性禁止)
长处
- 因为函数式组件不须要实例化,无状态,没有生命周期,所以渲染性能要好于一般组件
- 函数式组件构造比较简单,代码构造更清晰
应用场景:
- 一个简略的展现组件,作为容器组件应用 比方
router-view
就是一个函数式组件 - “高阶组件”——用于接管一个组件作为参数,返回一个被包装过的组件
例子
Vue.component('functional',{ // 构造函数产生虚构节点的
functional:true, // 函数式组件 // data={attrs:{}}
render(h){return h('div','test')
}
})
const vm = new Vue({el: '#app'})
源码相干
// functional component
if (isTrue(Ctor.options.functional)) { // 带有 functional 的属性的就是函数式组件
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on // 处理事件
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn // 解决原生事件
// install component management hooks onto the placeholder node
installComponentHooks(data) // 装置组件相干钩子(函数式组件没有调用此办法,从而性能高于一般组件)
双向数据绑定的原理
Vue.js 是采纳 数据劫持 联合 发布者 - 订阅者模式 的形式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时公布音讯给订阅者,触发相应的监听回调。次要分为以下几个步骤:
- 须要 observe 的数据对象进行递归遍历,包含子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变动
- compile 解析模板指令,将模板中的变量替换成数据,而后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,增加监听数据的订阅者,一旦数据有变动,收到告诉,更新视图
- Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,次要做的事件是: ①在本身实例化时往属性订阅器 (dep) 外面增加本人 ②本身必须有一个 update()办法 ③待属性变动 dep.notice()告诉时,能调用本身的 update()办法,并触发 Compile 中绑定的回调,则功成身退。
- MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听本人的 model 数据变动,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变动 -> 视图更新;视图交互变动(input) -> 数据 model 变更的双向绑定成果。
Vue 组件如何通信?
Vue 组件通信的办法如下:
props/$emit+v-on
: 通过 props 将数据自上而下传递,而通过 $emit 和 v -on 来向上传递信息。- EventBus: 通过 EventBus 进行信息的公布与订阅
- vuex: 是全局数据管理库,能够通过 vuex 治理全局的数据流
$attrs/$listeners
: Vue2.4 中退出的$attrs/$listeners
能够进行跨级的组件通信- provide/inject:以容许一个先人组件向其所有子孙后代注入一个依赖,不管组件档次有多深,并在起上下游关系成立的工夫里始终失效,这成为了跨组件通信的根底
还有一些用 solt 插槽或者 ref 实例进行通信的,应用场景过于无限就不赘述了。
Vue.extend 作用和原理
官网解释:Vue.extend 应用根底 Vue 结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。
其实就是一个子类结构器 是 Vue 组件的外围 api 实现思路就是应用原型继承的办法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并
虚构 DOM 实现原理?
- 虚构 DOM 实质上是 JavaScript 对象, 是对实在 DOM 的形象
- 状态变更时,记录新树和旧树的差别
- 最初把差别更新到真正的 dom 中
说说 Vue 的生命周期吧
什么时候被调用?
- beforeCreate:实例初始化之后,数据观测之前调用
- created:实例创立万之后调用。实例实现:数据观测、属性和办法的运算、
watch/event
事件回调。无$el
. - beforeMount:在挂载之前调用,相干
render
函数首次被调用 - mounted:了被新创建的
vm.$el
替换,并挂载到实例下来之后调用改钩子。 - beforeUpdate:数据更新前调用,产生在虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
- updated:因为数据更改导致的虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
- beforeDestroy:实例销毁前调用,实例依然可用。
- destroyed:实例销毁之后调用,调用后,Vue 实例批示的所有货色都会解绑,所有事件监听器和所有子实例都会被移除
每个生命周期外部能够做什么?
- created:实例曾经创立实现,因为他是最早触发的,所以能够进行一些数据、资源的申请。
- mounted:实例曾经挂载实现,能够进行一些 DOM 操作。
- beforeUpdate:能够在这个钩子中进一步的更改状态,不会触发重渲染。
- updated:能够执行依赖于 DOM 的操作,然而要防止更改状态,可能会导致更新无线循环。
- destroyed:能够执行一些优化操作,清空计时器,解除绑定事件。
ajax 放在哪个生命周期?:个别放在 mounted
中,保障逻辑统一性,因为生命周期是同步执行的, ajax
是异步执行的。复数服务端渲染 ssr
同一放在 created
中,因为服务端渲染不反对 mounted
办法。 什么时候应用 beforeDestroy?:以后页面应用 $on
,须要解绑事件。分明定时器。解除事件绑定, scroll mousemove
。
参考 前端进阶面试题具体解答
$nextTick 是什么?
Vue 实现响应式并不是在数据产生后立刻更新 DOM,应用 vm.$nextTick
是在下次 DOM 更新循环完结之后立刻执行提早回调。在批改数据之后应用,则 能够在回调中获取更新后的 DOM。
diff 算法
<details open=””><summary>答案 </summary>
<p>
</p><p> 工夫复杂度: 个树的齐全 diff
算法是一个工夫复杂度为 O(n*3)
,vue 进行优化转化成 O(n)
。</p>
<p> 了解:</p>
<ul>
<li>
<p> 最小量更新, key
很重要。这个能够是这个节点的惟一标识,通知 diff
算法,在更改前后它们是同一个 DOM 节点 </p>
<ul>
<li> 扩大 v-for
为什么要有 key
,没有 key
会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改 DOM),加 key
只会挪动缩小操作 DOM。</li>
</ul>
</li>
<li>
<p> 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。</p>
</li>
<li>
<p> 只进行同层比拟,不会进行跨层比拟。</p>
</li>
</ul>
<p>diff 算法的优化策略:四种命中查找,四个指针 </p>
<ol>
<li>
<p> 旧前与新前(先比结尾,后插入和删除节点的这种状况)</p>
</li>
<li>
<p> 旧后与新后(比结尾,前插入或删除的状况)</p>
</li>
<li>
<p> 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)</p>
</li>
<li>
<p> 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)</p>
</li>
</ol>
<p></p>
</details>
— 问完下面这些如果都能很分明的话,根本 O 了 —
以下的这些简略的概念,你必定也是没有问题的啦😉
vue 中应用了哪些设计模式
1. 工厂模式 – 传入参数即可创立实例
虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode
2. 单例模式 – 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉
3. 公布 - 订阅模式 (vue 事件机制)
4. 观察者模式 (响应式数据原理)
5. 装璜模式: (@装璜器的用法)
6. 策略模式 策略模式指对象有某个行为, 然而在不同的场景中, 该行为有不同的实现计划 - 比方选项的合并策略
… 其余模式欢送补充
keep-alive 应用场景和原理
keep-alive 是 Vue 内置的一个组件,能够实现组件缓存,当组件切换时不会对以后组件进行卸载。
- 罕用的两个属性 include/exclude,容许组件有条件的进行缓存。
- 两个生命周期 activated/deactivated,用来得悉以后组件是否处于沉闷状态。
- keep-alive 的中还使用了 LRU(最近起码应用) 算法,抉择最近最久未应用的组件予以淘汰。
你有对 Vue 我的项目进行哪些优化?
(1)代码层面的优化
- v-if 和 v-show 辨别应用场景
- computed 和 watch 辨别应用场景
- v-for 遍历必须为 item 增加 key,且防止同时应用 v-if
- 长列表性能优化
- 事件的销毁
- 图片资源懒加载
- 路由懒加载
- 第三方插件的按需引入
- 优化有限列表性能
- 服务端渲染 SSR or 预渲染
(2)Webpack 层面的优化
- Webpack 对图片进行压缩
- 缩小 ES6 转为 ES5 的冗余代码
- 提取公共代码
- 模板预编译
- 提取组件的 CSS
- 优化 SourceMap
- 构建后果输入剖析
- Vue 我的项目的编译优化
(3)根底的 Web 技术的优化
- 开启 gzip 压缩
- 浏览器缓存
- CDN 的应用
- 应用 Chrome Performance 查找性能瓶颈
理解 nextTick 吗?
异步办法,异步渲染最初一步,与 JS 事件循环分割严密。次要应用了宏工作微工作(setTimeout
、promise
那些),定义了一个异步办法,屡次调用 nextTick
会将办法存入队列,通过异步办法清空以后队列。
谈谈 Vue 和 React 组件化的思维
- 1. 咱们在各个页面开发的时候,会产生很多反复的性能,比方 element 中的 xxxx。像这种纯正非页面的 UI,便成为咱们罕用的 UI 组件,最后的前端组件也就仅仅指的是 UI 组件
- 2. 随着业务逻辑变得越来多是,咱们就想要咱们的组件能够解决很多事,这就是咱们常说的组件化,这个组件就不是 UI 组件了,而是包具体业务的业务组件
- 3. 这种开发思维就是分而治之。最大水平的升高开发难度和保护老本的成果。并且能够多人合作,每个人写不同的组件,最初像撘积木一样的把它形成一个页面
Vue3.0 和 2.0 的响应式原理区别
Vue3.x 改用 Proxy 代替 Object.defineProperty。因为 Proxy 能够间接监听对象和数组的变动,并且有多达 13 种拦挡办法。
相干代码如下
import {mutableHandlers} from "./baseHandlers"; // 代理相干逻辑
import {isObject} from "./util"; // 工具办法
export function reactive(target) {
// 依据不同参数创立不同响应式对象
return createReactiveObject(target, mutableHandlers);
}
function createReactiveObject(target, baseHandler) {if (!isObject(target)) {return target;}
const observed = new Proxy(target, baseHandler);
return observed;
}
const get = createGetter();
const set = createSetter();
function createGetter() {return function get(target, key, receiver) {
// 对获取的值进行喷射
const res = Reflect.get(target, key, receiver);
console.log("属性获取", key);
if (isObject(res)) {
// 如果获取的值是对象类型,则返回以后对象的代理对象
return reactive(res);
}
return res;
};
}
function createSetter() {return function set(target, key, value, receiver) {const oldValue = target[key];
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {console.log("属性新增", key, value);
} else if (hasChanged(value, oldValue)) {console.log("属性值被批改", key, value);
}
return result;
};
}
export const mutableHandlers = {
get, // 当获取属性时调用此办法
set, // 当批改属性时调用此办法
};
v-model 的原理?
咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:
- text 和 textarea 元素应用 value 属性和 input 事件;
- checkbox 和 radio 应用 checked 属性和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
以 input 表单元素为例:
<input v-model='something'>
相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:
父组件:<ModelChild v-model="message"></ModelChild>
子组件:<div>{{value}}</div>
props:{value: String},
methods: {test1(){this.$emit('input', '小红')
},
},
Vue 的生命周期办法有哪些
Vue
实例有一个残缺的生命周期,也就是从开始创立
、初始化数据
、编译模版
、挂载 Dom -> 渲染
、更新 -> 渲染
、卸载
等一系列过程,咱们称这是Vue
的生命周期Vue
生命周期总共分为 8 个阶段创立前 / 后
,载入前 / 后
,更新前 / 后
,销毁前 / 后
beforeCreate
=>created
=>beforeMount
=>Mounted
=>beforeUpdate
=>updated
=>beforeDestroy
=>destroyed
。keep-alive
下:activated
deactivated
生命周期 vue2 | 生命周期 vue3 | 形容 |
---|---|---|
beforeCreate |
beforeCreate |
在实例初始化之后,数据观测(data observer ) 之前被调用。 |
created |
created |
实例曾经创立实现之后被调用。在这一步,实例已实现以下的配置:数据观测(data observer ),属性和办法的运算,watch/event 事件回调。这里没有$el |
beforeMount |
beforeMount |
在挂载开始之前被调用:相干的 render 函数首次被调用 |
mounted |
mounted |
el 被新创建的 vm.$el 替换,并挂载到实例下来之后调用该钩子 |
beforeUpdate |
beforeUpdate |
组件数据更新之前调用,产生在虚构 DOM 打补丁之前 |
updated |
updated |
因为数据更改导致的虚构 DOM 从新渲染和打补丁,在这之后会调用该钩子 |
beforeDestroy |
beforeUnmount |
实例销毁之前调用。在这一步,实例依然齐全可用 |
destroyed |
unmounted |
实例销毁后调用。调用后,Vue 实例批示的所有货色都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。 |
其余几个生命周期
生命周期 vue2 | 生命周期 vue3 | 形容 |
---|---|---|
activated |
activated |
keep-alive 专属,组件被激活时调用 |
deactivated |
deactivated |
keep-alive 专属,组件被销毁时调用 |
errorCaptured |
errorCaptured |
捕捉一个来自子孙组件的谬误时被调用 |
– | renderTracked |
调试钩子,响应式依赖被收集时调用 |
– | renderTriggered |
调试钩子,响应式依赖被触发时调用 |
– | serverPrefetch |
ssr only ,组件实例在服务器上被渲染前调用 |
- 要把握每个生命周期外部能够做什么事
beforeCreate
初始化vue
实例,进行数据观测。执行时组件实例还未创立,通常用于插件开发中执行一些初始化工作created
组件初始化结束,能够拜访各种数据,获取接口数据等beforeMount
此阶段vm.el
虽已实现DOM
初始化,但并未挂载在el
选项上mounted
实例曾经挂载实现,能够进行一些DOM
操作beforeUpdate
更新前,可用于获取更新前各种状态。此时view
层还未更新,可用于获取更新前各种状态。能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。updated
实现view
层的更新,更新后,所有状态已是最新。能够执行依赖于DOM
的操作。然而在大多数状况下,你应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。destroyed
能够执行一些优化操作, 清空定时器,解除绑定事件- vue3
beforeunmount
:实例被销毁前调用,可用于一些定时器或订阅的勾销 - vue3
unmounted
:销毁一个实例。可清理它与其它实例的连贯,解绑它的全副指令及事件监听器
<div id="app">{{name}}</div>
<script>
const vm = new Vue({data(){return {name:'poetries'}
},
el: '#app',
beforeCreate(){// 数据观测(data observer) 和 event/watcher 事件配置之前被调用。console.log('beforeCreate');
},
created(){
// 属性和办法的运算,watch/event 事件回调。这里没有 $el
console.log('created')
},
beforeMount(){
// 相干的 render 函数首次被调用。console.log('beforeMount')
},
mounted(){
// 被新创建的 vm.$el 替换
console.log('mounted')
},
beforeUpdate(){
// 数据更新时调用,产生在虚构 DOM 从新渲染和打补丁之前。console.log('beforeUpdate')
},
updated(){
// 因为数据更改导致的虚构 DOM 从新渲染和打补丁,在这之后会调用该钩子。console.log('updated')
},
beforeDestroy(){
// 实例销毁之前调用 实例依然齐全可用
console.log('beforeDestroy')
},
destroyed(){
// 所有货色都会解绑定,所有的事件监听器会被移除
console.log('destroyed')
}
});
setTimeout(() => {
vm.name = 'poetry';
setTimeout(() => {vm.$destroy()
}, 1000);
}, 1000);
</script>
- 组合式 API 生命周期钩子
你能够通过在生命周期钩子后面加上“on
”来拜访组件的生命周期钩子。
下表蕴含如何在 setup()
外部调用生命周期钩子:
选项式 API | Hook inside setup |
---|---|
beforeCreate |
不须要 * |
created |
不须要 * |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeUnmount |
onBeforeUnmount |
unmounted |
onUnmounted |
errorCaptured |
onErrorCaptured |
renderTracked |
onRenderTracked |
renderTriggered |
onRenderTriggered |
因为
setup
是围绕beforeCreate
和created
生命周期钩子运行的,所以不须要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该间接在setup
函数中编写
export default {setup() {
// mounted
onMounted(() => {console.log('Component is mounted!')
})
}
}
setup
和 created
谁先执行?
beforeCreate
: 组件被创立进去,组件的methods
和data
还没初始化好setup
:在beforeCreate
和created
之间执行created
: 组件被创立进去,组件的methods
和data
曾经初始化好了
因为在执行
setup
的时候,created
还没有创立好,所以在setup
函数内咱们是无奈应用data
和methods
的。所以vue
为了让咱们防止谬误的应用,间接将setup
函数内的this
执行指向undefined
import {ref} from "vue"
export default {// setup 函数是组合 api 的入口函数,留神在组合 api 中定义的变量或者办法,要在 template 响应式须要 return{}进来
setup(){let count = ref(1)
function myFn(){count.value +=1}
return {count,myFn}
},
}
- 其余问题
- 什么是 vue 生命周期? Vue 实例从创立到销毁的过程,就是生命周期。从开始创立、初始化数据、编译模板、挂载 Dom→渲染、更新→渲染、销毁等一系列过程,称之为
Vue
的生命周期。
- vue 生命周期的作用是什么? 它的生命周期中有多个事件钩子,让咱们在管制整个 Vue 实例的过程时更容易造成好的逻辑。
- vue 生命周期总共有几个阶段? 它能够总共分为
8
个阶段:创立前 / 后、载入前 / 后、更新前 / 后、销毁前 / 销毁后。 - 第一次页面加载会触发哪几个钩子? 会触发上面这几个
beforeCreate
、created
、beforeMount
、mounted
。 - 你的接口申请个别放在哪个生命周期中? 接口申请个别放在
mounted
中,但须要留神的是服务端渲染时不反对mounted
,须要放到created
中 -
DOM 渲染在哪个周期中就曾经实现? 在
mounted
中,- 留神
mounted
不会承诺所有的子组件也都一起被挂载。如果你心愿等到整个视图都渲染结束,能够用vm.$nextTick
替换掉mounted
mounted: function () {this.$nextTick(function () { // Code that will run only after the // entire view has been rendered }) }
- 留神
### Vue 模版编译原理
vue 中的模板 template 无奈被浏览器解析并渲染,因为这不属于浏览器的规范,不是正确的 HTML 语法,所有须要将 template 转化成一个 JavaScript 函数,这样浏览器就能够执行这一个函数并渲染出对应的 HTML 元素,就能够让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。- ** 解析阶段 **:应用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为形象语法树 AST。- ** 优化阶段 **:遍历 AST,找到其中的一些动态节点并进行标记,不便在页面重渲染的时候进行 diff 比拟时,间接跳过这一些动态节点,优化 runtime 的性能。- ** 生成阶段 **:将最终的 AST 转化为 render 函数字符串。### Vue 中给 data 中的对象属性增加一个新的属性时会产生什么?如何解决?
<template>
<div>
<ul>
<li v-for="value in obj" :key="value"> {{value}} </li> </ul> <button @click="addObjB"> 增加 obj.b</button> </div>
</template>
<script>
export default {data () {return { obj: { a: 'obj.a'} } }, methods: {addObjB () {this.obj.b = 'obj.b' console.log(this.obj) } } }
</script>
点击 button 会发现,obj.b 曾经胜利增加,然而视图并未刷新。这是因为在 Vue 实例创立时,obj.b 并未申明,因而就没有被 Vue 转换为响应式的属性,天然就不会触发视图的更新,这时就须要应用 Vue 的全局 api **$set():**
addObjB () (
this.$set(this.obj, ‘b’, ‘obj.b’)
console.log(this.obj)
}
$set()办法相当于手动的去把 obj.b 解决成一个响应式的属性,此时视图也会跟着扭转了。### Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题?受古代 JavaScript 的限度,Vue ** 无奈检测到对象属性的增加或删除 **。因为 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在能力让 Vue 将它转换为响应式的。然而 Vue 提供了 `Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)` 来实现为对象增加响应式属性,那框架自身是如何实现的呢?咱们查看对应的 Vue 源码:`vue/src/core/instance/index.js`
export function set (target: Array<any> | Object, key: any, val: any): any {
// target 为数组
if (Array.isArray(target) && isValidArrayIndex(key)) {
// 批改数组的长度, 防止索引 > 数组长度导致 splcie()执行有误
target.length = Math.max(target.length, key)
// 利用数组的 splice 变异办法触发响应式
target.splice(key, 1, val)
return val
}
// key 曾经存在,间接批改属性值
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
// target 自身就不是响应式数据, 间接赋值
if (!ob) {
target[key] = val
return val
}
// 对属性进行响应式解决
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
咱们浏览以上源码可知,vm.$set 的实现原理是:- 如果指标是数组,间接应用数组的 splice 办法触发相应式;- 如果指标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决(defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法)## Vue 中如何进行依赖收集?* 每个属性都有本人的 `dep` 属性,寄存他所依赖的 `watcher`,当属性变动之后会告诉本人对应的 `watcher` 去更新
* 默认会在初始化时调用 `render` 函数,此时会触发属性依赖收集 `dep.depend`
* 当属性产生批改时会触发 `watcher` 更新 `dep.notify()`
![](https://img-blog.csdnimg.cn/img_convert/2585adec162987c743fbaa9085084f65.png)
** 依赖收集简版 **
let obj = {name: ‘poetry’, age: 20};
class Dep {
constructor() {this.subs = [] // subs [watcher]
}
depend() {this.subs.push(Dep.target)
}
notify() {this.subs.forEach(watcher => watcher.update())
}
}
Dep.target = null;
observer(obj); // 响应式属性劫持
// 依赖收集 所有属性都会减少一个 dep 属性,
// 当渲染的时候取值了,这个 dep 属性 就会将渲染的 watcher 收集起来
// 数据更新 会让 watcher 从新执行
// 观察者模式
// 渲染组件时 会创立 watcher
class Watcher {
constructor(render) {this.get();
}
get() {
Dep.target = this;
render(); // 执行 render
Dep.target = null;
}
update() {this.get();
}
}
const render = () => {
console.log(obj.name); // obj.name => get 办法
}
// 组件是 watcher、计算属性是 watcher
new Watcher(render);
function observer(value) {// proxy reflect
if (typeof value === 'object' && typeof value !== null)
for (let key in value) {defineReactive(value, key, value[key]);
}
}
function defineReactive(obj, key, value) {
// 创立一个 dep
let dep = new Dep();
// 递归察看子属性
observer(value);
Object.defineProperty(obj, key, {get() { // 收集对应的 key 在哪个办法(组件)中被应用
if (Dep.target) { // watcher
dep.depend(); // 这里会建设 dep 和 watcher 的关系}
return value;
},
set(newValue) {if (newValue !== value) {observer(newValue);
value = newValue; // 让 key 对应的办法(组件从新渲染)从新执行
dep.notify()}
}
})
}
// 模仿数据获取,触发 getter
obj.name = ‘poetries’
// 一个属性一个 dep,一个属性能够对应多个 watcher(一个属性能够在任何组件中应用、在多个组件中应用)
// 一个 dep 对应多个 watcher
// 一个 watcher 对应多个 dep(一个视图对应多个属性)
// dep 和 watcher 是多对多的关系