当前位置:网站首页 > Vue.js开发 > 正文

vue中的钩子函数有哪些(vue钩子函数有几种)



计算属性 computed
(1)支持缓存,只有依赖数据发生变化时,才会重新进行计算函数;
(2)计算属性内不支持异步操作
(3)计算属性的函数中都有一个 get(默认具有,获取计算属性)和 set(手动添加,设置计算属性)方法;
(4)计算属性是自动监听依赖值的变化,从而动态返回内容。

侦听属性 watch
(1)不支持缓存,只要数据发生变化,就会执行侦听函数;
(2)侦听属性内支持异步操作
(3)侦听属性的值可以是一个对象,接收 handler 回调,deep,immediate 三个属性
(3)监听是一个过程,在监听的值变化时,可以触发一个回调,并做一些其他事情

Vue2: 重新定义 中所有的属性, 可以使数据的获取与设置增加一个拦截的功能,拦截属性的获取,进行依赖收集。拦截属性的更新操作,进行通知。

具体的过程:首先Vue使用  初始化用户传入的参数,然后使用  对数据进行观测,如果数据是一个对象类型就会调用 对对象进行处理,内部使用  循环对象属性定义响应式变化,核心就是使用 重新定义数据。

指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素增加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。

自定义指令有五个生命周期(也叫钩子函数),分别是 bind、inserted、update、componentUpdated、unbind

1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。 4. componentUpdated:被绑定元素所在模板完成一次更新周期时调用。 5. unbind:只调用一次,指令与元素解绑时调用。

原理

1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性

2.通过 genDirectives 生成指令代码

3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子

4.当执行指令对应钩子函数时,调用对应指令定义的方法

思路分析

这个题目很有难度,首先思考解决的问题:存储用户全局状态并提供管理状态API。

回答范例

实践

的实现:

class Store {     constructor(options) {         this.state = reactive(options.state)         this.options = options    }     commit(type, payload) {         this.options.mutations[type].call(this, this.state, payload)     }}

vuex简易版

