精华内容
下载资源
问答
  • PLM产品全生命周期管理软件选型手册,自己制作的PDF版本。由图片制作而成,效果一般,但是字迹清楚,对于需要该书知识的朋友来讲,是一个非常好的资源。如果涉及到侵权,请联系我进行删除。
  • 设备全生命周期管理是从设备需求开始,然后投入使用,一直到因设备功能丧失而退出使用。包括设备前期管理,设备采购,设备运输动态信息,设备安装,设备档案建立,设备维检修保养,设备变动管理,设备报废等,以信息...
  • 一文读懂 Spring Bean 的生命周期

    千次阅读 多人点赞 2021-07-05 23:02:56
    今天我们来说一说 Spring Bean 的生命周期,小伙伴们应该在面试中经常遇到,这是正常现象。因为 Spring Bean 的生命周期是除了 IoC、AOP 几个核心概念之外最重要概念,大家务必拿下。可 Spring 源代码又比较复杂,...

    欢迎大家关注我的微信公众号【老周聊架构】,Java后端主流技术栈的原理、源码分析、架构以及各种互联网高并发、高性能、高可用的解决方案。

    一、前言

    今天我们来说一说 Spring Bean 的生命周期,小伙伴们应该在面试中经常遇到,这是正常现象。因为 Spring Bean 的生命周期是除了 IoC、AOP 几个核心概念之外最重要概念,大家务必拿下。可 Spring 源代码又比较复杂,跟着跟着就不知道跟到哪里去了,不太好拿下呀。这倒是真的,而且网上一上来就各种贴流程源码,对初学者来说是真的一脸懵逼,就像字都看的懂,但连在一块就不知道意思了,太绕了。

    本文老周试着讲的通俗易懂些,让更多的小伙伴们轻松的读懂 Spring Bean 的生命周期,并有对它有继续研究学习的想法,那我写此文的目的也就达到了。

    我们讲 Spring Bean 的生命周期之前先来了解两个概念:

    1.1 什么是 Bean

    我们来看下 Spring Framework 的官方文档:

    In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container. Otherwise, a bean is simply one of many objects in your application. Beans, and the dependencies among them, are reflected in the configuration metadata used by a container.

    简而言之,bean 是由 Spring IoC 容器实例化、组装和管理的对象。

    1.2 什么是 Spring Bean 的生命周期

    对于普通的 Java 对象,当 new 的时候创建对象,然后该对象就能够使用了。一旦该对象不再被使用,则由 Java 自动进行垃圾回收。

    而 Spring 中的对象是 bean,bean 和普通的 Java 对象没啥大的区别,只不过 Spring 不再自己去 new 对象了,而是由 IoC 容器去帮助我们实例化对象并且管理它,我们需要哪个对象,去问 IoC 容器要即可。IoC 其实就是解决对象之间的耦合问题,Spring Bean 的生命周期完全由容器控制。

    二、Spring Bean 的生命周期

    这里老周必须要提一下,这里我们说的 Spring Bean 的生命周期主要指的是 singleton bean,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。

    我们也来复习下 Spring 中的 bean 的作用域有哪些?

    • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的。
    • prototype : 每次请求都会创建一个新的 bean 实例。
    • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
    • session : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
    • global-session: 全局 session 作用域,仅仅在基于 Portlet 的 web 应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。

    我们知道对于普通的 Java 对象来说,它们的生命周期就是:

    • 实例化
    • 该对象不再被使用时通过垃圾回收机制进行回收

    而对于 Spring Bean 的生命周期来说:

    • 实例化 Instantiation
    • 属性赋值 Populate
    • 初始化 Initialization
    • 销毁 Destruction

    实例化 -> 属性赋值 -> 初始化 -> 销毁

    只有四个步骤,这样拆解的话是不是感觉也不难?不像其他人写的那样直接一上来就各种 BeanPostProcessor、BeanFactoryPostProcessor 全部怼进流程里去,别说读者看着头大,自己写的可能短时间内还记得流程,隔个一段时间,你可能都不知道自己写了个啥。

    本来老周想通过 Bean 创建流程入口
    AbstractApplicationContext#refresh() 方法的 finishBeanFactoryInitialization(beanFactory) 处带大家跟一下源码,想了想还是不带入过多的代码进来,直接给到最终的主要逻辑。

    protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = (BeanWrapper)this.factoryBeanInstanceCache.remove(beanName);
        }
    
        if (instanceWrapper == null) {
        	// 实例化阶段
            instanceWrapper = this.createBeanInstance(beanName, mbd, args);
        }
    
        ...
    
        Object exposedObject = bean;
    
        try {
        	// 属性赋值阶段
            this.populateBean(beanName, mbd, instanceWrapper);
            // 初始化阶段
            exposedObject = this.initializeBean(beanName, exposedObject, mbd);
        } catch (Throwable var18) {
            ...
        }
    
        ...
    }
    

    至于销毁,是在容器关闭时调用的,详见 ConfigurableApplicationContext#close()

    是不是很清爽了?至于 BeanPostProcessor、BeanFactoryPostProcessor 以及其他的类,在老周看来,只不过是对主流程四个步骤的一系列扩展点而已。

    三、Spring Bean 的生命周期的扩展点

    Spring Bean 的生命周期的扩展点超级多,老周这里不可能全部列出来,只说核心的扩展点。这也就是为什么 Spring 的扩展性很好的原因,开了很多的口子,尽可能让某个功能高内聚松耦合,用户需要哪个功能就用哪个,而不是直接来一个大而全的东西。

    3.1 Bean 自身的方法

    比如构造函数、getter/setter 以及 init-method 和 destory-method 所指定的方法等,也就对应着上文说的实例化 -> 属性赋值 -> 初始化 -> 销毁四个阶段。

    在这里插入图片描述
    3.2 容器级的方法(BeanPostProcessor 一系列接口)

    主要是后处理器方法,比如下图的 InstantiationAwareBeanPostProcessorBeanPostProcessor 接口方法。这些接口的实现类是独立于 Bean 的,并且会注册到 Spring 容器中。在 Spring 容器创建任何 Bean 的时候,这些后处理器都会发生作用。

    在这里插入图片描述
    3.2.1 InstantiationAwareBeanPostProcessor 源码分析

    我们翻一下源码发现 InstantiationAwareBeanPostProcessor 是继承了 BeanPostProcessor

    在这里插入图片描述
    在这里插入图片描述

    • InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 调用点

    Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName)
    返回值:如果返回的不为null,那么后续的Bean的创建流程【实例化、初始化afterProperties】都不会执行,而是直接使用返回的快捷Bean,此时的正常执行顺序如下:
    InstantiationAwareBeanPostProcessor接口中的postProcessBeforeInstantiation,在实例化之前调用。
    BeanPostProcessor接口中的postProcessAfterInitialization,在实例化之后调用。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    总之,postProcessBeforeInstantiation 在 doCreateBean 之前调用,也就是在 bean 实例化之前调用的,英文源码注释解释道该方法的返回值会替换原本的 Bean 作为代理,这也是 AOP 等功能实现的关键点。

    • InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation 调用点

    boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException
    正常情况下在实例化之后在执行populateBean之前调用
    返回值:如果有指定的bean的时候返回false,那么后续的属性填充和属性依赖注入【populateBean】将不会执行,同时后续的postProcessPropertyValues将不会执行,但是初始化和BeanPostProcessor的仍然会执行。

    在这里插入图片描述

    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
    实例化之后调用,在方法applyPropertyValues【属性填充】之前
    返回值:如果返回null,那么将不会进行后续的属性填充,比如依赖注入等,如果返回的pvs额外的添加了属性,那么后续会填充到该类对应的属性中。
    pvs:PropertyValues对象,用于封装指定类的对象,简单来说就是PropertyValue的集合,里面相当于以key-value形式存放类的属性和值。
    pds:PropertyDescriptor对象数组,PropertyDescriptor相当于存储类的属性,不过可以调用set,get方法设置和获取对应属性的值。

    在这里插入图片描述
    3.2.2 BeanPostProcessor 源码分析

    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

    在这里插入图片描述
    进入初始化接口:

    在这里插入图片描述
    我们先来看

    org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization

    在这里插入图片描述

    • 首先获取到所有的后置处理器 getBeanPostProcessors()
    • 在 for 循环中依次调用后置处理器的方法 processor.postProcessBeforeInitialization(result, beanName);
    • 进入 postProcessBeforeInitialization 方法

    org.springframework.context.support.ApplicationContextAwareProcessor#postProcessBeforeInitialization

    在这里插入图片描述

    进入 invokeAwareInterfaces(bean); 方法,当前 bean 实现了 ApplicationContextAware 接口。

    在这里插入图片描述

    • ApplicationContextAwareProcessor#postProcessBeforeInitialization 首先判断此 bean 是不是各种的Aware,如果是它列举的那几个 Aware 就获取 Bean 工厂的权限,可以向容器中导入相关的上下文环境,目的是为了 Bean 实例能够获取到相关的上下文,如果不是它列举的几个 Aware,那就调用 invokeAwareInterfaces(bean),向容器中添加相关接口的上下文环境。

    3.3 工厂后处理器方法(BeanFactoryProcessor 一系列接口)

    包括 AspectJWeavingEnablerCustomAutowireConfigurerConfigurationClassPostProcessor 等。这些都是 Spring 框架中已经实现好的 BeanFactoryPostProcessor,用来实现某些特定的功能。

    我们知道 Spring IoC 容器初始化的关键环节就在 org.springframework.context.support.AbstractApplicationContext#refresh 方法中 ,容器创建的主体流程都在这个方法里面,这个方法是真的重要!!!

    对于工厂后处理器方法老周这里直接带你看 invokeBeanFactoryPostProcessors(beanFactory); 方法,这个方法处理的是 BeanFactoryPostProcessor 接口的 Bean。调用方法如下:

    在这里插入图片描述
    跟到最重要的方法里去,代码虽长,但逻辑中规中矩。

    BeanFactoryPostProcessor:一切处理 BeanFactory 的父接口
    BeanDefinitionRegistryPostProcessor:实现了 BeanFactoryPostProcessor 接口的接口

    在这里插入图片描述
    流程说明:

    • 调用 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(registry) 方法。参数 beanFactoryPostProcessors 传入的优先处理掉。然后获取容器注册的,对于这些 Bean 按照 PriorityOrdered 接口、Ordered、没有排序接口的实例分别进行处理。
    • 调用 BeanFactoryPostProcessor#postProcessBeanFactory(beanFactory) 方法。备注:BeanDefinitionRegistryPostProcessor 属于 BeanFactoryPostProcessor 子接口。先处理属于 BeanDefinitionRegistryPostProcessor 接口实例的 postProcessBeanFactory(beanFactory) 方法,然后获取容器注册的。对于这些 Bean 按照 PriorityOrdered 接口、Ordered、没有排序接口的实例分别进行处理。

    3.4 Bean 级生命周期方法

    可以理解为 Bean 类直接实现接口的方法,比如 BeanNameAwareBeanFactoryAwareApplicationContextAwareInitializingBeanDisposableBean 等方法,这些方法只对当前 Bean 生效。

    3.4.1 Aware 类型的接口

    Aware 类型的接口的作用就是让我们能够拿到 Spring 容器中的一些资源。基本都能够见名知意,Aware 之前的名字就是可以拿到什么资源,例如 BeanNameAware 可以拿到 BeanName,以此类推。调用时机需要注意:所有的 Aware 方法都是在初始化阶段之前调用的

    Aware 接口众多,这里同样通过分类的方式帮助大家记忆。Aware 接口具体可以分为两组,至于为什么这么分,详见下面的源码分析。如下排列顺序同样也是 Aware 接口的执行顺序,能够见名知意的接口不再解释。

    Aware Group1

    • BeanNameAware
    • BeanClassLoaderAware
    • BeanFactoryAware

    Aware Group2

    • EnvironmentAware
    • EmbeddedValueResolverAware
      这个知道的人可能不多,实现该接口能够获取 Spring EL 解析器,用户的自定义注解需要支持 SPEL 表达式的时候可以使用,非常方便。
    • ApplicationContextAware(ResourceLoaderAware/ApplicationEventPublisherAware/MessageSourceAware)
      这几个接口可能让人有点懵,实际上这几个接口可以一起记,其返回值实质上都是当前的 ApplicationContext 对象,因为 ApplicationContext 是一个复合接口,如下:
      在这里插入图片描述

    Aware 调用时机源码分析

    在这里插入图片描述
    可以看到并不是所有的 Aware 接口都使用同样的方式调用。Bean××Aware 都是在代码中直接调用的,而 ApplicationContext 相关的 Aware 都是通过 BeanPostProcessor#postProcessBeforeInitialization() 实现的。感兴趣的可以自己看一下 ApplicationContextAwareProcessor 这个类的源码,就是判断当前创建的 Bean 是否实现了相关的 Aware 方法,如果实现了会调用回调方法将资源传递给 Bean。

    BeanPostProcessor 的调用时机也能在这里体现,包围住 invokeInitMethods 方法,也就说明了在初始化阶段的前后执行。

    关于 Aware 接口的执行顺序,其实只需要记住第一组在第二组执行之前就行了。

    3.4.2 生命周期接口

    至于剩下的两个生命周期接口就很简单了,实例化和属性赋值都是 Spring 帮助我们做的,能够自己实现的有初始化和销毁两个生命周期阶段。

    • InitializingBean 对应生命周期的初始化阶段,在上面源码的 invokeInitMethods(beanName, wrappedBean, mbd);方法中调用。
      有一点需要注意,因为 Aware 方法都是执行在初始化方法之前,所以可以在初始化方法中放心大胆的使用 Aware 接口获取的资源,这也是我们自定义扩展 Spring 的常用方式。
      除了实现 InitializingBean 接口之外还能通过注解或者 xml 配置的方式指定初始化方法,至于这几种定义方式的调用顺序其实没有必要记。因为这几个方法对应的都是同一个生命周期,只是实现方式不同,我们一般只采用其中一种方式。
    • DisposableBean 类似于 InitializingBean,对应生命周期的销毁阶段,以ConfigurableApplicationContext#close()方法作为入口,实现是通过循环取所有实现了 DisposableBean 接口的 Bean 然后调用其 destroy() 方法,感兴趣的可以自行跟一下源码。

    3.5 Spring Bean 生命周期流程图

    在这里插入图片描述

    四、常用接口说明

    4.1 BeanNameAware

    该接口只有一个方法 setBeanName(String name),用来获取 bean 的 id 或者 name

    4.2 BeanFactoryAware

    该接口只有一个方法 setBeanFactory(BeanFactory beanFactory),用来获取当前环境中的 BeanFactory

    4.3 ApplicationContextAware

    该接口只有一个方法 setApplicationContext(ApplicationContext applicationContext),用来获取当前环境中的 ApplicationContext

    4.4 InitializingBean

    该接口只有一个方法 afterPropertiesSet(),在属性注入完成后调用

    4.5 DisposableBean

    该接口只有一个方法 destroy(),在容器销毁的时候调用,在用户指定的 destroy-method 之前调用

    4.6 BeanPostProcessor

    该接口有两个方法:

    • postProcessBeforeInitialization(Object bean, String beanName):在初始化之前调用此方法
    • postProcessAfterInitialization(Object bean, String beanName):在初始化之后调用此方法

    通过方法签名我们可以知道,我们可以通过 beanName 来筛选出我们需要进行个性化定制的 bean。

    4.7 InstantiationAwareBeanPostProcessor

    该类是 BeanPostProcessor 的子接口,常用的有如下三个方法:

    • postProcessBeforeInstantiation(Class beanClass, String beanName):在bean实例化之前调用
    • postProcessProperties(PropertyValues pvs, Object bean, String beanName):在bean实例化之后、设置属性前调用
    • postProcessAfterInstantiation(Class beanClass, String beanName):在bean实例化之后调用

    五、代码演示

    思路:创建一个类 UserBean ,让其实现几个特殊的接口,并分别在接口实现的构造器、接口方法中断点,观察线程调用栈,分析出 Bean 对象创建和管理关键点的触发时机。

    5.1 UserBean 类

    @Component
    public class UserBean implements InitializingBean, BeanNameAware, DisposableBean, ApplicationContextAware {
    	private int id;
    
    	private String name;
    
    	public UserBean(int id, String name) {
    		this.id = id;
    		this.name = name;
    		System.out.println("2. 调用构造函数");
    	}
    
    	public int getId() {
    		return id;
    	}
    
    	public void setId(int id) {
    		this.id = id;
    		System.out.println("5. 属性注入 id");
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    		System.out.println("5. 属性注入 name");
    	}
    
    	@Override
    	public void setBeanName(String name) {
    		System.out.println("6. 调用 BeanNameAware.setBeanName() 方法");
    	}
    
    	@Override
    	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    		UserBean userBean = (UserBean) applicationContext.getBean("userBean");
    		System.out.println(userBean);
    		System.out.println("7. 调用 BeanNameAware.setBeanName() 方法");
    	}
    
    	@Override
    	public void afterPropertiesSet() throws Exception {
    		System.out.println("9. 调用 InitializingBean.afterPropertiesSet() 方法");
    	}
    
    	public void myInit() {
    		System.out.println("10. 调用 init-method 方法");
    	}
    
    	@Override
    	public void destroy() throws Exception {
    		System.out.println("12. 调用 DisposableBean.destroy() 方法");
    	}
    
    	public void myDestroy() {
    		System.out.println("13. 调用 destroy-method 方法");
    	}
    
    	@Override
    	public String toString() {
    		return "UserBean{" +
    				"id=" + id +
    				", name='" + name + '\'' +
    				'}';
    	}
    }
    

    5.2 InstantiationAwareBeanPostProcessor 接口实现类

    @Component
    public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
    	@Override
    	public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
    		if ("userBean".equals(beanName)) {
    			System.out.println("1. 调用 InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation() 方法");
    		}
    		return null;
    	}
    
    	@Override
    	public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
    		if ("userBean".equals(beanName)) {
    			UserBean userBean = (UserBean) bean;
    			System.out.println("3. 调用 InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation() 方法");
    			System.out.println(userBean);
    		}
    		return true;
    	}
    
    	@Override
    	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
    		if ("userBean".equals(beanName)) {
    			System.out.println("4. 调用 InstantiationAwareBeanPostProcessor.postProcessProperties() 方法");
    		}
    		return null;
    	}
    }
    

    5.3 BeanPostProcessor 接口实现类

    @Component
    public class MyBeanPostProcessor implements BeanPostProcessor {
    	@Override
    	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    		if ("userBean".equals(beanName)) {
    			System.out.println("8. 调用 BeanPostProcessor.postProcessBeforeInitialization() 方法");
    		}
    		return bean;
    	}
    
    	@Override
    	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    		if ("userBean".equals(beanName)) {
    			System.out.println("11. 调用 BeanPostProcessor.postProcessAfterInitialization() 方法");
    		}
    		return bean;
    	}
    }
    

    5.4 BeanFactoryPostProcessor 接口实现类

    @Component
    public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    	@Override
    	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    		System.out.println("0. 调用 BeanFactoryPostProcessor.postProcessBeanFactory() 方法");
    	}
    }
    

    5.5 applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	   xsi:schemaLocation="
    	    http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
    ">
    
    	<bean class="com.riemann.test.MyInstantiationAwareBeanPostProcessor" />
    
    	<bean id="userBean" class="com.riemann.test.UserBean" init-method="myInit" destroy-method="myDestroy">
    		<!-- 构造函数注入 -->
    		<constructor-arg index="0" type="int">
    			<value>1</value>
    		</constructor-arg>
    		<constructor-arg index="1" type="java.lang.String">
    			<value>微信公众号【老周聊架构】</value>
    		</constructor-arg>
    
    		<!-- setter方法注入 -->
    		<property name="id" value="2"/>
    		<property name="name" value="riemann"/>
    	</bean>
    
    	<bean class="com.riemann.test.MyBeanPostProcessor" />
    
    	<bean class="com.riemann.test.MyBeanFactoryPostProcessor" />
    	
    </beans>
    

    5.6 测试类

    public class BeanLifeCycleTest {
    	public static void main(String[] args) {
    		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    		UserBean user = (UserBean) applicationContext.getBean("userBean");
    		((AbstractApplicationContext) applicationContext).close();
    	}
    }
    

    5.7 控制台结果打印

    在这里插入图片描述

    展开全文
  • Vue 3 生命周期完整指南

    千次阅读 2021-03-31 08:28:34
    本文主要内容: Vue生命周期钩子有哪些 在选项API中使用 Vue 生命周期钩子 在组合API中使用Vue 3生命周期钩子 将 Vue2 的生命周期钩子代码更新到 Vue3 看看Vue 2和Vue 3中的每个生命周期钩子 创建 挂载 更新 卸载 ...

    highlight: a11y-dark
    theme: condensed-night-purple

    作者:Michael Thiessen
    译者:前端小智
    来源:news

    点赞再看,微信搜索**【大迁世界,B站关注前端小智】**这个没有大厂背景,但有着一股向上积极心态人。本文 GitHub https://github.com/qq449245884/xiaozhi 上已经收录,文章的已分类,也整理了很多我的文档,和教程资料。

    最近开源了一个 Vue 组件,还不够完善,欢迎大家来一起完善它,也希望大家能给个 star 支持一下,谢谢各位了。

    github 地址:https://github.com/qq449245884/vue-okr-tree

    Vue2 和 Vue3 中的生命周期钩子的工作方式非常相似,我们仍然可以访问相同的钩子,也希望将它们能用于相同的场景。

    如果项目使用 选项 API,就不必更改任何代码了,因为 Vue3 兼容以前的版本。

    当然,我们用 Vue3 就是要用它的 组合 API组合 API中访问这些钩子的方式略有不同,组合API在较大的Vue项目中特别有用。

    本文主要内容:

    1. Vue生命周期钩子有哪些
    2. 选项API中使用 Vue 生命周期钩子
    3. 组合API中使用Vue 3生命周期钩子
    4. 将 Vue2 的生命周期钩子代码更新到 Vue3
    5. 看看Vue 2和Vue 3中的每个生命周期钩子
    6. 创建
    7. 挂载
    8. 更新
    9. 卸载
    10. 激活
    11. Vue 3中的新调试钩子

    Vue生命周期钩子有哪些

    首先,来看一下 选项API 和 组合 API中 Vue 3生命周期钩子的图表。在深入细节之前,这能加深我们的理解。

    image.png

    本质上,每个主要Vue生命周期事件被分成两个钩子,分别在事件之前和之后调用。Vue应用程序中有4个主要事件(8个主要钩子)。

    • 创建 — 在组件创建时执行
    • 挂载 — DOM 被挂载时执行
    • 更新 — 当响应数据被修改时执行
    • 销毁 — 在元素被销毁之前立即运行

    选项API中使用 Vue 生命周期钩子

    使用 选项API,生命周期钩子是被暴露 Vue实例上的选项。我们不需要导入任何东西,只需要调用这个方法并为这个生命周期钩子编写代码。

    例如,假设我们想访问mounted()updated()生命周期钩子,可以这么写:

    // 选项 API
    <script>     
       export default {         
          mounted() {             
             console.log('mounted!')         
          },         
          updated() {             
             console.log('updated!')         
          }     
       }
    </script> 
    

    组合API中使用Vue 3生命周期钩子

    在组合API中,我们需要将生命周期钩子导入到项目中,才能使用,这有助于保持项目的轻量性。

    // 组合 API
    import { onMounted } from 'vue'
    

    除了beforecatecreated(它们被setup方法本身所取代),我们可以在setup方法中访问的API生命周期钩子有9个选项:

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

    • onMounted – 组件挂载时调用

    • onBeforeUpdate – 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。

    • onUpdated – 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

    • onBeforeUnmount – 在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。

    • onUnmounted – 卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。

    • onActivated – 被 keep-alive 缓存的组件激活时调用。

    • onDeactivated – 被 keep-alive 缓存的组件停用时调用。

    • onErrorCaptured – 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

    使用事例:

    // 组合 API
    <script>
    import { onMounted } from 'vue'
    
    export default {
       setup () {
         onMounted(() => {
           console.log('mounted in the composition api!')
         })
       }
    }
    </script>
    

    将 Vue2 的生命周期钩子代码更新到 Vue3

    这个从Vue2 到Vue3的生命周期映射是直接从Vue 3 Composition API文档中获得的:

    • beforeCreate -> 使用 setup()

    • created -> 使用 setup()

    • beforeMount -> onBeforeMount

    • mounted -> onMounted

    • beforeUpdate -> onBeforeUpdate

    • updated -> onUpdated

    • beforeDestroy -> onBeforeUnmount

    • destroyed -> onUnmounted

    • errorCaptured -> onErrorCaptured

    深入了解每个生命周期钩子

    我们现在了解了两件重要的事情:

    • 我们可以使用的不同的生命周期钩子
    • 如何在选项API和组合API中使用它们

    我们深入一下每个生命周期钩子,看看它们是如何被使用的,我们可以在每个钩子中编写特定代码,来测试在Options API和Composition API中的各自的区别。

    beforeCreate() – 选项 API

    由于创建的挂钩是用于初始化所有响应数据和事件的事物,因此beforeCreate无法访问组件的任何响应数据和事件。

    以下面的代码块为例:

    // 选项 API
    export default {
       data() { 
         return { 
           val: 'hello'    
         }
       },
       beforeCreate() {     
         console.log('Value of val is: ' + this.val)   
       }
    }
    

    val的输出值是 undefined,因为尚未初始化数据,我们也不能在这调用组件方法。

    如果你想查看可用内容的完整列表,建议只运行console.log(this)来查看已初始化的内容。当使用选项API时,这做法在其他钩子中也很有用。

    created() – 选项 API

    如果我们要在组件创建时访问组件的数据和事件,可以把上面的 beforeCreatecreated代替。

    // 选项API
    export default {
       data() { 
         return { 
           val: 'hello'    
         }
       },
       created() {     
         console.log('Value of val is: ' + this.val)   
       }
    }
    

    其输出为Value of val is: hello,因为我们已经初始化了数据。

    在处理读/写反应数据时,使用created 的方法很有用。 例如,要进行API调用然后存储该值,则可以在此处进行此操作。

    最好在这里执行此操作,而不是在mounted 中执行此操作,因为它发生在Vue的同步初始化过程中,并且我们需要执行所有数据读取/写入操作。

    那么组合API的创建钩子呢?

    对于使用 组合API 的 Vue3 生命周期钩子,使用setup()方法替换beforecatecreated。这意味着,在这些方法中放入的任何代码现在都只在setup方法中。

    // 组合AP
    import { ref } from 'vue'
    
    export default {
       setup() {    
         const val = ref('hello') 
         console.log('Value of val is: ' + val.value)       
         return {         
           val
         }
       }
    }
    

    beforeMount() and onBeforeMount()

    在组件DOM实际渲染安装之前调用。在这一步中,根元素还不存在。在选项API中,可以使用this.$els来访问。在组合API中,为了做到这一点,必须在根元素上使用ref

    // 选项 API
    export default {
       beforeMount() {
         console.log(this.$el)
       }
     }
    

    组合API中使用 ref:

    // 组合 API
    <template>
       <div ref='root'>
         Hello World
       </div>
    </template> 
    
    import { ref, onBeforeMount } from 'vue'
    
    export default {
       setup() {
          const root = ref(null) 
          onBeforeMount(() => {   
             console.log(root.value) 
          }) 
          return { 
             root
          }
        },
        beforeMount() {
          console.log(this.$el)
        }
     }
    

    因为app.$el还没有创建,所以输出将是undefined

    mounted() and onMounted()

    在组件的第一次渲染后调用,该元素现在可用,允许直接DOM访问

    同样,在 选项API中,我们可以使用this.$el来访问我们的DOM,在组合API中,我们需要使用refs来访问Vue生命周期钩子中的DOM。

    import { ref, onMounted } from 'vue'
     
    
     export default {
       setup() {    /* 组合 API */
     
         const root = ref(null)
     
         onMounted(() => {
           console.log(root.value)
         })
     
    
         return {
           root
         }
       },
       mounted() { /* 选项 API */
         console.log(this.$el)
       }
     } 
    

    beforeUpdate() and onBeforeUpdate()

    数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。

    beforeUpdate对于跟踪对组件的编辑次数,甚至跟踪创建“撤消”功能的操作很有用。

    updated() and onUpdated()

    DOM更新后,updated的方法即会调用。

    <template>
        <div>
          <p>{{val}} | edited {{ count }} times</p>
          <button @click='val = Math.random(0, 100)'>Click to Change</button>
        </div>
     </template> 
    

    选项 API 方式:

     export default {
       data() {
          return {
            val: 0
          }
       },
       beforeUpdate() {
          console.log("beforeUpdate() val: " + this.val)
       },
       updated() {
          console.log("updated() val: " + this.val
       }
     } 
    

    组合API的方式:

    import { ref, onBeforeUpdate, onUpdated } from 'vue'
     
     export default {
       setup () {
         const count = ref(0)
         const val = ref(0)
     
         onBeforeUpdate(() => {
           count.value++;
           console.log("beforeUpdate");
         })
     
         onUpdated(() => {
           console.log("updated() val: " + val.value)
         })
     
         return {
           count, val
         }
       }
     }
    

    这些方法很有用,但是对于更多场景,我们需要使用的watch方法检测这些数据更改。 watch 之所以好用,是因为它给出了更改后的数据的旧值和新值。

    另一种选择是使用计算属性来基于元素更改状态。

    beforeUnmount() 和 onBeforeUnmounted()

    在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。

    在 选项 API中,删除事件侦听器的示例如下所示。

    // 选项 API
    export default {
       mounted() {
         console.log('mount')
         window.addEventListener('resize', this.someMethod);
       },
       beforeUnmount() {
         console.log('unmount')
         window.removeEventListener('resize', this.someMethod);
       },
       methods: {
          someMethod() {
             // do smth
          }
       }
    } 
    
    // 组合API
    import { onMounted, onBeforeUnmount } from 'vue' 
    
     export default {
       setup () {
     
         const someMethod = () => {
           // do smth
         }
     
         onMounted(() => {
           console.log('mount')
           window.addEventListener('resize', someMethod);
         })
     
         onBeforeUnmount(() => {
           console.log('unmount')
           window.removeEventListener('resize', someMethod);
         })
     
       }
     }
    

    实际操作的一种方法是在Vite,vue-cli或任何支持热重载的开发环境中,更新代码时,某些组件将自行卸载并安装。

    unmounted() 和 onUnmounted()

    卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。

    import { onUnmounted } from 'vue'
    
    export default {
      setup () { /* 组合 API */
    
        onUnmounted(() => {
          console.log('unmounted')
        })
    
      },
      unmounted() { /* 选项 API */
        console.log('unmounted')
      }
    }
    

    activated() and onActivated()

    keep-alive 缓存的组件激活时调用。

    例如,如果我们使用keep-alive组件来管理不同的选项卡视图,每次在选项卡之间切换时,当前选项卡将运行这个 activated 钩子。

    假设我们使用keep-alive包装器进行以下动态组件。

    <template>
       <div>
         <span @click='tabName = "Tab1"'>Tab 1 </span>
         <span @click='tabName = "Tab2"'>Tab 2</span>
         <keep-alive>
           <component :is='tabName' class='tab-area'/>
         </keep-alive>
       </div>
    </template>
    
    <script>
    import Tab1 from './Tab1.vue'
    import Tab2 from './Tab2.vue'
    
    import { ref } from 'vue'
    
    export default {
      components: {
        Tab1,
        Tab2
      },
      setup () { /* 组合 API */
        const tabName = ref('Tab1')
    
        return {
          tabName
        }
      }
    }
    </script>
    

    Tab1.vue组件内部,我们可以像这样访问activated钩子。

    <template>
     <div>
     <h2>Tab 1</h2>
     <input type='text' placeholder='this content will persist!'/>
     </div>
    </template>
    
    <script>
    import { onActivated } from 'vue'
    
    export default {
     setup() {
        onActivated(() => {
           console.log('Tab 1 Activated')
        })
     }
    } 
    </script>
    

    deactivated() 和 onDeactivated()

    keep-alive 缓存的组件停用时调用。

    这个钩子在一些用例中很有用,比如当一个特定视图失去焦点时保存用户数据和触发动画。

    import { onActivated, onDeactivated } from 'vue'
    
    export default {
      setup() {
        onActivated(() => {
           console.log('Tab 1 Activated')
        })
    
        onDeactivated(() => {
           console.log('Tab 1 Deactivated')
        })
      }
    }
    
    

    现在,当我们在选项卡之间切换时,每个动态组件的状态都将被缓存和保存。

    4216299740-605e8878b7355_fix732.gif

    Vue3 调试钩子

    Vue3 为我们提供了两个可用于调试目的的钩子。

    1. onRenderTracked
    2. onRenderTriggered

    这两个事件都带有一个debugger event,此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。

    onRenderTracked

    跟踪虚拟 DOM 重新渲染时调用。钩子接收 debugger event 作为参数。此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。

    <div id="app">
      <button v-on:click="addToCart">Add to cart</button>
      <p>Cart({{ cart }})</p>
    </div>
    
    const app = Vue.createApp({
      data() {
        return {
          cart: 0
        }
      },
      renderTracked({ key, target, type }) {
        console.log({ key, target, type })
        /* 当组件第一次渲染时,这将被记录下来:
        {
          key: "cart",
          target: {
            cart: 0
          },
          type: "get"
        }
        */
      },
      methods: {
        addToCart() {
          this.cart += 1
        }
      }
    })
    
    app.mount('#app')
    

    renderTracked

    当虚拟 DOM 重新渲染为 triggered.Similarly 为renderTracked,接收 debugger event 作为参数。此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。

    用法:

    <div id="app">
      <button v-on:click="addToCart">Add to cart</button>
      <p>Cart({{ cart }})</p>
    </div>
    
    const app = Vue.createApp({
      data() {
        return {
          cart: 0
        }
      },
      renderTriggered({ key, target, type }) {
        console.log({ key, target, type })
      },
      methods: {
        addToCart() {
          this.cart += 1
          /* 这将导致renderTriggered调用
            {
              key: "cart",
              target: {
                cart: 1
              },
              type: "set"
            }
          */
        }
      }
    })
    
    app.mount('#app')
    

    总结

    无论你选择使用选项API还是 组合API,不仅要知道要使用哪个生命周期挂钩,而且要知道为什么要使用它,这一点很重要。

    对于许多问题,可以使用多个生命周期钩子。但是最好知道哪个是最适合你用例的。无论如何,你都应该好好考虑一下,并有充分的理由去选择一个特定的生命周期钩子。

    我希望这能帮助大家更多地理解生命周期钩子以及如何在大家的项目中实现它们。

    ~完,我是刷碗智,我要去刷碗了,骨的白。


    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

    原文:https://learnvue.co/2020/12/how-to-use-lifecycle-hooks-in-vue3/

    交流

    文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

    展开全文
  • 超详细vue生命周期解析(详解)

    万次阅读 多人点赞 2020-12-25 14:37:22
    每个生命周期又是什么时候开始执行的。我们今天来详细的看一看 首先,生命周期是个啥? 借用官网的一句话就是:每一个vue实例从创建到销毁的过程,就是这个vue实例的生命周期。在这个过程中,他经历了从开始创建、...

    vue是每一个前端开发人员都绕不过的一个技术,在国内的市场占有量也是非常的大,我们大部分人用着vue, 却不知道他内部其实经历了一些什么。每个生命周期又是什么时候开始执行的。我们今天来详细的看一看

    首先,生命周期是个啥?
    借用官网的一句话就是:每一个vue实例从创建到销毁的过程,就是这个vue实例的生命周期。在这个过程中,他经历了从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、卸载等一系列过程。那么这些过程中,具体vue做了些啥,我们今天来了解一下。

    语述

    了解之前,我们先贴上一张官网的生命周期图,从图上,我们再一步一步来理解vue生命周期。
    在这里插入图片描述
    我们先简单的来解说这张图,然后再通过例子来详看
    首先,从图上,我们可以看出,他的一个过程是

    1. new Vue()实例化一个vue实例,然后init初始化event 和 lifecycle, 其实这个过程中分别调用了3个初始化函数(initLifecycle(), initEvents(), initRender()),分别初始化了生命周期,事件以及定义createElement函数,初始化生命周期时,定义了一些属性,比如表示当前状态生命周期状态得_isMounted ,_isDestroyed ,_isBeingDestroyed,表示keep-alive中组件状态的_inactive,而初始化event时,实际上就是定义了$once、$off、$emit、$on几个函数。而createElement函数是在初始化render时定义的(调用了initRender函数)
    2. 执行beforeCreate生命周期函数
    3. beforeCreate执行完后,会开始进行数据初始化,这个过程,会定义data数据,方法以及事件,并且完成数据劫持observe以及给组件实例配置watcher观察者实例。这样,后续当数据发生变化时,才能感知到数据的变化并完成页面的渲染
    4. 执行created生命周期函数,所以,当这个函数执行的时候,我们已经可以拿到data下的数据以及methods下的方法了,所以在这里,我们可以开始调用方法进行数据请求了
    5. created执行完后,我们可以看到,这里有个判断,判断当前是否有el参数(这里为什么需要判断,是因为我们后面的操作是会依赖这个el的,后面会详细说),如果有,我们再看是否有template参数。如果没有el,那么我们会等待调用$mount(el)方法(后面会详细说)。
    6. 确保有了el后,继续往下走,判断当有template参数时,我们会选择去将template模板转换成render函数(其实在这前面是还有一个判断的,判断当前是否有render函数,如果有的话,则会直接去渲染当前的render函数,如果没有那么我们才开始去查找是否有template模板),如果没有template,那么我们就会直接将获取到的el(也就是我们常见的#app,#app里面可能还会有其他标签)编译成templae, 然后在将这个template转换成render函数。
    7. 之后再调用beforMount, 也就是说实际从creted到beforeMount之间,最主要的工作就是将模板或者el转换为render函数。并且我们可以看出一点,就是你不管是用el,还是用template, 或者是用我们最常用的.vue文件(如果是.vue文件,他其实是会先编译成为template),最终他都是会被转换为render函数的。
    8. beforeMount调用后,我们是不是要开始渲染render函数了,首先我们会先生产一个虚拟dom(用于后续数据发生变化时,新老虚拟dom对比计算),进行保存,然后再开始将render渲染成为真实的dom。渲染成真实dom后,会将渲染出来的真实dom替换掉原来的vm.$el(这一步我们可能不理解,请耐心往下看,后面我会举例说明),然后再将替换后的$el append到我们的页面内。整个初步流程就算是走完了
    9. 之后再调用mounted,并将标识生命周期的一个属性_isMounted 置为true。所以mounted函数内,我们是可以操作dom的,因为这个时候dom已经渲染完成了。
    10. 再之后,只有当我们状态数据发生变化时,我们在触发beforeUpdate,要开始将我们变化后的数据渲染到页面上了(实际上这里是有个判断的,判断当前的_isMounted是不是为ture并且_isDestroyed是不是为false,也就是说,保证dom已经被挂载的情况下,且当前组件并未被销毁,才会走update流程)
    11. beforeUpdate调用之后,我们又会重新生成一个新的虚拟dom(Vnode),然后会拿这个最新的Vnode和原来的Vnode去做一个diff算,这里就涉及到一系列的计算,算出最小的更新范围,从而更新render函数中的最新数据,再将更新后的render函数渲染成真实dom。也就完成了我们的数据更新
    12. 然后再执行updated,所以updated里面也可以操作dom,并拿到最新更新后的dom。不过这里我要插一句话了,mouted和updated的执行,并不会等待所有子组件都被挂载完成后再执行,所以如果你希望所有视图都更新完毕后再做些什么事情,那么你最好在mouted或者updated中加一个$nextTick(),然后把要做的事情放在$netTick()中去做(至于为什么,以后讲到$nextTick再说吧)
    13. 再之后beforeDestroy没啥说的,实例销毁前,也就是说在这个函数内,你还是可以操作实例的
    14. 之后会做一系列的销毁动作,解除各种数据引用,移除事件监听,删除组件_watcher,删除子实例,删除自身self等。同时将实例属性_isDestroyed置为true
    15. 销毁完成后,再执行destroyed

    示例

    大致过程就是这样,下面我们来通过例子来看一看

    <body>
        <div id="app">
            <p>{{message}}</p>
            <button @click="changeMsg">改变</button>
        </div>
    </body>
    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                message: 'hello world'
            },
            methods: {
                changeMsg () {
                    this.message = 'goodbye world'
                }
            },
            beforeCreate() {
                console.log('------初始化前------')
                console.log(this.message)
                console.log(this.$el)
            },
            created () {
                console.log('------初始化完成------')
                console.log(this.message)
                console.log(this.$el)
            },
            beforeMount () {
                console.log('------挂载前---------')
                console.log(this.message)
                console.log(this.$el)
            },
            mounted () {
                console.log('------挂载完成---------')
                console.log(this.message)
                console.log(this.$el)
            },
            beforeUpdate () {
                console.log('------更新前---------')
                console.log(this.message)
                console.log(this.$el)
            },
            updated() {
                console.log('------更新后---------')
                console.log(this.message)
                console.log(this.$el)
            }
        })
    </script>
    

    我们先看看首次加载时,输出了啥
    在这里插入图片描述

    从上面我们可以看出几点,

    • 首次,只执行了4个生命周期,beforeCreate,created, beforeMount, mounted。
    • 同时,我们可以看出,第一个生命周期中,我们拿不到data中的数据,因为这个时候数据还未初始化
    • created中,我们可以拿到data中的message数据了,因为初始化已经完成
    • beforeMount中,我们可以看出,我们拿到了$el,而mounted中,我们也拿到了$el, 不过好像有点不一样是吧。一个好像是渲染前的,一个是渲染后的。对的。看过MVVM响应式原来或者Vue源码你们就会发现,最初其实我们是会去让this.$el = new Vue时传入的那个el的dom。所以在beforMount中,其实我们拿到的就是页面中的#app。而再继续往后,首先我们是不是没有找到render函数啊,也没有找到template啊,所以他会怎么做啊,是不是会把我们的这个el(#app)编译成template模板啊,再转换为render函数,最后将render函数渲染成为真实dom,渲染成真实dom后,我们是不是会用这个渲染出来的dom去替换原来的vm.$el啊。这也就是我们前面所说到的替换$el是什么意思了。
    • 所以, 在mounted中,我们所得到的渲染完成后的$el。

    下面我们再看个例子

    var vm = new Vue({
            el: '#app',
            data: {
                message: 'hello world'
            },
            template: '<div>我是模板内的{{message}}</div>',
            methods: {
                changeMsg () {
                    this.message = 'goodbye world'
                }
            },
            beforeCreate() {
                console.log('------初始化前------')
                console.log(this.message)
                console.log(this.$el)
            },
            created () {
                console.log('------初始化完成------')
                console.log(this.message)
                console.log(this.$el)
            },
            beforeMount () {
                console.log('------挂载前---------')
                console.log(this.message)
                console.log(this.$el)
            },
            mounted () {
                console.log('------挂载完成---------')
                console.log(this.message)
                console.log(this.$el)
            },
            beforeUpdate () {
                console.log('------更新前---------')
                console.log(this.message)
                console.log(this.$el)
            },
            updated() {
                console.log('------更新后---------')
                console.log(this.message)
                console.log(this.$el)
            }
        })
    

    我们在new Vue实例的时候直接传入了一个template,这时候我们再看输出
    在这里插入图片描述
    这么看是不是就很清晰了啊 ,在beforeMount的时候,$el还是#app, 但是在mounted的时候就变成模板的div了,是不是因为我们传了个template啊,所以,他直接将这个template转换成render函数啦。再渲染成真实dom后,用渲染出来的真实dom替换了原来的$el。

    下面我们删除上面的template, 点击按钮更改下message,查看输出
    在这里插入图片描述
    哎。。。有没有看到一个很奇怪的东西啊,在beforeUpdate中输出的$el居然和updated里面输出的是一样的。这不对啊,以我们上面所说的逻辑的话,beforeUpdate内的$el应该是更新前的啊。这是怎么回事呢。这时候我们先来看一下mounted里面的。mounted里面我们看到p标签内依旧是hello world 对不对,其实这是因为,我是先点击了#app那个div的箭头,将这个div展开了以后,我再点击的按钮去更改了message,所以mounted里面还是原来的。那我现在如果先不展开mounted里面的div的话,我们来看看会怎么样

    在这里插入图片描述

    可以看到,初始输出,其实是这样的,我们看不到#app内的东西,需要点击箭头展开才能看到,现在,我不展开,然后我先点击按钮去改变message, 等beforUpdate和updated都执行完成后,我们再来一起展开,看下会怎么样
    在这里插入图片描述
    这是点击改变了message后的截图,然后我们现在展开div看看
    在这里插入图片描述
    看到没有,我们发现什么啦,怎么现在mounted里面的$el也变成更新后的啦。
    呵呵,不要慌,其实啊,因为this.$el是一个对象,其实本质就是一个指针,当我们刚console.log输出的时候,其实并没有显示内容,而当我们点击箭头去展开这个div的时候,将指针指向了当前的$el,所以我们看到的才会都是改变后的$el。这也就是为什么之前mounted里面的$el是改变之前的值,而现在是改变之后的值了,因为之前那张图,我是先展开了mounted中的div,再去改变的message。下面我们再来验证下是不是这么回事
    怎么验证,我们修改下代码

    mounted () {
                console.log('------挂载完成---------')
                console.log(this.message)
                console.log(this.$el.innerHTML)
                console.log(this.$el)
            },
            beforeUpdate () {
                console.log('------更新前---------')
                console.log(this.message)
                console.log(this.$el.innerHTML)
                console.log(this.$el)
            },
            updated() {
                console.log('------更新后---------')
                console.log(this.message)
                console.log(this.$el.innerHTML)
                console.log(this.$el)
            }
    

    我们增加一个输出 this.$el.innerHTML, 再查看结果
    在这里插入图片描述

    这么看是不是就很明了啦,beforeUpdate里面的$el的内容,确实还是改变之前的,而我们之前看到的,只是因为我们后面展开时指针指向了当前值才导致的,是个视觉差而已。

    后面两个销毁的,我就不举例说明了,没啥说的。下面我们再看一个问题,就是如果我们没有设置el时,会怎么样,我们在之前的生命周期图中,是说过,当没有找到el时, 说是不是会等待vm.$mount(el) 啊,这句话啥意思,我们来看一下
    在这里插入图片描述
    首先,我们看下,vue源码中,
    在这里插入图片描述
    在执行完,beforeCreate和created之后,是做了个判断,当存在el时,调用了 $mount方法,created之后的步骤,就是在这里面去走的。那如果没有el呢, 生命周期图中是说等待vm. $mount调用。那是不是只能等待我们手动去调用啊。

    var vm = new Vue({
            data: {
                message: 'hello world'
            },
            // template: '<div>我是模板内的{{message}}  <button @click="changeMsg">点我</button></div>',
            methods: {
                changeMsg () {
                    this.message = 'goodbye world'
                }
            },
            beforeCreate() {
                console.log('------初始化前------')
                console.log(this.message)
                console.log(this.$el)
            },
            created () {
                console.log('------初始化完成------')
                console.log(this.message)
                console.log(this.$el)
            },
            beforeMount () {
                console.log('------挂载前---------')
                console.log(this.message)
                console.log(this.$el)
            },
            mounted () {
                console.log('------挂载完成---------')
                console.log(this.message)
                console.log(this.$el.innerHTML)
                console.log(this.$el)
            },
            beforeUpdate () {
                console.log('------更新前---------')
                console.log(this.message)
                console.log(this.$el.innerHTML)
                console.log(this.$el)
            },
            updated() {
                console.log('------更新后---------')
                console.log(this.message)
                console.log(this.$el.innerHTML)
                console.log(this.$el)
            }
        })
    

    这个时候,我们删除了el属性,看看结果
    在这里插入图片描述
    是不是只走了前面两个生命周期啊,后面就没走了,这个时候其实就是在等$mount被调用了,那我们加个按钮,点击按钮,手动调用一下$mount看会怎样
    在这里插入图片描述
    没点击之前
    在这里插入图片描述
    点击后
    在这里插入图片描述
    可以看到,生命周期继续往下走了。
    这时候不知道大家是不是想起来,看到有些vue项目的main.js里面是这样的

    export default new Vue({
      el: '#app',
      router,
      store,
      i18n,
      render: h => h(App)
    })
    

    而有些vue项目中人家用的又是这样的

    export default new Vue({
      router,
      store,
      i18n,
      render: h => h(App)
    }).$mount('#app')
    

    其实后者,就相当于是手动调用了$mount了。

    好了,言尽于此,有没有看懂的朋友,请直接私信或者评论。

    展开全文
  • Vue生命周期详解

    千次阅读 多人点赞 2020-06-25 11:19:34
    一、生命周期流程图详解 1.beforeCreate、Created 2.编辑模板过程 3.beforeMount、Mounted 4.beforeUpdate、Updated 5.beforeDestroy、Destroyed 二、生命周期代码 1.父子组件加载生命周期 2.父子组件更新...

    目录

    前言:

    一、生命周期流程图详解

    1.beforeCreate、Created

    2.编辑模板过程

    3.beforeMount、Mounted

    4.beforeUpdate、Updated

    5.beforeDestroy、Destroyed

    二、生命周期代码

    1.父子组件加载生命周期

    2.父子组件更新生命周期

    3.父子组件销毁生命周期


    前言:

    1、什么是vue生命周期?

    Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。

    2、vue生命周期的作用是什么?

    让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

    3、vue生命周期总共有几个阶段?

    它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后

    4、第一次页面加载会触发哪几个钩子?

    第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子

    5、DOM 渲染在 哪个周期中就已经完成?

    DOM 渲染在 mounted 中就已经完成了。

    一、生命周期流程图详解

    1.beforeCreate、Created

    2.编辑模板过程

    3.beforeMount、Mounted

    4.beforeUpdate、Updated

    5.beforeDestroy、Destroyed

    二、生命周期代码

    老规矩,先展示生命周期的执行效果

    当点击button控件跳转后,befoeCreate、ceeated、beforeMount和mounted就已经执行了。在添加事件就会更新展示beforUpdate和updated;退出这个界面就执行销毁bdfoeDestroy、destroyed。

    展示代码:

    <template>
      <div>
        <div class="label-head">
          <label>生命周期详解</label>
        </div>
        <div :data="count">{{count}}</div>
        <p>
          <button @click="add">添加</button>
        </p>
      </div>
    </template>
    <script>
    export default {
      data() {
        return {
          count: "",
          filter: "all",
          states: ["all", "active", "completed"]
        };
      },
      methods: {
        add() {
          this.count++;
        }
      },
      beforeCreate() {
        console.log("=========" + "beforeCreated:初始化之前" + "======");
        console.log(this.$el);
        console.log(this.$data);
        console.log(this.filter);
      },
      created() {
        console.log("==========" + "created:创建完成" + "===========");
        console.log(this.$el);
        console.log(this.$data);
        console.log(this.filter);
      },
      beforeMount() {
        console.log("==========" + "beforeMount:挂载之前" + "=======");
        console.log(this.$el);
        console.log(this.$data);
        console.log(this.filter);
      },
      mounted() {
        console.log("==========" + "mounted:被挂载之后" + "==========");
        console.log(this.$el);
        console.log(this.$data);
        console.log(this.filter);
      },
      beforeUpdate() {
        console.log("========" + "beforeUpdate:数据更新前" + "========");
        console.log(this.$el);
        console.log(this.$data);
        console.log(this.filter);
      },
      updated() {
        console.log("========" + "updated:被更新之后" + "============");
        console.log(this.$el);
        console.log(this.$data);
        console.log(this.filter);
      },
      beforeDestroy() {
        console.log("=========" + "beforeDestroy:销毁之前" + "========");
        console.log(this.$el);
        console.log(this.$data);
        console.log(this.filter);
      },
      destroyed() {
        console.log("==========" + "destroyed:销毁之后" + "===========");
        console.log(this.$el);
        console.log(this.$data);
        console.log(this.filter);
      },
      activated() {
        console.log("");
      },
      deactivated() {
        console.log("");
      }
    };
    </script>
    <style scoped>
    .label-head {
      text-align: center;
      font-size: 40px;
    }
    </style>
    

    整体的函数知道后,遇到父子组件时他们的函数是如何执行的呢?

    1.父子组件加载生命周期

    父组件:parents

    子组件:child、grandson

    /*parents的打印代码*/
        created () {
        console.log('============"parents created":我第一============')
      },
      beforeMount () {
        console.log('============"parents befortemounted"我第二=======')
      },
      mounted () {
        console.log('============"parents mounted"我第九==============')
      },
      /*child的打印代码*/
        created () {
        console.log('----------------"child created"我第三-------------')
      },
      beforeMount () {
        console.log('----------------"child beforemounted"我第四-------')
      },
      mounted () {
        this.$parent.panes.push(this)
        console.log('----------------"child mounted"我第七-------------')
      },
      /*grandson的打印代码*/
       created () {
        console.log('~~~~~~~~~~~~~"grandson created"我第五~~~~~~~~~~~~~')
      }
      beforeMount () {
        console.log('~~~~~~~~~~~"grandson beforemounted"我第六~~~~~~~~~')
      },
      mounted () {
        console.log('~~~~~~~~~~~~"grandson mounted"我第八~~~~~~~~~~~~~~')
      }
    

    执行顺序:

    第一圈:先执行父组件的created和beforemount函数;created和beforeMount再按子组件的使用顺序执行,

    第二圈:折回去执行mounted,先子后父!

    结论:

    父组件准备挂载还没挂载时,子组件先完成挂载;

    最后父组件再挂载!

    2.父子组件更新生命周期

    /*parents的销毁代码*/
      beforeDestroy () {
        console.log('============"parents beforDestroy"我第一=======')
      },
      destroyed () {
        console.log('============"parents destroyed"我第四==========')
      },
      /*child的销毁代码*/
      beforeDestroy () {
        console.log('------------"child beforDestroy"我第二-------')
      },
      destroyed () {
        console.log('------------"child destroyed"我第三-----------')
      },

    执行顺序:

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

    3.父子组件销毁生命周期

    /*parents的销毁代码*/
      beforeDestroy () {
        console.log('============"parents beforDestroy"我第一=======')
      },
      destroyed () {
        console.log('============"parents destroyed"我第四==========')
      },
      /*child的销毁代码*/
      beforeDestroy () {
        console.log('------------"child beforDestroy"我第二-------')
      },
      destroyed () {
        console.log('------------"child destroyed"我第三-----------')
      },

    执行顺序:

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

    展开全文
  • SpringBean生命周期详解

    千次阅读 2020-10-28 17:51:28
    SpringBean生命周期详解 一、简述: Spring是我们每天都在使用的框架,Bean是被Spring管理的Java对象,是Spring框架最重要的部分之一,那么让我们一起了解一下Spring中Bean的生命周期是怎样的吧 二、流程图 我们先从...
  • 浅谈软件以及软件的生命周期 自1946年第一台计算机发明以来,对人类的生产活动和社会活动产生了极其重要的影响,并以强大的生命力飞速发展。计算机硬件、软件、操作系统等纷纷应运而生,并发展壮大,时至今日,...
  • 1、组件的生命周期 指的是组件自身的一些函数,这些函数在特殊的时间点或遇到一些特殊的框架事件时被自动触发。 其中,最重要的生命周期是 created、attached、 detached ,包含一个组件实例生命流程的最主要时间点...
  • Spring中bean的生命周期(最详细)

    万次阅读 多人点赞 2020-02-24 14:09:35
    Spring Bean的生命周期是Spring面试热点问题。Spring Bean的生命周期指的是从一个普通的Java类变成Bean的过程,深知Spring源码的人都知道这个给面试官将的话大可将30分钟以上,如果你不知道源码,那么Aware接口和...
  • 详解java类的生命周期

    热门讨论 2012-04-27 13:11:40
    本文详细讲述了一个java类自调入内存至被卸载的整个声明周期,对理解对象的生命周期,jvm中的类加载等内容有所帮助。
  • uniapp 常用生命周期以及执行顺序

    千次阅读 2021-02-28 17:19:27
    uniapp 生命周期分为应用生命周期,页面生命周期,还有组件生命周期 我们先看一个例子: 这是项目结构,里面的App.vue是我们的App页面,应用的生命周期有onLaunch,onShow和onHide等: // App.vue <script> ...
  • vue生命周期和react生命周期对比。

    千次阅读 2019-07-06 18:04:03
    指的是组件从初始化开始到结束的过程 或者是生命周期是描述组件从开始到结束的过程,每个组件都具有生命周期,都对组件通过生命周期给予的钩子函数进行管理。 钩子函数 指的是系统某些状态和参数发生改变的时候,...
  • 项目生命周期、开发生命周期与产品生命周期的区别一级目录 一级目录
  • 1. 生命周期 App(全局) 位置:项目根目录app.js文件 App({ onLaunch (options) { // console.log('小程序初始化') }, onShow(options) { console.log('监听小程序启动或切前台') }, onHide() { ...
  • Android学习之详解Activity的生命周期

    千次阅读 2020-07-06 19:46:13
    详解Activity的生命周期
  • Spring中bean的生命周期(易懂版)

    千次阅读 多人点赞 2021-01-15 19:35:14
    bean的生命周期写在前面的话bean的生命周期代码演示bean的更完整的生命周期添加后置处理器的代码演示 写在前面的话 关于bean的生命周期有很多的文章,但是大多数都是长篇的理论,说来说去也不是很好理解,再次我就...
  • 红帽企业Linux生命周期

    千次阅读 2019-11-14 15:22:35
    红帽企业Linux生命周期 总览 细节 生产阶段 全面支持阶段 维护支持一期 维护支持阶段(RHEL 8)阶段/ 维护支持2阶段(RHEL 5、6、7) 延长使用寿命 红帽企业Linux更长的支持附加组件 ...
  • Spring IOC容器生命周期阶段总结

    千次阅读 2021-01-19 10:49:31
    Spring容器的生命周期是面试的高频题目,但是这个生命周期非常复杂,想要完全说清楚几乎不可能。但对全局有个把控,知道每个阶段大概所做的事情是很重要的,尤其在编写框架或者扩展Spring的时候尤其重要。
  • .NET ASP.NET 页生命周期概述

    千次阅读 2019-05-07 10:47:49
    ASP.NET 页生命周期概述 ASP.NET 页运行时,此页将经历一个生命周期,在生命周期中将执行一系列处理步骤。 这些步骤包括初始化、实例化控件、还原和维护状态、运行事件处理程序代码以及进行呈现。 了解页生命周期...
  • uni-app支持的应用生命周期 函数名 说明 onLaunch 当uni-app 初始化完成时触发(全局只触发一次) onShow 当 uni-app 启动,或从后台进入前台显示 onHide 当 uni-app 从前台进入...
  • Servlet的生命周期

    万次阅读 多人点赞 2019-06-02 17:36:53
    Servlet的生命周期 servlet的生命周期就是从servlet出现到销毁的全过程。主要分为以下几个阶段: 加载类—>实例化(为对象分配空间)—>初始化(为对象的属性赋值)—>请求处理(服务阶段)—>销毁 服务器启动...
  • 1.页面生命周期 StatelessWidget只有createElement和build的生命周期 StatefulWidget: 初始化周期: createState,initState 更新期间: didChangeDependencies,build, didUpdateWidget 销毁期: ...
  • java的生命周期

    千次阅读 2019-08-20 17:57:49
    类的生命周期总览 类从被加载到虚拟机内存中开始, 到卸载出内存为止, 它的整个生命周期包括: 加载 (Loading) 、 验证(Verification) 、 准备(Preparation) 、 解析(Resolution) 、 初始化...
  • 生命周期

    千次阅读 2020-08-04 19:33:36
    什么是生命周期? 通俗的讲任何事物都有它的一个生命周期比如说: 1、自然界的动植物生命周期:从初生到死亡。 2、产品的生命周期:从立项到淘汰。(一般指提市场寿命) 那vue的生命周期是什么? vue生命周期是指vue...
  • vue生命周期

    万次阅读 多人点赞 2020-09-28 09:18:11
    随着对 vue 的不断了解,会越来越发现它生命周期的重要性,只有了解了它的生命周期,才能在开发项目的时候在逻辑上的很好的判断什么时候该发生什么事件,即很好的控制页面。 一、什么是 vue 生命周期 Vue 实例从...
  • Servlet生命周期

    千次阅读 多人点赞 2019-03-05 20:14:55
    Servlet生命周期
  • 软件项目的全生命周期

    万次阅读 多人点赞 2018-10-12 16:52:05
     根据不同的项目形态,一般项目运作的周期也不大相同,下述以笔者所在公司数通畅联SOA集成项目的生命周期为例进行阐述,在步骤上与传统项目生命周期存在细微差别。 2.1 IT咨询 2.1.1 具体内容  此类项目...
  • 1.项目生命周期的阶段之间可能相互交叉,而产品生命周期的阶段通常不相互交叉; 项目生命周期:项目初始->项目计划->项目执行控制->项目结束; 产品生命周期:投入期->成长期->饱和期->衰退期;...
  • React17.0生命周期调整

    千次阅读 2020-04-21 16:07:58
    在即将到来的react17.0版本,react团队对生命周期做了调整,将会移除componentWillMount,componentWillReceiveProps,componentWillUpdate这三个生命周期,因为这些生命周期方法容易被误解和滥用。 组件数据初始化...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 950,842
精华内容 380,336
关键字:

生命周期