热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

6.最俗学习之-Vue源码学习-数据篇(上)

源码地址这篇重点学习Vue的数据响应系统,文件路径srccoreinstanceexposerealselfvm._selfvminitLifecycle(

源码地址

这篇重点学习Vue的数据响应系统,文件路径src/core/instance


// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
callHook(vm, 'beforeCreate')
initState(vm)
callHook(vm, 'created')
initRender(vm)

// 先看event.js,只有这么一段

export function initEvents (vm: Component) {
  vm._events = Object.create(null)
  vm._hasHookEvent = false
  // init parent attached events
  const listeners = vm.$options._parentListeners
  if (listeners) {
    updateComponentListeners(vm, listeners)
  }
}

// 这里只做了两件事,至于这个vm.$options._parentListeners暂时是没有的,作用暂时不明

vm._events = Object.create(null)
vm._hasHookEvent = false

// 再看lifecycle.js,同样的这个if语句相关的也是不会执行的,因为没有这个parent,也是在vm实例上
// 添加各种内部的属性

export function initLifecycle (vm: Component) {
  const optiOns= vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

// 然后到了最下面的callHook方法,执行生命周期,也就是上面两步后执行callHook(vm, 'beforeCreate')

export function callHook (vm: Component, hook: string) {
  const handlers = vm.$options[hook]
  if (handlers) {
    for (let i = 0, j = handlers.length; i if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
}

// 这里一开始不太明白handlers为什么会是个数组,后来想了下,是mixin的原因,下面的例子可以测试


"en">

  "UTF-8">
  


  
"app">
// 然而这个就有点陌生了vm._hasHookEvent,看了下构建后的源码,使用到这个的只有三处地方 // 这里添加的vm._hasHookEvent function initEvents (vm) { vm._events = Object.create(null); vm._hasHookEvent = false; // init parent attached events var listeners = vm.$options._parentListeners; if (listeners) { updateComponentListeners(vm, listeners); } } // 这里添加事件方法(vm._events[event] || (vm._events[event] = [])).push(fn); var hookRE = /^hook:/; Vue.prototype.$on = function (event, fn) { var vm = this;(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._hasHookEvent function callHook (vm, hook) { var handlers = vm.$options[hook]; if (handlers) { for (var i = 0, j = handlers.length; i if (vm._hasHookEvent) { vm.$emit('hook:' + hook); } } // 根据官方文档api,下面用一个例子测试下,这里不能用beforeCreate钩子,因为这个钩子不会再执行 // 这个大概就是可以在实例上面注册生命周期事件吧,几乎没怎么用到过,暂时不明白这个设计得用途 "en"> "UTF-8">
"app">

{{ a }}

最后主角要登场了!!!噔噔蹬

这里还是看大神的文章,主线路还是这里的

Vue2.1.7源码学习


initState(vm)

// 我们用最简单的例子说起,之后慢慢丰富内容

let vm = new Vue({
    el: '#app',
    data: { a: 1, b: [1, 2, 3] }
})

// 这里只有data,则只会走initData(vm)

function initData (vm: Component) {
  // 获取data
  let data = vm.$options.data
  // 获取data,若是函数形式则执行,无值则是空对象
  data = vm._data = typeof data === 'function'
    ? data.call(vm)
    : data || {}
    // 检测是否为object
  if (!isPlainObject(data)) { data = {}
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  }
  // proxy data on instance
  const keys = Object.keys(data)
  const props = vm.$options.props
  let i = keys.length
  // 检测data和props不能冲突
  while (i--) {
    if (props && hasOwn(props, keys[i])) {
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "${keys[i]}" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
    } else {
    // 代理数据,这样我们就能通过 this.a 来访问 data.a 了
      proxy(vm, keys[i])
    }
  }
  // observe data
  observe(data, true /* asRootData */) // 重点是这个
}

src/core/observer/index.js


/** * Attempt to create an observer instance for a value, * returns the new observer if successfully observed, * or the existing observer if the value already has one. */
function observe (value, asRootData) {
  // 若不是object,返回,注意这里,这里不是标准的检测方式,这里数组也会被看做是object的
  if (!isObject(value)) {
    return
  }
  var ob;
  // 如果已经有这个属性,则说明已经绑定过数据了
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    observerState.shouldConvert &&  // 这个是定义的一个变量,为true
    !isServerRendering() &&           // 是否为服务端渲染
    (Array.isArray(value) || isPlainObject(value)) &&      // 是Array或者是Object
    Object.isExtensible(value) &&    // 是否为可拓展属性的对象
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {  // 如果是根数据,那么ob这个实例的属性vmCount++
    ob.vmCount++;
  }
  return ob
}

// 重点看这个ob = new Observer(value);

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           // value就是data值
    this.dep = new Dep()         // 这个是另一个类,依赖收集器
    this.vmCount = 0             // 自身的一个属性
    def(value, '__ob__', this)   // 定义一个属性,用的def方法,后面再说
    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. */
  walk (obj: Object) {
    // 遍历对象的所有属性,执行defineReactive(obj, keys[i], obj[keys[i]])
    const keys = Object.keys(obj)
    for (let i = 0; i /** * Observe a list of Array items. */
  observeArray (items: Array) {
    for (let i = 0, l = items.length; i // 按照例子

let vm = new Vue({
    el: '#app',
    data: {
        a: 1,
        b: [1, 2, 3]
    }
})

// 就是这个样子了

ob = new Observer({
        a: 1,
        b: [1, 2, 3]
    })

// 然后就是

this.walk({
        a: 1,
        b: [1, 2, 3]
    })

// 再然后

defineReactive(obj, a, 1)
defineReactive(obj, b, [1, 2, 3])

// 找到defineReactive方法

function defineReactive$$1 (
  obj,
  key,
  val,
  customSetter
) {
  var dep = new Dep();           // 先不管

  // getOwnPropertyDescriptor方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
  var property = Object.getOwnPropertyDescriptor(obj, key);
  // 如果是不可操作的
  if (property && property.cOnfigurable=== false) {
    return
  }

  // cater for pre-defined getter/setters
  // 获取对象上的get和set方法,这个应该和Object.defineProperty有关,这个方法网上教程很多,后面也会简单说下,这里应该就是之前自定义的方法吧,注释也说了pre-defined getter/setters,预定义的方法
  var getter = property && property.get;
  var setter = property && property.set;

  var childOb = observe(val);  // 检测它的子属性
  Object.defineProperty(obj, key, {   // 定义这个属性的get和set方法
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var 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) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if ("development" !== 'production' && customSetter) {
        customSetter();
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = observe(newVal);
      dep.notify();
    }
  });
}

// 重点在这个var childOb = observe(val); // 检测它的子属性,根据我们的例子这里会这样子

var childOb = observe(1);
var childOb = observe([1,2,3]);

// 然后再次进入observer方法,注意这个

// 若不是object,返回,注意这里,这里不是标准的检测方式,这里数组也会被看做是object的
if (!isObject(value)) {
  return
}

// 所以第一个observe(1)直接就return了,重点在observe([1,2,3]),这里会ob = new Observer(value)
// 即ob = new Observer([1,2,3]),这个时候数个数组就会走这一段

if (Array.isArray(value)) {
  const augment = hasProto
    ? protoAugment
    : copyAugment
  augment(value, arrayMethods, arrayKeys)
  this.observeArray(value)
}

// can we use __proto__?
export const hasProto = '__proto__' in {}

// 这个是判断当前的环境能否使用这个东东,然后决定取那个方法

这里还是推荐这位大神的文章,分析的非常到位,而且图文并茂

Javascript实现MVVM之我就是想监测一个普通对象的变化


// 首先是protoAugment,根据例子即

protoAugment([1,2,3], arrayMethods, arrayKeys)

// 然后找到arrayMethods和arrayKeys,分别如下

const arrayKeys = Object.getOwnPropertyNames(arrayMethods)

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

/** * Intercept mutating methods and emit events */
;[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator () {
    // avoid leaking arguments:
    // http://jsperf.com/closure-with-arguments
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})


// 这里弄个自己理解的例子,这个有点不好说,有点绕,还是看大神的分析,不能看懂也不勉强,
// 反正可以理解为在调用原生的数组方法前做点什么事情,数据绑定更新什么的等等


"en">

  "UTF-8">
  


  
"app">
// 回到例子protoAugment([1,2,3], arrayMethods, arrayKeys) // 这里arrayMethods我们已经知道了 arrayMethods = { pop: function () {} // 自己重写的方法 push: function () {} // 自己重写的方法 shift: function () {} // 自己重写的方法 unshift: function () {} // 自己重写的方法 splice: function () {} // 自己重写的方法 sort: function () {} // 自己重写的方法 reverse: function () {} // 自己重写的方法 __proto__: { // 这里面是原生数组的方法,也就是Array.prototype } } arrayKeys = ["push", "pop", "shift", "unshift", "splice", "sort", "reverse"] // 所以这个方法实际上就是,这个方法不需要第三个参数,也就是不需要arrayKeys function protoAugment (target, src: Object) { target.__proto__ = src } [1,2,3].__proto__ = arrayMethods // 这个操作类似我上面的那个例子的这一步 arr.__proto__ = myArr; arr2.__proto__ = myArr2; // 最后到了另一种情况就是,hasProto没有这个东东的情况,那就是这个形式 copyAugment([1,2,3], arrayMethods, arrayKeys) // 对应的方法是这个,这个方法需要arrayKeys这个值 function copyAugment (target: Object, src: Object, keys: Array) { for (let i = 0, l = keys.length; i const key = keys[i] def(target, key, src[key]) } } // 这里的def方法也有说过,这一步的操作就不解释了,如果到了这里看不懂的话,就说明你上面的都没看懂, // 思路应该还是比较乱的,也可能是我表达不好0.0,这里一定要弄懂,不然后面的数组操作会很懵逼 // 再次回到主线路 if (Array.isArray(value)) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) console.log(value.__proto__) // 到了这里打印出来看下其实就能明白了 this.observeArray(value) } // 最后剩下这个this.observeArray(value)了,对数组的每一项进行observe,又是这个东东- -# observeArray (items) { for (let i = 0, l = items.length; i // 但是我们这个例子会被直接return的,因为里面有这一段 if (!isObject(value)) { return } // 因为我们的例子是 b: [1,2,3] // 假如是这样的数据,则会继续递归调用 b: [{key1: value1}, {key2: value2}, {key3: value3}] b: [[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15]]

至此大概的思路应该都是这样了,也不知道说的对不对,看到最后自己脑袋都有点懵逼了,希望各路大神指点指点,感激不尽,最后还有个Dep的东东没有说,后面再看!

剩下的几个问题


Vue.set = set                       // 涉及到Vue的数据响应式系统,先保留
Vue.delete = del                    // 涉及到Vue的数据响应式系统,先保留
Vue.nextTick = util.nextTick        // 水平有限,看不懂 - -#
initExtend(Vue)                     // 水平有限,看不懂 - -#


extend(Vue.options.directives, platformDirectives)  // 水平有限,看不懂 - -#
extend(Vue.options.components, platformComponents)  // 水平有限,看不懂 - -#
Vue.prototype.__patch__                             // 水平有限,看不懂 - -#
compileToFunctions                                  // 水平有限,看不懂 - -#


const extendsFrom = child.extends                   // 水平有限,看不懂 - -#

initProxy(vm)                                       // 水平有限,看不懂 - -#

推荐阅读
  • VueCLI多页分目录打包的步骤记录
    本文介绍了使用VueCLI进行多页分目录打包的步骤,包括页面目录结构、安装依赖、获取Vue CLI需要的多页对象等内容。同时还提供了自定义不同模块页面标题的方法。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • YOLOv7基于自己的数据集从零构建模型完整训练、推理计算超详细教程
    本文介绍了关于人工智能、神经网络和深度学习的知识点,并提供了YOLOv7基于自己的数据集从零构建模型完整训练、推理计算的详细教程。文章还提到了郑州最低生活保障的话题。对于从事目标检测任务的人来说,YOLO是一个熟悉的模型。文章还提到了yolov4和yolov6的相关内容,以及选择模型的优化思路。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
  • iOS超签签名服务器搭建及其优劣势
    本文介绍了搭建iOS超签签名服务器的原因和优势,包括不掉签、用户可以直接安装不需要信任、体验好等。同时也提到了超签的劣势,即一个证书只能安装100个,成本较高。文章还详细介绍了超签的实现原理,包括用户请求服务器安装mobileconfig文件、服务器调用苹果接口添加udid等步骤。最后,还提到了生成mobileconfig文件和导出AppleWorldwideDeveloperRelationsCertificationAuthority证书的方法。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
author-avatar
f蓝色基因__987
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有