双向绑定原理

reduce()

// reduce是数组的一个api
const arr=[1,2,3,4,5,6,7]
const res=arr.reduce((val,item)=>{return val+item},0)
console.log(res);
const obj={
  name:'zs',
  info:{
    address:{
      location:'北京'
    }
  }
}
const attrs=['info','address','location']
// const location=attrs.reduce((newobj,k)=>{return newobj[k]},obj)
// const location=attrs.reduce((newobj,k)=>newobj[k],obj)
// console.log(location);
const attrStr='info.address.location'
const location=attrStr.split('.').reduce((newobj,k)=>newobj[k],obj)
console.log(location);

发布订阅

  • dep()
    • 定义一个数组存取订阅消息
    • 添加订阅
    • 遍历/发布订阅
  • watcher()

简单的发布订阅

// 收集依赖/收集订阅
class Deep{
  constructor(){
    // 存放所有订阅者信息
    this.subs=[]
  }

  // 向subs数组中添加订阅信息
  addSub(watcher){
    this.subs.push(watcher)
  }
  // 发布订阅方法
  notify(){

  }
}

// 订阅者
class Watcher{
  // 新建watcher实例的时候传一个cb进来挂载到自己身上
  constructor(cb){
    this.cb=cb
  }
  // 触发回调方法
  update(){
    this.cb()
  }
}

const w1=new Watcher(()=>{
  console.log('我是第一个订阅者');
})

const w2=new Watcher(()=>{
  console.log('我是第二个订阅者');
})

w1.update()
w2.update()

发布订阅

// 发布订阅方法
notify(){
  this.subs.forEach(watcher=>watcher.update())
}

当vue监听到数据变化时,会把新数据通过notify通知给订阅者(DOM),订阅者通过update来改变数据,重新渲染DOM节点。

数据劫持

Object.defineProperty的基本属性

const obj={
  name:'zs',
  age:20,
  info:{
    a:1,
    b:2
  }
}

console.log(obj.name);
// 赋值操作
obj.name='ls'

console.log(obj.name);
// 定义属性,监听?
Object.defineProperty(obj,'name',{
  enumerable:true, //当前属性允许被循环
  configurable:true,  //当前属性允许被配置
  // 拦截取值操作
  get(){
    console.log('有人获取了obj.name的值')
    return '我不是zs'
  },
  // 拦截赋值操作
  set(newValue){
    console.log('我不要你给的值',newValue);
  }
})

console.log(obj.name);

obj.name="ls"

何时通知DOM数据发生变化

数据变动之后,在Object.defineProperty的setter中通知watcher数据发生变化

  // 拦截赋值操作
  set(newValue){
    console.log('我不要你给的值',newValue);
    // 数据变动之后,在setter中通知watcher数据发生变化
    dep.notify()
  }

创建一个vue实例/以及写一个vue对象

const vm=new Vue({
  el:'#app',
  data:{
    name:'zs',
    age:20,
    info:{
      a:'a1',
      c:'c1'
    } 
  }
})
console.log(vm);

遍历所有对象一次设置getter/setter拦截

// vue.js
class Vue{
  constructor(options){
    this.$data=options.data

    // 进行调用数据劫持的方法
    Observe(this.$data)
  }
}
//定义一个数据劫持的方法
// obj为data
function Observe(obj){
  //拿到对象的属性值形成一个数组
  // console.log(Object.keys(obj));
  Object.keys(obj).forEach(key=>{
    // 需要为当前的key所对应的属性添加getter/setter
    // 当前被循环的key所对应的属性值
    let value=obj[key]
    Object.defineProperty(obj,key,{
      enumerable:true,
      configurable:true,
      get(){
        console.log(`有人获取了${key}的值`);
        return value
      },
      set(newValue){
        console.log(`有人修改了${key}的值`);
        value=newValue
      }
    })
  })
}

在这里插入图片描述
在这里插入图片描述
此时data内的对象并未被添加拦截,递归设置

