资讯专栏INFORMATION COLUMN

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

jhhfft / 2692人阅读

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

</>复制代码

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

原文地址
项目地址

init

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

</>复制代码

  1. import { mergeOptions } from "../../util/index"
  2. let uid = 0
  3. export default function (Vue) {
  4. Vue.prototype._init = function (options) {
  5. ...
  6. }
  7. }

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

</>复制代码

  1. function Vue(options) {
  2. this._init(options);
  3. }

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

在实现 $root 上比较有趣:

</>复制代码

  1. this.$parent = options.parent
  2. this.$root = this.$parent
  3. ? this.$parent.$root
  4. : this
state

src/instance/state.js

</>复制代码

  1. /**
  2. * Accessor for `$data` property, since setting $data
  3. * requires observing the new object and updating
  4. * proxied properties.
  5. */
  6. Object.defineProperty(Vue.prototype, "$data", {
  7. get () {
  8. return this._data
  9. },
  10. set (newData) {
  11. if (newData !== this._data) {
  12. this._setData(newData)
  13. }
  14. }
  15. })
_setData

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

</>复制代码

  1. Vue.prototype._setData = function (newData) {
  2. newData = newData || {}
  3. var oldData = this._data
  4. this._data = newData
  5. var keys, key, i
  6. // unproxy keys not present in new data
  7. keys = Object.keys(oldData)
  8. i = keys.length
  9. while (i--) {
  10. key = keys[i]
  11. if (!(key in newData)) {
  12. this._unproxy(key)
  13. }
  14. }
  15. // proxy keys not already proxied,
  16. // and trigger change for changed values
  17. keys = Object.keys(newData)
  18. i = keys.length
  19. while (i--) {
  20. key = keys[i]
  21. if (!hasOwn(this, key)) {
  22. // new property
  23. this._proxy(key)
  24. }
  25. }
  26. oldData.__ob__.removeVm(this)
  27. observe(newData, this)
  28. this._digest()
  29. }

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

_initComputed

</>复制代码

  1. /**
  2. * Setup computed properties. They are essentially
  3. * special getter/setters
  4. */
  5. function noop () {}
  6. Vue.prototype._initComputed = function () {
  7. var computed = this.$options.computed
  8. if (computed) {
  9. for (var key in computed) {
  10. var userDef = computed[key]
  11. var def = {
  12. enumerable: true,
  13. configurable: true
  14. }
  15. if (typeof userDef === "function") {
  16. def.get = makeComputedGetter(userDef, this)
  17. def.set = noop
  18. } else {
  19. def.get = userDef.get
  20. ? userDef.cache !== false
  21. ? makeComputedGetter(userDef.get, this)
  22. : bind(userDef.get, this)
  23. : noop
  24. def.set = userDef.set
  25. ? bind(userDef.set, this)
  26. : noop
  27. }
  28. Object.defineProperty(this, key, def)
  29. }
  30. }
  31. }
  32. function makeComputedGetter (getter, owner) {
  33. var watcher = new Watcher(owner, getter, null, {
  34. lazy: true
  35. })
  36. return function computedGetter () {
  37. if (watcher.dirty) {
  38. watcher.evaluate()
  39. }
  40. if (Dep.target) {
  41. watcher.depend()
  42. }
  43. return watcher.value
  44. }
  45. }

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

methods

</>复制代码

  1. /**
  2. * Setup instance methods. Methods must be bound to the
  3. * instance since they might be passed down as a prop to
  4. * child components.
  5. */
  6. Vue.prototype._initMethods = function () {
  7. var methods = this.$options.methods
  8. if (methods) {
  9. for (var key in methods) {
  10. this[key] = bind(methods[key], this)
  11. }
  12. }
  13. }
  14. /**
  15. * Initialize meta information like $index, $key & $value.
  16. */
  17. Vue.prototype._initMeta = function () {
  18. var metas = this.$options._meta
  19. if (metas) {
  20. for (var key in metas) {
  21. defineReactive(this, key, metas[key])
  22. }
  23. }
  24. }
watcher

