vue2 的响应式核心原理代码 其实就只有几个模块
1. 代理 Object.defineProperty
2.依赖收集dep,收集所有监听页面数据的watcher实例
3. 监听页面数据实例 watcher
4.观察者 Observer ,实现数据劫持
5.编译模块 Compiler
下面来简单实现一下各个模块代码,新建index.js文件
1.首先简单实现Vue类,和基本代理功能
export class Vue{
constructor(options = {}){
this.$options = options
this.$el = typeof options.el ==='string' ? document.querySelector(options.el):options.el
this.$data = options.data
this.$methods = options.methods
this.proxy(this.$data)
//observer 拦截 this.$data
new Observer(this.$data)
new Compiler(this)
}
//代理一下,this.$data.xx=>this.xx
proxy(data){
Object.keys(data).forEach(key=>{
Object.defineProperty(this,key,{
enumerable:true,
configurable:true,
get(){
return data[key]
},
set(newValue){
console.log(newValue)
if(data[key] === newValue || (Number.isNaN(data[key]) && Number.isNaN(newValue)) ) return
data[key] = newValue
}
})
})
}
}2.Dep依赖收集模块,收集所有watcher实例
class Dep{
constructor(){
this.deps = new Set()
}
//手机副作用代码,count
add(dep){
console.log("dep")
console.log(dep)
if(dep && dep.update) this.deps.add(dep)
}
notify(){
console.log("this.deps")
console.log(this.deps)
this.deps.forEach(dep=>dep.update())
}
}3. watcher模块,监听数据被修改后引发回调
// html -> <h1>{{count}}</h1> -> compiler 发现有 {{count}}
// -> new Watcher(vm,'count',()=>renderToView(count)) -> count getter 被触发
//-> dep.add(watcher实例) -> this.count++ -> count setter -> dep.notify
// -> ()=>renderToView(count) -> 页面就变了
class Watcher{
constructor(vm,key,cb){
this.vm = vm
this.key = key
this.cb = cb
window.vm = vm
Dep.target = this
this.__old = vm[key] //存下了初始值,触发 getter
Dep.target = null
}
update(){
let newValue = this.vm[this.key]
if(this.__old === newValue || (Number.isNaN(newValue) && Number.isNaN(this.__old)) ) return
this.cb(newValue)
}
}4.Observer模块,数据劫持的实现 (这里先不考虑数组的劫持实现,vue2的数组劫持是重写了数组的所有方法)
class Observer{
constructor(data){
this.walk(data)
}
walk(data){
if(!data || typeof data !== 'object') return
Object.keys(data).forEach(key=>{
return this.defineReactive(data,key,data[key])
})
}
defineReactive(data,key,value){
let that = this;
this.walk(value); //因为值也可能是对象 {a:{b:0}}
let dep = new Dep();
Object.defineProperty(data,key,{
configurable:true,
enumerable:true,
get(){
//watcher 实例
Dep.target && dep.add(Dep.target)
return value
},
set(newValue){
if(value === newValue || (Number.isNaN(data[key]) && Number.isNaN(newValue) )) return
value = newValue
that.walk(newValue)
dep.notify()
}
})
}
}
5.Compiler模块,编译页面代码,实现数据绑定
class Compiler{
constructor(vm){
this.el = vm.$el
this.vm = vm
this.methods = vm.$methods
this.compile(this.el)
}
compile(el){
let childNodes = el.childNodes
//类数组
Array.from(childNodes).forEach(node=>{
if(this.isTextNode(node)){
this.compileText(node)
}
else if(this.isElementNode(node)){
this.compileElement(node)
}
if(node.childNodes && node.childNodes.length >0){
this.compile(node)
}
})
}
//<input v-model="msg" />
compileElement(node){
if(node.attributes.length){
Array.from(node.attributes).forEach(attr=>{
let attrName = attr.name
if(this.isDirective(attrName)){
//v-on:click v-model
attrName = attrName.indexOf(':') > -1 ? attrName.substr(5) : attrName.substr(2)
let key = attr.value
this.update(node, key, attrName, this.vm[key])
}
})
}
}
update(node,key,attrName,value){
if(attrName ==='text'){
node.textContent = value
new Watcher(this.vm,key,val=>node.textContent = val)
}else if(attrName === 'model'){
node.value = value
new Watcher(this.vm,key,val=>node.value = val)
node.addEventListener('input',()=>{
//这里可以加防抖
this.vm[key] = node.value
})
}else if(attrName === 'click'){
node.addEventListener('click',this.methods[key].bind(this.vm))
}else if(attrName === 'html'){
node.innerHtml = value
new Watcher(this.vm,key,val=>node.innerHtml = val)
}
// ...
}
//'this is {{count}} {{msg}}'
compileText(node){
let reg = /\{\{(.+?)\}\}/g
let value = node.textContent;
node.textContent = value.replace(reg,(val,key)=>{
key = key.trim()
new Watcher(this.vm,key,val=>{
let newValue = value.replace(reg,(oneValue,oneKey)=>{
return this.vm[oneKey.trim()]
})
node.textContent = newValue
})
return this.vm[key]
})
}
isDirective(str){
return str.startsWith('v-')
}
isElementNode(node){
return node.nodeType === 1
}
isTextNode(node){
return node.nodeType === 3
}
}测试一下,新建index.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>
<script type='module'>
import { Vue } from './index.js'
let vm = new Vue({
el: '#app',
data: {
msg: 'Hello Vue2.x',
count: 666
},
methods: {
increase() {
this.count++
}
}
})
setTimeout(()=>{vm.msg = 'hello world'},2000)
</script>
</head>
<body>
<div id="app">
<h3>{{msg}} {{ count }}</h3>
<h3>{{ count }}</h3>
<h1>v-text</h1>
<div v-text="msg"></div>
<h1>v-model</h1>
<input type="text" v-model="msg" >
<input type="text" v-model="count">
<button v-on:click="increase">按钮</button>
</div>
</body>
</html>
效果

版权声明:本文为weixin_38407447原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。