/  * 1 实现插件,挂载$store  * 2 实现store  */let Vue;class Store {   constructor(options) {     // state响应式处理     // 外部访问: this.$store.state.*     // 第一种写法     // this.state = new Vue({     //   data: options.state     // })     // 第二种写法:防止外界直接接触内部vue实例,防止外部强行变更     this._vm = new Vue({       data: {         $$state: options.state      }     })     this._mutations = options.mutations    this._actions = options.actions    this.getters = {}     options.getters && this.handleGetters(options.getters)     this.commit = this.commit.bind(this)     this.dispatch = this.dispatch.bind(this)   }   get state () {     return this._vm._data.$$state  }   set state (val) {     return new Error('Please use replaceState to reset state')   }   handleGetters (getters) {     Object.keys(getters).map(key => {       Object.defineProperty(this.getters, key, {         get: () => getters[key](this.state)       })     })   }   commit (type, payload) {     let entry = this._mutations[type]     if (!entry) {       return new Error(`${type} is not defined`)     }     entry(this.state, payload)   }   dispatch (type, payload) {     let entry = this._actions[type]     if (!entry) {       return new Error(`${type} is not defined`)     }     entry(this, payload)   }}const install = (_Vue) => {   Vue = _Vue   Vue.mixin({     beforeCreate () {       if (this.$options.store) {         Vue.prototype.$store = this.$options.store      }     },   })}export default { Store, install }

验证方式

import Vue from 'vue'import Vuex from 'https://maimai.cn/article/vuex'// this.$storeVue.use(Vuex)export default new Vuex.Store({   state: {     counter: 0   },   mutations: {     // state从哪里来的     add (state) {       state.counter++     }   },   getters: {     doubleCounter (state) {       return state.counter * 2     }   },   actions: {     add ({ commit }) {       setTimeout(() => {         commit('add')       }, 1000)     }   },   modules: {   }})

Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,表明你对 Vue 掌握的越熟练。Vue 组件间通信只要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。

(1) 适用 父子组件通信

这种方法是 Vue 组件的基础,相信大部分同学耳闻能详,所以此处就不举例展开介绍。

(2) 与  适用 父子组件通信

(3) 适用于 父子、隔代、兄弟组件通信

这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。

(4)/ 适用于 隔代组件通信

(5) 适用于 隔代组件通信

祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

(6)Vuex 适用于 父子、隔代、兄弟组件通信

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

组件的调用顺序都是,渲染完成的顺序是。

组件的销毁操作是,销毁完成的顺序是。

加载渲染过程

父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted

子组件更新过程

父beforeUpdate->子beforeUpdate->子updated->父updated

父组件更新过程

父 beforeUpdate -> 父 updated

销毁过程

父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

答案

时间复杂度: 个树的完全 算法是一个时间复杂度为 ,vue进行优化转化成 。

理解:

diff算法的优化策略:四种命中查找,四个指针

— 问完上面这些如果都能很清楚的话,基本O了 —

以下的这些简单的概念,你肯定也是没有问题的啦😉

简单说,Vue的编译过程就是将转化为函数的过程。会经历以下阶段:

首先解析模版,生成(一种用JavaScript对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。

Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以,对运行时的模板起到很大的优化作用。

编译的最后一步是。

优点:

缺点:

参考:前端vue面试题详细解答

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;

运用场景:

看一下的代码

<template><div>   请输入firstName:  <input type="text" v-model="firstName"></div><div>   请输入lastName:  <input type="text" v-model="lastName"></div><div>   请输入obj.text:  <input type="text" v-model="obj.text"></div>  <div>  【obj.text】 {{obj.text}} </div></template><script>import {ref, reactive, watch, watchEffect} from 'vue'export default {   name: "HelloWorld",   props: {     msg: String,   },   setup(props,content){     let firstName = ref('')     let lastName = ref('')     let obj= reactive({       text:'hello'     })     watchEffect(()=>{       console.log('触发了watchEffect');       console.log(`组合后的名称为:${firstName.value}${lastName.value}`)     })     return{       obj,       firstName,       lastName    }   }};</script>

 

改造一下代码

watchEffect(()=>{   console.log('触发了watchEffect');   // 这里我们不使用firstName.value/lastName.value ,相当于是监控整个ref,对应第四点上面的结论   console.log(`组合后的名称为:${firstName}${lastName}`)})

watchEffect(()=>{   console.log('触发了watchEffect');   console.log(obj);})

稍微改造一下

let obj = reactive({   text:'hello'})watchEffect(()=>{   console.log('触发了watchEffect');   console.log(obj.text);})

再看一下watch的代码,验证一下

let obj= reactive({   text:'hello'})// watch是惰性执行, 默认初始化之后不会执行,只有值有变化才会触发,可通过配置参数实现默认执行watch(obj, (newValue, oldValue) => {   // 回调函数   console.log('触发监控更新了new',  newValue);   console.log('触发监控更新了old',  oldValue);},{   // 配置immediate参数,立即执行,以及深层次监听   immediate: true,   deep: true})

 

总结

watch和watchEffect异同总结

体验

立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数

const count = ref(0)watchEffect(() => console.log(count.value))// -> logs 0count.value++// -> logs 1

侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数

const state = reactive({ count: 0 })watch(   () => state.count,   (count, prevCount) => {     /* ... */   })

回答范例

定义如下

export function watchEffect(   effect: WatchEffect,   options?: WatchOptionsBase): WatchStopHandle {   return doWatch(effect, null, options)}

定义如下

export function watch<T = any, Immediate extends Readonly<boolean> = false>(   source: T | WatchSource<T>,   cb: any,   options?: WatchOptions<Immediate>): WatchStopHandle {   return doWatch(source as any, cb, options)}

很明显就是一种特殊的实现。

我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

以 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', '小红')   },},

简而言之,就是先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点),详细步骤如下:

首先,通过compile编译器把template编译成AST语法树(abstract syntax tree 即 源代码的抽象语法结构的树状表现形式),compile是createCompiler的返回值,createCompiler是用以创建编译器的。另外compile还负责合并option。

然后,AST会经过generate(将AST语法树转化成render funtion字符串的过程)得到render函数,render的返回值是VNode,VNode是Vue的虚拟DOM节点,里面有(标签名、子节点、文本等等)

beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问

created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有nextTick 来访问 Dom

beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。

mounted 在挂载完成后发生,在当前阶段,真实的 Dom 挂载完毕,数据完成双向绑定,可以访问到 Dom 节点

beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁(patch)之前。可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程

updated 发生在更新完成之后,当前阶段组件 Dom 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新,该钩子在服务器端渲染期间不被调用。

beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。我们可以在这时进行善后收尾工作,比如清除计时器。

destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。

activated keep-alive 专属,组件被激活时调用

deactivated keep-alive 专属,组件被销毁时调用

异步请求在哪一步发起?

可以在钩子函数 created、beforeMount、mounted 中进行异步请求,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

如果异步请求不需要依赖 Dom 推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

当一个Vue实例创建时,Vue会遍历data中的属性,用 Object.defineProperty(vue3.0使用proxy )将它们转为 getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)

<script>     // Vue.options 中会存放所有全局属性     // 会用自身的 + Vue.options 中的属性进行合并     // Vue.mixin({     //     beforeCreate() {     //         console.log('before 0')     //     },     // })     debugger;     const vm = new Vue({         el: '#app',         beforeCreate: [             function() {                 console.log('before 1')             },             function() {                 console.log('before 2')             }         ]     });     console.log(vm);</script>

相关代码如下

export function callHook(vm, hook) {   // 依次执行生命周期对应的方法   const handlers = vm.$options[hook];   if (handlers) {     for (let i = 0; i < handlers.length; i++) {       handlers[i].call(vm); //生命周期里面的this指向当前实例     }   }}// 调用的时候Vue.prototype._init = function (options) {   const vm = this;   vm.$options = mergeOptions(vm.constructor.options, options);   callHook(vm, "beforeCreate"); //初始化数据之前   // 初始化状态   initState(vm);   callHook(vm, "created"); //初始化数据之后   if (vm.$options.el) {     vm.$mount(vm.$options.el);   }};// 销毁实例实现Vue.prototype.$destory = function() {      // 触发钩子     callHook(vm, 'beforeDestory')     // 自身及子节点     remove()      // 删除依赖     watcher.teardown()      // 删除监听     vm.$off()      // 触发钩子     callHook(vm, 'destoryed')}

原理流程图

keep-alive是 Vue 提供的一个内置组件,用来对组件进行缓存——在组件切换过程中将状态保留在内存中,防止重复渲染DOM。

如果为一个组件包裹了 keep-alive,那么它会多出两个生命周期:deactivated、activated。同时,beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。

当组件被换掉时,会被缓存到内存中、触发 deactivated 生命周期;当组件被切回来时,再去缓存里找这个组件、触发 activated钩子函数。

在初始化 Vue 的每个组件时,会对组件的 data 进行初始化,就会将由普通对象变成响应式对象,在这个过程中便会进行依赖收集的相关逻辑,如下所示∶

function defieneReactive (obj, key, val){   const dep = new Dep();   ...   Object.defineProperty(obj, key, {     ...     get: function reactiveGetter () {       if(Dep.target){         dep.depend();         ...       }       return val    }     ...   })}

以上只保留了关键代码,主要就是 实例化一个 Dep 的实例,然后在 get 函数中通过  进行依赖收集。 (1)Dep Dep是整个依赖收集的核心,其关键代码如下:

class Dep {   static target;   subs;   constructor () {     ...     this.subs = [];   }   addSub (sub) {     this.subs.push(sub)   }   removeSub (sub) {     remove(this.sub, sub)   }   depend () {     if(Dep.target){       Dep.target.addDep(this)     }   }   notify () {     const subs = this.subds.slice();     for(let i = 0;i < subs.length; i++){       subs[i].update()     }   }}

Dep 是一个 class ,其中有一个关 键的静态属性 static,它指向了一个全局唯一 Watcher,保证了同一时间全局只有一个 watcher 被计算,另一个属性 subs 则是一个 Watcher 的数组,所以 Dep 实际上就是对 Watcher 的管理,再看看 Watcher 的相关代码∶

(2)Watcher

class Watcher {   getter;   ...   constructor (vm, expression){     ...     this.getter = expression;     this.get();   }   get () {     pushTarget(this);     value = this.getter.call(vm, vm)     ...     return value  }   addDep (dep){         ...     dep.addSub(this)   }   ...}function pushTarget (_target) {   Dep.target = _target}

Watcher 是一个 class,它定义了一些方法,其中和依赖收集相关的主要有 get、addDep 等。

(3)过程

在实例化 Vue 时,依赖收集的相关过程如下∶
初 始 化 状 态 initState , 这 中 间 便 会 通 过 defineReactive 将数据变成响应式对象,其中的 getter 部分便是用来依赖收集的。
初始化最终会走 mount 过程,其中会实例化 Watcher ,进入 Watcher 中,便会执行 this.get() 方法,

updateComponent = () => {   vm._update(vm._render())}new Watcher(vm, updateComponent)

get 方法中的 pushTarget 实际上就是把 Dep.target 赋值为当前的 watcher。

this.getter.call(vm,vm),这里的 getter 会执行 vm._render() 方法,在这个过程中便会触发数据对象的 getter。那么每个对象值的 getter 都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 方法,也就会执行 Dep.target.addDep(this)。刚才 Dep.target 已经被赋值为 watcher,于是便会执行 addDep 方法,然后走到 dep.addSub() 方法,便将当前的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目的是为后续数据变化时候能通知到哪些 subs 做准备。所以在 vm._render() 过程中,会触发所有数据的 getter,这样便已经完成了一个依赖收集的过程。

 的问题主要有三个:

Proxy的优势如下:

proxy详细使用点击查看(opens new window)

Object.defineProperty的优势如下:

兼容性好,支持 ,而  的存在浏览器兼容性问题,而且无法用  磨平

defineProperty的属性值有哪些

Object.defineProperty(obj, prop, descriptor)// obj 要定义属性的对象// prop 要定义或修改的属性的名称// descriptor 要定义或修改的属性描述符Object.defineProperty(obj,"name",{   value:"poetry", // 初始值   writable:true, // 该属性是否可写入   enumerable:true, // 该属性是否可被遍历得到(for...in, Object.keys等)   configurable:true, // 定该属性是否可被删除,且除writable外的其他描述符是否可被修改   get: function() {},   set: function(newVal) {}})

相关代码如下

import { mutableHandlers } from "https://maimai.cn/article/baseHandlers"; // 代理相关逻辑import { isObject } from "https://maimai.cn/article/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, // 当修改属性时调用此方法};

只会代理对象的第一层,那么又是怎样处理这个问题的呢?

判断当前返回值是否为,如果是则再通过方法做代理, 这样就实现了深度观测。

监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?

我们可以判断是否为当前被代理对象自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行

到此这篇vue中的钩子函数有哪些(vue钩子函数有几种)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • pcie5.0协议(pcie5.0有什么用)2025-02-26 21:54:04
  • vue3怎么安装(vue3怎么安装elementui)2025-02-26 21:54:04
  • lxml安装失败(msxml4.0安装失败)2025-02-26 21:54:04
  • 多级列表1.1,1.2,1.3怎么弄(多级列表1.1 2.1 3.1)2025-02-26 21:54:04
  • 合并数组和非合并数组怎么合并(合并数组js)2025-02-26 21:54:04
  • ubuntu镜像u盘安装教程(ubuntu20.04镜像)2025-02-26 21:54:04
  • js深拷贝数组对象(js 深拷贝数组)2025-02-26 21:54:04
  • pcie5.0显卡能插4.0主板吗(显卡pcie4.0能接3.0的口么)2025-02-26 21:54:04
  • 前端跨域怎么解决vue(vue前端解决跨域问题)2025-02-26 21:54:04
  • vue3与vue2.5区别大吗(vue3和vue2求区别)2025-02-26 21:54:04
  • 全屏图片