精华内容
下载资源
问答
  • Vue 源码学习

    千次阅读 2019-05-02 11:27:58
    Vue 源码学习 资料 熟悉设计模式 AST 抽象语法树 https://www.cnblogs.com/ztfjs/p/vue2.html (未完,待续)

    Vue 源码学习

    资料
    熟悉设计模式
    AST 抽象语法树
    htmlParse 解析器
    wue 仿Vue实现
    Vue.js 源码学习笔记
    Virtual DOM patching algorithm based on Snabbdom
    逐行学习vue 源码
    组件的本质

    以下是个人学习vue 源码的先后学习过程:(假设我是小白,按照下面顺序学习会轻松很多,循序渐进)

    1. 学习正则表达式

    2. 学习js 设计模式,重点观察者模式 50行代码的MVVM,感受闭包的艺术

    3. 了解AST 抽象语法树的概念,并通过博文开头的资料 htmlParse解析器 中分析parse 原理

    4. 参看githut 上的开源仿 Vue 实现项目 wue 仿Vue实现Vue 源码注释版(注释版可以放在后面看)

    5. 查看调试vue.js 2.1.3 版本单文件脚本(或其他新版本) https://cdn.bootcss.com/vue/2.1.3/vue.js

    6. 学习 es6 模块化编程,学习 rollup 构建工具,学习 flow 类型检查工具

    7. es6 版本的vue 学习 源码调试方法,对应版本V2.5.9

    8. 待补充~

    步骤5 举例

    单步调试如下代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
      <!-- 版本:vue 2.1.3 -->
      <script type="text/javascript" src="./vue.js"></script>
    </head>
    <body>
      
      <div id="app">
        <div><span v-text="reverse"></span></div>
        <div><span v-text="tip"></span></div>
        <div><bar :age="info.age"></bar></div>
      </div>
      
      <script>
          let bar = {
            name: 'Bar',
            template: '<div><span>name: {{name}}</span><br/><span>age: {{age}}</span></div>',
            props: [ 'age' ],
            created() {
              console.log('child created.')
            },
            data() {
              return {
                name: 11
              }
            }
          }
    
          var app = new Vue({
            el: "#app",
            components: {
              bar: bar
            },
            created() {
              console.log('created.')
            },
            data() {
              return {
                message: "hello.",
                info: {
                  age: 12
                }
              }
            },
            computed: {
              reverse() {
                return this.message.split("").reverse().join("")
              },
              tip() {
                return `${this.message} world.`
              }
            }
          })
    
          setTimeout(()=>{
            app.info.age = 23
          }, 400)
      </script>
      
    </body>
    </html>
    

    编译过程中会生成render code 如下:

    _h(
       'div',
       {attrs:{"id":"app"}},
       [
        _h(
           'div',
           [
            _h('span',{domProps:{"textContent":_s(reverse)}})]),
            " ",
            _h(
               'div',
               [_h('span',{domProps:{"textContent":_s(tip)}})]
              ),
            " ",
            _h('div',[_h('bar',{attrs:{"age":info.age}})]
          )
       ]
    )
    

    其中_h_s 方法分别对应:

    // shorthands used in render functions
    Vue.prototype._h = createElement;
    // toString for mustaches
    Vue.prototype._s = _toString;
    

    官网example 代码对应render code示例:render code 示例

    大体过程:

    1. render code 的执行,即代码调用 vm._render() , 后生成了包含dom 结构的vnode 实例;
    2. vmpatch 调用会把 vnode 树转为document 的对象并挂在 vm.$el 上,并挂载到根节点上,如示例中的 <div id='app'> 上;
    代码案例

    示例1
    index.html

    <body>
      <div id="demo">
        <div v-text="message"></div>
        <span>{{info.age}}</span>
      </div>
      <script src="./app.js"></script>
    </body>
    

    app.js

    var demo = new Vue({
      el: '#demo',
      data: {
        info: {
          name: {
            firstName: 'Lili'
          }
        }
      },
      computed: {
        message () {
          return 'Hello' + this.info.name.firstName
        }
      },
      created: function () {
        this.init()
      },
      methods: {
        init () {
          setTimeout(() => {
            this.info.age = 88
            delete this.info.name.firstName
            this.$forceUpdate()
          }, 1000)
        }
      }
    })
    

    执行结果:

    问题:上述示例如果注释掉 this.$forceUpdate() 的结果呢? 自己动手试一下吧


    示例2
    index.html

    <body>
      <div id="demo">
        <ul style="border-bottom: 1px solid plum;">
           <li v-for="item in info.list" :key="item">{{item}}</li>
        </ul>
      </div>
      <script src="./app.js"></script>
    </body>
    

    app.js

    var demo = new Vue({
      el: '#demo',
      data: {
        info: {
          list: ['app', 'web', 'ios']
        }
      }
    })
    // 通过数组索引修改数值
    demo.info.list[0] = 'apple'
    

    执行结果:

    问题1:那我如何修改数组的值呢? (下文回答)

    现在将app.js 做了如下调整:

    var demo = new Vue({
      el: '#demo',
      data: {
        info: {
          list: ['app', 'web', 'ios']
        }
      },
      created: function () {
        this.init()
      },
      methods: {
        init () {
          this.info.list[0] = 'apple'
        }
      }
    })
    

    发现执行结果有效了:

    问题2:为什么这样又可以了呢?

    现在来揭晓答案:
    问题1:原因是Object.defineProperty的局限,set方法在一些场景下不会触发(例如示例)。官方给的解决方法是利用 Vue.set或者vm.$set。可参看官网列表渲染

    问题2:原因是init方法是在created生命周期中调用的,此时dom结构还没开始生成。如果放在mounted生命周期中调用则同样不会更新。

    (未完,待续)

    展开全文
  • vue源码看观察者模式

    千次阅读 2018-01-30 00:42:56
    摘要:源码解读设计模式系列文章将陆陆续续进行更新中 ~ 观察者模式 首先话题下来,我们得反问一下自己,什么是观察者模式? 概念 观察者模式(Observer):通常又被称作为发布-订阅者模式。它定义了一种一...

    摘要:源码解读设计模式系列文章将陆陆续续进行更新中 ~

    观察者模式

    首先话题下来,我们得反问一下自己,什么是观察者模式?

    概念

    观察者模式(Observer):通常又被称作为发布-订阅者模式。它定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖于它的对象都会得到通知并自动更新,解决了主体对象与观察者之间功能的耦合。

    讲个故事

    上面对于观察者模式的概念可能会比较官方化,所以我们讲个故事来理解它。

    • A:是共产党派往国民党密探,代号 001(发布者)
    • B:是共产党的通信人员,负责与 A 进行秘密交接(订阅者)

      1. A 日常工作就是在明面采集国民党的一些情报
      2. B 则负责暗中观察着 A
      3. 一旦 A 传递出一些有关国民党的消息(更多时候需要对消息进行封装传递,后面根据源码具体分析)
      4. B 会立马订阅到该消息,然后做一些相对应的变更,比如说通知共产党们做一些事情应对国民党的一些动作。

    适用性

    以下任一场景都可以使用观察者模式

    1. 当一个抽象模型有两个方面,其中一个方面依赖于另一方面。讲这两者封装在独立的对象中可以让它们可以各自独立的改变和复用
    2. 当一个对象的改变的时候,需要同时改变其它对象,但是却不知道具体多少对象有待改变
    3. 当一个对象必须通知其它对象,但是却不知道具体对象到底是谁。换句话说,你不希望这些对象是紧密耦合的。

    vue 对于观察者模式的使用

    vue 使用到观察者模式的地方有很多,这里我们主要谈谈对于数据初始化这一块的。

    var vm = new Vue({
      data () {
        return {
          a: 'hello vue'
        }
      }
    })

    1、实现数据劫持

    上图我们可以看到,vue 是利用的是 Object.defineProperty() 对数据进行劫持。 并在数据传递变更的时候封装了一层中转站,即我们看到的 DepWatcher 两个类。

    这一小节,我们只看如何通过观察者模式对数据进行劫持。

    1.1、递归遍历

    我们都知道,vue 对于 data 里面的数据都做了劫持的,那只能对对象进行遍历从而完成每个属性的劫持,源码具体如下

    walk (obj: Object) {
      const keys = Object.keys(obj)
      // 遍历将其变成 vue 的访问器属性
      for (let i = 0; i < keys.length; i++) {
        defineReactive(obj, keys[i], obj[keys[i]])
      }
    }

    1.2、发布/订阅

    从上面对象的遍历我们看到了 defineReactive ,那么劫持最关键的点也在于这个函数,该函数里面封装了 gettersetter 函数,使用观察者模式,互相监听

    // 设置为访问器属性,并在其 getter 和 setter 函数中,使用发布/订阅模式,互相监听。
    export function defineReactive (
      obj: Object,
      key: string,
      val: any
    ) {
      // 这里用到了观察者(发布/订阅)模式进行了劫持封装,它定义了一种一对多的关系,让多个观察者监听一个主题对象,这个主题对象的状态发生改变时会通知所有观察者对象,观察者对象就可以更新自己的状态。
      // 实例化一个主题对象,对象中有空的观察者列表
      const dep = new Dep()
    
      // 获取属性描述符对象(更多的为了 computed 里面的自定义 get 和 set 进行的设计)
      const property = Object.getOwnPropertyDescriptor(obj, key)
      if (property && property.configurable === false) {
        return
      }
    
      const getter = property && property.get
      const setter = property && property.set
    
      let childOb = observe(val)
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        // 收集依赖,建立一对多的的关系,让多个观察者监听当前主题对象
        get: function reactiveGetter () {
          const value = getter ? getter.call(obj) : val
          if (Dep.target) {
            dep.depend()
            if (childOb) {
              childOb.dep.depend()
              // 这里是对数组进行劫持
              if (Array.isArray(value)) {
                dependArray(value)
              }
            }
          }
          return value
        },
        // 劫持到数据变更,并发布消息进行通知
        set: function reactiveSetter (newVal) {
          const value = getter ? getter.call(obj) : val
          if (newVal === value || (newVal !== newVal && value !== value)) {
            return
          }
          if (setter) {
            setter.call(obj, newVal)
          } else {
            val = newVal
          }
          childOb = observe(newVal)
          dep.notify()
        }
      })
    }

    1.3、返回 Observer 实例

    上面我们看到了observe 函数,核心就是返回一个 Observer 实例

    return new Observer(value)

    2、消息封装,实现 “中转站”

    首先我们要理解,为什么要做一层消息传递的封装?

    我们在讲解观察者模式的时候有提到它的 适用性 。这里也同理,我们在劫持到数据变更的时候,并进行数据变更通知的时候,如果不做一个”中转站”的话,我们根本不知道到底谁订阅了消息,具体有多少对象订阅了消息。

    这就好比上文中我提到的故事中的密探 A(发布者) 和共产党 B(订阅者)。密探 A 与 共产党 B 进行信息传递,两人都知道对方这么一个人的存在,但密探 A 不知道具体 B 是谁以及到底有多少共产党(订阅者)订阅着自己,可能很多共产党都订阅着密探 A 的信息,so 密探 A(发布者) 需要通过暗号 收集到所有订阅着其消息的共产党们(订阅者),这里对于订阅者的收集其实就是一层封装。然后密探 A 只需将消息发布出去,而订阅者们接受到通知,只管进行自己的 update 操作即可。

    简单一点,即收集完订阅者们的密探 A 只管发布消息,共产党 B 以及更多的共产党只管订阅消息并进行对应的 update 操作,每个模块确保其独立性,实现高内聚低耦合这两大原则。

    废话不多说,我们接下来直接开始讲 vue 是如何做的消息封装的

    2.1、Dep

    Dep,全名 Dependency,从名字我们也能大概看出 Dep 类是用来做依赖收集的,具体怎么收集呢。我们直接看源码

    let uid = 0
    
    export default class Dep {
      static target: ?Watcher;
      id: number;
      subs: Array<Watcher>;
    
      constructor () {
        // 用来给每个订阅者 Watcher 做唯一标识符,防止重复收集
        this.id = uid++
        // 定义subs数组,用来做依赖收集(收集所有的订阅者 Watcher)
        this.subs = []
      }
    
      // 收集订阅者
      addSub (sub: Watcher) {
        this.subs.push(sub)
      }
    
      depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
    
      notify () {
        // stabilize the subscriber list first
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
        }
      }
    }
    
    // the current target watcher being evaluated.
    // this is globally unique because there could be only one
    // watcher being evaluated at any time.
    Dep.target = null

    代码很简短,但它做的事情却很重要

    1. 定义subs数组,用来收集订阅者Watcher
    2. 当劫持到数据变更的时候,通知订阅者Watcher进行update操作

    源码中,还抛出了两个方法用来操作 Dep.target ,具体如下

    // 定义收集目标栈
    const targetStack = []
    
    export function pushTarget (_target: Watcher) {
      if (Dep.target) targetStack.push(Dep.target)
      // 改变目标指向
      Dep.target = _target
    }
    
    export function popTarget () {
      // 删除当前目标,重算指向
      Dep.target = targetStack.pop()
    }

    2.2、 Watcher

    Watcher 意为观察者,它负责做的事情就是订阅 Dep ,当Dep 发出消息传递(notify)的时候,所以订阅着 DepWatchers 会进行自己的 update 操作。废话不多说,直接看源码就知道了。

    export default class Watcher {
      vm: Component;
      expression: string;
      cb: Function;
    
      constructor (
        vm: Component,
        expOrFn: string | Function,
        cb: Function,
        options?: Object
      ) {
        this.vm = vm
        vm._watchers.push(this)
        this.cb = cb
        // parse expression for getter
        if (typeof expOrFn === 'function') {
          this.getter = expOrFn
        } else {
          // 解析表达式
          this.getter = parsePath(expOrFn)
          if (!this.getter) {
            this.getter = function () {}
          }
        }
        this.value = this.get()
      }
    
      get () {
        // 将目标收集到目标栈
        pushTarget(this)
        const vm = this.vm
    
        let value = this.getter.call(vm, vm)
        // 删除目标
        popTarget()
    
        return value
      }
    
      // 订阅 Dep,同时让 Dep 知道自己订阅着它
      addDep (dep: Dep) {
        const id = dep.id
        if (!this.newDepIds.has(id)) {
          this.newDepIds.add(id)
          this.newDeps.push(dep)
          if (!this.depIds.has(id)) {
            // 收集订阅者
            dep.addSub(this)
          }
        }
      }
    
      // 订阅者'消费'动作,当接收到变更时则会执行
      update () {
        this.run()
      }
    
      run () {
        const value = this.get()
        const oldValue = this.value
        this.value = value
        this.cb.call(this.vm, value, oldValue)
      }
    }

    上述代码中,我删除了一些与目前探讨无关的代码,如果需要进行详细研究的,可以自行查阅 vue2.5.3 版本的源码。

    现在再去看 DepWatcher,我们需要知道两个点

    1. Dep 负责收集所有的订阅者 Watcher ,具体谁不用管,具体有多少也不用管,只需要通过 target 指向的计算去收集订阅其消息的 Watcher 即可,然后只需要做好消息发布 notify 即可。
    2. Watcher 负责订阅 Dep ,并在订阅的时候让 Dep 进行收集,接收到 Dep 发布的消息时,做好其 update 操作即可。

    两者看似相互依赖,实则却保证了其独立性,保证了模块的单一性。

    更多的应用

    vue 还有一些地方用到了”万能”的观察者模式,比如我们熟知的组件之间的事件传递,$on 以及 $emit 的设计。

    $emit 负责发布消息,并对订阅者 $on 做统一消费,即执行 cbs 里面所有的事件。

    Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
      const vm: Component = this
      if (Array.isArray(event)) {
        for (let i = 0, l = event.length; i < l; i++) {
          this.$on(event[i], fn)
        }
      } else {
        (vm._events[event] || (vm._events[event] = [])).push(fn)
      }
      return vm
    }
    
    Vue.prototype.$emit = function (event: string): Component {
      const vm: Component = this
      let cbs = vm._events[event]
      if (cbs) {
        cbs = cbs.length > 1 ? toArray(cbs) : cbs
        const args = toArray(arguments, 1)
        for (let i = 0, l = cbs.length; i < l; i++) {
          cbs[i].apply(vm, args)
        }
      }
      return vm
    }

    总结

    本文探讨了观察者模式的基本概念、适用场景,以及在 vue 源码中的具体应用。这一节将总结一下观察者模式的一些优缺点

    1. 目标和观察者间的抽象耦合:一个目标只知道他有一系列的观察者(目标进行依赖收集),却不知道其中任意一个观察者属于哪一个具体的类,这样目标与观察者之间的耦合是抽象的和最小的。
    2. 支持广播通信:观察者里面的通信,不像其它通常的一些请求需要指定它的接受者。通知将会自动广播给所有已订阅该目标对象的相关对象,即上文中的 dep.notify() 。当然,目标对象并不关心到底有多少对象对自己感兴趣,它唯一的职责就是通知它的各位观察者,处理还是忽略一个通知取决于观察者本身。
    3. 一些意外的更新:因为一个观察者它自己并不知道其它观察者的存在,它可能对改变目标的最终代价一无所知。如果观察者直接在目标上做操作的话,可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新,所以一般我们会把一些操作放在目标内部,防止出现上述的问题。

    OK,本文到这就差不多了,更多的源码设计思路细节将在同系列的其它文章中进行一一解读。

    展开全文
  • 结合 Vue 源码谈谈发布-订阅模式

    千次阅读 2018-10-21 10:23:23
    该思想编程中的应用也是很广泛的, 例如在 Vue中也大量使用了该设计模式,所以会结合Vue源码和大家谈谈自己粗浅的理解. 发布订阅模式主要包含哪些内容呢? 发布函数,发布的时候执行相应的回调 订阅函数,添加...

    最近的工作学习中接触到了发布-订阅模式。该思想编程中的应用也是很广泛的, 例如在 Vue中也大量使用了该设计模式,所以会结合Vue的源码和大家谈谈自己粗浅的理解.

    发布订阅模式主要包含哪些内容呢?

    1. 发布函数,发布的时候执行相应的回调
    2. 订阅函数,添加订阅者,传入发布时要执行的函数,可能会携额外参数
    3. 一个缓存订阅者以及订阅者的回调函数的列表
    4. 取消订阅(需要分情况讨论)

    这么看下来,其实就像 JavaScript 中的事件模型,我们在DOM节点上绑定事件函数,触发的时候执行就是应用了发布-订阅模式.

    我们先按照上面的内容自己实现一个 Observer 对象如下:

    //用于存储订阅的事件名称以及回调函数列表的键值对
    function Observer() {
        this.cache = {}  
    }
    
    //key:订阅消息的类型的标识(名称),fn收到消息之后执行的回调函数
    Observer.prototype.on = function (key,fn) {
        if(!this.cache[key]){
            this.cache[key]=[]
        }
        this.cache[key].push(fn)
    }
    
    
    //arguments 是发布消息时候携带的参数数组
    Observer.prototype.emit = function (key) {
        if(this.cache[key]&&this.cache[key].length>0){
            var fns = this.cache[key]
        }
        for(let i=0;i<fns.length;i++){
            Array.prototype.shift.call(arguments)
            fns[i].apply(this,arguments)
        }
    }
    // remove 的时候需要注意,如果你直接传入一个匿名函数fn,那么你在remove的时候是无法找到这个函数并且把它移除的,变通方式是传入一个
    //指向该函数的指针,而 订阅的时候存入的也是这个指针
    Observer.prototype.remove = function (key,fn) {
        let fns = this.cache[key]
        if(!fns||fns.length===0){
            return
        }
        //如果没有传入fn,那么就是取消所有该事件的订阅
        if(!fn){
            fns=[]
        }else {
            fns.forEach((item,index)=>{
                if(item===fn){
                    fns.splice(index,1)
                }
            })
        }
    }
    
    
    //example
    
    
    var obj = new Observer()
    obj.on('hello',function (a,b) {
        console.log(a,b)
    })
    obj.emit('hello',1,2)
    //取消订阅事件的回调必须是具名函数
    obj.on('test',fn1 =function () {
        console.log('fn1')
    })
    obj.on('test',fn2 = function () {
        console.log('fn2')
    })
    obj.remove('test',fn1)
    obj.emit('test')
    
    

    为什么会使用发布订阅模式呢? 它的优点在于:

    1. 实现时间上的解耦(组件,模块之间的异步通讯)
    2. 对象之间的解耦,交由发布订阅的对象管理对象之间的耦合关系.

    发布-订阅模式在 Vue中的应用

    1. Vue的实例方法中的应用:(当前版本:2.5.16)
    // vm.$on
    export function eventsMixin (Vue: Class<Component>) {
        const hookRE = /^hook:/
        //参数类型为字符串或者字符串组成的数组
        Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
            const vm: Component = this
            // 传入类型为数组
            if (Array.isArray(event)) {
                for (let i = 0, l = event.length; i < l; i++) {
                    this.$on(event[i], fn)
                    //递归并传入相应的回调
                }
            } else {
            //
                (vm._events[event] || (vm._events[event] = [])).push(fn)
                // optimize hook:event cost by using a boolean flag marked at registration
                // instead of a hash lookup
                if (hookRE.test(event)) {
                    vm._hasHookEvent = true
                }
            }
            return vm
        }
    
    
    // vm.$emit
    
     Vue.prototype.$emit = function (event: string): Component {
        const vm: Component = this
        if (process.env.NODE_ENV !== 'production') {
          const lowerCaseEvent = event.toLowerCase()
          if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
            tip(
              `Event "${lowerCaseEvent}" is emitted in component ` +
              `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
              `Note that HTML attributes are case-insensitive and you cannot use ` +
              `v-on to listen to camelCase events when using in-DOM templates. ` +
              `You should probably use "${hyphenate(event)}" instead of "${event}".`
            )
          }
        }
        let cbs = vm._events[event]
        if (cbs) {
          cbs = cbs.length > 1 ? toArray(cbs) : cbs
          const args = toArray(arguments, 1)
          for (let i = 0, l = cbs.length; i < l; i++) {
            try {
              cbs[i].apply(vm, args)// 执行之前传入的回调
            } catch (e) {
              handleError(e, vm, `event handler for "${event}"`)
            }
          }
        }
        return vm
      }
    
    

    Vue中还实现了vm.$once (监听一次);以及vm.$off (取消订阅) ,大家可以在同一文件中看一下是如何实现的.

    1. Vue数据更新机制中的应用
    • observer每个对象的属性,添加到订阅者容器Dependency(Dep)中,当数据发生变化的时候发出notice通知。
    • Watcher:某个属性数据的监听者/订阅者,一旦数据有变化,它会通知指令(directive)重新编译模板并渲染UI
    • 部分源码如下: 源码传送门-observer
    export class Observer {
      value: any;
      dep: Dep;
      vmCount: number; // number of vms that has this object as root $data
    
      constructor (value: any) {
        this.value = value
        this.dep = new Dep()
        this.vmCount = 0
        def(value, '__ob__', this)
        if (Array.isArray(value)) {
          const augment = hasProto
            ? protoAugment
            : copyAugment
          augment(value, arrayMethods, arrayKeys)
          this.observeArray(value)
        } else {
          this.walk(value)
        }
      }
    
      /**
       * Walk through each property and convert them into
       * getter/setters. This method should only be called when
       * value type is Object.
       */
       // 属性为对象的时候,observe 对象的属性
      walk (obj: Object) {
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
          defineReactive(obj, keys[i])
        }
      }
    
      /**
       * Observe a list of Array items.
       */
      observeArray (items: Array<any>) {
        for (let i = 0, l = items.length; i < l; i++) {
          observe(items[i])
        }
      }
    }
    
    export default class Dep {
      static target: ?Watcher;
      id: number;
      subs: Array<Watcher>;
    
      constructor () {
        this.id = uid++
        this.subs = []   //存储订阅者 
      }
      // 添加watcher
      addSub (sub: Watcher) {
        this.subs.push(sub)
      }
     // 移除
      removeSub (sub: Watcher) {
        remove(this.subs, sub)
      }
    
      depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
     // 变更通知
      notify () {
        // stabilize the subscriber list first
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
        }
      }
    }
    

    工作中小应用举例

    1. 场景: 基于wepy的小程序. 由于项目本身不是足够的复杂到要使用提供的 redux进行状态管理.但是在不同的组件(不限于父子组件)之间,存在相关联的异步操作.所以在wepy对象上挂载了一个本文最开始实现的Observer对象.作为部分组件之间通信的总线机制:
    wepy.$bus = new Observer()
    // 然后就可以在不同的模块和组件中订阅和发布消息了
    

    要注意的点

    当然,发布-订阅模式也是有缺点的.

    1. 创建订阅者本身会消耗内存,订阅消息后,也许,永远也不会有发布,而订阅者始终存在内存中.
    2. 对象之间解耦的同时,他们的关系也会被深埋在代码背后,这会造成一定的维护成本.

    当然设计模式的存在是帮助我们解决特定场景的问题的,学会在正确的场景中使用才是最重要的.

    广而告之

    本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请

    展开全文
  • 手写Vue源码,实现核心技术,麻雀虽小五脏俱全:”响应式原理,模板编译,依赖收集”算法:Diff算法实现”设计模式:发布-订阅模式,装饰者模式,代理模式”数据结构:AST树,vnode,vDom 文件名,方法名称,变量得...
  • 项目描述:自己实现的vue源码(自我实现的vue源代码)目前v-model命令,响应式对象,副本,双向绑定已经完成 基本原理:非数组使用object.defineProperty设置获取和设置监听,在数组原型对象中extend7种变量方法来...
  • 【Vue原理】Vue源码阅读总结大会 - 序阅读源码准备了什么1、掌握 Vue 所有API2、JavaScript 扎实基础3、看完 JavaScript 设计模式4、学会调试Vue 源码的简短的总结封装了很多常用的函数!节点操作兼容函数2、真的用...

    专注 Vue 源码分享,为了方便大家理解,分为了白话版和 源码版,白话版可以轻松理解工作原理和设计思想,源码版可以更清楚内部操作和 Vue的美,喜欢我就关注我的公众号,公众号的文章,排版更好看

    如果你觉得排版难看,请点击下面链接 或者 关注公众号

    【Vue原理】Vue源码阅读总结大会 - 序

    公众号

    阅读源码是需要很多的勇气的,特别是对这种 Vue 源码的框架,十分抽象,使用了好多设计模式,封装得十分精密。很难短时间内能看得明白。

    而我显然也是做好了心里准备 和 知识准备啦

    我老早就想攻破 Vue 源码 这座城堡

    但是显然我当时还没有做好准备,我认为自己不可以贸然去看,不然自己烦,自己累,还难以收获

    本篇文章算是一个简单地吹水,就是跟大家谈谈我的感想,没有什么知识含量,其实也有的。



    阅读源码准备了什么


    1、掌握 Vue 所有API

    我把 Vue 的所有 API 都详细研究使用过了一遍,而且尽量在项目中都有使用,让自己有深一点的体会

    而且我对着官方文档,一个个做了详细的笔记,而且联想过了使用场景。


    2、JavaScript 扎实基础

    幸好自己之前花了大力气去给自己打基础,让自己现在的 JavaScript 基础还算不错。

    逼着自己把很多本 JavaScript 书都看完了,并且做了详细笔记。像是【 JavaScript易维护】【JavaScript性能 】,【JavaScript 高级程序设计】【巴菲特给股东的信】看了两遍,说不上精通,也算是还可以?


    3、看完 JavaScript 设计模式

    光是 JavaScript 设计模式 这本书 我就看了一年半,不能说自己把所有设计模式都掌握了,掌握了大部分吧,设计模式港真真的很有趣,不然我也不会决心学

    在这里推荐 张容铭的 【JavaScript设计模式】,书讲得非常透彻和详细,我是从完全不懂开始看的

    也经常使用一部分,我一直以设计模式为我的项目基构。就是 能用设计模式的地方,我都尽量使用设计模式。

    设计模式看起来就像是 剑客 的剑谱,有招有式,连人家武侠剧发功的时候都知道 喊出 招式的名字… 降龙十八掌!!!!

    野路子难登大雅之堂,主要是不好看啊,代码为了好维护,易扩展


    4、学会调试

    我很大胆地说,如果你不会调试,你看 Vue 源码,或者你会想死,你会出现这个场景…

    MMP,这个方法是怎么跳到 那个方法的,那个方法和 这个方法又是怎么联系起来的?
    也许你可以慢慢 使用 函数名字 去寻找,但是无疑你会多消耗几倍时间,而且你会更烦

    使用调试真的方便,以前我也真的不喜欢调试,觉得好像很难???

    更喜欢使用 console.log 去打印信息…

    是啊,我自己写项目的时候,我还是会使用 console.log 去调试…

    那是因为我自己代码,我知道怎么跑,你 看别人的代码,还是超级抽象的框架,使用 console.log 的方式…

    放心,相信我,你会掉很多头发…
    这里,我使用的是 VSCode 去调试,真的简单又方便,我当时也真的很难去让自己又要学一个东西

    但是我咬咬牙,我还是学了,感谢自己…

    我可以保证,你从不懂到掌握,只要不到十分钟,简直就是 现实版的 十分钟精通到入门

    好吧,下面开始说,Vue 的简单总结。



    Vue 源码的简短的总结


    封装了很多常用的函数!

    为了 复用 且 易维护

    常用的类型判断、 类型转换 、数据格式转换(数组转对象)…

    举些例子

    function isObject(obj) {    return obj !== null && typeof obj === 'object'}
    function isUndef(v) {    return v === undefined || v === null}
    function isDef(v) {    return v !== undefined && v !== null}
    function toString(val) {    
        return val == null ?    '' :    
        typeof val === 'object' ?    
        JSON.stringify(val, null, 2) :    String(val)
    }
    function toObject(arr) {    
        var res = {};    
        for (var i = 0; i < arr.length; i++) {        
            if (arr[i]) {
                extend(res, arr[i]);
            }
        }    return res
    }
    ....
    

    你说说不定过了几年,判断是否是一个对象,不再是 什么 typeof obj==“object”

    如果没有封装,那岂不是所有代码涉及到的都要改一遍,且不说如果有很多个都变了…那你就头大了

    节点操作兼容函数

    addClass ,removeClass,createElement,appendChild,removeChild

    function addClass(el, cls) {    
        if (!cls || !(cls = cls.trim())) return
        if (el.classList) {        
            if (cls.indexOf(' ') > -1) {
                cls.split(/\s+/).forEach(function(c) { return el.classList.add(c); });
            } else {
                el.classList.add(cls);
            }
    
        } else {        
           var cur = " " + (el.getAttribute('class') || '') + " ";        
           if (cur.indexOf(' ' + cls + ' ') < 0) {
                el.setAttribute('class', (cur + cls).trim());
           }
        }
    }
    ....
    

    这些函数都很有用,所以我都记下来了,毕竟是 框架封装的,肯定是最完善的

    function isObject(obj) {    return obj !== null && typeof obj === 'object'}
    function isUndef(v) {    return v === undefined || v === null}
    function isDef(v) {    return v !== undefined && v !== null}
    function toString(val) {    
        return val == null ?    '' :    
        typeof val === 'object' ?    
        JSON.stringify(val, null, 2) :    String(val)
    }
    function toObject(arr) {    
        var res = {};    
        for (var i = 0; i < arr.length; i++) {        
            if (arr[i]) {
                extend(res, arr[i]);
            }
        }    return res
    }
    
    

    2、真的用了很多设计模式

    就我看到的设计模式就有

    观察者模式、状态模式、节流模式、 参与者模式、备忘录模式、单例模式 装饰者模式、组合继承模式、链模式…
    我怀疑 Vue 把所有的设计模式都用完了… 真的… 如果你不懂设计模式

    你真不会领悟到他这么写的精髓

    我就选 Vue 常用的一个设计模式来讲

    【参与者模式】

    Vue 封装的很多函数都是用了 参与者模式,也可以叫做柯里化

    先来简单解释下 参与者模式

    1、保存第一次调用 传入参数

    2、返回定制函数,函数内使用 参数

    简单实现像这样

    function add(a){    
        return function(b){ return a+b }
    }
    // 为了定制函数,把第一次调用时的参数闭包保存
    add5 = add(5)var result  = add5(9)
    

    看一下 Vue其中一个 使用柯里化 的封装函数

    makeMap

    创建 对象 map,返回函数,用于后面查找 某个东西是否存在 map 中

    function makeMap( str,  expectsLowerCase ) {    
        var map = Object.create(null);   
        var list = str.split(',');    
        for (var i = 0; i < list.length; i++) {
            map[list[i]] = true;
        }    
        return expectsLowerCase ?        
            function(val) { return map[val.toLowerCase()]; } :        
            function(val) { return map[val]; }
    }
    
    // 应用
    var isUnaryTag = makeMap(   
     'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' +  
     'link,meta,param,source,track,wbr');
    
    // 查找 area 标签是否存在 上面保存过的 字符串中
    isUnaryTag('area')
    

    3、使用很多闭包!

    据我看过的地方

    1、解析组件模板 使用了闭包作为缓存,为了重复解析

    2、cached 函数,一个专门使用闭包 为缓存的函数

    3、上面所讲到 的 柯里化所有涉及的函数,makeMap,parthPath,

    4、createPatchFunction 当属篇幅最大的使用闭包的函数了,把一堆函数作为闭包,然后返回 一个函数。他最大的作用是 比较更新DOM 节点


    4、使用很多标志位

    Vue 常用标志位来

    1、表明是否已经做了某件事

    _isMounted:// dom 是否已经挂载
    _isDestroyed // 组件是否已经摧毁
    pending //表明更新回调的 setTimeout 已经执行
    waiting //是否已经初始化更新队列,在等待新的成员进入对垒
    flushing //更新队列是否已经开始逐个更新成员
    ....
    

    2、指明当前东西的身份

    isStatic// 是否是静态节点
    isComment// 是否是注释节点
    isClone:// 是否是克隆节点
    isOnce// 是否有v-once 指令(如果有当前指令,会跳过编译)
    _isComponent// 是否是组件
    

    多用标志位,控制流程,替代多余的判断(直接判断标志位来确认身份,不用做太多的判断),减少开销

    上面那些变量,大家没看源码,可能有些懵逼,没关系,就当先知道有这个东西就好了


    Vue 源码分几步走


    我给自己定的任务是 分为两个部分

    Vue 的主体内容

    1、依赖收集

    2、依赖更新

    3、Virtual DOM ,dom 节点 生成虚拟Vnode 节点

    4、Compile, 模板编译

    5、Diff、Patch, 节点比较更新

    6、NextTick ,延迟执行回调

    7、Render, 渲染机制

    8、LifeCircle ,生命周期

    9、Model ,双向绑定

    10、Event ,事件机制


    Vue 组件选项

    1、computed

    2、filter

    3、mixin

    4、directive

    5、slot

    6、props

    7、watch

    我就大约以这些为我的学习目标进行 源码阅读的,每一块都是一个非常大的内容,每一块内容都不是几天能看完的,有时候还需要一点灵感。当然还有很多内容,但是我的目标也并不是全部,一字不漏读完,我要的是他的精髓即可,或许等我掌握了这些,再去开发其他的内容,这样或许更简单

    反正我始终提醒自己不要焦躁,因为这个东西真的是急不来,长期以往,不要妄想一步登天,一开始总会很难,但是久了也一样很难,哈哈哈哈哈

    如果你有兴趣也读源码,我们可以一起讨论学习…

    展开全文
  • 专注 Vue 源码分享,为了...请点击下面链接移步公众号,另附上公众号二维码【Vue原理】Vue源码阅读总结大会 - 序阅读源码是需要很多的勇气的,特别是对这种 Vue 源码的框架,十分抽象,使用了好多设计模式,封装得...
  • Vue项目做了不少,最近在学习设计模式Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后出不来~ 从package.json文件知道我们在执行命令...
  • 前言:好久没写博客了,今年确实有点小忙,学习跟工作都很充实,身边也有小伙伴也经常说:技术是学不完的,能不能专一搞点自己喜欢的东西呢?学完新技术然后过几年又没...前面有写过两篇vue源码的文章,有兴趣的童...
  • vue已是目前国内前端web端三分天下之一,同时也作为本人主要技术栈之一,在日常使用中知其然也好奇着所以然,另外最近的社区涌现了一大票vue源码阅读类的文章,在下借这个机会从大家的文章和讨论中汲取了一些营养,...
  • 这方面的文章很多,但是我感觉很多写的比较抽象,本文会通过举例...了解Observer,Dep,Watcher的源码实现原理3.getter/setter 拦截数据方式的不足及解决方案一、设计模式Vue 采用数据劫持结合发布者-订阅者模式的方式来
  • Vue-源码

    2021-01-31 02:18:42
    教程简介 本教程要实现一个简单的后台管理系统,包含着陆,数据列表...关于Vue的描述有过多,不外乎都会拿来与Angular和React对比,同样头顶MVVM双向数据驱动设计模式光环的Angular自然被对比的最多,但仍然,Angular在
  • 这是一个分析vue源码的系列专栏,争取用最简单的文字,解析Vue背后的思想以及设计模式,有不懂的欢迎在下面提问。前置知识包括es6,写过一些vue的项目或者demo,会搭建vue的开发环境,具体遇到什么问题,再去解决。 开始 ...
  • vue源码分析(二)

    2020-01-10 11:15:44
    观察者模式 观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一...vue的响应式原理采用的就是观察者设计模式。下面是观察者模式在vue中的运用解析。 图片来源:https://blog.csdn.net/g...
  • Vue源码探秘之 数据响应式原理 从MVVM模式说开去 模板 <p>我{{age}}岁了</p> 数据变化 this.age++; 数据变化,视图会自动变化 侵入式和非侵入式 尤小右找到了“上帝的钥匙...
  • 2018年首个计划是学习vue源码,查阅了一番资料之后,决定从第一个commit开始看起,这将是一场持久战!本篇介绍directive的简单实现,主要学习其实现的思路及代码的设计(directive和filter扩展起来非常方便,符合...
  • 有些人以为知道 Object.defineProperty 这个 API 和 发布订阅模式(有些人还把观察者模式和发布/订阅模式混淆一谈)就是了解设计思路了,其实不然。这篇文章会告诉你该如何学习 Vue,同样给一些辅助视频资料。01....
  • vue中事件方法一共就四个,挂载在vue实例上的$emit在我们做子组件向父组件传值时,通常会用到,那么$emit的内部实现原理是什么呢?...$emit也是采用了发布订阅者设计模式Vue.prototype.$emit .
  • vue2.0源码学习资源

    2020-12-09 10:10:54
    vue2.0源码学习资源,深入分析vue设计原理,ast对象,编译原理,观察者模式,双向绑定及相关的实例等
  • 1、Vue源码分析 2、手把手教 保姆级 撸代码 3、无惧面试,学以致用,继承创新 4、谈谈前端发展与学习心得 5、手写源码技术栈,附上详细注释 6、从源码中学习设计模式,一举两得 7、编程思想的提升及代码质量的提高 8...
  • 设计模式-外观模式 HTML5历史记录API 如果你对这些还没有了解的话,可以看一下此末尾的推介阅读。 1.文件结构 首先我们来看看文件结构: . ├── build // 打包相关配置 ├── scripts // 构建
  • VueComponent-源码

    2021-04-26 22:54:06
    参考Element设计模式,最终做成博客模式,来展示一些自己写的有关 vue/Extjs 的组件/插件 (本人现在的技术栈是vue、Extjs,最近有点忙,还没有及时更新文档) Build Setup # install dependencies npm install # ...
  • 这一篇我们先不着急阅读源码,因为接下来需要用到一个比较复杂的设计模式:观察者模式,而且还需要理解依赖收集才能继续向下阅读源码,所以这次我们先做一个铺垫。 依赖收集 通过之前阅读的源码我们了解到Vue2.x版本...
  • Vue数据响应式响应式是一种设计模式,数据响应就是代码根据数据变化作出反应,页面布局响应式就是根据页面缩放大小调整布局。Vue 采用数据劫持结合发布者-订阅者模式的方式来实现数据的响应式,通过Object....

空空如也

空空如也

1 2 3 4 5 ... 15
收藏数 287
精华内容 114
关键字:

vue源码设计模式

vue 订阅