vue 中那些理解应用的点=》生命周期=》自定义指令=》this=>defineProperty=>proxy

发布于:2024-12-06 ⋅ 阅读:(129) ⋅ 点赞:(0)

vue阶段详解,对标上篇文章 点这里

vue2 created 阶段 详解

  1. Vue2中created阶段概述
    • 在Vue实例的生命周期中,created是一个重要的阶段。它在实例创建完成后被调用,此时,Vue实例已经完成了数据观察(data observer)的配置,但还没有开始挂载到DOM元素上。
    • 这个阶段的主要作用是进行一些初始化操作,比如数据的获取、初始化一些非响应式的数据等。
  2. 事件机制在created阶段的情况
    • 事件定义
      • created阶段,可以定义组件的事件。例如,通过this.$on方法来监听自定义事件。假设我们有一个名为MyComponent的Vue组件,在created钩子函数中可以这样定义事件监听:
        export default {
          created() {
            this.$on('custom - event', (data) => {
              console.log('Received custom - event with data:', data);
            });
          }
        }
        
      • 这里定义了一个对custom - event的监听。当组件的其他部分或者父组件通过this.$emit('custom - event', someData)触发这个事件时,在created阶段定义的这个监听器就会执行相应的操作。不过要注意,在created阶段,组件还没有挂载,所以一些和DOM操作相关的事件(如click等)可能还不能直接绑定到实际的DOM元素上。
    • 事件传播(冒泡和捕获)相关初始化
      • 对于组件内部的事件传播机制,虽然在created阶段DOM还未挂载,但是可以对事件传播相关的逻辑进行初始化。例如,确定组件是否要阻止某些事件的冒泡或者捕获。如果组件在后续的挂载过程中会包含子组件,并且子组件可能会触发一些事件,在created阶段可以设置一些标志或者逻辑来处理事件传播。
  3. 数据检测在created阶段的完成情况
    • 数据响应式系统初始化完成
      • Vue2的数据响应式是基于Object.defineProperty()来实现的。在created阶段,这个数据响应式的设置已经完成。这意味着当你在data选项中定义的数据属性,如:
        export default {
          data() {
            return {
              message: 'Hello, Vue!'
            };
          },
          created() {
            // 此时,message已经是响应式数据
            this.message = 'New message';
            console.log(this.$data.message); 
          }
        }
        
      • 当在created阶段修改this.message的值时,Vue的响应式系统会追踪这个变化。如果这个组件在模板中有对message的绑定,如<p>{{message}}</p>,那么当数据发生变化时,Vue会在后续的更新阶段(如updated生命周期钩子)自动更新DOM中的相关内容。
    • 数据验证(如果有)可以开始执行
      • 如果在组件中使用了一些数据验证库(如vee - validate等),或者自定义的数据验证逻辑,在created阶段可以开始进行数据验证。例如,假设我们有一个简单的表单数据验证逻辑:
        export default {
          data() {
            return {
              formData: {
                name: ''
              }
            };
          },
          created() {
            if (!this.formData.name) {
              console.log('Name field is empty');
            }
          }
        }
        
      • 这里在created阶段检查了formData.name是否为空。这种数据验证可以帮助确保数据的完整性和合法性,在后续的操作(如向服务器发送数据等)之前发现潜在的问题。

beforemount 之 render 函数

  1. render函数在beforeMount周期首次渲染时的作用
    • 生成虚拟DOM(VNode)
      • render函数的主要任务是创建虚拟DOM节点。它会根据组件的template(如果是通过模板定义组件)或者渲染函数内部的逻辑来生成一个虚拟DOM树。例如,对于一个简单的组件:
        export default {
          data() {
            return {
              message: 'Hello, Vue!'
            };
          },
          render(h) {
            return h('div', [this.message]);
          }
        }
        
      • 这里的render函数通过h函数(hcreateElement的别名,在Vue的渲染函数中经常使用)创建了一个<div>标签的虚拟DOM节点,并且将message数据作为它的子节点。这个虚拟DOM树描述了组件最终要渲染成的DOM结构的样子。
    • 数据绑定和插值处理
      • 在生成虚拟DOM的过程中,render函数会处理数据绑定。例如,如果有像{{message}}这样的插值表达式(假设是在template中,同样的逻辑也适用于render函数),render函数会正确地将数据绑定到相应的虚拟DOM节点上。在上面的例子中,this.message的值会被正确地插入到<div>标签内部。
      • 对于更复杂的指令,如v - ifv - for等,render函数也会根据数据的状态和这些指令的逻辑来生成相应的虚拟DOM结构。例如,对于v - for指令:
        export default {
          data() {
            return {
              items: ['item1', 'item2', 'item3']
            };
          },
          render(h) {
            return h('ul', this.items.map((item) => {
              return h('li', [item]);
            }));
          }
        }
        
      • 这里的render函数根据items数组的长度和内容,生成了一个包含多个<li>标签的<ul>标签的虚拟DOM结构,每个<li>标签的内容是数组中的一个元素。
  2. render函数的属性(在Vue2的语境下)
    • hcreateElement)参数
      • 这是render函数最重要的参数之一。它是一个用于创建虚拟DOM节点的函数。h函数接受三个参数,通常使用的是前两个。第一个参数是要创建的标签名(可以是一个HTML标签名,如'div',也可以是一个组件的选项对象),第二个参数是一个包含节点属性、事件监听器等的数据对象,第三个参数是子节点(可以是一个字符串、数组或者其他虚拟DOM节点)。例如:
        export default {
          render(h) {
            return h('a', {
              attrs: {
                href: 'https://example.com'
              },
              on: {
                click: () => {
                  console.log('Link clicked');
                }
              }
            }, 'Click me');
          }
        }
        
      • 这里通过h函数创建了一个<a>标签的虚拟DOM节点,设置了它的href属性和click事件监听器,并添加了一个文本子节点Click me
    • this上下文
      • render函数内部,this指向当前的Vue组件实例。这使得render函数可以访问组件的data数据、计算属性(computed)、方法(methods)等。例如,通过this.message访问data中的message属性,就像前面的例子中展示的那样。这为动态地生成虚拟DOM提供了基础,因为可以根据组件的状态来创建不同的虚拟DOM结构。
    • 返回值
      • render函数必须返回一个虚拟DOM节点(VNode)或者一个包含虚拟DOM节点的数组。这个返回值将作为组件最终要渲染的内容的描述。如果返回值不符合要求,会导致渲染错误。例如,不能返回一个非虚拟DOM相关的值,如一个普通的JavaScript对象(不是VNode格式)或者null(在非预期的情况下)等。

