博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(解析)vue源码解读
阅读量:6174 次
发布时间:2019-06-21

本文共 7505 字,大约阅读时间需要 25 分钟。

前言

A: 为什么要做源码解读?

Q: 我们新到一个环境,第一件事情就是熟悉环境熟悉项目,这个很考验阅读源码的能力以及耐心。vue是个很好的库,知名度高,对js的学习具有向上性,所以搞清楚逻辑是有好处的。
A: 阅读源码的程度?
Q: 我们完全没必要从头到尾细细品味,只需要知道一些核心的实现就好了,毕竟vue也算是个产品,我们没必要搞清楚一个产品,我们只需要知道产品的核心就够了,多余的也是业务代码。(相对来说)

开始

Vue应用的实例化以及挂载

main.js

new Vue({                      // 初始化vue实例  //components: { App }        // vue1.0的写法  render: h => h(App)          // 最先执行,返回一个符合component的对象,vue2.0的写法}).$mount('#app')                // 挂载vue实例到 ‘#app’

render函数

render: h => h(App) 就是render:function(h){    return h(App)}即render: function (createElement) {    return createElement(App)}

找到Vue引用源文件(debug顺序)

import Vue from 'vue'

找到

node-modules/vue

打开package.json 找到

"main": "dist/vue.runtime.common.js"

"main"是 npm模块曝光的主要文件.

打开vue.runtime.common.js

ctrl/command + a, ctrl/command + k, ctrl/command + 1
,快捷键把所有方法折叠

debug执行顺序

你会发现 update执行了2次,我明明只初始化了一次vue实例,为什么update2次了呢?原因在下方表明。

构造函数

拉到最下面,

module.exports = Vue;

导出的是一个Vue构造函数。

当前文件搜索 Vue 找到 构造函数

function Vue (options) {  if (process.env.NODE_ENV !== 'production' &&    !(this instanceof Vue)  ) {    warn('Vue is a constructor and should be called with the `new` keyword');  }  this._init(options); // 调用 初始化方法}

初始化函数

var uid$3 = 0;vm._uid = uid$3++; //每个vue实例 拥有唯一id,从0开始 ++// 合并初始化vue实例的参数(入参options和默认参数)vm.$options = mergeOptions(        resolveConstructorOptions(vm.constructor),        options || {},        vm      );initLifecycle();// 初始化实例生命周期相关的参数使用Object.create(null)用来获取一个没有原型链的对象类型initEvents(); // 初始化实例事件触发相关的参数initRender(); // 初始化实例渲染相关的参数//create的准备工作做好了,触发beforeCreate的生命周期callHook(vm, 'beforeCreate');initState(); // 初始化实例状态// 状态也初始化好了,触发create的生命周期,所以 create和 breforCreate的区别就在 create的时候  有状态。callHook(vm, 'created');

至此,vue实例的初始化完成,然后挂载到节点

挂载实例到节点

// 将mount('#app') => query('#app') 查找到dom对象,赋值给vue.$el// 触发beforeMount的生命周期,所以beforeMount 和 create的区别就在  beforeMount的时候 有挂载节点。callHook(vm, 'beforeMount');// 拿到当前将要挂载的Vnode(虚拟dom对象)vm._render()  vm.$vnode// (更新)渲染页面vm._update(vm._render(), hydrating);vm.__patch__(); createElm(); // 按照虚拟dom生成真实dom

createElm函数

// 这是一个递归方法,vue实例的初始化是创建一个根节点,然后再将render函数传入的组件挂载,这就是流程图执行2次update的原因。// 如果是组件,则去做 组件的初始化if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {  return}// 已经删减,只留主要逻辑,判断虚拟dom 的 tag属性if (isDef(tag)) {  {    // 遍历虚拟dom的子节点,并且创建,然后递归当前方法。    createChildren(vnode, children, insertedVnodeQueue);     if (isDef(data)) {      invokeCreateHooks(vnode, insertedVnodeQueue);    }    insert(parentElm, vnode.elm, refElm);  }}// 如果没有子节点的,直接创建dom,然后插入 else if (isTrue(vnode.isComment)) {  vnode.elm = nodeOps.createComment(vnode.text);  insert(parentElm, vnode.elm, refElm);} else {  vnode.elm = nodeOps.createTextNode(vnode.text);  insert(parentElm, vnode.elm, refElm);}// 触发Mount的生命周期callHook(vm, 'mounted');

至此就是 Vue根节点初始化挂载和渲染的流程.

Vue数据更新

首先 我们改造下 app.vue,像官网一样,我们新增一个双向绑定的文本框。

现在我们知道了,第一次的update只是挂载了根节点,那么我们新增了文本框的组件其实是在第二次init的时候初始化的。
我们可以着重看第二次的流程,搞清楚,数据的监听与更新。

initState函数

在每次vue实例初始化的时候 都会执行initState,这里面做了vue实例 数据的监听。

function initState (vm) {  vm._watchers = [];  var opts = vm.$options;  if (opts.props) { initProps(vm, opts.props); }  if (opts.methods) { initMethods(vm, opts.methods); }  if (opts.data) {   // 这里判断vm.$options.data,从而执行initData()或者 直接 监听 vm._data={}作为根状态     initData(vm);  } else {    observe(vm._data = {}, true /* asRootData */);  }  if (opts.computed) { initComputed(vm, opts.computed); }  if (opts.watch && opts.watch !== nativeWatch) {    initWatch(vm, opts.watch);  }}

vm.$options.data 哪里来的呢?

是在 Vue._init方法中

if (options && options._isComponent) {      // optimize internal component instantiation      // since dynamic options merging is pretty slow, and none of the      // internal component options needs special treatment.      initInternalComponent(vm, options);    } else {      vm.$options = mergeOptions(     // 这个方法中  给data赋值,也就是我们render中 的  data(){return{//我们组件的数据}}        resolveConstructorOptions(vm.constructor),        options || {},        vm      );    }

initData函数

function initData (vm) {  var data = vm.$options.data;  // 把从render函数返回的data函数对象赋值给data,然后data.call(this,this),也就是vm.data();拿到data返回值  data = vm._data = typeof data === 'function'          ? getData(data, vm)    : data || {};  // observe data  observe(data, true /* asRootData */);}function getData (data, vm) {  pushTarget();  try {    return data.call(vm, vm)  } catch (e) {    handleError(e, vm, "data()");    return {}  } finally {    popTarget();  }}

拿到data返回值

function observe (value, asRootData) {  var ob;  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {    ob = value.__ob__;  } else if (    shouldObserve &&    !isServerRendering() &&    (Array.isArray(value) || isPlainObject(value)) &&    Object.isExtensible(value) &&    !value._isVue  ) {    ob = new Observer(value);   // 返回一个 新建的 Observer  }  return ob}

Observer对象(监听者)

var Observer = function Observer (value) {  this.value = value;  this.dep = new Dep(); // 这里的dep 稍后会说  this.vmCount = 0;  def(value, '__ob__', this);  if (Array.isArray(value)) {    var augment = hasProto      ? protoAugment      : copyAugment;    augment(value, arrayMethods, arrayKeys);    this.observeArray(value);   } else {    this.walk(value);  }};// 如果是对象,则按照key来劫持Observer.prototype.walk = function walk (obj) {  var keys = Object.keys(obj);  for (var i = 0; i < keys.length; i++) {    defineReactive(obj, keys[i]);  }};// 如果是数组,就遍历每个数组元素,再每个元素再判断是否为数组,对象Observer.prototype.observeArray = function observeArray (items) {  for (var i = 0, l = items.length; i < l; i++) {    observe(items[i]);  }};

数据劫持

function defineReactive (  obj,  key,  val,  customSetter,  shallow) {  var dep = new Dep(); // 这里的dep 稍后会说      var property = Object.getOwnPropertyDescriptor(obj, key);  if (property && property.configurable === false) {    return  }  // cater for pre-defined getter/setters  var getter = property && property.get;  if (!getter && arguments.length === 2) {    val = obj[key];  }  var setter = property && property.set;  var childOb = !shallow && observe(val);  Object.defineProperty(obj, key, {    enumerable: true,    configurable: true,    get: function reactiveGetter () {      var value = getter ? getter.call(obj) : val;      if (Dep.target) {        dep.depend(); // 这里的dep 稍后会说        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 (process.env.NODE_ENV !== 'production' && customSetter) {        customSetter();      }      if (setter) {        setter.call(obj, newVal);      } else {        val = newVal;      }      childOb = !shallow && observe(newVal);      dep.notify(); // 这里的dep 稍后会说    }  });}

依赖收集

我们在使用vue的时候,data方法会返回对象,包含了所有我们想要观察的数据属性,同样 vue也会帮我们监听这些属性的变化,但是,假如我们在data中设置了多个属性,但是在模板中只使用了1个,又会如何呢?我们在脚本中设置value2的值(this.value3= 'hello world'),那么vue监听到变化 还会去通知模板重新渲染么?

new Vue({    template:         `
value1: {
{value1}}
`, data: { value1: 'value1', value2: 'value2', value3: 'value3', ... }});

自然是不会的,vue很聪明的使用了依赖收集

Dep : 一个订阅者的容器,可以增加或删除订阅者,可以向订阅者发送消息;
Watcher : 订阅者类。它在初始化时可以接受getter, callback两个函数作为参数。getter用来计算Watcher对象的值。当Watcher被触发时,会重新通过getter计算当前Watcher的值,如果值改变,则会执行callback.

Dep(订阅者容器)

Watcher对象(订阅者)

小结

未完待续。。。

转载地址:http://faqba.baihongyu.com/

你可能感兴趣的文章
其实很简单 微星为你详解Z77主板BIOS设置
查看>>
在Ubuntu Kylin下安装JDK1.8
查看>>
Hadoop 学习一
查看>>
Linux中生成/etc/shadow的加密密码
查看>>
《gcc五分钟系列》第三节:-o选项
查看>>
批量检测主机存活状态
查看>>
解决 error: gnu/stubs-32.h: No such file or directory
查看>>
imread 函数 的相关细节
查看>>
分布式和事务
查看>>
C#学习常用类(1002)---KeyValuePair<TKey, TValue> 结构
查看>>
浅谈grep命令查找匹配内容的使用、参数、正则
查看>>
磁盘配额
查看>>
UserInputControls用户输入控制
查看>>
我的友情链接
查看>>
Nginx+Lua架构开发目录贴
查看>>
mysql备份方法(热备)
查看>>
scala匿名函数
查看>>
vlan技术【实现】vlan简介和SVI实现不同vlan间通信
查看>>
scrapy爬虫初步尝试
查看>>
陈松松:视频制作不出来,跟这7个思维有九成关系
查看>>