共计 17694 个字符,预计需要花费 45 分钟才能阅读完成。
params 和 query 的区别
用法:query 要用 path 来引入,params 要用 name 来引入,接管参数都是相似的,别离是 this.$route.query.name
和 this.$route.params.name
。
url 地址显示:query 更加相似于 ajax 中 get 传参,params 则相似于 post,说的再简略一点,前者在浏览器地址栏中显示参数,后者则不显示
留神:query 刷新不会失落 query 外面的数据 params 刷新会失落 params 外面的数据。
为什么 Vue 采纳异步渲染呢?
Vue
是组件级更新,如果不采纳异步更新,那么每次更新数据都会对以后组件进行从新渲染,所以为了性能, Vue
会在本轮数据更新后,在异步更新视图。核心思想 nextTick
。
dep.notify()
告诉 watcher 进行更新, subs[i].update
顺次调用 watcher 的 update
, queueWatcher
将 watcher 去重放入队列,nextTick( flushSchedulerQueue
)在下一 tick 中刷新 watcher 队列(异步)。
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 中 computed 和 watch 有什么区别?** 计算属性 computed**:(1)** 反对缓存 **,只有依赖数据发生变化时,才会从新进行计算函数;(2)计算属性内 ** 不反对异步操作 **;(3)计算属性的函数中 ** 都有一个 get**(默认具备,获取计算属性)** 和 set**(手动增加,设置计算属性)办法;(4)计算属性是主动监听依赖值的变动,从而动静返回内容。** 侦听属性 watch**:(1)** 不反对缓存 **,只有数据发生变化,就会执行侦听函数;(2)侦听属性内 ** 反对异步操作 **;(3)侦听属性的值 ** 能够是一个对象,接管 handler 回调,deep,immediate 三个属性 **;(3)监听是一个过程,在监听的值变动时,能够触发一个回调,并 ** 做一些其余事件 **。参考:[前端 vue 面试题具体解答](https://kc7474.com/archives/1333?url=vue)
### Vue template 到 render 的过程
vue 的模版编译过程次要如下:**template -> ast -> render 函数 **
vue 在模版编译版本的码中会执行 compileToFunctions 将 template 转化为 render 函数:
// 将模板编译为 render 函数 const {render, staticRenderFns} = compileToFunctions(template,options// 省略}, this)
CompileToFunctions 中的次要逻辑如下∶ **(1)调用 parse 办法将 template 转化为 ast(形象语法树)**
constast = parse(template.trim(), options)
- **parse 的指标 **:把 tamplate 转换为 AST 树,它是一种用 JavaScript 对象的模式来形容整个模板。- ** 解析过程 **:利用正则表达式程序解析模板,当解析到开始标签、闭合标签、文本的时候都会别离执行对应的 回调函数,来达到结构 AST 树的目标。AST 元素节点总共三种类型:type 为 1 示意一般元素、2 为表达式、3 为纯文本
**(2)对动态节点做优化 **
optimize(ast,options)
这个过程次要剖析出哪些是动态节点,给其打一个标记,为后续更新渲染能够间接跳过动态节点做优化
深度遍历 AST,查看每个子树的节点元素是否为动态节点或者动态节点根。如果为动态节点,他们生成的 DOM 永远不会扭转,这对运行时模板更新起到了极大的优化作用。**(3)生成代码 **
const code = generate(ast, options)
generate 将 ast 形象语法树编译成 render 字符串并将动态局部放到 staticRenderFns 中,最初通过 `new Function(`` render``)` 生成 render 函数。### action 与 mutation 的区别
- ` mutation` 是同步更新,` $watch` 严格模式下会报错
- ` action` 是异步操作,能够获取数据后调用 ` mutation` 提交最终数据
### assets 和 static 的区别
** 相同点:** `assets` 和 `static` 两个都是寄存动态资源文件。我的项目中所须要的资源文件图片,字体图标,款式文件等都能够放在这两个文件下,这是相同点
** 不相同点:**`assets` 中寄存的动态资源文件在我的项目打包时,也就是运行 `npm run build` 时会将 `assets` 中搁置的动态资源文件进行打包上传,所谓打包简略点能够了解为压缩体积,代码格式化。而压缩后的动态资源文件最终也都会搁置在 `static` 文件中跟着 `index.html` 一起上传至服务器。`static` 中搁置的动态资源文件就不会要走打包压缩格式化等流程,而是间接进入打包好的目录,间接上传至服务器。因为防止了压缩间接进行上传,在打包时会进步肯定的效率,然而 `static` 中的资源文件因为没有进行压缩等操作,所以文件的体积也就绝对于 `assets` 中打包后的文件提交较大点。在服务器中就会占据更大的空间。** 倡议:** 将我的项目中 `template` 须要的款式文件 js 文件等都能够搁置在 `assets` 中,走打包这一流程。缩小体积。而我的项目中引入的第三方的资源文件如 `iconfoont.css` 等文件能够搁置在 `static` 中,因为这些引入的第三方文件曾经通过解决,不再须要解决,间接上传。### 双向数据绑定的原理
Vue.js 是采纳 ** 数据劫持 ** 联合 ** 发布者 - 订阅者模式 ** 的形式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时公布音讯给订阅者,触发相应的监听回调。次要分为以下几个步骤:1. 须要 observe 的数据对象进行递归遍历,包含子属性对象的属性,都加上 setter 和 getter 这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变动
2. compile 解析模板指令,将模板中的变量替换成数据,而后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,增加监听数据的订阅者,一旦数据有变动,收到告诉,更新视图
3. Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,次要做的事件是: ①在本身实例化时往属性订阅器 (dep) 外面增加本人 ②本身必须有一个 update()办法 ③待属性变动 dep.notice()告诉时,能调用本身的 update()办法,并触发 Compile 中绑定的回调,则功成身退。4. MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听本人的 model 数据变动,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变动 -> 视图更新;视图交互变动(input) -> 数据 model 变更的双向绑定成果。### Computed 和 Methods 的区别
能够将同一函数定义为一个 method 或者一个计算属性。对于最终的后果,两种形式是雷同的
** 不同点:**
- computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相干依赖产生扭转时才会从新求值;- method 调用总会执行该函数。### 什么是 mixin?- Mixin 使咱们可能为 Vue 组件编写可插拔和可重用的性能。- 如果心愿在多个组件之间重用一组组件选项,例如生命周期 hook、办法等,则能够将其编写为 mixin,并在组件中简略的援用它。- 而后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。## 了解 Vue 运行机制全局概览
#### 全局概览
首先咱们来看一下笔者画的外部流程图。![](https://img-blog.csdnimg.cn/img_convert/00c11ba8ff00da643b2b2c2ae04048c0.png)
大家第一次看到这个图肯定是一头雾水的,没有关系,咱们来一一讲一下这些模块的作用以及调用关系。置信讲完之后大家对 `Vue.js` 外部运行机制会有一个大略的意识。#### 初始化及挂载
![](https://img-blog.csdnimg.cn/img_convert/38254a061430cd441351f83339654679.png)
> 在 `new Vue()` 之后。Vue 会调用 `_init` 函数进行初始化,也就是这里的 `init` 过程,它会初始化生命周期、事件、props、methods、data、computed 与 watch 等。其中最重要的是通过 `Object.defineProperty` 设置 `setter` 与 `getter` 函数,用来实现「** 响应式 **」以及「** 依赖收集 **」,前面会具体讲到,这里只有有一个印象即可。> 初始化之后调用 `$mount` 会挂载组件,如果是运行时编译,即不存在 render function 然而存在 template 的状况,须要进行「** 编译 **」步骤。#### 编译
compile 编译能够分成 `parse`、`optimize` 与 `generate` 三个阶段,最终须要失去 render function。![](https://img-blog.csdnimg.cn/img_convert/2801d1d3c31ac8dcf0e68a751fb0e970.png)
**1. parse**
`parse` 会用正则等形式解析 template 模板中的指令、class、style 等数据,造成 AST。**2. optimize**
> `optimize` 的次要作用是标记 static 动态节点,这是 Vue 在编译过程中的一处优化,前面当 `update` 更新界面时,会有一个 `patch` 的过程,diff 算法会间接跳过动态节点,从而缩小了比拟的过程,优化了 `patch` 的性能。**3. generate**
> `generate` 是将 AST 转化成 `render function` 字符串的过程,失去后果是 `render` 的字符串以及 staticRenderFns 字符串。* 在经验过 `parse`、`optimize` 与 `generate` 这三个阶段当前,组件中就会存在渲染 `VNode` 所需的 `render function` 了。#### 响应式
接下来也就是 Vue.js 响应式外围局部。![](https://img-blog.csdnimg.cn/img_convert/0815dfa005d37e7e6deffdbf1ee46a09.png)
> 这里的 `getter` 跟 `setter` 曾经在之前介绍过了,在 `init` 的时候通过 `Object.defineProperty` 进行了绑定,它使得当被设置的对象被读取的时候会执行 `getter` 函数,而在当被赋值的时候会执行 `setter` 函数。* 当 `render function` 被渲染的时候,因为会读取所需对象的值,所以会触发 `getter` 函数进行「** 依赖收集 **」,「** 依赖收集 **」的目标是将观察者 `Watcher` 对象寄存到以后闭包中的订阅者 `Dep` 的 `subs` 中。造成如下所示的这样一个关系。![](https://img-blog.csdnimg.cn/img_convert/e237f6910a6a0198208852715051ab8f.png)
> 在批改对象的值的时候,会触发对应的 `setter`,`setter` 告诉之前「** 依赖收集 **」失去的 Dep 中的每一个 Watcher,通知它们本人的值扭转了,须要从新渲染视图。这时候这些 Watcher 就会开始调用 `update` 来更新视图,当然这两头还有一个 `patch` 的过程以及应用队列来异步更新的策略,这个咱们前面再讲。#### Virtual DOM
> 咱们晓得,`render function` 会被转化成 `VNode` 节点。`Virtual DOM` 其实就是一棵以 JavaScript 对象(VNode 节点)作为根底的树,用对象属性来形容节点,实际上它只是一层对实在 DOM 的形象。最终能够通过一系列操作使这棵树映射到实在环境上。因为 Virtual DOM 是以 JavaScript 对象为根底而不依赖实在平台环境,所以使它具备了跨平台的能力,比如说浏览器平台、Weex、Node 等。比如说上面这样一个例子:
{
tag: 'div', /* 阐明这是一个 div 标签 */
children: [ /* 寄存该标签的子节点 */
{
tag: 'a', /* 阐明这是一个 a 标签 */
text: 'click me' /* 标签的内容 */
}
]
}
渲染后能够失去
<div>
<a>click me</a>
</div>
> 这只是一个简略的例子,实际上的节点有更多的属性来标记节点,比方 isStatic(代表是否为动态节点)、isComment(代表是否为正文节点)等。#### 更新视图
![](https://img-blog.csdnimg.cn/img_convert/9fe665ec341361c3d1f1b1c12be61922.png)
* 后面咱们说到,在批改一个对象值的时候,会通过 `setter -> Watcher -> update` 的流程来批改对应的视图,那么最终是如何更新视图的呢?* 当数据变动后,执行 render function 就能够失去一个新的 VNode 节点,咱们如果想要失去新的视图,最简略粗犷的办法就是间接解析这个新的 `VNode` 节点,而后用 `innerHTML` 间接全副渲染到实在 `DOM` 中。然而其实咱们只对其中的一小块内容进行了批改,这样做仿佛有些「** 节约 **」。* 那么咱们为什么不能只批改那些「扭转了的中央」呢?这个时候就要介绍咱们的「**`patch`**」了。咱们会将新的 `VNode` 与旧的 `VNode` 一起传入 `patch` 进行比拟,通过 diff 算法得出它们的「** 差别 **」。最初咱们只须要将这些「** 差别 **」的对应 DOM 进行批改即可。#### 再看全局
![](https://img-blog.csdnimg.cn/img_convert/9d6d5e9ca39bd34cb49e343db6bf4ffb.png)
回过头再来看看这张图,是不是大脑中曾经有一个大略的脉络了呢?### keep-alive 中的生命周期哪些
keep-alive 是 Vue 提供的一个内置组件,用来对组件进行缓存——在组件切换过程中将状态保留在内存中,避免反复渲染 DOM。如果为一个组件包裹了 keep-alive,那么它会多出两个生命周期:deactivated、activated。同时,beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。当组件被换掉时,会被缓存到内存中、触发 deactivated 生命周期;当组件被切回来时,再去缓存里找这个组件、触发 activated 钩子函数。### v-model 能够被用在自定义组件上吗?如果能够,如何应用?能够。v-model 实际上是一个语法糖,如:
<input v-model=”searchText”>
实际上相当于:
<input
v-bind:value=”searchText”
v-on:input=”searchText = $event.target.value”
用在自定义组件上也是同理:
<custom-input v-model=”searchText”>
相当于:
<custom-input
v-bind:value=”searchText”
v-on:input=”searchText = $event”
</custom-input>
显然,custom-input 与父组件的交互如下:1. 父组件将 `searchText` 变量传入 custom-input 组件,应用的 prop 名为 `value`;2. custom-input 组件向父组件传出名为 `input` 的事件,父组件将接管到的值赋值给 `searchText`;所以,custom-input 组件的实现应该相似于这样:
Vue.component(‘custom-input’, {
props: [‘value’],
template: <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" >
})
### 子组件能够间接扭转父组件的数据吗?子组件不能够间接扭转父组件的数据。这样做次要是为了保护父子组件的单向数据流。每次父级组件产生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果这样做了,Vue 会在浏览器的控制台中收回正告。Vue 提倡单向数据流,即父级 props 的更新会流向子组件,然而反过来则不行。这是为了避免意外的扭转父组件状态,使得利用的数据流变得难以了解,导致数据流凌乱。如果毁坏了单向数据流,当利用简单时,debug 的老本会十分高。** 只能通过 **`$emit`** 派发一个自定义事件,父组件接管到后,由父组件批改。**
### 应用 Object.defineProperty() 来进行数据劫持有什么毛病?在对一些属性进行操作时,应用这种办法无奈拦挡,比方通过下标形式批改数组数据或者给对象新增属性,这都不能触发组件的从新渲染,因为 Object.defineProperty 不能拦挡到这些操作。更准确的来说,对于数组而言,大部分操作都是拦挡不到的,只是 Vue 外部通过重写函数的形式解决了这个问题。在 Vue3.0 中曾经不应用这种形式了,而是通过应用 Proxy 对对象进行代理,从而实现数据劫持。应用 Proxy 的益处是它能够完满的监听到任何形式的数据扭转,惟一的毛病是兼容性的问题,因为 Proxy 是 ES6 的语法。## Vue2.x 响应式数据原理
整体思路是数据劫持 + 观察者模式
对象外部通过 `defineReactive` 办法,应用 `Object.defineProperty` 来劫持各个属性的 `setter`、`getter`(只会劫持曾经存在的属性),数组则是通过 ` 重写数组 7 个办法 ` 来实现。当页面应用对应属性时,每个属性都领有本人的 `dep` 属性,寄存他所依赖的 `watcher`(依赖收集),当属性变动后会告诉本人对应的 `watcher` 去更新(派发更新)
**Object.defineProperty 根本应用 **
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) {
observer(value);
Object.defineProperty(obj, key, {get() { // 收集对应的 key 在哪个办法(组件)中被应用
return value;
},
set(newValue) {if (newValue !== value) {observer(newValue);
value = newValue; // 让 key 对应的办法(组件从新渲染)从新执行
}
}
})
}
let obj1 = {school: { name: ‘poetry’, age: 20} };
observer(obj1);
console.log(obj1)
** 源码剖析 **
![](https://img-blog.csdnimg.cn/img_convert/2d663b454ecd8ab745557e44ee28cd10.png)
class Observer {
// 观测值
constructor(value) {
this.walk(value);
}
walk(data) {
// 对象上的所有属性顺次进行观测
let keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {let key = keys[i];
let value = data[key];
defineReactive(data, key, value);
}
}
}
// Object.defineProperty 数据劫持外围 兼容性在 ie9 以及以上
function defineReactive(data, key, value) {
observe(value); // 递归要害
// – 如果 value 还是一个对象会持续走一遍 odefineReactive 层层遍历始终到 value 不是对象才进行
// 思考?如果 Vue 数据嵌套层级过深 >> 性能会受影响
Object.defineProperty(data, key, {
get() {console.log("获取值");
// 须要做依赖收集过程 这里代码没写进去
return value;
},
set(newValue) {if (newValue === value) return;
console.log("设置值");
// 须要做派发更新过程 这里代码没写进去
value = newValue;
},
});
}
export function observe(value) {
// 如果传过来的是对象或者数组 进行属性劫持
if (
Object.prototype.toString.call(value) === "[object Object]" ||
Array.isArray(value)
) {
return new Observer(value);
}
}
说一说你对 vue 响应式了解答复范例
* 所谓数据响应式就是 ** 可能使数据变动能够被检测并对这种变动做出响应的机制 **
* `MVVM` 框架中要解决的一个外围问题是连贯数据层和视图层,通过 ** 数据驱动 ** 利用,数据变动,视图更新,要做到这点的就须要对数据做响应式解决,这样一旦数据发生变化就能够立刻做出更新解决
* 以 `vue` 为例阐明,通过数据响应式加上虚构 `DOM` 和 `patch` 算法,开发人员只须要操作数据,关怀业务,齐全不必接触繁琐的 DOM 操作,从而大大晋升开发效率,升高开发难度
* `vue2` 中的数据响应式会依据数据类型来做不同解决,如果是 ** 对象则采纳 `Object.defineProperty()` 的形式定义数据拦挡,当数据被拜访或发生变化时,咱们感知并作出响应;如果是数组则通过笼罩数组对象原型的 7 个变更办法 **,使这些办法能够额定的做更新告诉,从而作出响应。这种机制很好的解决了数据响应化的问题,但在理论应用中也存在一些毛病:比方初始化时的递归遍历会造成性能损失;新增或删除属性时须要用户应用 `Vue.set/delete` 这样非凡的 `api` 能力失效;对于 `es6` 中新产生的 `Map`、`Set` 这些数据结构不反对等问题
* 为了解决这些问题,`vue3` 从新编写了这一部分的实现:利用 `ES6` 的 `Proxy` 代理要响应化的数据,它有很多益处,编程体验是统一的,不须要应用非凡 `api`,初始化性能和内存耗费都失去了大幅改善;另外因为响应化的实现代码抽取为独立的 `reactivity` 包,使得咱们能够更灵便的应用它,第三方的扩大开发起来更加灵便了
### Vue 的父子组件生命周期钩子函数执行程序
* ** 渲染程序 **:先父后子,实现程序:先子后父
* ** 更新程序 **:父更新导致子更新,子更新实现后父
* ** 销毁程序 **:先父后子,实现程序:先子后父
** 加载渲染过程 **
父 `beforeCreate`-> 父 `created`-> 父 `beforeMount`-> 子 `beforeCreate`-> 子 `created`-> 子 `beforeMount`-> 子 `mounted`-> 父 `mounted`。** 子组件先挂载,而后到父组件 **
** 子组件更新过程 **
父 `beforeUpdate`-> 子 `beforeUpdate`-> 子 `updated`-> 父 `updated`
** 父组件更新过程 **
父 `beforeUpdate`-> 父 `updated`
** 销毁过程 **
父 `beforeDestroy`-> 子 `beforeDestroy`-> 子 `destroyed`-> 父 `destroyed`
> 之所以会这样是因为 `Vue` 创立过程是一个递归过程,先创立父组件,有子组件就会创立子组件,因而创立时先有父组件再有子组件;子组件首次创立时会增加 `mounted` 钩子到队列,等到 `patch` 完结再执行它们,可见子组件的 `mounted` 钩子是先进入到队列中的,因而等到 `patch` 完结执行这些钩子时也先执行。![](https://img-blog.csdnimg.cn/img_convert/553b51ad2a8a00396f1834281603e16f.png)
function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return
}
let isInitialPatch = false
const insertedVnodeQueue = [] // 定义收集所有组件的 insert hook 办法的数组 // somthing ...
createElm(
vnode,
insertedVnodeQueue, oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)// somthing...
// 最终会顺次调用收集的 insert hook
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm
}
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
// createChildren 会递归创立儿子组件
createChildren(vnode, children, insertedVnodeQueue) // something...
}
// 将组件的 vnode 插入到数组中
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {cbs.create[i](emptyNode, vnode)
}
i = vnode.data.hook // Reuse variable
if (isDef(i)) {if (isDef(i.create)) i.create(emptyNode, vnode)
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
// insert 办法中会顺次调用 mounted 办法
insert (vnode: MountedComponentVNode) {
const {context, componentInstance} = vnode
if (!componentInstance._isMounted) {
componentInstance._isMounted = true
callHook(componentInstance, 'mounted')
}
}
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the // element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {vnode.parent.data.pendingInsert = queue} else {for (let i = 0; i < queue.length; ++i) {queue[i].data.hook.insert(queue[i]); // 调用 insert 办法
}
}
}
Vue.prototype.$destroy = function () {
callHook(vm, 'beforeDestroy')
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null) // 先销毁儿子
// fire destroyed hook
callHook(vm, 'destroyed')
}
### Vue 中封装的数组办法有哪些,其如何实现页面更新
在 Vue 中,对响应式解决利用的是 Object.defineProperty 对数据进行拦挡,而这个办法并不能监听到数组外部变动,数组长度变动,数组的截取变动等,所以须要对这些操作进行 hack,让 Vue 能监听到其中的变动。那 Vue 是如何实现让这些数组办法实现元素的实时更新的呢,上面是 Vue 中对这些办法的封装:
// 缓存数组原型
const arrayProto = Array.prototype;
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto);
// 须要进行性能拓展的办法
const methodsToPatch = [
“push”,
“pop”,
“shift”,
“unshift”,
“splice”,
“sort”,
“reverse”
];
/* Intercept mutating methods and emit events */
methodsToPatch.forEach(function(method) {
// 缓存原生数组办法
const original = arrayProto[method];
def(arrayMethods, method, function mutator(…args) {
// 执行并缓存原生数组性能
const result = original.apply(this, args);
// 响应式解决
const ob = this.__ob__;
let inserted;
switch (method) {
// push、unshift 会新增索引,所以要手动 observer
case "push":
case "unshift":
inserted = args;
break;
// splice 办法,如果传入了第三个参数,也会有索引退出,也要手动 observer。case "splice":
inserted = args.slice(2);
break;
}
//
if (inserted) ob.observeArray(inserted);// 获取插入的值,并设置响应式监听
// notify change
ob.dep.notify();// 告诉依赖更新
// 返回原生数组办法的执行后果
return result;
});
});
简略来说就是,重写了数组中的那些原生办法,首先获取到这个数组的__ob__,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 持续对新的值察看变动(也就是通过 `target__proto__ == arrayMethods` 来扭转了数组实例的型),而后手动调用 notify,告诉渲染 watcher,执行 update。### v-model 是如何实现的,语法糖理论是什么?**(1)作用在表单元素上 ** 动静绑定了 input 的 value 指向了 messgae 变量,并且在触发 input 事件的时候去动静把 message 设置为目标值:
<input v-model=”sth” />
// 等同于
<input v-bind:value=”message” v-on:input=”message=$event.target.value”
//$event 指代以后触发的事件对象;//$event.target 指代以后触发的事件对象的 dom;//$event.target.value 就是以后 dom 的 value 值;// 在 @input 办法中,value => sth;// 在:value 中,sth => value;
**(2)作用在组件上 ** 在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件
** 实质是一个父子组件通信的语法糖,通过 prop 和 $.emit 实现。** 因而父组件 v-model 语法糖实质上能够批改为:
<child :value=”message” @input=”function(e){message = e}”></child>
在组件的实现中,能够通过 v-model 属性来配置子组件接管的 prop 名称,以及派发的事件名称。例子:
// 父组件
<aa-input v-model=”aa”></aa-input>
// 等价于
<aa-input v-bind:value=”aa” v-on:input=”aa=$event.target.value”></aa-input>
// 子组件:
<input v-bind:value=”aa” v-on:input=”onmessage”></aa-input>
props:{value:aa,}
methods:{
onmessage(e){$emit('input',e.target.value)
}
}
默认状况下,一个组件上的 v -model 会把 value 用作 prop 且把 input 用作 event。然而一些输出类型比方单选框和复选框按钮可能想应用 value prop 来达到不同的目标。应用 model 选项能够回避这些状况产生的抵触。js 监听 input 输入框输出数据扭转,用 oninput,数据扭转当前就会立即登程这个事件。通过 input 事件把数据 $emit 进来,在父组件承受。父组件设置 v -model 的值为 input `$emit` 过去的值。### delete 和 Vue.delete 删除数组的区别
- `delete` 只是被删除的元素变成了 `empty/undefined` 其余的元素的键值还是不变。- `Vue.delete` 间接删除了数组 扭转了数组的键值。