beforemount 之 dom 处理

  1. 修改虚拟DOM(VNode)的结构
    • 调整节点顺序
      • beforeMount阶段,可以通过修改render函数返回的虚拟DOM树来调整节点顺序。例如,假设原始的render函数生成的虚拟DOM是一个包含标题和段落的简单布局:
        export default {
          data() {
            return {
              title: 'Main Title',
              content: 'This is the content.'
            };
          },
          render(h) {
            return h('div', [
              h('h1', this.title),
              h('p', this.content)
            ]);
          }
        }
        
      • 如果想要在beforeMount阶段调整节点顺序,将段落放在标题前面,可以修改render函数如下:
        export default {
          beforeMount() {
            this.render = function(h) {
              return h('div', [
                h('p', this.content),
                h('h1', this.title)
              ]);
            };
          },
          data() {
            return {
              title: 'Main Title',
              content: 'This is the content.'
            };
          },
          render(h) {
            return h('div', [
              h('h1', this.title),
              h('p', this.content)
            ]);
          }
        }
        
      • 这里通过在beforeMount阶段重新定义render函数,改变了虚拟DOM中h1p标签的顺序。
    • 添加或删除节点
      • 同样通过修改render函数,可以添加或删除虚拟DOM节点。例如,要在某个条件下添加一个额外的div标签,可以这样做:
        export default {
          data() {
            return {
              showExtraDiv: false
            };
          },
          beforeMount() {
            this.render = function(h) {
              let nodes = [h('h1', 'Title')];
              if (this.showExtraDiv) {
                nodes.push(h('div', 'Extra Content'));
              }
              return h('div', nodes);
            };
          },
          render(h) {
            return h('div', [h('h1', 'Title')]);
          }
        }
        
      • showExtraDivtrue时,在beforeMount阶段重新定义的render函数会添加一个额外的div标签到虚拟DOM中。
  2. 设置节点属性和样式(通过虚拟DOM)
    • 属性设置
      • beforeMount阶段,可以通过修改虚拟DOM节点的属性来影响最终的DOM结构。例如,对于一个链接(<a>标签),可以设置它的href属性、target属性等。假设原始的render函数创建了一个简单的链接:
        export default {
          data() {
            return {
              linkUrl: 'https://example.com'
            };
          },
          render(h) {
            return h('a', {href: this.linkUrl}, 'Visit Site');
          }
        }
        
      • 如果要在beforeMount阶段修改链接的target属性为_blank,可以这样操作:
        export default {
          beforeMount() {
            this.render = function(h) {
              return h('a', {href: this.linkUrl, target: '_blank'}, 'Visit Site');
            };
          },
          data() {
            return {
              linkUrl: 'https://example.com'
            };
          },
          render(h) {
            return h('a', {href: this.linkUrl}, 'Visit Site');
          }
        }
        
    • 样式设置
      • 对于样式,也可以通过虚拟DOM来设置。可以通过style属性或者class属性来修改节点的外观。例如,要给一个div标签添加一个背景颜色:
        export default {
          data() {
            return {
              backgroundColor: 'lightblue'
            };
          },
          beforeMount() {
            this.render = function(h) {
              return h('div', {
                style: {
                  backgroundColor: this.backgroundColor
                }
              }, 'Content');
            };
          },
          render(h) {
            return h('div', 'Content');
          }
        }
        
  3. 事件绑定调整
    • beforeMount阶段,可以修改虚拟DOM节点上的事件绑定。例如,原始的render函数可能为一个按钮绑定了一个点击事件:
      export default {
        data() {
          return {
            clickCount: 0
          };
        },
        render(h) {
          return h('button', {
            on: {
              click: () => {
                this.clickCount++;
              }
            }
          }, 'Click Me');
        }
      }
      
    • 如果要在beforeMount阶段修改这个事件,比如添加一个防抖(debounce)功能,可以通过重新定义render函数来调整事件绑定:
      import _debounce from 'lodash/debounce';
      export default {
        beforeMount() {
          this.render = function(h) {
            return h('button', {
              on: {
                click: _debounce(() => {
                  this.clickCount++;
                }, 300)
              }
            }, 'Click Me');
          };
        },
        data() {
          return {
            clickCount: 0
          };
        },
        render(h) {
          return h('button', {
            on: {
              click: () => {
                this.clickCount++;
              }
            }
          }, 'Click Me');
        }
      }
      
    • 这里使用lodash库中的debounce函数对点击事件进行了防抖处理,通过在beforeMount阶段重新定义render函数来实现。

