文章目录
vue阶段详解,对标上篇文章 点这里
vue2 created 阶段 详解
- Vue2中created阶段概述
- 在Vue实例的生命周期中,
created
是一个重要的阶段。它在实例创建完成后被调用,此时,Vue实例已经完成了数据观察(data observer)的配置,但还没有开始挂载到DOM元素上。 - 这个阶段的主要作用是进行一些初始化操作,比如数据的获取、初始化一些非响应式的数据等。
- 在Vue实例的生命周期中,
- 事件机制在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
阶段可以设置一些标志或者逻辑来处理事件传播。
- 对于组件内部的事件传播机制,虽然在
- 事件定义
- 数据检测在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中的相关内容。
- Vue2的数据响应式是基于
- 数据验证(如果有)可以开始执行
- 如果在组件中使用了一些数据验证库(如
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 函数
render
函数在beforeMount
周期首次渲染时的作用- 生成虚拟DOM(VNode)
render
函数的主要任务是创建虚拟DOM节点。它会根据组件的template
(如果是通过模板定义组件)或者渲染函数内部的逻辑来生成一个虚拟DOM树。例如,对于一个简单的组件:export default { data() { return { message: 'Hello, Vue!' }; }, render(h) { return h('div', [this.message]); } }
- 这里的
render
函数通过h
函数(h
是createElement
的别名,在Vue的渲染函数中经常使用)创建了一个<div>
标签的虚拟DOM节点,并且将message
数据作为它的子节点。这个虚拟DOM树描述了组件最终要渲染成的DOM结构的样子。
- 数据绑定和插值处理
- 在生成虚拟DOM的过程中,
render
函数会处理数据绑定。例如,如果有像{{message}}
这样的插值表达式(假设是在template
中,同样的逻辑也适用于render
函数),render
函数会正确地将数据绑定到相应的虚拟DOM节点上。在上面的例子中,this.message
的值会被正确地插入到<div>
标签内部。 - 对于更复杂的指令,如
v - if
、v - 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>
标签的内容是数组中的一个元素。
- 在生成虚拟DOM的过程中,
- 生成虚拟DOM(VNode)
render
函数的属性(在Vue2的语境下)h
(createElement
)参数- 这是
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 处理
- 修改虚拟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中h1
和p
标签的顺序。
- 在
- 添加或删除节点
- 同样通过修改
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')]); } }
- 当
showExtraDiv
为true
时,在beforeMount
阶段重新定义的render
函数会添加一个额外的div
标签到虚拟DOM中。
- 同样通过修改
- 调整节点顺序
- 设置节点属性和样式(通过虚拟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'); } }
- 对于样式,也可以通过虚拟DOM来设置。可以通过
- 属性设置
- 事件绑定调整
- 在
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 函数应用
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
周期进行地图的更新操作。
- 更新图表数据:如果组件中集成了图表库(如Echarts、Chart.js等),当数据更新时,需要在
- 数据一致性检查与修复
- 在
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; } } }
- 在
- DOM操作相关的更新
- 需要注意的事项
- 避免无限循环更新
- 在
updated
周期内,如果不小心修改了会触发组件重新渲染的数据,就会导致无限循环更新。例如,在updated
周期内修改了data
中的某个属性,而这个属性又绑定在模板上,就会导致组件不断地重新渲染。为了避免这种情况,需要谨慎地操作数据,确保修改的数据不会再次触发更新。
- 在
- 性能考虑
updated
周期会在组件每次更新后被触发,所以如果在这个周期内进行复杂的操作,可能会影响性能。例如,频繁地进行大量的DOM操作或者复杂的计算,可能会导致页面卡顿。尽量减少在updated
周期内的复杂操作,或者对这些操作进行性能优化(如使用requestAnimationFrame
来优化DOM操作的时机)。
- 子组件更新的影响
- 当父组件更新导致子组件也更新时,子组件的
updated
周期也会被触发。在这种情况下,需要考虑子组件的更新是否会对父组件或者其他相关组件产生影响。例如,子组件的更新可能会改变其尺寸,从而影响父组件的布局。在设计组件时,需要考虑这种级联更新的影响,并合理地处理相关问题。
- 当父组件更新导致子组件也更新时,子组件的
- 避免无限循环更新
vue中全局属性设置
- 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实例。
- 在Vue2中,可以通过
- 使用插件添加全局属性
- 可以创建一个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等。使用插件的方式更加模块化,方便管理和维护全局属性,尤其是当全局属性的设置涉及到多个步骤或者需要传递参数时,插件的优势更加明显。
- 可以创建一个Vue插件来添加全局属性。一个Vue插件通常是一个包含
- 使用
- 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中,通过
vue3中 this 为什么去除
- Vue3中去除“this”的方式
- 在
setup
函数中使用组合式API- Vue3引入了组合式API,其中
setup
函数是一个关键部分。在setup
函数内部,没有像Vue2中那样的this
上下文。例如,在访问组件的props
和context
时,它们是作为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
函数中可以通过ref
或reactive
来创建响应式数据。例如,使用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
。
- Vue3引入了组合式API,其中
- 使用组合式函数(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
等方式来处理响应式数据,并且在组件中通过调用组合式函数来获取数据。
- 组合式函数是一种将相关逻辑组合在一起的函数,在Vue3中广泛使用。这些函数也不依赖
- 使用
- 为什么要去除“this”
- 提高代码的可组合性和可读性
- 在Vue2中,
this
的指向可能会因为组件的嵌套、函数的调用方式等因素变得复杂。例如,在一个复杂的组件中,有多个方法和生命周期钩子函数,this
可能指向不同的对象,这使得代码理解起来比较困难。而在Vue3的组合式API中,通过明确的参数传递(如setup
函数的参数)和返回值的方式,代码的逻辑更加清晰。组合式函数可以将相关的逻辑封装在一起,不依赖于this
,方便在不同的组件之间共享和复用。
- 在Vue2中,
- 更好地支持TypeScript
this
在TypeScript中的类型推断比较复杂,尤其是在Vue2的组件中,因为this
的指向会根据上下文变化。在Vue3中去除this
后,使用组合式API可以更方便地进行类型定义和类型推断。例如,在setup
函数中,传入的props
和context
可以明确地定义类型,ref
和reactive
等创建的响应式数据也可以很方便地进行类型标注,这使得在使用TypeScript开发Vue3应用时,代码的类型安全性更高。
- 逻辑内聚和代码复用
- 没有了
this
的限制,组合式函数可以更自由地组合和复用逻辑。在Vue2中,由于this
的存在,复用组件的逻辑(如生命周期钩子函数中的逻辑)可能会受到影响,因为this
指向的问题可能需要重新绑定或者调整。在Vue3中,组合式函数可以独立于具体的组件实例,将相关的功能(如数据获取、状态管理等)封装起来,方便在不同的组件中使用相同的逻辑,而不用担心this
的干扰。
- 没有了
- 提高代码的可组合性和可读性
vue 中 directive 函数介绍
- Vue2 Directive参数介绍与实例应用
- 参数介绍
bind
: 只调用一次,指令第一次绑定到元素时调用。这个钩子函数可以进行一些初始化操作。它接收以下参数:el
: 指令所绑定的元素,可以用来直接操作DOM。例如,可以修改元素的样式、添加类名等。binding
: 一个包含指令相关信息的对象。它有几个重要的属性:name
: 指令名,比如对于v - my - directive
,name
就是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>
- 自定义指令实现元素聚焦
- 参数介绍
- Vue3 Directive参数介绍与实例应用
- 参数介绍
beforeMount
: 在元素挂载之前调用,类似于Vue2中的bind
和inserted
的部分功能。它接收以下参数:el
: 指令所绑定的元素。binding
: 包含指令相关信息的对象,属性和Vue2中的类似,包括value
、oldValue
(在update
阶段可用)、arg
、modifiers
等。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 中混入介绍
- Vue2中的混入(Mixins)
- 解释
- 在Vue2中,混入是一种分发Vue组件中可复用功能的方式。它允许你创建一个包含组件选项(如
data
、methods
、mounted
等生命周期钩子)的对象,然后将这个对象混入到一个或多个组件中。混入对象中的选项会被合并到组件自身的选项中。如果组件和混入对象中有相同的选项,它们会按照一定的规则进行合并。
- 在Vue2中,混入是一种分发Vue组件中可复用功能的方式。它允许你创建一个包含组件选项(如
- 实例
- 创建混入对象
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
逻辑。并且,组件可以访问混入中的data
和methods
。在模板中,sharedData
(来自混入)和componentData
(来自组件自身)都被正确地渲染。 - 合并规则细节
- 数据合并:当组件和混入对象都有
data
函数时,它们会被合并为一个新的data
函数。组件中的data
函数会在混入的data
函数之后被调用,这意味着如果有相同的属性名,组件中的属性会覆盖混入中的属性。 - 生命周期钩子合并:对于生命周期钩子,如
mounted
、created
等,混入中的钩子和组件中的钩子都会被调用。它们会按照混入的顺序和组件自身的钩子顺序依次执行。 - 方法合并:如果组件和混入对象中有相同名称的方法,组件中的方法会覆盖混入中的方法。但是,在方法内部仍然可以通过
super
关键字(在某些构建工具支持下)来调用混入中的方法。不过这种方式在实际开发中不太常用,因为它可能会使代码变得复杂。
- 数据合并:当组件和混入对象都有
- 创建混入对象
- 解释
- Vue3中的混入(Mixins)
- 解释
- Vue3仍然支持混入,但它的使用场景在一定程度上被组合式API(如
setup
函数)所替代。在Vue3中,混入的概念和Vue2类似,也是将一个包含组件选项的对象混入到组件中,并且选项会按照一定规则合并。不过,由于Vue3中setup
函数的重要性和灵活性,混入在Vue3中的应用可能相对较少。
- Vue3仍然支持混入,但它的使用场景在一定程度上被组合式API(如
- 实例
- 创建混入对象
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
函数中可以使用ref
和reactive
来创建更灵活的响应式数据,而不是完全依赖混入中的data
函数。
- 创建混入对象
- 解释
Object.defineProperty 与 new proxy 介绍
- 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()
来实现简单的数据响应式。通过定义get
和set
函数,可以在访问和修改属性时执行自定义的操作,这里只是简单地打印了日志。在Vue2中,会在set
函数触发时进行更复杂的操作,如更新DOM等。
- 基本用法
- 介绍
- 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
中的get
和set
函数会被触发,执行自定义的操作。 - 深度代理(用于处理嵌套对象)
在这个例子中,当访问或修改嵌套对象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
对象来处理嵌套对象的情况。
- 基本用法
- 介绍
- 性能对比
- 初始化性能
Object.defineProperty()
在初始化时需要遍历对象的每个属性来进行定义,对于大型对象可能会有一定的性能开销。特别是当对象有大量的属性,并且需要为每个属性都定义get
和set
操作时,这个过程可能会比较耗时。Proxy
在初始化时相对简单,只需要创建一个代理对象,并且定义好拦截器(handler
)即可。它不需要像Object.defineProperty()
那样逐个属性地进行定义,因此在初始化大型对象时可能会有更好的性能。
- 访问和修改性能
- 在简单的属性访问和修改场景下,
Object.defineProperty()
和Proxy
的性能差异不大。但是,当处理嵌套对象时,Object.defineProperty()
可能会面临一些挑战。因为它没有自动的深度监听机制,需要手动地对嵌套对象的每个属性进行响应式处理,这可能会导致代码复杂并且性能下降。 Proxy
具有天然的深度代理优势,它可以自动地处理嵌套对象的访问和修改,在处理复杂的数据结构(如嵌套的对象或数组)时,性能和便利性上可能会优于Object.defineProperty()
。不过,Proxy
的get
和set
拦截函数在每次访问和修改属性时都会被调用,这可能会带来一些额外的性能开销,但在现代浏览器和JavaScript引擎的优化下,这种开销通常是可以接受的。
- 在简单的属性访问和修改场景下,
- 初始化性能
Object.defineProperty 参数介绍
Object.defineProperty()
基本语法Object.defineProperty(obj, prop, descriptor)
是用于在对象obj
上定义(或修改)属性prop
,并且通过descriptor
来描述该属性的特性。
- 参数详细介绍
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
(可写性)- 这是一个布尔值,用于确定属性是否可以被重新赋值。
- 当
writable
为true
时let box = {}; Object.defineProperty(box, 'width', { value: 10, writable: true }); box.width = 20; console.log(box.width); // 输出: 20
- 当
writable
为false
时let circle = {}; Object.defineProperty(circle, 'radius', { value: 5, writable: false }); circle.radius = 7; // 在严格模式下会抛出TypeError, // 在非严格模式下赋值操作无效 console.log(circle.radius); // 输出: 5
enumerable
(可枚举性)- 布尔值,用于确定属性是否可以在
for...in
循环和Object.keys()
等枚举操作中被访问到。 - 当
enumerable
为true
时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"]
- 当
enumerable
为false
时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
(可配置性)- 布尔值,用于确定属性是否可以被删除或其特性是否可以被修改。
- 当
configurable
为true
时let building = {}; Object.defineProperty(building, 'height', { value: 100, configurable: true }); delete building.height; console.log(building.height); // 输出: undefined
- 当
configurable
为false
时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 参数介绍
new Proxy()
基本语法new Proxy(target, handler)
用于创建一个Proxy
对象,其中target
是要代理的目标对象,handler
是一个包含拦截器(trap)方法的对象,用于定义代理的行为。
参数详细介绍
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.property
或proxy['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 = value
或proxy['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
- 拦截
- 这个对象包含了一系列用于拦截对目标对象操作的方法,这些方法被称为“陷阱”(trap)。以下是一些常见的