大家好,我是小杨,一个写了6年前端的老码农。今天咱们不聊Vue,来点刺激的——用原生JS实现Vue的数据双向绑定!看完这篇,你会恍然大悟:"原来Vue的黑魔法这么简单?"
1. 先看Vue的双向绑定多香
<!-- Vue版 -->
<input v-model="message">
<p>{{ message }}</p>
数据一变,视图自动更新,舒服吧?那原生JS咋实现呢?
2. 核心原理拆解
双向绑定其实就是:
- 数据变 → 视图变(数据劫持)
- 视图变 → 数据变(事件监听)
3. 手把手实现
第一步:数据劫持(Object.defineProperty)
const data = {
message: '我是初始值'
}
// 劫持数据
function observe(obj) {
Object.keys(obj).forEach(key => {
let value = obj[key]
Object.defineProperty(obj, key, {
get() {
console.log(`读取了${key}`)
return value
},
set(newVal) {
console.log(`${key}被修改为${newVal}`)
value = newVal
updateView() // 数据变时更新视图
}
})
})
}
observe(data)
第二步:更新视图
function updateView() {
document.getElementById('text').innerText = data.message
}
第三步:监听输入(事件绑定)
<input id="input" type="text">
<p id="text"></p>
<script>
document.getElementById('input').addEventListener('input', (e) => {
data.message = e.target.value // 视图变时修改数据
})
</script>
4. 效果演示
现在试试:
- 在控制台修改
data.message = "新值"
→ 页面自动更新 - 在输入框打字 →
data.message
同步变化
这不就是简易版v-model吗!
5. 我踩过的坑
第一次实现时我忘了处理嵌套对象:
const data = {
user: {
name: '小杨' // 这个子对象没被劫持!
}
}
解决方案:递归劫持
function observe(obj) {
if (typeof obj !== 'object') return
Object.keys(obj).forEach(key => {
let value = obj[key]
observe(value) // 递归劫持
// ...原来的defineProperty逻辑
})
}
6. 进阶版:用Proxy实现(ES6)
更优雅的现代写法:
const data = new Proxy({ message: '你好' }, {
set(target, key, value) {
target[key] = value
updateView()
return true
}
})
// 使用方式完全一样
data.message = '新消息' // 自动触发更新
7. 和Vue的差别在哪?
我们实现的简易版缺少:
- 虚拟DOM优化
- 依赖收集(Dep/Watcher)
- 批量异步更新
- 数组特殊处理
但核心思想一模一样!
8. 实际应用场景
我曾在老项目中用这个思路:
- 实现表单联动(A输入框变,B选择框选项变)
- 低代码平台的数据绑定
- 简单的状态管理
最后送大家两句话:
- "理解原理最好的方式就是自己造轮子"
- "框架用着爽,但别忘记原生JS才是基本功"
⭐ 写在最后
请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.
✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式
✅ 认为我部分代码过于老旧,可以提供新的API或最新语法
✅ 对于文章中部分内容不理解
✅ 解答我文章中一些疑问
✅ 认为某些交互,功能需要优化,发现BUG
✅ 想要添加新功能,对于整体的设计,外观有更好的建议
✅ 一起探讨技术加qq交流群:906392632
最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!