updated 函数应用

  1. updated周期内可以做的事情
    • DOM操作相关的更新
      • 更新样式:在updated周期中,可以根据组件状态的变化来调整DOM元素的样式。例如,假设组件中有一个元素的高度需要根据数据的变化动态调整。当数据更新后,在updated周期可以获取到DOM元素(通过this.$el或者document.querySelector等方式),然后修改其样式。
        export default {
          data() {
            return {
              contentHeight: '100px'
            };
          },
          updated() {
            let element = this.$el.querySelector('.content');
            if (element) {
              element.style.height = this.contentHeight;
            }
          }
        }
        
      • 更新位置或布局相关属性:如果组件涉及到一些布局的变化,比如一个元素的位置需要根据其他元素的状态来动态调整,updated周期是一个合适的时机。例如,在一个包含多个可拖动元素的组件中,当某个元素的位置数据更新后,可以在updated周期重新计算并设置其在DOM中的位置。
    • 第三方插件或库的更新
      • 更新图表数据:如果组件中集成了图表库(如Echarts、Chart.js等),当数据更新时,需要在updated周期重新渲染图表。以Echarts为例,假设组件中有一个柱状图,数据存储在barData中。
        import echarts from 'echarts';
        export default {
          data() {
            return {
              barData: [10, 20, 30]
            };
          },
          mounted() {
            this.renderChart();
          },
          updated() {
            this.renderChart();
          },
          methods: {
            renderChart() {
              let chartDom = this.$el.querySelector('#chart');
              let myChart = echarts.init(chartDom);
              myChart.setOption({
                xAxis: {
                  type: 'category',
                  data: ['A', 'B', 'C']
                },
                yAxis: {
                  type: 'value'
                },
                series: [
                  {
                    data: this.barData,
                    type: 'bar'
                  }
                ]
              });
            }
          }
        }
        
      • 更新地图数据:类似地,对于地图组件(如使用百度地图、高德地图等JavaScript API),当地图相关的数据(如标记点位置、区域颜色等)更新后,在updated周期进行地图的更新操作。
    • 数据一致性检查与修复
      • updated周期,可以检查组件内部数据与DOM展示的数据是否一致。例如,对于一个包含表单元素的组件,检查输入框的值是否与组件内部data中的值一致。如果发现不一致,可以进行修复操作。假设组件中有一个文本输入框和一个数据属性inputValue
        export default {
          data() {
            return {
              inputValue: ''
            };
          },
          updated() {
            let inputElement = this.$el.querySelector('input');
            if (inputElement.value!== this.inputValue) {
              inputElement.value = this.inputValue;
            }
          }
        }
        
  2. 需要注意的事项
    • 避免无限循环更新
      • updated周期内,如果不小心修改了会触发组件重新渲染的数据,就会导致无限循环更新。例如,在updated周期内修改了data中的某个属性,而这个属性又绑定在模板上,就会导致组件不断地重新渲染。为了避免这种情况,需要谨慎地操作数据,确保修改的数据不会再次触发更新。
    • 性能考虑
      • updated周期会在组件每次更新后被触发,所以如果在这个周期内进行复杂的操作,可能会影响性能。例如,频繁地进行大量的DOM操作或者复杂的计算,可能会导致页面卡顿。尽量减少在updated周期内的复杂操作,或者对这些操作进行性能优化(如使用requestAnimationFrame来优化DOM操作的时机)。
    • 子组件更新的影响
      • 当父组件更新导致子组件也更新时,子组件的updated周期也会被触发。在这种情况下,需要考虑子组件的更新是否会对父组件或者其他相关组件产生影响。例如,子组件的更新可能会改变其尺寸,从而影响父组件的布局。在设计组件时,需要考虑这种级联更新的影响,并合理地处理相关问题。

