手写vue2 的双向绑定原理
知识点概要
- Object.definePorperty
- Object.keys
- 数组的reduce 方法
- es6 clss 类
直接上代码
html部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h1>{{name}}</h1>
<h2>{{info.userName}}</h2>
<h2>{{info.password}}</h2>
<input type="text" name="" id="" v-model="info.userName">
<input type="password" name="" id="" v-model="info.password">
</div>
<script src="./my_vue.js"></script>
<script>
const vm = new myVue({
el:'#app',
data:{
name:'晤西迪西',
info:{
userName:'justkang',
password:'123456'
}
}
})
console.log(vm)
</script>
</body>
</html>
js部分
// 实现简单版的vue 只有数据劫持双向绑定功能
// 定义一个myVue构造函数
class myVue {
constructor(options) {
this.$data = options.data
// 调用数据劫持的方法 把this.$data 传入进去 把data中的 数据添加getter和setter
Observe(this.$data)
// 属性代理 把this.$data 的属性代理到this上
Object.keys(this.$data).forEach(key=>{
Object.defineProperty(this,key,{
enumerable:true,
configurable:true,
get(){
return this.$data[key]
},
set(newValue) {
this.$data[key] = newValue
}
})
})
// 调用模版编译
Compile(options.el,this)
}
}
// 数据劫持方法
function Observe (data) {
// 递归的结束条件
if(!data || typeof data != 'object') return
const dep = new Dep()
// 拿到data对象的每一个key
Object.keys(data).forEach(key=>{
// 当前key的值
let value = data[key]
// 把vuale这个值在进行递归 把里面的key也添加getter和setter
Observe (value)
// 为每个key添加getter和setter
Object.defineProperty(data,key,{
enumerable:true,
configurable:true,
get(){
console.log(`有人获取了${key}的值`)
Dep.target && dep.addSub(Dep.target)
return value
},
set(newValue){
value = newValue
// 为新赋值的value 添加getter和setter
Observe(value)
dep.notify()
}
})
})
}
// 模版编译方法
function Compile(el,vm) {
// 获取el对应的dom区域
vm.$el = document.querySelector(el)
// 创建文档碎片 提高dom操作性能
const fragment = document.createDocumentFragment()
while(childNode = vm.$el.firstChild) {
fragment.appendChild(childNode)
}
// 模版编译
Replace(fragment)
vm.$el.appendChild(fragment)
// Replace 函数
function Replace(node) {
const regMustache = /\{\{\s*(\S+)\s*\}\}/
// 如果节点是文本
if(node.nodeType == 3) {
// 获取文本
const text = node.textContent
// 对文本内容进行正则匹配和提取
const execResult = regMustache.exec(text)
if(execResult) {
const value = execResult[1].split('.').reduce((newData,key)=>newData[key],vm)
// console.log(value)
node.textContent = text.replace(regMustache,value)
new Watcher(vm,execResult[1],(newValue)=>{
node.textContent = text.replace(regMustache,newValue)
})
}
return
}
// 如果节点是input
if(node.nodeType == 1 && node.tagName == 'INPUT') {
// 获取当前元素的所有属性
const attrs = Array.from(node.attributes)
const findResult = attrs.find((x)=>x.name === 'v-model')
if(findResult) {
const expStr = findResult.value
const value = expStr.split('.').reduce((data,key)=>data[key],vm)
node.value = value
new Watcher(vm,expStr,(newValue)=>{
node.value = newValue
})
node.addEventListener('input',(e)=>{
const keyArr = expStr.split('.')
const obj = keyArr.slice(0,keyArr.length-1).reduce((data,k)=>data[k],vm)
console.log(obj[keyArr[keyArr.length-1]])
obj[keyArr[keyArr.length-1]] = e.target.value
})
}
}
node.childNodes.forEach(child=>{
Replace(child)
})
}
}
// 定义一个Dep 类 / 收集watcher订阅者的类
class Dep {
constructor(){
this.subs = []
}
// 向subs添加wathcer方法
addSub(watcher) {
this.subs.push(watcher)
}
// 负责通知每个watcher方法
notify(){
this.subs.forEach(item=>item.updata())
}
}
// 定义一个watcher类
class Watcher {
// cb 回调函数记录者watcher如何更新自己内容
// 但是只知道更新自己不行,需要知道更新的数据,因此还要把vm传进来
// 除此之外 还要是知道在vm中哪个是我需要的数据 key
constructor(vm,key,cb) {
this.vm = vm
this.key = key
this.cb = cb
Dep.target = this
key.split('.').reduce((data,key)=>data[key],vm)
Dep.target = null
}
// updata方法 让发布者能通知我们进行更新
updata() {
const value = this.key.split('.').reduce((data,key)=>data[key],vm)
this.cb(value)
}
}