1. 说一下Vue的生命周期
Vue 生命周期钩子
Vue 的生命周期钩子函数分为以下几个阶段:
-
创建阶段:
- beforeCreate(创建前): 数据观测和初始化事件还未开始,此时
data
的响应式追踪、event/watcher
都还没有被设置,也就是说不能访问到data
、computed
、watch
、methods
上的方法和数据。 - created(创建后): 实例创建完成,数据观测和事件配置已完成,此时可以访问
data
、computed
、watch
、methods
上的方法和数据,但 DOM 还未生成。
- beforeCreate(创建前): 数据观测和初始化事件还未开始,此时
-
挂载阶段:
- beforeMount(挂载前): 模板编译完成,但还未将 DOM 挂载到页面,此时无法访问 DOM 元素。
- mounted(挂载后): DOM 挂载完成,可以访问 DOM 元素,此时组件已经渲染到页面中。
-
更新阶段:
- beforeUpdate(更新前): 数据更新时触发,此时 DOM 还未重新渲染,可以在更新前访问现有的 DOM。
- updated(更新后): 数据更新完成,DOM 重新渲染,此时可以操作更新后的 DOM。
-
销毁阶段:
- beforeDestroy(销毁前): 实例销毁之前触发,此时实例仍可用,可以清理定时器、解绑事件等。
- destroyed(销毁后): 实例销毁完成,所有事件监听器和子组件已被移除,此时无法再访问实例的属性和方法。
以下是基于您的要求,结合上述内容,对组件通信方式的详细结构化回答,采用 Markdown 格式输出,适合在掘金发布博客。
1.组件通信的方式
1. Props 和 Events
父组件向子组件传值
• 特点:
• props
只能是父组件向子组件进行传值,形成单向下行绑定。
• 子组件的数据会随着父组件不断更新。
• props
可以定义多种数据类型,也可以传递函数。
• 属性名规则:若在 props
中使用驼峰形式,模板中需使用短横线形式。
• 示例:
// 父组件
<template>
<div id="father">
<son :msg="msgData" :fn="myFunction"></son>
</div>
</template>
<script>
import son from "./son.vue";
export default {
name: father,
data() {
msgData: "父组件数据";
},
methods: {
myFunction() {
console.log("vue");
}
},
components: {
son
}
};
</script>
// 子组件
<template>
<div id="son">
<p>{{msg}}</p>
<button @click="fn">按钮</button>
</div>
</template>
<script>
export default {
name: "son",
props: ["msg", "fn"]
};
</script>
子组件向父组件传值
• 特点:
• 子组件通过 $emit
触发自定义事件,父组件通过 v-on
监听并接收参数。
• 示例:
// 父组件
<template>
<div class="section">
<com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article>
<p>{{currentIndex}}</p>
</div>
</template>
<script>
import comArticle from './test/article.vue'
export default {
name: 'comArticle',
components: { comArticle },
data() {
return {
currentIndex: -1,
articleList: ['红楼梦', '西游记', '三国演义']
}
},
methods: {
onEmitIndex(idx) {
this.currentIndex = idx
}
}
}
</script>
// 子组件
<template>
<div>
<div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)">{{item}}</div>
</div>
</template>
<script>
export default {
props: ['articles'],
methods: {
emitIndex(index) {
this.$emit('onEmitIndex', index) // 触发父组件的方法,并传递参数index
}
}
}
</script>
2. EventBus 事件总线
特点
• 适用于父子组件、非父子组件等之间的通信。 • 通过一个空的 Vue 实例作为事件中心管理组件之间的通信。
示例
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 发送事件
<template>
<div>
<button @click="add">加法</button>
</div>
</template>
<script>
import {EventBus} from './event-bus.js'
export default {
data(){
return{
num:0
}
},
methods:{
add(){
EventBus.$emit('addition', {
num:this.num++
})
}
}
}
</script>
// 接收事件
<template>
<div>求和: {{count}}</div>
</template>
<script>
import { EventBus } from './event-bus.js'
export default {
data() {
return {
count: 0
}
},
mounted() {
EventBus.$on('addition', param => {
this.count = this.count + param.num;
})
}
}
</script>
3. 依赖注入(Provide / Inject)
特点
• 用于父子组件或祖孙组件之间的通信。
• provide
钩子用来发送数据或方法,inject
钩子用来接收数据或方法。
• 依赖注入提供的属性是非响应式的。
示例
// 父组件
provide() {
return {
num: this.num
};
}
// 子组件
inject: ['num']
4. Ref / $refs
特点
• 通过 ref
属性访问子组件实例,实现父子组件通信。
示例
// 子组件
<template>
<div>
<span>{{message}}</span>
</div>
</template>
<script>
export default {
data () {
return {
message: 'JavaScript'
}
},
methods: {
sayHello () {
console.log('hello')
}
}
}
</script>
// 父组件
<template>
<child ref="child"></component-a>
</template>
<script>
import child from './child.vue'
export default {
components: { child },
mounted () {
console.log(this.$refs.child.name); // JavaScript
this.$refs.child.sayHello(); // hello
}
}
</script>
5. children
特点
• $parent
访问父组件实例,$children
访问子组件实例。
• $children
是无序数组,且数据非响应式。
示例
// 子组件
<template>
<div>
<span>{{message}}</span>
<p>获取父组件的值为: {{parentVal}}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Vue'
}
},
computed:{
parentVal(){
return this.$parent.msg;
}
}
}
</script>
// 父组件
<template>
<div class="hello_world">
<div>{{msg}}</div>
<child></child>
<button @click="change">点击改变子组件值</button>
</div>
</template>
<script>
import child from './child.vue'
export default {
components: { child },
data() {
return {
msg: 'Welcome'
}
},
methods: {
change() {
this.$children[0].message = 'JavaScript'
}
}
}
</script>
6. listeners
特点
• 用于跨代组件通信。
• $attrs
继承父组件属性(除 props
、class
和 style
)。
• $listeners
继承父组件的事件监听器。
示例
// A组件(APP.vue)
<template>
<div id="app">
<child1 :p-child1="child1" :p-child2="child2" @test1="onTest1" @test2="onTest2"></child1>
</div>
</template>
<script>
import Child1 from './Child1.vue';
export default {
components: { Child1 },
methods: {
onTest1() {
console.log('test1 running');
},
onTest2() {
console.log('test2 running');
}
}
};
</script>
// B组件(Child1.vue)
<template>
<div class="child-1">
<p>props: {{pChild1}}</p>
<p>$attrs: {{$attrs}}</p>
<child2 v-bind="$attrs" v-on="$listeners"></child2>
</div>
</template>
<script>
import Child2 from './Child2.vue';
export default {
props: ['pChild1'],
components: { Child2 },
inheritAttrs: false,
mounted() {
this.$emit('test1');
}
};
</script>
// C组件(Child2.vue)
<template>
<div class="child-2">
<p>props: {{pChild2}}</p>
<p>$attrs: {{$attrs}}</p>
</div>
</template>
<script>
export default {
props: ['pChild2'],
inheritAttrs: false,
mounted() {
this.$emit('test2');
}
};
</script>
总结
Vue 提供了多种组件通信方式,适用于不同场景:
- • Props / Events:父子组件通信。
- • EventBus:跨组件通信。
- • Provide / Inject:祖孙组件通信。
- • Ref / $refs:访问子组件实例。
- • children:访问父组件或子组件实例。
- • listeners:跨代组件通信。
根据具体需求选择合适的通信方式,可以提升代码的可维护性和开发效率。
2. 路由的hash和history模式的区别
Vue-Router 提供了两种路由模式:hash 模式和 history 模式。默认情况下,Vue-Router 使用的是 hash 模式。以下是两种模式的详细介绍和对比。
1. Hash 模式
简介
• Hash 模式的 URL 中会带有一个 #
,例如:http://www.abc.com/#/vue
,其中 #/vue
就是 hash 值。
• Hash 模式是开发中默认的模式,也是单页面应用(SPA)的标配。
特点
• URL 特征:Hash 值会出现在 URL 中,但不会出现在 HTTP 请求中,对后端完全没有影响。 • 页面刷新:改变 hash 值不会重新加载页面。 • 兼容性:支持低版本浏览器,包括 IE8。 • 前端路由:Hash 路由被称为前端路由,完全由前端控制。
原理
Hash 模式的核心原理是 onhashchange
事件:
window.onhashchange = function(event) {
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1); // 获取当前 hash 值
};
• 优点:
• 无需向后端发起请求,window
可以监听 hash 值的变化,并按规则加载相应的代码。
• 浏览器会记录 hash 值变化对应的 URL,从而实现页面的前进和后退。
• 缺点:
• URL 中带有 #
,影响美观。
2. History 模式
简介
• History 模式的 URL 中没有 #
,例如:http://abc.com/user/id
。
• 它使用的是传统的路由分发模式,用户在输入 URL 时,服务器会接收并解析这个 URL,然后做出相应的逻辑处理。
特点
• URL 特征:URL 更加美观,没有 #
。
• 后端支持:需要后端配置支持,否则刷新页面时会返回 404。
• API:基于 HTML5 的 History API
,包括 pushState()
和 replaceState()
。
原理
History 模式的核心是 History API
,分为两部分:
- 修改历史状态:
•
pushState()
:向历史记录栈中添加一条记录。 •replaceState()
:替换当前历史记录栈中的记录。 • 这两个方法可以修改 URL,但不会触发页面刷新。 - 切换历史状态:
•
forward()
:前进。 •back()
:后退。 •go()
:跳转到指定页面。
配置
要切换到 history 模式,需要在 Vue-Router 中进行以下配置:
const router = new VueRouter({
mode: 'history', // 使用 history 模式
routes: [...]
});
缺点
• 刷新问题:如果后端没有正确配置,刷新页面时会返回 404。 • 兼容性:不支持 IE9 及以下版本。
3. 两种模式对比
特性 | Hash 模式 | History 模式 |
---|---|---|
URL 特征 | 带 # ,例如 http://abc.com/#/vue | 无 # ,例如 http://abc.com/vue |
兼容性 | 支持低版本浏览器(包括 IE8) | 不支持 IE9 及以下版本 |
页面刷新 | 不会重新加载页面 | 需要后端支持,否则返回 404 |
HTTP 请求 | Hash 值不会出现在请求中 | URL 会出现在请求中 |
美观性 | URL 不美观 | URL 美观 |
实现原理 | 基于 onhashchange 事件 | 基于 History API |
后端支持 | 无需后端支持 | 需要后端支持 |
调用 pushState()
的优势
- URL 灵活性:
pushState()
可以设置与当前 URL 同源的任意 URL,而 hash 只能修改#
后面的部分。 - 数据传递:
pushState()
可以通过stateObject
参数添加任意类型的数据到记录中,而 hash 只能添加短字符串。 - 记录添加:
pushState()
设置的新 URL 可以与当前 URL 相同,仍会添加记录;而 hash 必须设置不同的值才会触发记录添加。 - SEO 友好:History 模式的 URL 更符合传统 URL 格式,对搜索引擎更友好。
总结
- • Hash 模式:
- 优点:兼容性好,无需后端支持,适合简单的单页面应用。
- 缺点:URL 不美观,SEO 不友好。
- • History 模式:
- 优点:URL 美观,SEO 友好,适合复杂的单页面应用。
- 缺点:需要后端支持,兼容性较差。
根据实际项目需求选择合适的路由模式:
- 如果项目需要兼容低版本浏览器且无需考虑 SEO,可以选择 Hash 模式。
- 如果项目需要美观的 URL 和良好的 SEO,且后端支持 History 模式,可以选择 History 模式。
1. Vuex 的原理
Vuex 的核心概念
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库),它是一个容器,包含了应用中大部分的状态(state)。
主要特点
- 响应式状态存储: • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态时,如果 store 中的状态发生变化,相应的组件也会高效更新。
- 状态变更的唯一途径: • 改变 store 中状态的唯一途径是显式地提交(commit)mutation。这种方式可以方便地跟踪每一个状态的变化。
Vuex 的核心流程
Vuex 为 Vue Components 建立起了一个完整的生态圈,其核心流程如下:
- Vue Components:
• Vue 组件负责接收用户操作等交互行为,通过
dispatch
方法触发对应的 action。 - Actions: • Actions 负责处理 Vue Components 接收到的所有交互行为,包含同步/异步操作。Actions 可以触发其他 action 或提交 mutation。
- Mutations: • Mutations 是修改 state 的唯一推荐方法,只能进行同步操作。每个 mutation 都有一个字符串的事件类型(type)和一个回调函数(handler),用于实际修改 state。
- State: • State 是页面状态管理容器对象,集中存储 Vue Components 中零散的数据。页面显示所需的数据从 state 中读取。
- Getters: • Getters 是 state 的读取方法,用于从 state 中派生出计算属性。
流程图
Vue Components -> dispatch -> Actions -> commit -> Mutations -> State -> render -> Vue Components
各模块的功能
• Vue Components:接收用户操作,触发 action。 • dispatch:唯一能执行 action 的方法。 • actions:处理交互行为,支持异步操作,提交 mutation。 • commit:唯一能执行 mutation 的方法。 • mutations:修改 state 的唯一途径,同步操作。 • state:集中存储页面状态数据。 • getters:从 state 中读取数据。
2. Vuex 中 action 和 mutation 的区别
Mutation
• 特点: • 必须是同步函数。 • 直接修改 state。 • 每个 mutation 都有一个字符串的事件类型(type)和一个回调函数(handler)。 • 示例:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment(state) {
state.count++; // 变更状态
}
}
});
// 触发 mutation
store.commit('increment');
Action
• 特点:
• 可以包含任意异步操作。
• 通过提交 mutation 来间接修改 state。
• Action 函数接受一个与 store 实例具有相同方法和属性的 context
对象。
• 示例:
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
increment(context) {
context.commit('increment'); // 提交 mutation
}
}
});
对比
特性 | Mutation | Action |
---|---|---|
同步/异步 | 必须是同步操作 | 可以包含异步操作 |
修改 state | 直接修改 state | 通过提交 mutation 间接修改 state |
参数 | 接受 state 作为第一个参数 | 接受 context 对象 |
触发方式 | 通过 commit 触发 | 通过 dispatch 触发 |
4. Redux 和 Vuex 的区别,它们的共同思想
区别
- API 设计:
• Redux:核心是
store
、reducer
和action
。 • Vuex:核心是state
、mutations
、actions
和getters
。 - 异步处理:
• Redux:需要中间件(如
redux-thunk
)处理异步操作。 • Vuex:内置支持异步操作,通过actions
实现。 - 数据流: • Redux:View -> Action -> Reducer -> State -> View。 • Vuex:View -> Action -> Mutation -> State -> View。
- 与框架的集成: • Redux:独立于框架,通常与 React 结合使用。 • Vuex:专为 Vue 设计,深度集成于 Vue 生态。
共同思想
- 单一数据源: • 应用的状态集中存储在一个全局的 store 中。
- 状态可预测: • 通过严格的规则(如 mutation 或 reducer)确保状态的变化是可预测的。
- 服务于 MVVM: • 两者都是对 MVVM 思想的服务,将数据从视图中抽离出来,实现数据与视图的分离。
对比图
特性 | Redux | Vuex |
---|---|---|
核心概念 | Store, Reducer, Action | State, Mutations, Actions, Getters |
异步处理 | 需要中间件(如 redux-thunk) | 内置支持异步操作 |
数据流 | View -> Action -> Reducer -> State -> View | View -> Action -> Mutation -> State -> View |
框架依赖 | 独立于框架,通常与 React 结合 | 专为 Vue 设计,深度集成 |
总结
- Vuex 的原理: • Vuex 通过集中式状态管理,解决了复杂应用中的数据共享和状态管理问题。其核心是 store,通过 actions、mutations 和 state 实现数据流的闭环。
- Action 和 Mutation 的区别: • Mutation 用于同步修改 state,而 Action 可以包含异步操作,通过提交 mutation 间接修改 state。
- Redux 和 Vuex 的区别: • Redux 和 Vuex 的核心思想一致,都是单一数据源和状态可预测,但在 API 设计、异步处理和框架集成上有明显区别。
1. Vue3.0 有什么更新
Vue 3.0 是一次重大的版本升级,带来了许多新特性和改进。以下是主要更新内容:
(1)监测机制的改变
• 基于 Proxy 的响应式系统:
• Vue 3.0 使用 Proxy 替代 Vue 2.x 中的 Object.defineProperty
,提供了全语言覆盖的反应性跟踪。
• 解决了 Vue 2.x 中 Object.defineProperty
的诸多限制:
◦ 只能监测属性,不能监测整个对象。
◦ 无法检测属性的添加和删除。
◦ 无法直接监听数组索引和长度的变化。
• 支持更多数据结构,如 Map、Set、WeakMap 和 WeakSet。
(2)模板优化
• 作用域插槽的改进:
• Vue 2.x 中,作用域插槽的变化会导致父组件重新渲染。
• Vue 3.0 将作用域插槽改为函数的方式,只会影响子组件的重新渲染,提升了性能。
• Render 函数的优化:
• 对 render
函数进行了改进,方便开发者直接使用 API 生成虚拟 DOM。
(3)对象式的组件声明方式
• 类式组件声明:
• Vue 2.x 中,组件是通过声明式的方式传入一系列 option
,与 TypeScript 结合需要通过装饰器实现,较为麻烦。
• Vue 3.0 改用了类式的写法,使得与 TypeScript 的结合更加自然和简单。
(4)其他方面的改进
• 自定义渲染器: • 支持自定义渲染器,使得 Weex 等可以通过自定义渲染器扩展,而无需直接修改源码。 • Fragment 和 Portal 组件: • 支持 Fragment(多个根节点)和 Portal(在 DOM 其他部分渲染组件内容),为特殊场景提供了更好的支持。 • 函数式编程(FP): • 从面向对象编程(OOP)切换到函数式编程(FP),基于 Tree Shaking 优化,提供了更多的内置功能。
总结
Vue 3.0 通过基于 Proxy 的响应式系统、模板优化、类式组件声明、自定义渲染器等改进,提升了性能、开发体验和灵活性。
2. defineProperty 和 Proxy 的区别
Vue 2.x 使用 Object.defineProperty
实现响应式系统,而 Vue 3.0 使用 Proxy
。以下是两者的主要区别:
(1)Object.defineProperty 的局限性
- 只能监听属性:
•
Object.defineProperty
只能监听对象的属性,无法监听整个对象。 - 无法检测新增/删除属性:
• 在初始化后,新增或删除的属性无法被监听,需要通过
Vue.set
或Vue.delete
手动处理。 - 数组监听问题: • 无法直接监听数组索引和长度的变化。
(2)Proxy 的优势
- 监听整个对象:
•
Proxy
直接代理整个对象,可以监听对象的所有属性变化,包括新增和删除属性。 - 支持更多操作:
•
Proxy
可以监听数组索引和长度的变化,支持更多数据结构(如 Map、Set 等)。 - 性能更好:
•
Proxy
只需一层代理即可监听所有属性变化,性能优于Object.defineProperty
。
对比表
特性 | Object.defineProperty | Proxy |
---|---|---|
监听范围 | 只能监听属性 | 监听整个对象 |
新增/删除属性 | 不支持,需手动处理 | 支持 |
数组监听 | 不支持索引和长度变化 | 支持 |
性能 | 较差,需为每个属性单独设置代理 | 更好,只需一层代理 |
数据结构支持 | 仅支持普通对象 | 支持 Map、Set、WeakMap、WeakSet |
总结
• Object.defineProperty:在 Vue 2.x 中实现响应式系统,但存在诸多局限性。
• Proxy:在 Vue 3.0 中替代 Object.defineProperty
,提供了更强大和灵活的响应式能力,解决了 Vue 2.x 中的许多问题。
以上内容详细描述了 Vue 3.0 的更新内容以及 Object.defineProperty
和 Proxy
的区别,适合在掘金发布博客。
1. 对虚拟 DOM 的理解
什么是虚拟 DOM?
虚拟 DOM(Virtual DOM)是一个 JavaScript 对象,用于描述真实 DOM 的结构。它将页面的状态抽象为 JS 对象的形式,配合不同的渲染工具,使跨平台渲染成为可能。
虚拟 DOM 的核心特点
- 轻量级描述: • 虚拟 DOM 是对真实 DOM 的抽象,比直接操作真实 DOM 更轻量。
- 跨平台支持: • 虚拟 DOM 的本质是 JS 对象,因此可以在没有 DOM 的环境(如 Node.js)中使用,支持 SSR(服务器端渲染)。
- 高效更新: • 通过事务处理机制,将多次 DOM 修改的结果一次性更新到页面上,减少页面渲染的次数,提高性能。
- 减少手动操作: • 现代前端框架(如 Vue、React)通过虚拟 DOM 避免了手动操作 DOM,既保证了性能,又提高了开发效率。
虚拟 DOM 的工作流程
- 初始化:
• 在代码渲染到页面之前,Vue 会将模板转换为虚拟 DOM 对象。
• 虚拟 DOM 对象包含
TagName
、props
和Children
等属性,用于描述真实 DOM 的结构。 - 更新: • 当数据发生变化时,Vue 会生成一个新的虚拟 DOM 对象。 • 通过 DIFF 算法,比较新旧虚拟 DOM 的差异。
- 渲染: • 将记录的有差异的地方应用到真实 DOM 树中,视图更新。
虚拟 DOM 的优势
- 性能优化: • 减少直接操作真实 DOM 的次数,提升渲染效率。
- 跨平台支持: • 虚拟 DOM 可以渲染到不同的平台(如 Web、Native)。
- 简化开发: • 开发者只需关注数据变化,无需手动操作 DOM。
总结
虚拟 DOM 通过抽象和优化 DOM 操作,提升了渲染性能和开发体验,是现代前端框架的核心技术之一。
5. DIFF 算法的原理
什么是 DIFF 算法?
DIFF 算法是虚拟 DOM 的核心,用于比较新旧虚拟 DOM 的差异,并将差异应用到真实 DOM 中,实现高效更新。
DIFF 算法的核心思想
- 同层比较: • 只比较同一层级的节点,不跨层级比较。
- Key 值优化:
• 通过
key
标识节点,减少不必要的节点操作。 - 节点复用: • 如果节点类型相同,则复用节点,只更新属性。
DIFF 算法的工作流程
- 初始化虚拟 DOM: • 根据模板生成初始的虚拟 DOM 对象。
- 生成新虚拟 DOM: • 当数据变化时,生成新的虚拟 DOM 对象。
- 比较差异: • 通过 DIFF 算法,比较新旧虚拟 DOM 的差异。 • 记录需要更新的节点和属性。
- 应用差异: • 将差异应用到真实 DOM 中,完成视图更新。
DIFF 算法的优化策略
- 深度优先遍历: • 逐层比较节点,减少比较次数。
- 最小化操作: • 只更新有变化的节点,减少 DOM 操作。
- 节点复用: • 如果节点类型相同,则复用节点,只更新属性。
DIFF 算法的示例
// 旧虚拟 DOM
const oldVNode = {
tag: 'div',
props: { id: 'app' },
children: [
{ tag: 'p', props: {}, children: ['Hello'] }
]
};
// 新虚拟 DOM
const newVNode = {
tag: 'div',
props: { id: 'app' },
children: [
{ tag: 'p', props: {}, children: ['World'] }
]
};
// DIFF 算法比较
function diff(oldVNode, newVNode) {
if (oldVNode.tag !== newVNode.tag) {
// 替换整个节点
replaceNode(oldVNode, newVNode);
} else {
// 更新属性
updateProps(oldVNode.props, newVNode.props);
// 比较子节点
diffChildren(oldVNode.children, newVNode.children);
}
}
总结
DIFF 算法通过高效比较虚拟 DOM,最小化 DOM 操作,提升了渲染性能,是虚拟 DOM 实现的核心。
以上内容详细描述了虚拟 DOM 的理解和 DIFF 算法的原理,适合在掘金发布博客。