vue中全局属性设置

  1. Vue2中添加全局属性
    • 使用Vue.prototype添加全局属性
      • 在Vue2中,可以通过Vue.prototype来添加全局属性。这使得在任何Vue实例(包括组件)中都可以访问这些属性。例如,要添加一个全局的$http属性用于发送HTTP请求(假设使用axios库):
        import axios from 'axios';
        Vue.prototype.$http = axios;
        
      • 完成上述设置后,在任何Vue组件中都可以使用this.$http来发送HTTP请求。例如:
        export default {
          methods: {
            fetchData() {
              this.$http.get('https://example.com/api/data')
              .then((response) => {
                  console.log(response.data);
                })
              .catch((error) => {
                  console.log(error);
                });
            }
          }
        }
        
      • 这种方式添加的全局属性在整个应用程序的Vue组件范围内都是可用的,就好像这些属性是每个组件自身的属性一样。不过需要注意的是,这些属性是挂载在Vue的原型上,所以在使用时需要确保this指向正确的Vue实例。
    • 使用插件添加全局属性
      • 可以创建一个Vue插件来添加全局属性。一个Vue插件通常是一个包含install方法的对象。例如,创建一个插件来添加一个全局的$config属性用于存储应用程序配置信息:
        const MyPlugin = {
          install(Vue, options) {
            Vue.prototype.$config = {
              apiUrl: 'https://example.com/api',
              appName: 'My App'
            };
          }
        };
        Vue.use(MyPlugin);
        
      • 这样,在组件中就可以访问this.$config来获取应用程序的配置信息,如this.$config.apiUrl用于构建API请求的URL等。使用插件的方式更加模块化,方便管理和维护全局属性,尤其是当全局属性的设置涉及到多个步骤或者需要传递参数时,插件的优势更加明显。
  2. Vue3中添加全局属性
    • 使用app.config.globalProperties添加全局属性
      • 在Vue3中,通过app.config.globalProperties来添加全局属性。首先需要获取应用程序实例app,通常在main.js(或者创建应用程序的主要文件)中进行操作。例如,要添加一个全局的$fetch属性用于数据获取(假设使用fetch API):
        import { createApp } from 'vue';
        import App from './App.vue';
        const app = createApp(App);
        app.config.globalProperties.$fetch = async (url) => {
          const response = await fetch(url);
          return response.json();
        };
        app.mount('#app');
        
      • 之后,在任何Vue组件中,可以通过getCurrentInstance或者inject来访问这个全局属性。如果在组件的setup函数中访问,可以使用getCurrentInstance
        import { getCurrentInstance } from 'vue';
        export default {
          setup() {
            const { proxy } = getCurrentInstance();
            const fetchData = async () => {
              const data = await proxy.$fetch('https://example.com/api/data');
              console.log(data);
            };
            return {
              fetchData
            };
          }
        }
        
      • 或者使用inject来访问全局属性,这种方式更符合Vue3的依赖注入机制。首先需要在main.js中提供这个全局属性作为一个可注入的资源:
        import { createApp, provide } from 'vue';
        import App from './App.vue';
        const app = createApp(App);
        app.config.globalProperties.$fetch = async (url) => {
          const response = await fetch(url);
          return response.json();
        };
        app.mount('#app');
        provide('fetch', app.config.globalProperties.$fetch);
        
        然后在组件的setup函数中可以这样访问:
        import { inject } from 'vue';
        export default {
          setup() {
            const $fetch = inject('fetch');
            const fetchData = async () => {
              const data = await $fetch('https://example.com/api/data');
              console.log(data);
            };
            return {
              fetchData
            };
          }
          }
        

    vue3中 this 为什么去除

    1. Vue3中去除“this”的方式
    • setup函数中使用组合式API
      • Vue3引入了组合式API,其中setup函数是一个关键部分。在setup函数内部,没有像Vue2中那样的this上下文。例如,在访问组件的propscontext时,它们是作为setup函数的参数直接传入的。
        import { defineComponent } from 'vue';
        export default defineComponent({
          props: {
            message: String
          },
          setup(props, context) {
            // 直接使用props,不需要this
            console.log(props.message);
            // 可以访问context中的内容,如emit等
            context.emit('custom - event', 'data');
          }
        });
        
      • 对于组件内部的数据,在setup函数中可以通过refreactive来创建响应式数据。例如,使用ref
        import { defineComponent, ref } from 'vue';
        export default defineComponent({
          setup() {
            const count = ref(0);
            // 修改count的值,不需要this
            count.value++;
            return {
              count
            };
          }
        });
        
      • 这里的count是一个ref类型的数据,通过count.value来访问和修改其实际的值,完全不需要this
    • 使用组合式函数(Composables)
      • 组合式函数是一种将相关逻辑组合在一起的函数,在Vue3中广泛使用。这些函数也不依赖this。例如,假设有一个组合式函数用于获取用户数据:
        import { ref, onMounted } from 'vue';
        const useUserData = () => {
          const userData = ref({});
          onMounted(() => {
            // 模拟获取用户数据的过程
            userData.value = {
              name: 'John',
              age: 30
            };
          });
          return userData;
        };
        
        在组件的setup函数中可以这样使用:
        import { defineComponent } from 'vue';
        import { useUserData } from './composables/userData';
        export default defineComponent({
          setup() {
            const userData = useUserData();
            return {
              userData
            };
          }
        });
        
        整个过程中没有使用this,组合式函数内部的逻辑通过ref等方式来处理响应式数据,并且在组件中通过调用组合式函数来获取数据。
  3. 为什么要去除“this”
    • 提高代码的可组合性和可读性
      • 在Vue2中,this的指向可能会因为组件的嵌套、函数的调用方式等因素变得复杂。例如,在一个复杂的组件中,有多个方法和生命周期钩子函数,this可能指向不同的对象,这使得代码理解起来比较困难。而在Vue3的组合式API中,通过明确的参数传递(如setup函数的参数)和返回值的方式,代码的逻辑更加清晰。组合式函数可以将相关的逻辑封装在一起,不依赖于this,方便在不同的组件之间共享和复用。
    • 更好地支持TypeScript
      • this在TypeScript中的类型推断比较复杂,尤其是在Vue2的组件中,因为this的指向会根据上下文变化。在Vue3中去除this后,使用组合式API可以更方便地进行类型定义和类型推断。例如,在setup函数中,传入的propscontext可以明确地定义类型,refreactive等创建的响应式数据也可以很方便地进行类型标注,这使得在使用TypeScript开发Vue3应用时,代码的类型安全性更高。
    • 逻辑内聚和代码复用
      • 没有了this的限制,组合式函数可以更自由地组合和复用逻辑。在Vue2中,由于this的存在,复用组件的逻辑(如生命周期钩子函数中的逻辑)可能会受到影响,因为this指向的问题可能需要重新绑定或者调整。在Vue3中,组合式函数可以独立于具体的组件实例,将相关的功能(如数据获取、状态管理等)封装起来,方便在不同的组件中使用相同的逻辑,而不用担心this的干扰。

