vue实现原理
vue是通过数据劫持的方式来做到数据绑定的,最核心的方法是通过Object.defineProperty()来实现对数据的劫持,达到数据变动的目的
实现MVVM
整合Observer、compile、和watcher,通过compile来解析编译,利用watcher搭起Observer和compile之间的通信桥梁,达到数据的拜年话—>视图更新—>视图交互变化(input)---->数据双向绑定的效果
MVVM 双向数据绑定 数据劫持+发布者订阅模式(不兼容低版本 Object.defineProperty)
注意:vue不能新增不存在的属性没有get和set方法,不会监控数据的变化
深度响应 因为每次赋予一个新对象增加prototypey
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Com patible" content="ie=edge">
<title>Document</title>
<script>
function Vue(options={}){
this.$options=options//将所有属性挂在$options上
var data=this._data=this.$options.data
observe(data)
//数据代理
// this代理了this_data 所以我们可以在源码中使用vue.a vue._data.a
for(let key in data){
Object.defineProperty(data,key,{
enumerable:true,
get(){
return this._data[key]
},
set(newval){
this._data[key]=newval
}
})
}
}
//vue.$options
// 数据劫持
//观察对象,给对象增加Object.defineProperty
function Observe(data){
for(let key in data){
let dep=new Dep()
let val=data[key]
observe(val)//深层对复制
Object.defineProperty(data,key,{
enumerable:true,
get(){
Dep.target&&dep.addsub(Dep,target)//[watcher]
return val
},
set(newval){//更改值
if(val===newval){//设置的值和以前的一样
return
}
val=newval//如果以后获取值的时候,将刚才获得的值再丢回去
observe(newval)//深度数 据观察 确保新添加的对象上也能够做到数据劫持
dep.show()//让所有的watch的updata方法执行
}
})
}
}
function observe(data){
if(typeof data!=='object'){
return
}
return new Observe(data)
}
// 编译模式 插值表达式
function Compile(el,vue){
//el表表示替换的范围
vue.$el=document.querySelector(el)
let fragment=document.createDocumentFragment()
while(child=vue.$el.firstChild){//将app中的内容移入到内存中
fragment.appendChild(child)
}
replace(fragment)
function replace(fragment){
Arrar.from(fragment.childNodes).forEach(function(node){
let text=node.textContent;
let reg=/\{\{(.*)\}\}/
if(node.nodeType===3&®.test(text)){
let arr=RegExp.$1.split(",")
let val=vue//val变成this
arr.forEach(function(k){
val=val[k]
})
//连接视图和数据
new Wather(vue,RegExp.$1,function(newval){//该函数需要接受一个新的值
node.textContent=text.replace(/\{\{(.*)\}\}/)
})
node.textContent=text.replace(/\{\{(.*)\}\}/)
}
// 实现双向绑定
if(node.nodeType===1){
let nodeAttrs=node.nodeAttrs//获取当前节点的属性
Array.from(nodeAttrs).forEach(item=>{
let name=item.name
let exp=item.value//即在页面上往输入框中添加的内容
if(name.indexOf("v-")){
node.value=vue[exp]
}
new Wather(vue,exp,function(newval){
node.value=newval//当watcher触发时自动将内容放到输入框内
})
node.addEventListener("input",function(e){
let newVal=e.target.value
vue[exp]=newVal
})
})
}
if(node.childNodes){
replace(node)
}
})
}
vue.$el.appendChild(fragment)
}
//发布订阅模式
function Dep(){
this.subs=[]
}
Dep.prototype.addsub=function(sub){//订阅
this.subs.push(sub)
}
Dep.prototype.show=function(){//发布
this.subs.forEach(item=>item.updata())
}
function Wather(vue,exp,fn){//Watch是一个类,通过它创建的每一个实例都拥有updata方法,并且该方法执行传入的函数
this.fn=fn
this.vue=vue
this.exp=exp//添加到订阅中
Dep.target=this
let val=vue
let arr=exp.split(",")
arr.forEach(function(k){
val=val[k]
})
Dep.target=null
}
Wather.prototype.updata=function(){
let val=this.vue
let arr=this.exp.split(",")
arr.forEach(function(k){
val=val[k]
})
this.fn()
}
</script>
<!-- <script>
//观察者模式可以写为
function observer(data){
for (let key in data){
let val=data[key]
if(val!=null&&typeof val ==="object"){
observer(val);
}
Object.defineProperty(data,key,{
enumerable:true,
get(){
return val;
},
set(newval){
if(newval===val){
return;
}
val=newval;
if(val!=null&&typeof val==="object"){
observer(val);
}
}
})
}
}
</script> -->
<script>
let vue=new Vue({
el:"app",
data:{a:1}
})
</script>
</head>
<body>
<input type="text" v-model="a">
</body>
</html>
<!-- vue不能新增不存在的属性没有get和set方法,不会监控数据的变化
深度响应 因为每次赋予一个新对象增加prototypey -->