//定义一个数据劫持的方法
// obj为data
function Observe(obj){
  // 递归的终止条件
  if(!obj||typeof obj!=='object') return
  //拿到对象的属性值形成一个数组
  // console.log(Object.keys(obj));
  Object.keys(obj).forEach(key=>{
    // 需要为当前的key所对应的属性添加getter/setter
    // 当前被循环的key所对应的属性值
    let value=obj[key]
    // 把value子节点进行递归
    Observe(value)
    Object.defineProperty(obj,key,{
      enumerable:true,
      configurable:true,
      get(){
        console.log(`有人获取了${key}的值`);
        return value
      },
      set(newValue){
        console.log(`有人修改了${key}的值`);
        value=newValue
      }
    })
  })
}

在这里插入图片描述
此时重新赋值getter/setter又消失了,因此需要再次调用递归

set(newValue){
  console.log(`有人修改了${key}的值`);
  value=newValue
  Observe(newValue)
}

属性代理

通过vm就能调用data中的数据(属性代理)

// 属性代理
    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
        }
      })
    })

在这里插入图片描述
如今已经可以实现监听到数据的变化

单向绑定

在vue构造函数中调用模板函数/

    // 调用模板编译的函数
    Compile(options.el,this)
// 对HTML结构进行模板编译,数据渲染
// el获取区域,vm实例获取数据
// new Vue实例的时候执行
function Compile(el,vm) {
  // 获取到的dom元素直接挂载到vm的$el身上
  vm.$el=document.querySelector(el)
  // 创建文档碎片,提高DOM操作性能
  // 文档碎片:开辟内存存放dom节点,在内存中操作
  const fragment=document.createDocumentFragment()
  // 放入文档碎片
  while(childNode=vm.$el.firstChild){
    fragment.appendChild(childNode)
  }
  // 进行模板编译
  

  // 拿出来
  vm.$el.appendChild(fragment)
}

获取根节点

// Compile()
// 进行模板编译
replace(fragment)

// 对dom节点进行编译
function replace(node){
  // 正则提取插值表达式{{value}}
  const regMustache=/\{\{\s*(\S+)\s*\}\}/
  // 证明当前的node节点是文本子节点,进行正则替换
  if(node.nodeType===3){
    // 文本子节点也是dom,获取值需要调用textContent属性
    console.log(node.textContent)
    // 终止递归条件
    return
  }
  // 证明不是文本节点,调用递归
  node.childNodes.forEach(child=>replace(child))
}

获取根节点(包括对象的根节点)的数据,赋值给代码片的nodeContent

  // 对dom节点进行编译
  function replace(node){
    // 正则提取插值表达式{{value}}
    const regMustache=/\{\{\s*(\S+)\s*\}\}/
    // 证明当前的node节点是文本子节点,进行正则替换
    if(node.nodeType===3){
      // 文本子节点也是dom,获取值需要调用textContent属性
      // console.log(node.textContent)
      const text=node.textContent
      // 正则获取代码片段到一个数组
      const execres=regMustache.exec(text)
      // console.log(execres);
      if(execres){
        // node.textContent=vm[execres[1]]
        // 分割对象(split),且获取最后的值(reduce)
        const value=execres[1].split('.').reduce((newObj,k)=>newObj[k],vm)
        // 此replace是字符串的替换?
        node.textContent=text.replace(regMustache,value)
      }
      // 终止递归条件
      return
    }
    // 证明不是文本节点,调用递归
    node.childNodes.forEach(child=>replace(child))
  }

此时只能实现数据的绑定,渲染到页面,不能监听数据的改变,接下来要发布订阅了

发布者告诉watcher数据改变,watcher调用update告诉自己的回调

watcher的回调应该包含数据的更新
node.textContent=text.replace(regMustache,value)

创建发布订阅的类

// 收集watcher订阅者的类
class Dep{
  constructor(){
    // 所有的watcher都要存放在数组中
    this.subs=[]
  }

  // 加watcher
  addSub(watcher){
    this.subs.push(watcher)
  }

  // 通知watcher
  notify(){
    // 循环每一个watcher调用更新他们的函数
    this.subs.forEach(watcher=>watcher.update())
  }
}

// watcher订阅者的类
class Watcher{
  // cb回调函数中记录着watcher如何更新自己的文本
  // vm保存着需要更新自己的数据
  // key表示着更新文本内容的属性
  constructor(vm,key,cb){
    this.vm=vm
    this.key=key
    this.cb=cb
  }