vue 中 directive 函数介绍

  1. Vue2 Directive参数介绍与实例应用
    • 参数介绍
      • bind: 只调用一次,指令第一次绑定到元素时调用。这个钩子函数可以进行一些初始化操作。它接收以下参数:
        • el: 指令所绑定的元素,可以用来直接操作DOM。例如,可以修改元素的样式、添加类名等。
        • binding: 一个包含指令相关信息的对象。它有几个重要的属性:
          • name: 指令名,比如对于v - my - directivename就是my - directive
          • value: 指令绑定的值。例如,对于v - my - directive="10"value就是10
          • oldValue: 仅在update钩子函数中可用,用于比较新旧值的变化。
          • expression: 指令绑定的表达式的字符串形式。例如,对于v - my - directive="count + 1"expression就是count + 1
          • arg: 指令的参数。例如,对于v - my - directive:arg="value"arg就是arg
          • modifiers: 指令的修饰符对象。例如,对于v - my - directive.modifier1.modifier2="value"modifiers就是{modifier1: true, modifier2: true}
        • vnode: Vue虚拟DOM节点,通过它可以访问组件的其他信息,不过在bind阶段,组件可能还没有完全渲染。
        • oldVnode: 上一个虚拟DOM节点,仅在组件更新时可用,用于比较虚拟DOM的变化。
      • inserted: 被绑定元素插入父节点时调用(仅保证父节点存在)。这个钩子函数在bind之后调用,是操作DOM元素在插入到父节点后的好时机。参数和bind类似。
      • update: 所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前。可以用来根据数据的变化更新指令的相关操作。接收和bind相同的参数,并且binding.oldValue在这里可用,用于比较新旧值。
      • componentUpdated: 指令所在组件的VNode及其子VNode全部更新后调用。用于在组件和其子组件全部更新后执行一些操作,比如检查DOM元素是否符合预期等。参数和bind相同。
      • unbind: 只调用一次,指令与元素解绑时调用。用于清理指令在绑定期间可能创建的一些资源,比如移除事件监听器等。参数和bind相同。
    • 实例应用
      • 自定义指令实现元素聚焦
        // 注册一个全局指令
        Vue.directive('focus', {
          inserted: function(el) {
            el.focus();
          }
        });
        
        在组件的模板中可以这样使用:
        <input v - focus>
        
      • 自定义指令实现权限验证(简单示例)
        Vue.directive('permission', {
          bind: function(el, binding) {
            let permission = binding.value;
            let userPermissions = ['read', 'write']; // 假设这是用户拥有的权限列表
            if (!userPermissions.includes(permission)) {
              el.style.display = 'none';
            }
          }
        });
        
        在模板中使用:
        <div v - permission="edit">
          This content requires edit permission.
        </div>
        
  2. Vue3 Directive参数介绍与实例应用
    • 参数介绍
      • beforeMount: 在元素挂载之前调用,类似于Vue2中的bindinserted的部分功能。它接收以下参数:
        • el: 指令所绑定的元素。
        • binding: 包含指令相关信息的对象,属性和Vue2中的类似,包括valueoldValue(在update阶段可用)、argmodifiers等。
        • vnode: 虚拟DOM节点。
        • prevVnode: 上一个虚拟DOM节点,在更新阶段可用。
      • mounted: 在元素挂载之后调用,主要用于操作已经挂载的DOM元素。参数和beforeMount相同。
      • beforeUpdate: 在元素自身更新之前调用,类似于Vue2中的update的部分功能。参数和beforeMount相同。
      • updated: 在元素自身更新之后调用,类似于Vue2中的componentUpdated的部分功能。参数和beforeMount相同。
      • beforeUnmount: 在元素卸载之前调用,用于清理资源,类似于Vue2中的unbind。参数和beforeMount相同。
      • unmounted: 在元素卸载之后调用,再次确认资源清理情况等。参数和beforeMount相同。
    • 实例应用
      • 自定义指令实现鼠标悬停显示提示信息
        const app = createApp(App);
        app.directive('tooltip', {
          mounted(el, binding) {
            let tooltipText = binding.value;
            el.setAttribute('title', tooltipText);
          }
        });
        
        在模板中使用:
        <button v - tooltip=" 'Click me!' ">Button</button>
        
      • 自定义指令实现懒加载图片(简单示例)
        app.directive('lazyload', {
          beforeMount(el, binding) {
            let lazyImageSrc = binding.value;
            el.setAttribute('src', 'placeholder.jpg');
            el.setAttribute('data - lazy - src', lazyImageSrc);
          },
          mounted(el) {
            let observer = new IntersectionObserver((entries) => {
              if (entries[0].isIntersecting) {
                let lazySrc = el.getAttribute('data - lazy - src');
                el.setAttribute('src', lazySrc);
                observer.disconnect();
              }
            });
            observer.observe(el);
          }
        });
        
        在模板中使用:
        <img v-lazyload=" 'https://example.com/image.jpg' " 
        alt="Lazy-loaded Image">
        

