前言:最近学了vue的响应式原理,但是学了之后,感觉模模糊糊的,不知道自己是否真的理解其中的精髓,所以就自己动手简单的实现一下vue的响应式原理。毕竟概念终究还是概念,实践才是检验自己会不会的硬道理!
obj
:表示需要定义属性的当前对象。prop
:当前需要定义的属性名。 desc
:属性描述符,就是更精确的控制对象属性。Object.defineProperty(obj,prop,desc)
Object.defineProperty()
html页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
</head>
<body>
<div id="app">
msg
</div>
</body>
</html>
开始编写js代码:
let data = {
msg: '你好'
}
解释:定义一个我们要渲染到页面的数据。
class Render {
constructor() {
this.app = document.querySelector('#app')
this.arg = this.app.innerText
this.render()
}
render() {
this.app.innerText = data[this.arg]
}
}
解释:为了方便获取参数,我们就使用一个参数简单模拟,不像vue那样用{{}}来包括变量,如果用{{}}包裹变量,就需要利用正则表达式获取里面内容。
class Observe {
constructor() {
this.init(data.msg)
}
init(value) {
Object.defineProperty(data, 'msg', {
set(newVal) {
if (newVal !== value) {
value = newVal
dep.notify()
}
},
get() {
return value
}
})
}
}
解释:我们想要知道一个变量的值有没有改变呢,就需要监听数据的变化。vue中就是使用Object.defineProperty(obj,prop,desc)
这个方法中的set()
和get()
来监听的。set()
就是当值要改变的时候,就触发。而get()
就是当你获取这个变量的时候触发。我们利用这个方法就可以监听得到数据的变化了。
class Dep {
constructor() {
this.subs = []
}
addSub(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach(w => w.update())
}
}
解释:收集者的作用就存储观察者和通知观察者去更新页面的。(观察者在下面。)
class Watcher {
constructor(node, arg, callback) {
this.node = node // 变量所在的节点
this.arg = arg // 变量名
this.oldVal = this.getOldVal() // 没更新前的值
this.callback = callback // 值更新后执行的操作
}
getOldVal() {
Dep.target = this
let oldVal = data[this.arg]
Dep.target = null
return oldVal
}
update() {
this.callback(data.msg)
}
}
解释:我们想要知道一个值有没有改变,改变之后重新渲染的操作是怎么样的。这个时候就需要一个观察者了。在数据渲染到页面的时候,为这个数据添加一个观察者,当这个数据改变的时候,就执行回调函数,去更新页面。
let data = {
msg: '你好'
}
class Dep {
constructor() {
this.subs = []
}
addSub(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach(w => w.update())
}
}
class Watcher {
constructor(node, arg, callback) {
this.node = node
this.arg = arg
this.oldVal = this.getOldVal()
this.callback = callback
}
getOldVal() {
Dep.target = this
let oldVal = data[this.arg]
Dep.target = null
return oldVal
}
update() {
this.callback(data.msg)
}
}
class Render {
constructor() {
this.app = document.querySelector('#app')
this.arg = this.app.innerText
this.render()
}
render() {
// 安排观察者监视数据
new Watcher(this.app, this.arg, (newVal) => {
this.app.innerText = newVal
})
this.app.innerText = data[this.arg]
}
}
class Observe {
constructor() {
this.init(data.msg)
}
init(value) {
let dep = new Dep() // 创建收集者
Object.defineProperty(data, 'msg', {
set(newVal) {
if (newVal !== value) {
value = newVal
dep.notify() // 通知观察者
}
},
get() {
Dep.target && dep.addSub(Dep.target) // 添加观察者
return value
}
})
}
}
new Observe()
new Render()
解释:在有注释的方法,就是使用收集者和观察者的地方。
Object.defineProperty
去监听所有数据,然后获取页面中的内容,看看页面有没有使用data中的属性,如果有,就将对应的变量渲染成对应的值。oldval
中。回调函数,就是当数据更新之后才触发的。oldVal
,先为Dep.target设置为this,然后再获取oldVal
,获取的时候就会触发get方法,Dep.target
有值就会添加进收集者(Dep)中,只会把Dep.target
该成null,因为get方法会在很多时候触发,添加进收集者中,我就们就不要要添加了。dep.notify()
就会通知它里面的观察者就进更新页面的操作,watcher就会调用他们的callback函数更新页面。总的代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
</head>
<body>
<div id="app">
msg
</div>
</body>
<script>
let data = {
msg: '你好'
}
class Dep {
constructor() {
this.subs = []
}
addSub(watcher) {
this.subs.push(watcher)
}
notify() {
this.subs.forEach(w => w.update())
}
}
class Watcher {
constructor(node, arg, callback) {
this.node = node
this.arg = arg
this.oldVal = this.getOldVal()
this.callback = callback
}
getOldVal() {
Dep.target = this
let oldVal = data[this.arg]
Dep.target = null
return oldVal
}
update() {
this.callback(data.msg)
}
}
class Render {
constructor() {
this.app = document.querySelector('#app')
this.arg = this.app.innerText
this.render()
}
render() {
new Watcher(this.app, this.arg, (newVal) => {
this.app.innerText = newVal
})
this.app.innerText = data[this.arg]
}
}
class Observe {
constructor() {
this.init(data.msg)
}
init(value) {
let dep = new Dep()
Object.defineProperty(data, 'msg', {
set(newVal) {
if (newVal !== value) {
value = newVal
dep.notify()
}
},
get() {
Dep.target && dep.addSub(Dep.target)
return value
}
})
}
}
new Observe()
new Render()
</script>
</html>