  // 让发布者通知你该更新了
  update(){
    this.cb()
  }
}

创建watcher的实例

// 你已经学会更新自己了
// 此replace是字符串的替换?
node.textContent=text.replace(regMustache,value)
// 创建watcher的实例
new Watcher(vm,execres[1],(newValue)=>{
  node.textContent=text.replace(regMustache,newValue)
})

存到dep数组中去

// watcher的构造
// 负责把创建的watcher实例存到Dep实例的subs数组中,搞不懂
// 此this为watcher
Dep.target=this
// 整个reduce走完,那么就可以取到新值,从vm取值会触发数据劫持的getter=>getter
key.split('.').reduce((newObj,k)=>newObj[k],vm)
Dep.target=null
// obsever实例
// 创建一个订阅者实例
const dep=new Dep()
	// get方法
 	Dep.target&&dep.addSub(Dep.target)

什么时候调用notify呢,在setter中

// 通知订阅者更新自己的文本
dep.notify()

在update的cb中传一个新值

// 让发布者通知你该更新了
update(){
  const value=this.key.split('.').reduce((newObj,k)=>newObj[k],this.vm)
  this.cb(value)
}

单向数据绑定实现
全部代码

class Vue{
  constructor(options){
    // this===vm
    this.$data=options.data

    // 进行调用数据劫持的方法
    Observe(this.$data)

    // 属性代理
    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)
  }
}

//定义一个数据劫持的方法
// obj为data
function Observe(obj){
  // 递归的终止条件
  if(!obj||typeof obj!=='object') return
  // 创建一个订阅者实例
  const dep=new Dep()
  //拿到对象的属性值形成一个数组
  // console.log(Object.keys(obj));
  Object.keys(obj).forEach(key=>{
    // 需要为当前的key所对应的属性添加getter/setter
    // 当前被循环的key所对应的属性值
    let value=obj[key]
    // 把value子节点进行递归
    Observe(value)
    Object.defineProperty(obj,key,{
      enumerable:true,
      configurable:true,
      get(){
        // 只要执行了下边这一行,那么new的watcher实例就被放入了dep.subs数组中了,666
        Dep.target&&dep.addSub(Dep.target)
        console.log(`有人获取了${key}的值`);
        return value
      },
      set(newValue){
        console.log(`有人修改了${key}的值`);
        value=newValue
        Observe(newValue)
        // 通知订阅者更新自己的文本
        dep.notify()
      }
    })
  })
}

// 对HTML结构进行模板编译,数据渲染
// el获取区域,vm实例获取数据
// new Vue实例的时候执行
function Compile(el,vm) {
  // 获取到的dom元素直接挂载到vm的$el身上
  vm.$el=document.querySelector(el)
  // 创建文档碎片,提高DOM操作性能
  // 文档碎片:开辟内存存放dom节点,在内存中操作
  const fragment=document.createDocumentFragment()
  // 放入文档碎片
  while(childNode=vm.$el.firstChild){
    fragment.appendChild(childNode)
  }

  // 进行模板编译
  replace(fragment)

  // 拿出来
  vm.$el.appendChild(fragment)

  // 对dom节点进行编译
  function replace(node){
    // 正则提取插值表达式{{value}}
    const regMustache=/\{\{\s*(\S+)\s*\}\}/
    // 证明当前的node节点是文本子节点,进行正则替换
    if(node.nodeType===3){
      // 文本子节点也是dom,获取值需要调用textContent属性
      // console.log(node.textContent)
      const text=node.textContent
      // 正则获取代码片段到一个数组
      const execres=regMustache.exec(text)
      // console.log(execres);
      if(execres){
        // node.textContent=vm[execres[1]]
        // 分割对象(split),且获取最后的值(reduce)
        const value=execres[1].split('.').reduce((newObj,k)=>newObj[k],vm)
        // 你已经学会更新自己了
        // 此replace是字符串的替换?
        node.textContent=text.replace(regMustache,value)
        // 创建watcher的实例
        new Watcher(vm,execres[1],(newValue)=>{
          node.textContent=text.replace(regMustache,newValue)
        })
      }
      // 终止递归条件
      return
    }
    // 证明不是文本节点,调用递归
    node.childNodes.forEach(child=>replace(child))
  }

}