vue 中混入介绍

  1. Vue2中的混入(Mixins)
    • 解释
      • 在Vue2中,混入是一种分发Vue组件中可复用功能的方式。它允许你创建一个包含组件选项(如datamethodsmounted等生命周期钩子)的对象,然后将这个对象混入到一个或多个组件中。混入对象中的选项会被合并到组件自身的选项中。如果组件和混入对象中有相同的选项,它们会按照一定的规则进行合并。
    • 实例
      • 创建混入对象
        const myMixin = {
          data() {
            return {
              sharedData: 'This is shared data from mixin'
            };
          },
          methods: {
            sharedMethod() {
              console.log('This method is from mixin');
            }
          },
          mounted() {
            console.log('Mixin mounted');
          }
        };
        
      • 在组件中使用混入
        Vue.component('my - component', {
          mixins: [myMixin],
          data() {
            return {
              componentData: 'This is component - specific data'
            };
          },
          mounted() {
            console.log('Component mounted');
            // 可以调用混入中的方法
            this.sharedMethod();
          },
          template: `
            <div>
              <p>{{sharedData}}</p>
              <p>{{componentData}}</p>
            </div>
          `
        });
        
      • 在这个例子中,my - component组件混入了myMixin。在组件的mounted生命周期钩子中,既会执行混入中的mounted逻辑,也会执行组件自身的mounted逻辑。并且,组件可以访问混入中的datamethods。在模板中,sharedData(来自混入)和componentData(来自组件自身)都被正确地渲染。
      • 合并规则细节
        • 数据合并:当组件和混入对象都有data函数时,它们会被合并为一个新的data函数。组件中的data函数会在混入的data函数之后被调用,这意味着如果有相同的属性名,组件中的属性会覆盖混入中的属性。
        • 生命周期钩子合并:对于生命周期钩子,如mountedcreated等,混入中的钩子和组件中的钩子都会被调用。它们会按照混入的顺序和组件自身的钩子顺序依次执行。
        • 方法合并:如果组件和混入对象中有相同名称的方法,组件中的方法会覆盖混入中的方法。但是,在方法内部仍然可以通过super关键字(在某些构建工具支持下)来调用混入中的方法。不过这种方式在实际开发中不太常用,因为它可能会使代码变得复杂。
  2. Vue3中的混入(Mixins)
    • 解释
      • Vue3仍然支持混入,但它的使用场景在一定程度上被组合式API(如setup函数)所替代。在Vue3中,混入的概念和Vue2类似,也是将一个包含组件选项的对象混入到组件中,并且选项会按照一定规则合并。不过,由于Vue3中setup函数的重要性和灵活性,混入在Vue3中的应用可能相对较少。
    • 实例
      • 创建混入对象
        const myMixin = {
          data() {
            return {
              sharedData: 'This is shared data from mixin in Vue3'
            };
          },
          methods: {
            sharedMethod() {
              console.log('This method is from mixin in Vue3');
            }
          },
          mounted() {
            console.log('Mixin mounted in Vue3');
          }
        };
        
      • 在组件中使用混入
        import { defineComponent } from 'vue';
        export default defineComponent({
          mixins: [myMixin],
          data() {
            return {
              componentData: 'This is component - specific data in Vue3'
            };
          },
          mounted() {
            console.log('Component mounted in Vue3');
            this.sharedMethod();
          },
          template: `
            <div>
              <p>{{sharedData}}</p>
              <p>{{componentData}}</p>
            </div>
          `
        });
        
      • 这个例子在Vue3中的执行方式和Vue2类似。组件混入了myMixin,数据、方法和生命周期钩子都会按照合并规则进行合并。不过,在Vue3中,如果要处理更复杂的逻辑,可能会更多地考虑使用组合式API。例如,对于数据响应式,在setup函数中可以使用refreactive来创建更灵活的响应式数据,而不是完全依赖混入中的data函数。

Object.defineProperty 与 new proxy 介绍

  1. Object.defineProperty()介绍与实例
    • 介绍
      • Object.defineProperty()是JavaScript中用于在一个对象上定义一个新属性,或者修改一个现有属性的特性。它允许精确地添加或修改属性的可枚举性、可配置性、可写性和值。并且,它是Vue2实现数据响应式的核心方法。
    • 实例
      • 基本用法
        let obj = {};
        Object.defineProperty(obj, 'name', {
          value: 'John',
          writable: true,
          enumerable: true,
          configurable: true
        });
        console.log(obj.name); // 输出: John
        
        在这个例子中,通过Object.defineProperty()obj对象上定义了一个名为name的属性,并且设置了它的可写性、可枚举性和可配置性为true,属性值为John
      • 实现简单的数据响应式(类似Vue2的部分原理)
        let data = {
          count: 0
        };
        let target = {};
        function defineReactive(obj, key, value) {
          Object.defineProperty(obj, key, {
            get: function() {
              console.log(`getting ${key}`);
              return value;
            },
            set: function(newValue) {
              console.log(`setting ${key} to ${newValue}`);
              value = newValue;
            }
          });
        }
        for (let key in data) {
          defineReactive(target, key, data[key]);
        }
        console.log(target.count); // 输出: getting count,0
        target.count = 1; // 输出: setting count to 1
        
        这个例子展示了如何使用Object.defineProperty()来实现简单的数据响应式。通过定义getset函数,可以在访问和修改属性时执行自定义的操作,这里只是简单地打印了日志。在Vue2中,会在set函数触发时进行更复杂的操作,如更新DOM等。
  2. Proxy介绍与实例
    • 介绍
      • Proxy是ES6中新增的一个特性,它可以对目标对象进行拦截操作,包括对属性的访问、赋值、函数调用等多种操作。Proxy对象用于定义基本操作的自定义行为,它可以看作是目标对象的一个“代理”,外界对目标对象的访问都需要通过这个代理来进行,并且代理可以在这个过程中进行各种拦截和处理。在Vue3中,使用Proxy来实现数据响应式。
    • 实例
      • 基本用法
        let target = {
          name: 'John'
        };
        let handler = {
          get: function(obj, prop) {
            console.log(`getting ${prop}`);
            return obj[prop];
          },
          set: function(obj, prop, value) {
            console.log(`setting ${prop} to ${value}`);
            obj[prop] = value;
            return true;
          }
        };
        let proxy = new Proxy(target, handler);
        console.log(proxy.name); // 输出: getting name,John
        proxy.name = 'Jane'; // 输出: setting name to Jane
        
        这里创建了一个Proxy对象,target是被代理的对象,handler定义了代理的行为。当访问proxy.name和修改proxy.name时,handler中的getset函数会被触发,执行自定义的操作。
      • 深度代理(用于处理嵌套对象)
        let deepTarget = {
          user: {
            name: 'John',
            age: 30
          }
        };
        let deepHandler = {
          get: function(obj, prop) {
            console.log(`getting ${prop}`);
            if (typeof obj[prop] === 'object' && obj[prop]!== null) {
              return new Proxy(obj[prop], deepHandler);
            }
            return obj[prop];
          },
          set: function(obj, prop, value) {
            console.log(`setting ${prop} to ${value}`);
            obj[prop] = value;
            return true;
          }
        };
        let deepProxy = new Proxy(deepTarget, deepHandler);
        console.log(deepProxy.user.name);
        deepProxy.user.name = 'Jane';
        
        在这个例子中,当访问或修改嵌套对象user中的属性时,Proxy也能正确地进行拦截,通过递归地创建Proxy对象来处理嵌套对象的情况。
  3. 性能对比
    • 初始化性能
      • Object.defineProperty()在初始化时需要遍历对象的每个属性来进行定义,对于大型对象可能会有一定的性能开销。特别是当对象有大量的属性,并且需要为每个属性都定义getset操作时,这个过程可能会比较耗时。
      • Proxy在初始化时相对简单,只需要创建一个代理对象,并且定义好拦截器(handler)即可。它不需要像Object.defineProperty()那样逐个属性地进行定义,因此在初始化大型对象时可能会有更好的性能。
    • 访问和修改性能
      • 在简单的属性访问和修改场景下,Object.defineProperty()Proxy的性能差异不大。但是,当处理嵌套对象时,Object.defineProperty()可能会面临一些挑战。因为它没有自动的深度监听机制,需要手动地对嵌套对象的每个属性进行响应式处理,这可能会导致代码复杂并且性能下降。
      • Proxy具有天然的深度代理优势,它可以自动地处理嵌套对象的访问和修改,在处理复杂的数据结构(如嵌套的对象或数组)时,性能和便利性上可能会优于Object.defineProperty()。不过,Proxygetset拦截函数在每次访问和修改属性时都会被调用,这可能会带来一些额外的性能开销,但在现代浏览器和JavaScript引擎的优化下,这种开销通常是可以接受的。

