资讯专栏INFORMATION COLUMN

VueJS源码学习——实例构造函数

jhhfft / 2627人阅读

摘要:大概过了一遍工具类后,开始看实例的具体实现原文地址项目地址实现了的初始化函数方法会在实例创建的时候被调用初始化了实例的共有属性如还有一堆私有属性如等等最后再是初始化实例状态事件生命周期等等在实现上比较有趣使用来实现对的和方法利用获取

大概过了一遍 util 工具类后,开始看 Vue 实例的具体实现

原文地址
项目地址

init

src/instance/init.js 实现了 Vue 的 _init 初始化函数

import { mergeOptions } from "../../util/index"

let uid = 0

export default function (Vue) {

  Vue.prototype._init = function (options) {
    ...
  }
}

_init 方法会在实例创建的时候被调用:

function Vue(options) {
    this._init(options);
}

init 初始化了 Vue 实例的共有属性如 $el, $parent, $root, $children, $refs, $els还有一堆私有属性如_watchers, _directives, _uid, isVue, _events等等,最后再是初始化实例状态、事件、生命周期等等

在实现 $root 上比较有趣:

this.$parent = options.parent
this.$root = this.$parent
  ? this.$parent.$root
  : this
state

src/instance/state.js

  /**
   * Accessor for `$data` property, since setting $data
   * requires observing the new object and updating
   * proxied properties.
   */

  Object.defineProperty(Vue.prototype, "$data", {
    get () {
      return this._data
    },
    set (newData) {
      if (newData !== this._data) {
        this._setData(newData)
      }
    }
  })
_setData

使用 defineProperty 来实现对 Vue.$data 的 get 和 set

  Vue.prototype._setData = function (newData) {
    newData = newData || {}
    var oldData = this._data
    this._data = newData
    var keys, key, i
    // unproxy keys not present in new data
    keys = Object.keys(oldData)
    i = keys.length
    while (i--) {
      key = keys[i]
      if (!(key in newData)) {
        this._unproxy(key)
      }
    }
    // proxy keys not already proxied,
    // and trigger change for changed values
    keys = Object.keys(newData)
    i = keys.length
    while (i--) {
      key = keys[i]
      if (!hasOwn(this, key)) {
        // new property
        this._proxy(key)
      }
    }
    oldData.__ob__.removeVm(this)
    observe(newData, this)
    this._digest()
  }

_setData 方法利用 Object.keys 获取对象的属性列表,在利用 key in obj来判断是否存在属性 key,然后决定是否 proxy 或者 unproxy