</>复制代码

  1. import { extend, warn, isArray, isObject, nextTick } from "./util/index"
  2. import config from "./config"
  3. import Dep from "./observer/dep"
  4. import { parseExpression } from "./parsers/expression"
  5. import { pushWatcher } from "./batcher"
  6. let uid = 0
  7. export default function Watcher (vm, expOrFn, cb, options) {
  8. // mix in options
  9. if (options) {
  10. extend(this, options)
  11. }
  12. var isFn = typeof expOrFn === "function"
  13. this.vm = vm
  14. vm._watchers.push(this)
  15. this.expression = isFn ? expOrFn.toString() : expOrFn
  16. this.cb = cb
  17. this.id = ++uid // uid for batching
  18. this.active = true
  19. this.dirty = this.lazy // for lazy watchers
  20. this.deps = Object.create(null)
  21. this.newDeps = null
  22. this.prevError = null // for async error stacks
  23. // parse expression for getter/setter
  24. if (isFn) {
  25. this.getter = expOrFn
  26. this.setter = undefined
  27. } else {
  28. var res = parseExpression(expOrFn, this.twoWay)
  29. this.getter = res.get
  30. this.setter = res.set
  31. }
  32. this.value = this.lazy
  33. ? undefined
  34. : this.get()
  35. // state for avoiding false triggers for deep and Array
  36. // watchers during vm._digest()
  37. this.queued = this.shallow = false
  38. }
  39. /**
  40. * Add a dependency to this directive.
  41. *
  42. * @param {Dep} dep
  43. */
  44. Watcher.prototype.addDep = function (dep) {
  45. var id = dep.id
  46. if (!this.newDeps[id]) {
  47. this.newDeps[id] = dep
  48. if (!this.deps[id]) {
  49. this.deps[id] = dep
  50. dep.addSub(this)
  51. }
  52. }
  53. }
  54. /**
  55. * Evaluate the getter, and re-collect dependencies.
  56. */
  57. Watcher.prototype.get = function () {
  58. this.beforeGet()
  59. var scope = this.scope || this.vm
  60. var value
  61. try {
  62. value = this.getter.call(scope, scope)
  63. } catch (e) {
  64. if (
  65. process.env.NODE_ENV !== "production" &&
  66. config.warnExpressionErrors
  67. ) {
  68. warn(
  69. "Error when evaluating expression "" +
  70. this.expression + "". " +
  71. (config.debug
  72. ? ""
  73. : "Turn on debug mode to see stack trace."
  74. ), e
  75. )
  76. }
  77. }
  78. // "touch" every property so they are all tracked as
  79. // dependencies for deep watching
  80. if (this.deep) {
  81. traverse(value)
  82. }
  83. if (this.preProcess) {
  84. value = this.preProcess(value)
  85. }
  86. if (this.filters) {
  87. value = scope._applyFilters(value, null, this.filters, false)
  88. }
  89. if (this.postProcess) {
  90. value = this.postProcess(value)
  91. }
  92. this.afterGet()
  93. return value
  94. }
  95. /**
  96. * Set the corresponding value with the setter.
  97. *
  98. * @param {*} value
  99. */
  100. Watcher.prototype.set = function (value) {
  101. var scope = this.scope || this.vm
  102. if (this.filters) {
  103. value = scope._applyFilters(
  104. value, this.value, this.filters, true)
  105. }
  106. try {
  107. this.setter.call(scope, scope, value)
  108. } catch (e) {
  109. if (
  110. process.env.NODE_ENV !== "production" &&
  111. config.warnExpressionErrors
  112. ) {
  113. warn(
  114. "Error when evaluating setter "" +
  115. this.expression + """, e
  116. )
  117. }
  118. }
  119. // two-way sync for v-for alias
  120. var forContext = scope.$forContext
  121. if (forContext && forContext.alias === this.expression) {
  122. if (forContext.filters) {
  123. process.env.NODE_ENV !== "production" && warn(
  124. "It seems you are using two-way binding on " +
  125. "a v-for alias (" + this.expression + "), and the " +
  126. "v-for has filters. This will not work properly. " +
  127. "Either remove the filters or use an array of " +
  128. "objects and bind to object properties instead."
  129. )
  130. return
  131. }
  132. forContext._withLock(function () {
  133. if (scope.$key) { // original is an object
  134. forContext.rawValue[scope.$key] = value
  135. } else {
  136. forContext.rawValue.$set(scope.$index, value)
  137. }
  138. })
  139. }
  140. }
  141. /**
  142. * Prepare for dependency collection.
  143. */
  144. Watcher.prototype.beforeGet = function () {
  145. Dep.target = this
  146. this.newDeps = Object.create(null)
  147. }
  148. /**
  149. * Clean up for dependency collection.
  150. */
  151. Watcher.prototype.afterGet = function () {
  152. Dep.target = null
  153. var ids = Object.keys(this.deps)
  154. var i = ids.length
  155. while (i--) {
  156. var id = ids[i]
  157. if (!this.newDeps[id]) {
  158. this.deps[id].removeSub(this)
  159. }
  160. }
  161. this.deps = this.newDeps
  162. }
  163. /**
  164. * Subscriber interface.
  165. * Will be called when a dependency changes.
  166. *
  167. * @param {Boolean} shallow
  168. */
  169. Watcher.prototype.update = function (shallow) {
  170. if (this.lazy) {
  171. this.dirty = true
  172. } else if (this.sync || !config.async) {
  173. this.run()
  174. } else {
  175. // if queued, only overwrite shallow with non-shallow,
  176. // but not the other way around.
  177. this.shallow = this.queued
  178. ? shallow
  179. ? this.shallow
  180. : false
  181. : !!shallow
  182. this.queued = true
  183. // record before-push error stack in debug mode
  184. /* istanbul ignore if */
  185. if (process.env.NODE_ENV !== "production" && config.debug) {
  186. this.prevError = new Error("[vue] async stack trace")
  187. }
  188. pushWatcher(this)
  189. }
  190. }
  191. /**
  192. * Batcher job interface.
  193. * Will be called by the batcher.
  194. */
  195. Watcher.prototype.run = function () {
  196. if (this.active) {
  197. var value = this.get()
  198. if (
  199. value !== this.value ||
  200. // Deep watchers and Array watchers should fire even
  201. // when the value is the same, because the value may
  202. // have mutated; but only do so if this is a
  203. // non-shallow update (caused by a vm digest).
  204. ((isArray(value) || this.deep) && !this.shallow)
  205. ) {
  206. // set new value
  207. var oldValue = this.value
  208. this.value = value
  209. // in debug + async mode, when a watcher callbacks
  210. // throws, we also throw the saved before-push error
  211. // so the full cross-tick stack trace is available.
  212. var prevError = this.prevError
  213. /* istanbul ignore if */
  214. if (process.env.NODE_ENV !== "production" &&
  215. config.debug && prevError) {
  216. this.prevError = null
  217. try {
  218. this.cb.call(this.vm, value, oldValue)
  219. } catch (e) {
  220. nextTick(function () {
  221. throw prevError
  222. }, 0)
  223. throw e
  224. }
  225. } else {
  226. this.cb.call(this.vm, value, oldValue)
  227. }
  228. }
  229. this.queued = this.shallow = false
  230. }
  231. }
  232. /**
  233. * Evaluate the value of the watcher.
  234. * This only gets called for lazy watchers.
  235. */
  236. Watcher.prototype.evaluate = function () {
  237. // avoid overwriting another watcher that is being
  238. // collected.
  239. var current = Dep.target
  240. this.value = this.get()
  241. this.dirty = false
  242. Dep.target = current
  243. }
  244. /**
  245. * Depend on all deps collected by this watcher.
  246. */
  247. Watcher.prototype.depend = function () {
  248. var depIds = Object.keys(this.deps)
  249. var i = depIds.length
  250. while (i--) {
  251. this.deps[depIds[i]].depend()
  252. }
  253. }
  254. /**
  255. * Remove self from all dependencies" subcriber list.
  256. */
  257. Watcher.prototype.teardown = function () {
  258. if (this.active) {
  259. // remove self from vm"s watcher list
  260. // we can skip this if the vm if being destroyed
  261. // which can improve teardown performance.
  262. if (!this.vm._isBeingDestroyed) {
  263. this.vm._watchers.$remove(this)
  264. }
  265. var depIds = Object.keys(this.deps)
  266. var i = depIds.length
  267. while (i--) {
  268. this.deps[depIds[i]].removeSub(this)
  269. }
  270. this.active = false
  271. this.vm = this.cb = this.value = null
  272. }
  273. }
  274. /**
  275. * Recrusively traverse an object to evoke all converted
  276. * getters, so that every nested property inside the object
  277. * is collected as a "deep" dependency.
  278. *
  279. * @param {*} val
  280. */
  281. function traverse (val) {
  282. var i, keys
  283. if (isArray(val)) {
  284. i = val.length
  285. while (i--) traverse(val[i])
  286. } else if (isObject(val)) {
  287. keys = Object.keys(val)
  288. i = keys.length
  289. while (i--) traverse(val[keys[i]])
  290. }
  291. }

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

转载请注明本文地址: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元查看
<