// 收集watcher订阅者的类
class Dep{
  constructor(){
    // 所有的watcher都要存放在数组中
    this.subs=[]
  }

  // 加watcher
  addSub(watcher){
    this.subs.push(watcher)
  }

  // 通知watcher
  notify(){
    // 循环每一个watcher调用更新他们的函数
    this.subs.forEach((watcher)=>watcher.update())
  }
}

// watcher订阅者的类
class Watcher{
  // cb回调函数中记录着watcher如何更新自己的文本
  // vm保存着需要更新自己的数据
  // key表示着更新文本内容的属性
  constructor(vm,key,cb){
    this.vm=vm
    this.key=key
    this.cb=cb
    // 负责把创建的watcher实例存到Dep实例的subs数组中,搞不懂
    // 此this为watcher
    Dep.target=this
    // 整个reduce走完,那么就可以取到新值,从vm取值会触发数据劫持的getter=>getter
    key.split('.').reduce((newObj,k)=>newObj[k],vm)
    Dep.target=null
  }

  // 让发布者通知你该更新了
  update(){
    const value=this.key.split('.').reduce((newObj,k)=>newObj[k],this.vm)
    this.cb(value)
  }
}

文本框实现单向数据绑定

// Compile
// 判断当前的node节点是否为input输入框
if(node.nodeType===1||node.tagName==='INPUT'){
  console.log(node);
  // 得到元素的所有节点,将为数组变为真数组
  const attrs=Array.from(node.attributes)

  const findRes=attrs.find(x=>x.name==='v-model')

  if(findRes){
    // 获取到v-model的值
    const expStr=findRes.value
    // 取对象的根节点的值
    const value=expStr.split('.').reduce((newObj,k)=>newObj[k],vm)
    node.value=value
    // 创建watcher的实例
    new Watcher(vm,expStr,(newValue)=>{
      node.value=newValue
    })
  }
  // console.dir(findRes);
}

文本框实现双向数据绑定

监听文本框输入事件

// 监听文本框的输入事件,拿到文本框的值,更新到vm上
node.addEventListener('input',(e)=>{
  // console.log(e.target.value);
  // 分割data属性数组
  const keyArr=expStr.split('.')
  // 从零截到倒数第二个
  const obj=keyArr.slice(0,keyArr.length-1).reduce((newObj,k)=>newObj[k],vm)
  obj[keyArr[keyArr.length-1]]=e.target.value
})

完结,撒花

全部代码

// vue.js
class Vue{
  constructor(options){
    // this===vm
    this.$data=options.data

    // 进行调用数据劫持的方法
    Observe(this.$data)

    // 属性代理
    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)
  }
}

//定义一个数据劫持的方法
// obj为data
function Observe(obj){
  // 递归的终止条件
  if(!obj||typeof obj!=='object') return
  // 创建一个订阅者实例
  const dep=new Dep()
  //拿到对象的属性值形成一个数组
  // console.log(Object.keys(obj));
  Object.keys(obj).forEach(key=>{
    // 需要为当前的key所对应的属性添加getter/setter
    // 当前被循环的key所对应的属性值
    let value=obj[key]
    // 把value子节点进行递归
    Observe(value)
    Object.defineProperty(obj,key,{
      enumerable:true,
      configurable:true,
      get(){
        // 只要执行了下边这一行,那么new的watcher实例就被放入了dep.subs数组中了,666
        Dep.target&&dep.addSub(Dep.target)
        console.log(`有人获取了${key}的值`);
        return value
      },
      set(newValue){
        console.log(`有人修改了${key}的值`);
        value=newValue
        Observe(newValue)
        // 通知订阅者更新自己的文本
        dep.notify()
      }
    })
  })
}

