关于vue.js:一个合格的vue工程师必会的20道面试题

4次阅读

共计 17694 个字符,预计需要花费 45 分钟才能阅读完成。

params 和 query 的区别

用法:query 要用 path 来引入,params 要用 name 来引入,接管参数都是相似的,别离是 this.$route.query.namethis.$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 的生命周期办法有哪些

  1. Vue 实例有一个残缺的生命周期,也就是从 开始创立 初始化数据 编译模版 挂载 Dom -> 渲染 更新 -> 渲染 卸载 等一系列过程,咱们称这是 Vue 的生命周期
  2. Vue生命周期总共分为 8 个阶段 创立前 / 后 载入前 / 后 更新前 / 后 销毁前 / 后

beforeCreate => created => beforeMount => Mounted => beforeUpdate => updated => beforeDestroy => destroyedkeep-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,组件实例在服务器上被渲染前调用
  1. 要把握每个生命周期外部能够做什么事
  2. beforeCreate 初始化 vue 实例,进行数据观测。执行时组件实例还未创立,通常用于插件开发中执行一些初始化工作
  3. created 组件初始化结束,能够拜访各种数据,获取接口数据等
  4. beforeMount 此阶段 vm.el 虽已实现 DOM 初始化,但并未挂载在 el 选项上
  5. mounted 实例曾经挂载实现,能够进行一些 DOM 操作
  6. beforeUpdate 更新前,可用于获取更新前各种状态。此时 view 层还未更新,可用于获取更新前各种状态。能够在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  7. updated 实现 view 层的更新,更新后,所有状态已是最新。能够执行依赖于 DOM 的操作。然而在大多数状况下,你应该防止在此期间更改状态,因为这可能会导致更新有限循环。该钩子在服务器端渲染期间不被调用。
  8. destroyed 能够执行一些优化操作, 清空定时器,解除绑定事件
  9. vue3 beforeunmount:实例被销毁前调用,可用于一些定时器或订阅的勾销
  10. 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>
  1. 组合式 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 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不须要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该间接在 setup 函数中编写

export default {setup() {
    // mounted
    onMounted(() => {console.log('Component is mounted!')
    })
  }
}

setupcreated 谁先执行?

  • beforeCreate: 组件被创立进去,组件的 methodsdata还没初始化好
  • setup:在 beforeCreatecreated之间执行
  • created: 组件被创立进去,组件的 methodsdata曾经初始化好了

因为在执行 setup 的时候,created还没有创立好,所以在 setup 函数内咱们是无奈应用 datamethods的。所以 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}
  },

}
  1. 其余问题
  2. 什么是 vue 生命周期? Vue 实例从创立到销毁的过程,就是生命周期。从开始创立、初始化数据、编译模板、挂载 Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。
  • vue 生命周期的作用是什么? 它的生命周期中有多个事件钩子,让咱们在管制整个 Vue 实例的过程时更容易造成好的逻辑。
  • vue 生命周期总共有几个阶段? 它能够总共分为 8 个阶段:创立前 / 后、载入前 / 后、更新前 / 后、销毁前 / 销毁后。
  • 第一次页面加载会触发哪几个钩子? 会触发上面这几个beforeCreatecreatedbeforeMountmounted
  • 你的接口申请个别放在哪个生命周期中? 接口申请个别放在 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` 间接删除了数组 扭转了数组的键值。
正文完
 0