Object.defineProperty 参数介绍

  1. Object.defineProperty()基本语法
    • Object.defineProperty(obj, prop, descriptor)是用于在对象obj上定义(或修改)属性prop,并且通过descriptor来描述该属性的特性。
  2. 参数详细介绍
    • obj(目标对象)
      • 这是要在其上定义或修改属性的对象。例如:
        let person = {};
        // 在这里,person就是目标对象,后续将在person对象上定义属性
        
    • prop(属性名称)
      • 可以是一个字符串或Symbol类型,表示要在目标对象上定义或修改的属性名称。
      • 字符串作为属性名示例
        let car = {};
        Object.defineProperty(car, 'brand', {
          value: 'Toyota'
        });
        console.log(car.brand); // 输出: Toyota
        
      • Symbol作为属性名示例
        let uniqueIdSymbol = Symbol('uniqueId');
        let product = {};
        Object.defineProperty(product, uniqueIdSymbol, {
          value: 12345
        });
        console.log(product[uniqueIdSymbol]); // 输出: 12345
        
    • descriptor(属性描述符)
      • 这是一个对象,用于描述属性的各种特性,它可以包含以下可配置的属性:
        • value(属性值)
          • 该属性定义了要添加或修改的属性的值。例如:
            let book = {};
            Object.defineProperty(book, 'title', {
              value: 'JavaScript Guide'
            });
            console.log(book.title); // 输出: JavaScript Guide
            
        • writable(可写性)
          • 这是一个布尔值,用于确定属性是否可以被重新赋值。
          • writabletrue
            let box = {};
            Object.defineProperty(box, 'width', {
              value: 10,
              writable: true
            });
            box.width = 20;
            console.log(box.width); // 输出: 20
            
          • writablefalse
            let circle = {};
            Object.defineProperty(circle, 'radius', {
              value: 5,
              writable: false
            });
            circle.radius = 7; // 在严格模式下会抛出TypeError,
            // 在非严格模式下赋值操作无效
            console.log(circle.radius); // 输出: 5
            
        • enumerable(可枚举性)
          • 布尔值,用于确定属性是否可以在for...in循环和Object.keys()等枚举操作中被访问到。
          • enumerabletrue
            let animal = {
              type: 'Dog'
            };
            Object.defineProperty(animal, 'color', {
              value: 'Brown',
              enumerable: true
            });
            for (let key in animal) {
              console.log(key); // 输出: type和color
            }
            console.log(Object.keys(animal)); // 输出: ["type", "color"]
            
          • enumerablefalse
            let fruit = {
              name: 'Apple'
            };
            Object.defineProperty(fruit, 'taste', {
              value: 'Sweet',
              enumerable: false
            });
            for (let key in fruit) {
              console.log(key); // 输出: name
            }
            console.log(Object.keys(fruit)); // 输出: ["name"]
            
        • configurable(可配置性)
          • 布尔值,用于确定属性是否可以被删除或其特性是否可以被修改。
          • configurabletrue
            let building = {};
            Object.defineProperty(building, 'height', {
              value: 100,
              configurable: true
            });
            delete building.height;
            console.log(building.height); // 输出: undefined
            
          • configurablefalse
            let vehicle = {};
            Object.defineProperty(vehicle, 'speed', {
              value: 60,
              configurable: false
            });
            // 尝试删除属性会在严格模式下抛出TypeError,在非严格模式下删除操作无效
            delete vehicle.speed;
            console.log(vehicle.speed); // 输出: 60
            // 尝试修改属性的特性也会在严格模式下抛出TypeError,
            // 在非严格模式下修改操作无效
            Object.defineProperty(vehicle, 'speed', {
              value: 80
            });
            console.log(vehicle.speed); // 输出: 60
            
        • get(访问器函数 - 获取属性值)
          • 当访问属性时会调用的函数。它没有参数,并且应该返回属性的值。
          • 示例
            let person = {
              _age: 30
            };
            Object.defineProperty(person, 'age', {
              get: function() {
                console.log('Getting age...');
                return this._age;
              }
            });
            console.log(person.age); // 输出: Getting age...,30
            
        • set(访问器函数 - 设置属性值)
          • 当设置属性值时会调用的函数。它接受一个参数,即要设置的新值。
          • 示例
            let box = {
              _width: 10
            };
            Object.defineProperty(box, 'width', {
              set: function(newValue) {
                console.log('Setting width to', newValue);
                this._width = newValue;
              },
              get: function() {
                return this._width;
              }
            });
            box.width = 20; // 输出: Setting width to 20
            console.log(box.width); // 输出: 20
            

