目录
- vue2 的双向绑定的原理
- vue3 的双向绑定原理
- vue 的生命周期
- vue 子组件为何不能修改父组件的值
- js delete 删除数组的某一个值会怎么样
- vue 和 react 的 diff 算法
- 什么是闭包
- 原型链
- this指向
vue2 的双向绑定的原理
以下是 Vue 2 双向绑定的原理:
1. 核心概念
Vue 2 的双向绑定是通过数据劫持和发布订阅模式实现的。它允许数据和视图之间的双向通信,即数据的改变可以更新视图,视图的操作也可以更新数据。
2. 实现步骤
数据劫持:
Vue 2 使用 Object.defineProperty
对数据对象的属性进行劫持,在属性被访问或修改时添加自定义的行为。
function defineReactive(obj, key, value) {
let dep = new Dep(); // 创建一个空的订阅者列表
Object.defineProperty(obj, key, {
enumerable: true, // 允许属性被枚举
configurable: true, // 允许属性被修改
get: function() {
if (Dep.target) {
dep.addSub(Dep.target);
}
return value;
},
set: function(newVal) {
if (value!== newVal) {
value = newVal;
dep.notify();
}
}
});
}
在这个函数中:
-
defineReactive
函数使用Object.defineProperty
对obj
的key
属性进行定义。 -
get
方法:当该属性被访问时,如果Dep.target
存在(在 Vue 中,Dep.target
通常是当前正在编译的Watcher
),将其添加到dep
的订阅者列表中。 -
set
方法:当该属性被修改时,如果新值与旧值不同,更新属性值并通知dep
的订阅者列表中的所有订阅者。
依赖收集:
每个组件实例都有一个 Watcher
对象,它是一个订阅者,当组件的模板中使用到某个数据时,会触发该数据的 get
方法,将该 Watcher
添加到该数据的订阅者列表中。
class Watcher {
constructor(vm, exp, cb) {
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.value = this.get();
}
get() {
Dep.target = this;
let value = this.vm[this.exp];
Dep.target = null;
return value;
}
update() {
let newValue = this.vm[this.exp];
let oldValue = this.value;
if (newValue!== oldValue) {
this.value = newValue;
this.cb.call(this.vm, newValue, oldValue);
}
}
}
在这个类中:
-
Watcher
的get
方法会将自己设置为Dep.target
,并访问数据,触发数据的get
方法,从而将自己添加到该数据的订阅者列表中。 -
update
方法会在数据更新时被调用,调用回调函数cb
更新视图。
发布订阅模式:
Dep
类是一个简单的发布者,它维护一个订阅者列表,并在数据变化时通知订阅者。
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
removeSub(sub) {
this.subs = this.subs.filter(s => s!== sub);
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
在这个类中:
-
constructor
方法创建一个空的订阅者列表。 -
addSub(sub)
方法添加一个订阅者。 -
removeSub(sub)
方法移除一个订阅者。 -
notify()
方法通知所有订阅者更新。
3. 整体流程
- 当 Vue 实例化时,会对
data
属性中的数据进行遍历,使用defineReactive
进行数据劫持。 - 当编译模板时,会创建
Watcher
对象,在使用数据时触发数据的get
方法,将Watcher
添加到该数据的订阅者列表中。 - 当数据发生变化时,触发数据的
set
方法,通知Watcher
进行更新,Watcher
调用update
方法更新视图。
4. 示例代码
<div id="app">
<input v-model="message">
{{ message }}
</div>
function Vue(options) {
this.data = options.data;
observe(this.data);
new Compile('#app', this);
}
function observe(data) {
if (!data || typeof data!== 'object') return;
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key]);
});
}
function Compile(el, vm) {
this.vm = vm;
this.el = document.querySelector(el);
this.compileElement(this.el);
}
Compile.prototype.compileElement = function (el) {
let childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
if (node.nodeType === 1) {
// 元素节点
this.compileElement(node);
} else if (node.nodeType === 3) {
// 文本节点
this.compileText(node);
}
});
};
Compile.prototype.compileText = function (node) {
let reg = /\{\{(.*?)\}\}/g;
let value = node.textContent;
if (reg.test(value)) {
let exp = RegExp.$1.trim();
node.textContent = value.replace(reg, this.vm.data[exp]);
new Watcher(this.vm, exp, function (newVal) {
node.textContent = value.replace(reg, newVal);
});
}
};
let app = new Vue({
data: {
message: 'Hello, Vue!'
}
});
代码解释
Vue
函数:
- 接收
options
,初始化data
属性,并调用observe
对数据进行观察。 - 调用
Compile
进行模板编译。
observe
函数:
- 遍历
data
对象,对每个属性使用defineReactive
进行数据劫持。
Compile
类:
- 编译模板元素,对于文本节点,如果存在
{{...}}
插值表达式,使用Watcher
进行数据监听和更新。
Watcher
类:
- 在实例化时,会将自己添加到数据的订阅者列表中,并在更新时更新视图。
Dep
类:
- 作为发布者,管理订阅者列表,在数据更新时通知订阅者。
面试回答示例
“Vue 2 的双向绑定是通过数据劫持和发布订阅模式实现的。首先,使用 Object.defineProperty
对数据对象的属性进行劫持,在属性的 get
方法中进行依赖收集,将使用该数据的 Watcher
订阅者添加到该数据的订阅者列表中,在 set
方法中,当数据发生变化时通知订阅者列表中的 Watcher
。Watcher
是一个订阅者,负责更新视图,它会在实例化时将自己添加到数据的订阅者列表中,并在更新时调用回调函数更新视图。Dep
类是一个发布者,负责维护订阅者列表并通知订阅者更新。当 Vue 实例化时,会对 data
中的数据进行劫持,在模板编译时,会创建 Watcher
进行依赖收集,从而实现数据和视图的双向绑定。这样,当数据变化时,视图会更新;当用户操作视图(如通过 v-model
)时,会触发数据的更新,形成双向绑定的效果。”
通过这样的解释,可以向面试官展示你对 Vue 2 双向绑定原理的深入理解,包括数据劫持、依赖收集、发布订阅模式的使用以及整体的工作流程。
2. vue3 的双向绑定原理
Vue 3 的双向绑定机制相比 Vue 2 有了显著改进,主要通过 组合式 API 和 Proxy 实现。以下是完整解析:
一、核心机制演进
特性 | Vue 2 | Vue 3 |
---|---|---|
响应式基础 | Object.defineProperty | Proxy |
检测范围 | 对象属性 | 完整对象 |
数组检测 | 需特殊方法 | 原生支持 |
性能 | 递归转换属性 | 惰性代理 |
二、响应式系统核心实现
1. reactive() - 对象响应化
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key) // 依赖收集
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver)
trigger(target, key) // 触发更新
return true
}
})
}
2. ref() - 原始值响应化
function ref(value) {
const refObject = {
get value() {
track(refObject, 'value')
return value
},
set value(newVal) {
value = newVal
trigger(refObject, 'value')
}
}
return refObject
}
三、依赖收集与触发流程
-
依赖收集阶段:
-
触发更新阶段:
四、v-model 双向绑定实现
组件示例:
<CustomInput v-model="searchText" />
<!-- 等价于 -->
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
组件实现:
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
const emitUpdate = (e) => {
emit('update:modelValue', e.target.value)
}
五、性能优化策略
-
编译时优化:
- 静态节点提升(Hoist Static)
- 补丁标志(Patch Flags)
- 树结构拍平(Tree Flattening)
-
响应式优化:
- 依赖关系缓存(effect缓存)
- 批量异步更新(nextTick合并)
-
源码结构优化:
// 惰性代理示例 function shallowReactive(obj) { const proxy = new Proxy(obj, handlers) // 不立即递归代理嵌套对象 return proxy }
六、与 Vue 2 的对比升级
-
数组处理改进:
// Vue 2 需要特殊处理 this.$set(this.items, index, newValue) // Vue 3 直接操作 state.items[index] = newValue // 自动触发更新
-
动态属性检测:
// Vue 2 无法检测新增属性 this.$set(this.obj, 'newProp', value) // Vue 3 自动检测 state.newProp = value // 自动响应
七、开发注意事项
-
响应式丢失场景:
// 解构会导致响应式丢失 const { x, y } = reactive({ x: 1, y: 2 }) // 正确做法 const pos = reactive({ x: 1, y: 2 }) const { x, y } = toRefs(pos)
-
性能敏感操作:
// 大数据量使用shallowRef/shallowReactive const bigList = shallowRef([]) // 非响应式数据使用markRaw const foo = markRaw({ complex: object })
Vue 3 的双向绑定通过 Proxy 实现了更精细的依赖跟踪,配合编译时优化,在保证开发体验的同时提供了更好的运行时性能。理解其原理有助于编写更高效的 Vue 代码。
3. vue 的生命周期
一、Vue 2 和 Vue 3 生命周期对比
生命周期钩子对照表
Vue 2 选项式API | Vue 3 组合式API | 触发时机描述 |
---|---|---|
beforeCreate |
无直接对应 | 实例初始化前,data/methods未初始化 |
created |
setup() |
实例创建完成,data/methods可用 |
beforeMount |
onBeforeMount |
挂载开始前,DOM尚未生成 |
mounted |
onMounted |
挂载完成,DOM已生成 |
beforeUpdate |
onBeforeUpdate |
数据变化导致DOM更新前 |
updated |
onUpdated |
数据变化导致DOM更新后 |
beforeDestroy |
onBeforeUnmount |
实例销毁前(vue3改名更准确) |
destroyed |
onUnmounted |
实例销毁后(vue3改名更准确) |
activated |
onActivated |
keep-alive组件激活时 |
deactivated |
onDeactivated |
keep-alive组件停用时 |
errorCaptured |
onErrorCaptured |
捕获子孙组件错误时 |