_initComputed
  /**
   * Setup computed properties. They are essentially
   * special getter/setters
   */

  function noop () {}
  Vue.prototype._initComputed = function () {
    var computed = this.$options.computed
    if (computed) {
      for (var key in computed) {
        var userDef = computed[key]
        var def = {
          enumerable: true,
          configurable: true
        }
        if (typeof userDef === "function") {
          def.get = makeComputedGetter(userDef, this)
          def.set = noop
        } else {
          def.get = userDef.get
            ? userDef.cache !== false
              ? makeComputedGetter(userDef.get, this)
              : bind(userDef.get, this)
            : noop
          def.set = userDef.set
            ? bind(userDef.set, this)
            : noop
        }
        Object.defineProperty(this, key, def)
      }
    }
  }

  function makeComputedGetter (getter, owner) {
    var watcher = new Watcher(owner, getter, null, {
      lazy: true
    })
    return function computedGetter () {
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }

初始化计算属性,即 computed 的实现,从文档可以看到我们既可以使用 function 来确定怎么获取某个值,也可以使用 get 和 set 对象来确定值的获取和更新,底层的实现是 watcher

methods
  /**
   * Setup instance methods. Methods must be bound to the
   * instance since they might be passed down as a prop to
   * child components.
   */

  Vue.prototype._initMethods = function () {
    var methods = this.$options.methods
    if (methods) {
      for (var key in methods) {
        this[key] = bind(methods[key], this)
      }
    }
  }

  /**
   * Initialize meta information like $index, $key & $value.
   */

  Vue.prototype._initMeta = function () {
    var metas = this.$options._meta
    if (metas) {
      for (var key in metas) {
        defineReactive(this, key, metas[key])
      }
    }
  }
watcher
import { extend, warn, isArray, isObject, nextTick } from "./util/index"
import config from "./config"
import Dep from "./observer/dep"
import { parseExpression } from "./parsers/expression"
import { pushWatcher } from "./batcher"

let uid = 0

export default function Watcher (vm, expOrFn, cb, options) {
  // mix in options
  if (options) {
    extend(this, options)
  }
  var isFn = typeof expOrFn === "function"
  this.vm = vm
  vm._watchers.push(this)
  this.expression = isFn ? expOrFn.toString() : expOrFn
  this.cb = cb
  this.id = ++uid // uid for batching
  this.active = true
  this.dirty = this.lazy // for lazy watchers
  this.deps = Object.create(null)
  this.newDeps = null
  this.prevError = null // for async error stacks
  // parse expression for getter/setter
  if (isFn) {
    this.getter = expOrFn
    this.setter = undefined
  } else {
    var res = parseExpression(expOrFn, this.twoWay)
    this.getter = res.get
    this.setter = res.set
  }
  this.value = this.lazy
    ? undefined
    : this.get()
  // state for avoiding false triggers for deep and Array
  // watchers during vm._digest()
  this.queued = this.shallow = false
}

/**
 * Add a dependency to this directive.
 *
 * @param {Dep} dep
 */

Watcher.prototype.addDep = function (dep) {
  var id = dep.id
  if (!this.newDeps[id]) {
    this.newDeps[id] = dep
    if (!this.deps[id]) {
      this.deps[id] = dep
      dep.addSub(this)
    }
  }
}

/**
 * Evaluate the getter, and re-collect dependencies.
 */

Watcher.prototype.get = function () {
  this.beforeGet()
  var scope = this.scope || this.vm
  var value
  try {
    value = this.getter.call(scope, scope)
  } catch (e) {
    if (
      process.env.NODE_ENV !== "production" &&
      config.warnExpressionErrors
    ) {
      warn(
        "Error when evaluating expression "" +
        this.expression + "". " +
        (config.debug
          ? ""
          : "Turn on debug mode to see stack trace."
        ), e
      )
    }
  }
  // "touch" every property so they are all tracked as
  // dependencies for deep watching
  if (this.deep) {
    traverse(value)
  }
  if (this.preProcess) {
    value = this.preProcess(value)
  }
  if (this.filters) {
    value = scope._applyFilters(value, null, this.filters, false)
  }
  if (this.postProcess) {
    value = this.postProcess(value)
  }
  this.afterGet()
  return value
}

/**
 * Set the corresponding value with the setter.
 *
 * @param {*} value
 */

Watcher.prototype.set = function (value) {
  var scope = this.scope || this.vm
  if (this.filters) {
    value = scope._applyFilters(
      value, this.value, this.filters, true)
  }
  try {
    this.setter.call(scope, scope, value)
  } catch (e) {
    if (
      process.env.NODE_ENV !== "production" &&
      config.warnExpressionErrors
    ) {
      warn(
        "Error when evaluating setter "" +
        this.expression + """, e
      )
    }
  }
  // two-way sync for v-for alias
  var forContext = scope.$forContext
  if (forContext && forContext.alias === this.expression) {
    if (forContext.filters) {
      process.env.NODE_ENV !== "production" && warn(
        "It seems you are using two-way binding on " +
        "a v-for alias (" + this.expression + "), and the " +
        "v-for has filters. This will not work properly. " +
        "Either remove the filters or use an array of " +
        "objects and bind to object properties instead."
      )
      return
    }
    forContext._withLock(function () {
      if (scope.$key) { // original is an object
        forContext.rawValue[scope.$key] = value
      } else {
        forContext.rawValue.$set(scope.$index, value)
      }
    })
  }
}

/**
 * Prepare for dependency collection.
 */

Watcher.prototype.beforeGet = function () {
  Dep.target = this
  this.newDeps = Object.create(null)
}

/**
 * Clean up for dependency collection.
 */

Watcher.prototype.afterGet = function () {
  Dep.target = null
  var ids = Object.keys(this.deps)
  var i = ids.length
  while (i--) {
    var id = ids[i]
    if (!this.newDeps[id]) {
      this.deps[id].removeSub(this)
    }
  }
  this.deps = this.newDeps
}

/**
 * Subscriber interface.
 * Will be called when a dependency changes.
 *
 * @param {Boolean} shallow
 */

Watcher.prototype.update = function (shallow) {
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync || !config.async) {
    this.run()
  } else {
    // if queued, only overwrite shallow with non-shallow,
    // but not the other way around.
    this.shallow = this.queued
      ? shallow
        ? this.shallow
        : false
      : !!shallow
    this.queued = true
    // record before-push error stack in debug mode
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== "production" && config.debug) {
      this.prevError = new Error("[vue] async stack trace")
    }
    pushWatcher(this)
  }
}

/**
 * Batcher job interface.
 * Will be called by the batcher.
 */

Watcher.prototype.run = function () {
  if (this.active) {
    var value = this.get()
    if (
      value !== this.value ||
      // Deep watchers and Array watchers should fire even
      // when the value is the same, because the value may
      // have mutated; but only do so if this is a
      // non-shallow update (caused by a vm digest).
      ((isArray(value) || this.deep) && !this.shallow)
    ) {
      // set new value
      var oldValue = this.value
      this.value = value
      // in debug + async mode, when a watcher callbacks
      // throws, we also throw the saved before-push error
      // so the full cross-tick stack trace is available.
      var prevError = this.prevError
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== "production" &&
          config.debug && prevError) {
        this.prevError = null
        try {
          this.cb.call(this.vm, value, oldValue)
        } catch (e) {
          nextTick(function () {
            throw prevError
          }, 0)
          throw e
        }
      } else {
        this.cb.call(this.vm, value, oldValue)
      }
    }
    this.queued = this.shallow = false
  }
}

/**
 * Evaluate the value of the watcher.
 * This only gets called for lazy watchers.
 */

Watcher.prototype.evaluate = function () {
  // avoid overwriting another watcher that is being
  // collected.
  var current = Dep.target
  this.value = this.get()
  this.dirty = false
  Dep.target = current
}

/**
 * Depend on all deps collected by this watcher.
 */

Watcher.prototype.depend = function () {
  var depIds = Object.keys(this.deps)
  var i = depIds.length
  while (i--) {
    this.deps[depIds[i]].depend()
  }
}

/**
 * Remove self from all dependencies" subcriber list.
 */

Watcher.prototype.teardown = function () {
  if (this.active) {
    // remove self from vm"s watcher list
    // we can skip this if the vm if being destroyed
    // which can improve teardown performance.
    if (!this.vm._isBeingDestroyed) {
      this.vm._watchers.$remove(this)
    }
    var depIds = Object.keys(this.deps)
    var i = depIds.length
    while (i--) {
      this.deps[depIds[i]].removeSub(this)
    }
    this.active = false
    this.vm = this.cb = this.value = null
  }
}

/**
 * Recrusively traverse an object to evoke all converted
 * getters, so that every nested property inside the object
 * is collected as a "deep" dependency.
 *
 * @param {*} val
 */

function traverse (val) {
  var i, keys
  if (isArray(val)) {
    i = val.length
    while (i--) traverse(val[i])
  } else if (isObject(val)) {
    keys = Object.keys(val)
    i = keys.length
    while (i--) traverse(val[keys[i]])
  }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85145.html

相关文章

  • VueJS源码学习——项目结构&目录

    摘要:所以整个的核心,就是如何实现这三样东西以上摘自囧克斯博客的一篇文章从版本开始这个时候的项目结构如下源码在里面,为打包编译的代码,为打包后代码放置的位置,为测试代码目录。节点类型摘自资源另一位作者关于源码解析 本项目的源码学习笔记是基于 Vue 1.0.9 版本的也就是最早的 tag 版本,之所以选择这个版本,是因为这个是最原始没有太多功能拓展的版本,有利于更好的看到 Vue 最开始的骨...

    ad6623 评论0 收藏0
  • JS每日一题: 请简述一下vuex实现原理

    摘要:给的实例注入一个的属性,这也就是为什么我们在的组件中可以通过访问到的各种数据和状态源码位置,是怎么实现的源码位置是对的的初始化,它接受个参数,为当前实例,为的,为执行的回调函数,为当前模块的路径。 20190221 请简述一下vuex实现原理 对vuex基础概念有不懂的可以点这里 vuex实现原理我们简单过一遍源码 地址 https://github.com/vuejs/vuex 首...

    JohnLui 评论0 收藏0
  • 如何实现全屏遮罩(附Vue.extend和el-message源码学习

    摘要:如何优雅的动态添加这里我们需要用到的实例化,首先我们来看的思路,贴一段源码使用基础构造器,创建一个子类。因为作为一个的扩展构造器,所以基础的功能还是需要保持一致,跟构造器一样在构造函数中初始化。 [Vue]如何实现全屏遮罩(附Vue.extend和el-message源码学习) 在做个人项目的时候需要做一个类似于电子相册浏览的控件,实现过程中首先要实现全局遮罩,结合自己的思路并阅读了(...

    malakashi 评论0 收藏0
  • 如何实现全屏遮罩(附Vue.extend和el-message源码学习

    摘要:如何优雅的动态添加这里我们需要用到的实例化,首先我们来看的思路,贴一段源码使用基础构造器,创建一个子类。因为作为一个的扩展构造器,所以基础的功能还是需要保持一致,跟构造器一样在构造函数中初始化。 [Vue]如何实现全屏遮罩(附Vue.extend和el-message源码学习) 在做个人项目的时候需要做一个类似于电子相册浏览的控件,实现过程中首先要实现全局遮罩,结合自己的思路并阅读了(...

    Zack 评论0 收藏0

发表评论

0条评论

jhhfft

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<