// 对HTML结构进行模板编译,数据渲染
// el获取区域,vm实例获取数据
// new Vue实例的时候执行
function Compile(el,vm) {
  // 获取到的dom元素直接挂载到vm的$el身上
  vm.$el=document.querySelector(el)
  // 创建文档碎片,提高DOM操作性能
  // 文档碎片:开辟内存存放dom节点,在内存中操作
  const fragment=document.createDocumentFragment()
  // 放入文档碎片
  while(childNode=vm.$el.firstChild){
    fragment.appendChild(childNode)
  }

  // 进行模板编译
  replace(fragment)

  // 拿出来
  vm.$el.appendChild(fragment)

  // 对dom节点进行编译
  function replace(node){
    // 正则提取插值表达式{{value}}
    const regMustache=/\{\{\s*(\S+)\s*\}\}/
    // 证明当前的node节点是文本子节点,进行正则替换
    if(node.nodeType===3){
      // 文本子节点也是dom,获取值需要调用textContent属性
      // console.log(node.textContent)
      const text=node.textContent
      // 正则获取代码片段到一个数组
      const execres=regMustache.exec(text)
      // console.log(execres);
      if(execres){
        // node.textContent=vm[execres[1]]
        // 分割对象(split),且获取最后的值(reduce)
        const value=execres[1].split('.').reduce((newObj,k)=>newObj[k],vm)
        // 你已经学会更新自己了
        // 此replace是字符串的替换?
        node.textContent=text.replace(regMustache,value)
        // 创建watcher的实例
        new Watcher(vm,execres[1],(newValue)=>{
          node.textContent=text.replace(regMustache,newValue)
        })
      }
      // 终止递归条件
      return
    }

    // 判断当前的node节点是否为input输入框
    if(node.nodeType===1||node.tagName==='INPUT'){
      // 得到元素的所有节点,将为数组变为真数组
      const attrs=Array.from(node.attributes)
      // 获取到v-model的属性值
      const findRes=attrs.find(x=>x.name==='v-model')

      if(findRes){
        // 获取到v-model的值
        const expStr=findRes.value
        // 取对象的根节点的值
        const value=expStr.split('.').reduce((newObj,k)=>newObj[k],vm)
        node.value=value

        // 创建watcher的实例
        new Watcher(vm,expStr,(newValue)=>{
          node.value=newValue
        })

        // 监听文本框的输入事件,拿到文本框的值,更新到vm上
        node.addEventListener('input',(e)=>{
          // console.log(e.target.value);
          // 分割data属性数组
          const keyArr=expStr.split('.')
          // 从零截到倒数第二个
          const obj=keyArr.slice(0,keyArr.length-1).reduce((newObj,k)=>newObj[k],vm)
          obj[keyArr[keyArr.length-1]]=e.target.value
        })
      }
      // console.dir(findRes);
    }

    // 证明不是文本节点,调用递归
    node.childNodes.forEach(child=>replace(child))
  }

}

// 收集watcher订阅者的类
class Dep{
  constructor(){
    // 所有的watcher都要存放在数组中
    this.subs=[]
  }

  // 加watcher
  addSub(watcher){
    this.subs.push(watcher)
  }

  // 通知watcher
  notify(){
    // 循环每一个watcher调用更新他们的函数
    this.subs.forEach((watcher)=>watcher.update())
  }
}

// watcher订阅者的类
class Watcher{
  // cb回调函数中记录着watcher如何更新自己的文本
  // vm保存着需要更新自己的数据
  // key表示着更新文本内容的属性
  constructor(vm,key,cb){
    this.vm=vm
    this.key=key
    this.cb=cb
    // 负责把创建的watcher实例存到Dep实例的subs数组中,搞不懂
    // 此this为watcher
    Dep.target=this
    // 整个reduce走完,那么就可以取到新值,从vm取值会触发数据劫持的getter=>getter
    key.split('.').reduce((newObj,k)=>newObj[k],vm)
    Dep.target=null
  }

  // 让发布者通知你该更新了
  update(){
    const value=this.key.split('.').reduce((newObj,k)=>newObj[k],this.vm)
    this.cb(value)
  }
}
//1.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">
    <h3>姓名是:{{name}}</h3>
    <h3>年龄是:{{age}}</h3>
    <h3>info.a是:{{info.a}}</h3>
    <div>name的值是<input type="text" v-model="name"></div>
    <div>info.a的值是<input type="text" v-model="info.a"></div>
  </div>

  <script src="./vue.js"></script>
  <script>
    const vm=new Vue({
      el:'#app',
      data:{
        name:'zs',
        age:20,
        info:{
          a:'a1',
          c:'c1'
        } 
      }
    })
    console.log(vm);
  </script>
</body>
</html>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值