vue2 揭秘响应式原理

一、如何理解MVVM - Model-View-ViewModel

1.png

二、设计模式之 -- 观察者模式/发布订阅模式 【从某种意义上可以视为一样】

1、概念: 定义对象间的一种一对多的依赖关系, 使得每当一个对象状态发生改变,其依赖对象皆得到通知并被自动更新。
2、特点:

  • 发布 & 订阅
  • 一对n [n可以为1]


    11.png

观察者模式

//  主题、保存状态,状态变化之后触发所有观察者对象  ---- 【是发布者】
class Subject {
  constructor() {
    this.state = 0
    this.observers = []
  }
  getState () {
    return this.state
  }
  setState (state) {
    this.state = state
    this.notifyAllObservers()
  }
  notifyAllObservers () {
    this.observers.forEach(observer => {
      observer.update() // 触发事件
    })
  }
  attach (observer) {
    this.observers.push(observer)
  }
}
//  观察者 --- 订阅目标
class Observer {
  constructor(name, subject) {
    this.name = name
    this.subject = subject
    this.subject.attach(this) // 订阅目标
  }
  update () {
    console.log(`${this.name} update, state: ${this.subject.getState()}`)
  }
}
const s = new Subject()

const o1 = new Observer('o1', s)
const o2 = new Observer('o2', s)
const o3 = new Observer('o3', s)
s.setState(1)

发布订阅模式

/**
 * on 和 once 注册函数,存储起来
 * 
 * emit时找到对应的函数,执行
 * 
 * off找到对应的函数, 从对象中删除
 */

class EventBus {
  /**
   * {
    *  key1: [
    *       { fn: fn1, isOnce: false },
    *       { fn: fn1, isOnce: false }
    *  ],
    *  key2: [] // 有序 
   * }
   */

  constructor() {
    this.events = {}
  }
  on (type, fn, bool = false) {
    const events = this.events
    if (!events[type]) {
      events[type] = []
    } else {
      events[type].push({fn, isOnce: bool})
    }
  }
  once (type,fn, key) {
    this.on(type, fn, true)
  }
  off (type, fn) {
    // const events = this.events
    if (!fn) {
      this.events[type] = {} // 解绑所有的
    } else {
      const fnList = this.events[type]
      if (fnList) {
        this.events[type] = fnList.filter(item => item.fn !== fn)
      }
    }
  }
  emit (type, ...args) {
    const events = this.events
    const fnList = events[type]
    console.log(events, fnList)
    if (fnList === null) return
    events[type] = fnList.filter(item => {
      const { fn, isOnce } = item
      fn(...args)
      // once 执行一次就要被过滤掉
      if (!isOnce) { return true }
      return false
    })
  }
}
const e = new EventBus()

function fn1 (a, b) { console.log('fn1', a, b) }
function fn2 (a, b) { console.log('fn2', a, b) }
function fn3 (a, b) { console.log('fn3', a, b) }
e.on('key1', fn1)
e.on('key1', fn2)
e.once('key3', fn3)
e.on('xxxx', fn3)
e.emit('key1', 10, 20) // 触发fn1 fn2 fn3
/*
 *  e ----- 就是事件处理中心
*  emit ---- 发布者
* on --- 订阅者
*/

一、响应式原理 【官网 vs other】

1.png
2.png

官网的图 data 和 watcher中间少了一个Dep, 图中nodify 和 Collect as DesPendency都是Dep实例做的 【可能是为了方便理解吧, 故意省略Dep类】

Dep是什么东西??

Dep理解
依赖收集器 : 在对data进行响应式的时候, 需要使用 依赖收集器 将所有data的依赖收集起来。Vue中的依赖收集器的具体表现形式就是Dep。

Dep类的具体作用就是在数据get过程中, 收集数据的相关依赖项, 用于之后的更新依赖操作

Dep实例的不会直接存放依赖项,Dep实例存放的其实是各个依赖项对应的watcher【订阅者/观察者】实例,由watcher实例去调用对应依赖项的更新。

<!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>
    /**
     * 1、根据上图实现整体一个架构(MVVM类或者VUE类,Watcher类), 这里用到一个发布订阅者模式
     * 2、然后实现mvvm中的M -> V,把模型里面的数据绑定到视图
     * 3、最后实现V -> M,当文本框输入文本的时候,由文本事件触发更新模型中的数据, 同时也更新相对应的视图
     */

    // 发布者
    class Vue {
      constructor (options) {
        this.$data = options.data
        this.$el = document.querySelector(options.el); // 获取元素对象
        console.log(this.$el, this.$data)
        // 容器
        // {myText: [订阅者1, 订阅者2], myBox: [订阅者1, 订阅者2] }
        // {myText: [Watcher1, Watcher2], myBox: [Watcher1, Watcher2] }
        this._directive = {}
        this.Observer(this.$data)
        this.Complie(this.$el)
      }
      Observer (data) { // 劫持数据
        for (let key in data) {
          this._directive[key] = []
          let value = data[key]
          let watchArr = this._directive[key]
          Object.defineProperty(data, key, {
            get () {
              return value
            },
            set (newValue) {
              if (newValue !== value) {
                value = newValue
                // item ----> 一个个订阅者的实力对象 , 可以调用其updata方法 更新视图
                watchArr.forEach(item => {
                  item.updata()
                });
              }
             }
          })
        }
        // console.log(this._directive) // {myText: [], myBox: []}
      }
      // 主要功能: 解析指令
      // 为什么需要解析指令???? ----> 依赖收集 ----> 更新视图 ----> 订阅   
      Complie (el) {
        let nodes = el.children; // 获取app div对象下面的所有子对象
        for (let i = 0; i< nodes.length; i++ ) {
          let node = nodes[i]
          if (node.children.length) {
            this.Complie(node)
          }
          if (node.hasAttribute('v-text')) {
            // 订阅
            // console.log(node.hasAttribute('v-text'))
            const attrVal = node.getAttribute('v-text')
            // push什么??? ----> 订阅者 ----> 订阅者是谁???
            this._directive[attrVal].push(new Watcher(node, this, attrVal, "innerHTML"))
          }
          if (node.hasAttribute('v-model')) {
            // 订阅
            const attrVal = node.getAttribute('v-model')
            // console.log(node.hasAttribute('v-model'))
            this._directive[attrVal].push(new Watcher(node, this, attrVal, "value"))
            node.addEventListener('input', () => {
              this.$data[attrVal] = node.value
            })
          }
        }
      }
    }

    // 订阅者 ---> 负责更新本身的状态
    class Watcher { // 主要功能更新视图
      constructor (el, vm, exp, attr) {
        this.el = el
        this.vm = vm
        this.exp = exp
        this.attr = attr
        this.updata()
      }
      updata() {
        // div.innerHTML = this.vm.$data['myText']
        this.el[this.attr] = this.vm.$data[this.exp]
      }
    }
  </script>
</head>
<body>
  <div id="app">
    <h1>数据响应式</h1>
    <div>
      <div v-text="myText"></div>
      <div v-text="myBox"></div>
      <input type="text" v-model="myText">
      <input type="text" v-model="myBox">
    </div>
  </div>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        myText: '我是:myText',
        myBox: '我是:myBox'
      }
    })
  </script>
</body>
</html>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容