new proxy 参数介绍

  1. new Proxy()基本语法

    • new Proxy(target, handler)用于创建一个Proxy对象,其中target是要代理的目标对象,handler是一个包含拦截器(trap)方法的对象,用于定义代理的行为。
  2. 参数详细介绍

    • target(目标对象)
      • 这是被代理的原始对象,可以是任何类型的对象,包括普通对象、数组、函数等。
      • 普通对象示例
        let person = {
          name: 'John',
          age: 30
        };
        let proxy = new Proxy(person, {
          // 代理的行为定义在handler中
        });
        
      • 数组示例
        let numbers = [1, 2, 3];
        let proxy = new Proxy(numbers, {
          // 代理数组的操作
        });
        
      • 函数示例
        function add(a, b) {
          return a + b;
        }
        let proxy = new Proxy(add, {
          // 代理函数的调用等操作
        });
        
    • handler(代理行为拦截器对象)
      • 这个对象包含了一系列用于拦截对目标对象操作的方法,这些方法被称为“陷阱”(trap)。以下是一些常见的handler方法:
        • get(target, property, receiver)
          • 拦截对象属性的读取操作。当通过代理对象访问属性时(如proxy.propertyproxy['property']),这个方法会被触发。
          • 参数说明
            • target:被代理的目标对象,与new Proxy()中的target相同。
            • property:要访问的属性名称。
            • receiver:通常是代理对象本身,在某些情况下(如继承场景)可能会有所不同。
          • 示例
            let target = {
              name: 'John'
            };
            let handler = {
              get(target, property) {
                console.log(`Getting property ${property}`);
                return target[property];
              }
            };
            let proxy = new Proxy(target, handler);
            console.log(proxy.name); 
            // 输出: Getting property name,John
            
        • set(target, property, value, receiver)
          • 拦截对象属性的赋值操作。当通过代理对象设置属性值时(如proxy.property = valueproxy['property'] = value),这个方法会被触发。
          • 参数说明
            • target:被代理的目标对象。
            • property:要设置的属性名称。
            • value:要设置的新值。
            • receiver:通常是代理对象本身。
          • 示例
            let target = {
              count: 0
            };
            let handler = {
              set(target, property, value) {
                console.log(`Setting property ${property} to ${value}`);
                target[property] = value;
                return true;
              }
            };
            let proxy = new Proxy(target, handler);
            proxy.count = 1;
            // 输出: Setting property count to 1
            
        • has(target, prop)
          • 拦截in操作符。当使用prop in proxy这样的表达式时,这个方法会被触发。
          • 参数说明
            • target:被代理的目标对象。
            • prop:要检查是否存在的属性名称。
          • 示例
            let target = {
              name: 'John'
            };
            let handler = {
              has(target, prop) {
                console.log(`Checking if ${prop} exists`);
                return prop in target;
              }
            };
            let proxy = new Proxy(target, handler);
            console.log('name' in proxy);
            // 输出: Checking if name exists,true
            
        • deleteProperty(target, property)
          • 拦截delete操作符。当使用delete proxy.property这样的表达式时,这个方法会被触发。
          • 参数说明
            • target:被代理的目标对象。
            • property:要删除的属性名称。
          • 示例
            let target = {
              flag: true
            };
            let handler = {
              deleteProperty(target, property) {
                console.log(`Deleting property ${property}`);
                delete target[property];
                return true;
              }
            };
            let proxy = new Proxy(target, handler);
            delete proxy.flag;
            // 输出: Deleting property flag
            
        • apply(target, thisArg, argumentsList)
          • 拦截函数调用。当代理对象是一个函数并且被调用时(如proxy()proxy.apply()等),这个方法会被触发。
          • 参数说明
            • target:被代理的函数对象。
            • thisArg:函数调用时的this值。
            • argumentsList:函数调用时的参数列表。
          • 示例
            function add(a, b) {
              return a + b;
            }
            let handler = {
              apply(target, thisArg, argumentsList) {
                console.log(`Calling function with 
                arguments ${argumentsList}`);
                return target.apply(thisArg, argumentsList);
              }
            };
            let proxy = new Proxy(add, handler);
            console.log(proxy(1, 2));
            // 输出: Calling function with arguments 1,2,3
            
        • construct(target, argumentsList, newTarget)
          • 拦截new操作符。当使用new proxy()这样的表达式创建对象时,这个方法会被触发。
          • 参数说明
            • target:被代理的构造函数对象。
            • argumentsList:传递给构造函数的参数列表。
            • newTarget:通常是代理对象本身,在继承场景下可能会有所不同。
          • 示例
            function Person(name) {
              this.name = name;
            }
            let handler = {
              construct(target, argumentsList, newTarget) {
                console.log(`Constructing object with 
                arguments ${argumentsList}`);
                return new target(...argumentsList);
              }
            };
            let proxy = new Proxy(Person, handler);
            let person = new proxy('John');
            // 输出: Constructing object with arguments John
            console.log(person.name); 
            // 输出: John
            

网站公告

今日签到

点亮在社区的每一天
去签到