加载 JSBundle

一、Native

每一个页面都是一个 instance,framework 是全局唯一的,js 和 native 交互,第一个参数都是 instanceID,依靠 instanceID 来去区分不同的页面直接的调用。

在 controller 中创建 instance

_instance = [[WXSDKInstance alloc] init];
1、renderURL

以 iOS 为例:每一个 controller 都持有一个 WXSDKInstance,加载到 JSBundle 代码后,客户端通过 renderURL 的方式来进行加载。

[_instance renderWithURL:[NSURL URLWithString:randomURL] options:@{@"bundleUrl":URL.absoluteString} data:nil];

WXSDKInstance 在初始化方法里面生成 一个 唯一 instanceID,这个 instanceID 就是和 js 交互时 第一个参数,
在 Weex 架构里面,每一个页面都是一个 instance,通过 instanceID 来区分不同的页面。

renderWithURL
    WXResourceRequest *request = [WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy];
    [self _renderWithRequest:request options:options data:data];

renderWithURL 生成 request 去加载 jsBundle,里面会判断是否是 本地 file 还是需要从服务器下载,下载成功后调用
- (void)_renderWithMainBundleString:(NSString *)mainBundleString

_renderWithMainBundleString
    WXPerformBlockOnMainThread(^{
        _rootView = [[WXRootView alloc] initWithFrame:self.frame];
        _rootView.instance = self;
        if(self.onCreate) {
            self.onCreate(_rootView);
        }
    });
    // ensure default modules/components/handlers are ready before create instance
    [WXSDKEngine registerDefaults];

    [self _handleConfigCenter];

    [[WXSDKManager bridgeMgr] createInstance:self.instanceId template:mainBundleString options:dictionary data:_jsData];
  1. create WXRootView 作为 instance 的跟 view
  2. 确保 components,module 注册。
  3. 是否需要替换 使用 CoreText 和 Slider
  4. 调用 js 的 createInstance
2、createInstance

renderURL 最终会调用到 WXBridgeContext 的 createInstance 方法:

- (void)createInstance:(NSString *)instance
              template:(NSString *)temp
               options:(NSDictionary *)options
                  data:(id)data
{
    。。。。。。
    NSArray *args = nil;
    if (data){
        args = @[instance, temp, options ?: @{}, data];
    } else {
        args = @[instance, temp, options ?: @{}];
    }

    WX_MONITOR_INSTANCE_PERF_START(WXPTJSCreateInstance, [WXSDKManager instanceForID:instance]);
    [self callJSMethod:@"createInstance" args:args];
    WX_MONITOR_INSTANCE_PERF_END(WXPTJSCreateInstance, [WXSDKManager instanceForID:instance]);
}

传递的 args是一个 array:
instance: instanceID
temp: js 代码
options:包含 bundleUrl 和 debug

options: {
    bundleUrl = "file:///Users/yangshichang/Library/Developer/CoreSimulator/Devices/D5993070-3351-4E74-AEC5-40D97FEC8440/data/Containers/Bundle/Application/5919B561-0EC0-4927-89D8-B2895256D9CF/OperatorWeex.app/bundlejs/index.js";
    debug = 1;
}

js

1、crateInstance

native 调起的 crateInstance 会转发到 runtime/init.js 中的 createInstance

function createInstance (id, code, config, data) {
  let info = instanceMap[id]

  if (!info) {
    // Init instance info.
    info = checkVersion(code) || {}
    if (!frameworks[info.framework]) {
      info.framework = 'Weex'
    }

    // Init instance config.
    config = JSON.parse(JSON.stringify(config || {}))
    config.bundleVersion = info.version
    config.env = JSON.parse(JSON.stringify(global.WXEnvironment || {}))
    console.debug(`[JS Framework] create an ${info.framework}@${config.bundleVersion} instance from ${config.bundleVersion}`)

    const env = {
      info,
      config,
      created: Date.now(),
      framework: info.framework
    }
    env.services = createServices(id, env, runtimeConfig)
    instanceMap[id] = env

    return frameworks[info.framework].createInstance(id, code, config, data, env)
  }
  return new Error(`invalid instance id "${id}"`)
}

这里判断 instance 是否存在,避免重复创建。读取环境信息,判断是哪一个 framework,调用对应 framework 的 createInstance。

生成的 env 对象:

Object = $1

config: {debug: true, bundleUrl: "file:///Users/yangshichang/Library/Developer/CoreS…8-4BB8A4747BC4/OperatorWeex.app/bundlejs/hello.js", bundleVersion: undefined, env: Object}

created: 1500520452139

framework: "Vue"

info: {framework: "Vue"}

services: {service: {}, BroadcastChannel: function}

“Object”原型

因为使用 Vue 写的,所以这里会调用到 weex-vuew-framework.js

function createInstance (
  instanceId,
  appCode,
  config,
  data,
  env
) {
  if ( appCode === void 0 ) appCode = '';
  if ( config === void 0 ) config = {};
  if ( env === void 0 ) env = {};

  // 1. create Document
  var document = new renderer.Document(instanceId, config.bundleUrl);

  // 2. create instance
  var instance = instances[instanceId] = {
    instanceId: instanceId, config: config, data: data,
    document: document
  };

  // 3. create 获取 Module 对象的函数,需要 instance 来获取 instance.document.taskCenter。
  var moduleGetter = genModuleGetter(instanceId);
  // 4. create timerAPIs module
  var timerAPIs = getInstanceTimer(instanceId, moduleGetter);


  // 5. create weex module
  var weexInstanceVar = {
    config: config,
    document: document,
    requireModule: moduleGetter
  };
  Object.freeze(weexInstanceVar);

  // 6. 给 instance 创建 Vue module。
  var Vue = instance.Vue = createVueModuleInstance(instanceId, moduleGetter);

  // 7. create instanceVars,把 上面创建的对象,打包传递给 callFunction, 生成 执行 JSBundle 的匿名函数。
  // The function which create a closure the JS Bundle will run in.
  // It will declare some instance variables like `Vue`, HTML5 Timer APIs etc.
  var instanceVars = Object.assign({
    Vue: Vue,
    weex: weexInstanceVar,
    // deprecated
    __weex_require_module__: weexInstanceVar.requireModule // eslint-disable-line
  }, timerAPIs, env.services);

  // 8. callFunction(instanceVars, appCode),生成 执行 JSBundle 的匿名函数
  if (!callFunctionNative(instanceVars, appCode)) {
    // If failed to compile functionBody on native side,
    // fallback to 'callFunction()'.
    callFunction(instanceVars, appCode);
  }

  // Send `createFinish` signal to native.
  instance.document.taskCenter.send('dom', { action: 'createFinish' }, []);
}

  1. create Document
  2. create instance
  3. create 获取 Module 对象的函数,需要 instance 来获取 instance.document.taskCenter。
  4. create timerAPIs module
  5. create weex 对象,Vue 中写的 weex.requireModue 就是调用这里的 genModuleGetter 的这个方法
  6. 给 instance 创建 Vue module。
  7. create instanceVars,把 上面创建的对象,打包传递给 callFunction, 生成 执行 JSBundle 的匿名函数。
  8. callFunction(instanceVars, appCode),生成 执行 JSBundle 的匿名函数
  9. 发送 instance createFinish 事件。

createInstance(1),创建 Document 对象

Document 在 vdom/document.js

export default function Document (id, url, handler) {
  id = id ? id.toString() : ''
  this.id = id
  this.URL = url

  // 1. 将生成的 doc 添加到 `docMap` 中
  addDoc(id, this)
  // 2. create nodeMap
  this.nodeMap = {}
  // 3. 读取 Listener,Listener 已经废弃掉了,
  const L = Document.Listener || Listener
  // 4. 初始化 Listener,这里 handler 是 undefined。没有值的
  this.listener = new L(id, handler || createHandler(id, Document.handler)) // deprecated
  // 5. create TaskCenter。每一个 Document 都持有一个 TaskCenter。这里 handler 为 undefined,用的是 Document.handler
  this.taskCenter = new TaskCenter(id, handler ? (id, ...args) => handler(...args) : Document.handler)
  // 6. create 一个 document element。也是一个 Element 的对象,把 role 设置为 'documentElement',
  this.createDocumentElement()
}

1、Doucument.handler config.js中 赋值为 sendTasks,调用 global.callNative

2、每一个 Document 都持有一个 TaskCenter,作为 业务代码和 native 交互的通道。TaskCenter 发送的消息 第一个参数都会带上 instanceID。

TaskCenter 的代码在 runtime/task-center.js 。这里 handler 为 nil,所以用的是 Document.handler

taskCenter 的初始化只是设置 instanceId 和 callbackManager,fallback

(1)callbackManager 的作用就是 在 js 调用 native 时,如果参数带有回调函数,则将这个 callback 暂存在 callbackManager 中,将 生成的 callback id 发送给 native,
当 native callback 给 js 时,根据 callback id,取出对应的 callback 执行。

(2)fallback 赋值给 sendTasks

3、documentElement 是作为 根节点 body 的父节点而存在的。
在一个 document 被创建时会自动生成一个 documentElement ,并且 body 应该手动创建并添加到 documentElement 才能工作。bodytype 必须是一个 divlistscroller

documentElement 另外添加了 2 个 函数,appendChildinsertBefore

    Object.defineProperty(el, 'appendChild', {
        configurable: true,
        enumerable: true,
        writable: true,
        value: (node) => {
          appendBody(this, node)
        }
      })

      Object.defineProperty(el, 'insertBefore', {
        configurable: true,
        enumerable: true,
        writable: true,
        value: (node, before) => {
          appendBody(this, node, before)
        }
      })

createInstance(2)创建 instance

var instance = instances[instanceId] = {
  instanceId: instanceId, config: config, data: data,  document: document
};

生成的 instance 示例:

 config: Object

            bundleUrl: "file://…",bundleVersion: undefined,debug: true,

            env: {scale: 3, appVersion: "1.8.3", deviceModel: "x86_64", appName: "OperatorWeex", platform: "iOS", …}

            “Object”原型

    data: undefined

    document: Document

            URL: "file:///…"

            documentElement: Element

                    appendChild: function(node)

                    attr: {}

                    children: [] (0)

                    classStyle: {}

                    depth: 0

                    docId: "2"

                    event: {}

                    insertBefore: function(node, before)

                    nodeId: "139"

                    nodeType: 1

                    ownerDocument: Document {id: "2", URL: "file:///Users/yangshichang/Library/Developer/CoreS…8-4BB8A4747BC4/OperatorWeex.app/bundlejs/hello.js", nodeMap: Object, listener: Listener, taskCenter: TaskCenter, …}

                    pureChildren: [] (0)

                    ref: "_documentElement"

                    role: "documentElement"

                    style: {}

                    type: "document"

                    “Element”原型

            id: "2"

            listener: Listener

                    batched: false

                    handler: function(tasks)

                    id: "2"

                    updates: [] (0)

                    “Listener”原型

            nodeMap: Object

            _documentElement: Element {nodeType: 1, nodeId: "139", ref: "_documentElement", type: "document", attr: {}, …}

                    “Object”原型

            taskCenter: TaskCenter

                    callbackManager: CallbackManager

                            callbacks: [] (0)

                            instanceId: undefined

                            lastCallbackId: 0

                            “CallbackManager”原型

                    instanceId: "2"

                    “TaskCenter”原型

            “Document”原型

    instanceId: "2"

createInstance(3)生成 获取 module 的方法

genModuleGetter: 根据 module name,遍历 native 暴露出来的 methodName,生成 js 对应的 module 。调用对应的方法 都是 转发到 taskCenter 去做转发。

genModuleGetter 会作为 weex.requieModule 传递到 JSBundle 里面,JSBundle 中执行的 weex.requireModule 就是这个函数。

function genModuleGetter (instanceId) {
  var instance = instances[instanceId];
  return function (name) {
    var nativeModule = modules[name] || [];
    var output = {};
    var loop = function ( methodName ) {
      Object.defineProperty(output, methodName, {
        enumerable: true,
        configurable: true,
        get: function proxyGetter () {
          return function () {
            var args = [], len = arguments.length;
            while ( len-- ) args[ len ] = arguments[ len ];

            return instance.document.taskCenter.send('module', { module: name, method: methodName }, args)
          }
        },
        set: function proxySetter (val) {
          if (typeof val === 'function') {
            return instance.document.taskCenter.send('module', { module: name, method: methodName }, [val])
          }
        }
      });
    };

    for (var methodName in nativeModule) loop( methodName );
    return output
  }
}

createInstance(4)创建 timer

超时 等 回调方法。

createInstance(5)创建 Vue

var Vue = instance.Vue = createVueModuleInstance(instanceId, moduleGetter);

创建一个 Vue 函数,作用就是 JSBundle 的解析器。发送指令到 native 进行界面渲染。

function createVueModuleInstance (instanceId, moduleGetter) {

  // 1. create Vue 对象
  var exports = {};
  VueFactory(exports, renderer);
  var Vue = exports.Vue;

  var instance = instances[instanceId];

  // 2. 创建一个函数,判断 element type 是否保留 element
  // patch reserved tag detection to account for dynamically registered
  // components
  var isReservedTag = Vue.config.isReservedTag || (function () { return false; });
  Vue.config.isReservedTag = function (name) {
    return components[name] || isReservedTag(name)
  };

  // 3. 给 Vue 添加 instanceID 和 document 属性
  // expose weex-specific info
  Vue.prototype.$instanceId = instanceId;
  Vue.prototype.$document = instance.document;

  // 4. 给 Vue 添加 requireModule 属性
  // expose weex native module getter on subVue prototype so that
  // vdom runtime modules can access native modules via vnode.context
  Vue.prototype.$requireWeexModule = moduleGetter;

  // 5. 添加一个 beforeCreate 钩子函数,在 root component 创建之前,将 外部 传入的 data 和 $options 中的 data 合并。
  // data 是 createInstance 的时候,从 native 传过来的。
  // 把根 vm 设置为 instance 的 app 属性。
  // Hack `Vue` behavior to handle instance information and data
  // before root component created.
  Vue.mixin({
    beforeCreate: function beforeCreate () {
      var options = this.$options;
      // root component (vm)
      if (options.el) {
        // set external data of instance
        var dataOption = options.data;
        var internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {};
        options.data = Object.assign(internalData, instance.data);
        // record instance by id
        instance.app = this;
      }
    }
  });

  /**
   * @deprecated Just instance variable `weex.config`
   * Get instance config.
   * @return {object}
   */
  Vue.prototype.$getConfig = function () {
    if (instance.app instanceof Vue) {
      return instance.config
    }
  };

  return Vue
}
  1. create Vue 函数
  2. 创建一个函数,判断 element type 是否保留 element
  3. 给 Vue 添加 instanceID 和 document 属性
  4. 给 Vue 添加 requireModule 属性
  5. 添加一个 beforeCreate 钩子函数,在 root component 创建之前,将 外部 传入的 data 和 $options 中的 data 合并。
    data 是 createInstance 的时候,从 native 传过来的。
VueFactory(exports, renderer);

VueFactory 定义在 weex-vue-framework factory.js。 是打包好的 weex 平台的 vue 核心库。

初始化一个 Vue 函数。

'use strict';

module.exports = function weexFactory (exports, renderer) {

。。。。。。

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

。。。。。

function Vue$2 (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue$2)) {
    warn('Vue is a constructor and should be called with the `new` keyword');
  }
  this._init(options);
}

initMixin(Vue$2);
stateMixin(Vue$2);
eventsMixin(Vue$2);
lifecycleMixin(Vue$2);
renderMixin(Vue$2);
。。。。。

/*  */

// install platform specific utils
Vue$2.config.mustUseProp = mustUseProp;
Vue$2.config.isReservedTag = isReservedTag;
Vue$2.config.isUnknownElement = isUnknownElement;

// install platform runtime directives and components
Vue$2.options.directives = platformDirectives;
Vue$2.options.components = platformComponents;

// install platform patch function
Vue$2.prototype.__patch__ = patch;

// wrap mount
Vue$2.prototype.$mount = function (
  el,
  hydrating
) {
  return mountComponent(
    this,
    el && query(el, this.$document),
    hydrating
  )
};

// this entry is built and wrapped with a factory function
// used to generate a fresh copy of Vue for every Weex instance.

exports.Vue = Vue$2;

}
isReservedTag

判断是否是 保留 tag,根据 components 里面的类型 和 Vue.config.isReservedTag

var isReservedTag = makeMap(
  'template,script,style,element,content,slot,link,meta,svg,view,' +
  'a,div,img,image,text,span,richtext,input,switch,textarea,spinner,select,' +
  'slider,slider-neighbor,indicator,trisition,trisition-group,canvas,' +
  'list,cell,header,loading,loading-indicator,refresh,scrollable,scroller,' +
  'video,web,embed,tabbar,tabheader,datepicker,timepicker,marquee,countdown',
  true
);

components 就是 native 注册的 组件。

createInstance callFunction 解析 JSBundle 代码

callFunction 的作用就是解析执行 JSBundle 的代码。

function callFunction (globalObjects, body) {
  var globalKeys = [];
  var globalValues = [];
  for (var key in globalObjects) {
    globalKeys.push(key);
    globalValues.push(globalObjects[key]);
  }
  globalKeys.push(body);

  var result = new (Function.prototype.bind.apply( Function, [ null ].concat( globalKeys) ));
  return result.apply(void 0, globalValues)
}

globalKeys:

Array (10) = $2
0 "Vue"
1 "weex"
2 "__weex_require_module__"
3 "setTimeout"
4 "setInterval"
5 "clearTimeout"
6 "clearInterval"
7 "service"
8 "BroadcastChannel"
9 "// { \"framework\": \"Vue\" }↵/******/ (function(modules) { // webpackBootstrap↵/******/ // The module cache↵/******/ var installedModules …"

“Array”原型

var result = new (Function.prototype.bind.apply( Function, [ null ].concat( globalKeys) ));
生成一个匿名函数,参数为 Vue….BroadcastChannel。函数体为 body

result.apply(void 0, globalValues)
执行 这个匿名函数

function anonymous(Vue, weex, __weex_require_module__, setTimeout, setInterval, clearTimeout, clearInterval, service, BroadcastChannel) {
// { "framework": "Vue" }
        /******/ (function(modules){ // webpackBootstrap
        /******/  // The module cache
        /******/  var installedModules = {};
        /******/  // The require function
        /******/  function __webpack_require__(moduleId){}

        /******/  // expose the modules object (__webpack_modules__)
        /******/  __webpack_require__.m = modules;

        /******/  // expose the module cache
        /******/  __webpack_require__.c = installedModules;

        /******/  // __webpack_public_path__
        /******/  __webpack_require__.p = "";

        /******/  // Load entry module and return exports
        /******/  return __webpack_require__(0);

        })


        ({
        /***/ 0:
        /***/ (function(module, exports, __webpack_require__) {
            .........
            var __vue_exports__, __vue_options__
            var __vue_styles__ = []

            /* styles */
            __vue_styles__.push(__webpack_require__(133)
            )

            /* script */
            __vue_exports__ = __webpack_require__(134)

            /* template */
            var __vue_template__ = __webpack_require__(135)

            .........

            module.exports = __vue_exports__
            module.exports.el = 'true'
            new Vue(module.exports)
        }),

        /***/ 133:
        /***/ (function(module, exports) {}),

        /***/ 134:
        /***/ (function(module, exports) {}),

        /***/ 135:
        /***/ (function(module, exports) {})
        });
}

入口就是 moduleId==0的 module。里面会 把所有的 module 都执行起来,

一般 最后三个就是 Vue 代码中的 templatescriptstyle。具体可在0的函数体中看到

如果 script 中有 require 则 调用 moduleGetter 读取 Module 的方法,并生成 Module。
例如 var event = weex.requireModule('event'); 就会 生成 Event module 对象。

最后一句 new Vue(module.exports) 用解析好的对象作为参数 实例化一个 Vue对象。

new Vue(exports)

从上面的 Vue 函数可以看出 new Vue() 会调用到 Vue._init 方法

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

    // 1. create vm, Vue 的实例化对象。
    var vm = this;

    // 2. 设置 uid
    // a uid
    vm._uid = uid++;

    var startTag, endTag;
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = "vue-perf-init:" + (vm._uid);
      endTag = "vue-perf-end:" + (vm._uid);
      mark(startTag);
    }

    // a flag to avoid this being observed
    vm._isVue = true;
    // merge options
    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 {

      // 3. 将传入的这些options选项挂载到vm.$options属性上
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm);
    } else {
      vm._renderProxy = vm;
    }
    // expose real self
    vm._self = vm; // 自身的实例

    // 接下来所有的操作都是在这个实例上添加方法

    // lifecycle初始化
    initLifecycle(vm);
    // events初始化 vm._events, 主要是提供vm实例上的$on/$emit/$off/$off等方法
    initEvents(vm);
    // 初始化渲染函数,在vm上绑定$createElement方法
    initRender(vm);
    // 执行钩子函数, beforeCreate
    callHook(vm, 'beforeCreate');
    initInjections(vm); // resolve injections before data/props
    // Observe data添加对data的监听, 将data中的 key 转化为getters/setters
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    // 执行钩子函数, created
    callHook(vm, 'created');

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false);
      mark(endTag);
      measure(((vm._name) + " init"), startTag, endTag);
    }

    // vm挂载的根元素, el 在 JSBundle 中设置为 true
    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  };
}

options 就是 解析好的 JSBundle 的对象。

Vue.init(options) (1) mergeOptions

merge options 的作用就是 把 连个 对象合成一个。这里 就是 把 Vue 的方法 合并到 vm.$options 上。

function mergeOptions (
  parent,
  child,
  vm
) {
  。。。。。。
  var options = {};
  var key;
  for (key in parent) {
    mergeField(key);
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key);
    }
  }
  function mergeField (key) {
    var strat = strats[key] || defaultStrat;
    options[key] = strat(parent[key], child[key], vm, key);
  }
  return options
}

这里的 parent 就是 Vue 的构造函数,是:

parent:

Object = $5

_base: function(options)

beforeCreate: [function] (1)

components: {Transition: Object, TransitionGroup: Object}

directives: {}

filters: {}

“Object”原型

Vue.init(options)(2)initProxy(vm)

代理 vm 的 get 方法,拦截 vm 属性的 读取操作。如果 vm 的某一个属性不存在,则抛出一个warning。

Vue.init(options)(3)initLifecycle(vm)

初始化 vm 生命周期的关键属性值。

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

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

  vm._watcher = null;
  vm._inactive = null;
  vm._directInactive = false;
  vm._isMounted = false;
  vm._isDestroyed = false;
  vm._isBeingDestroyed = false;

Vue.init(options)(4)initEvents(vm)

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);
  }
}

初始化 节点上绑定的 on 事件。这里先初始化一个空的 event对象,判断如果 判断 vm 是否有 _parentListeners,有才会更新。

其实 每一个独立文件的 component 都会创建一个 vm。这里初始化,就是指更新 该 vm 作为 子节点时候,是否绑定有事件。

Vue.init(options)(5)initRender(vm)

function initRender (vm) {
  ......
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates
  vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };
}

绑定 createElement 方法到 vm,渲染 js 都是调用这里的 createElement。
这两个绑定 最后一个参数不同,alwaysNormalize

Vue.init(options)(6)callHook(vm, 'beforeCreate')

beforeCreatecreateVueModuleInstance 方法中进行了设置,主要是为了 把 vm 中的 data 和 native 传过来的 data 合并。

beforeCreate:

var internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {};

如果 dataOption 是 函数,则只需,否则直接读取。

Vue.init(options)(3)initInjections(vm)

Vue.init(options)(3)initProvide(vm)

provideinject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。

var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // -> "bar"
  }
  // ...
}

Vue.init(options)(3)initState(vm)

这里面初始化 vm 的data,method 等等,非常重要

function initState (vm) {
  // 1. 在vm上初始化一个_watchers数组,缓存这个vm上的所有watcher
  vm._watchers = [];
  // 2. 读取 vm 的 options。
  var opts = vm.$options;

  // 下面这些都对应 <script> 标签 定义的 属性。
  // 3. init props
  if (opts.props) { initProps(vm, opts.props); }
  // 4. init methods
  if (opts.methods) { initMethods(vm, opts.methods); }
  // 如果 data 存在,则调用 initData,不存在,则 创建一个空的 data,直接作为 rootData 添加 观察者。
  if (opts.data) {
    initData(vm);
  } else {
    observe(vm._data = {}, true /* asRootData */);
  }
  if (opts.computed) { initComputed(vm, opts.computed); }
  if (opts.watch) { initWatch(vm, opts.watch); }
}
initState(1)initProps

初始化 props 中的数据。

function initProps (vm: Component, propsOptions: Object) {
  // propsData主要是为了方便测试使用
  const propsData = vm.$options.propsData || {}
  // 新建vm._props对象,可以通过app实例去访问
  const props = vm._props = {}
  // cache prop keys so that future props updates can iterate using Array
  // instead of dynamic object key enumeration.
  // 缓存的prop key
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  // root instance props should be converted
  observerState.shouldConvert = isRoot
  for (const key in propsOptions) {
    // this._init传入的options中的props属性
    keys.push(key)
    // 注意这个validateProp方法,不仅完成了prop属性类型验证的,同时将prop的值都转化为了getter/setter,并返回一个observer
    const value = validateProp(key, propsOptions, propsData, vm)

    // 将这个key对应的值转化为getter/setter
      defineReactive(props, key, value)
    if (process.env.NODE_ENV !== 'production') {
            if (isReservedProp[key]) {
            warn(
              ("\"" + key + "\" is a reserved attribute and cannot be used as component prop."),
              vm
            );
          }
          defineReactive$$1(props, key, value, function () {
            if (vm.$parent && !observerState.isSettingProps) {
              warn(
                "Avoid mutating a prop directly since the value will be " +
                "overwritten whenever the parent component re-renders. " +
                "Instead, use a data or computed property based on the prop's " +
                "value. Prop being mutated: \"" + key + "\"",
                vm
              );
            }
          });
     } else {
        defineReactive$$1(props, key, value);
     }

    // 如果在vm这个实例上没有key属性,那么就通过proxy转化为proxyGetter/proxySetter, 并挂载到vm实例上,
    // 可以直接通过 vm.[key] 访问
    if (!(key in vm)) {
      proxy(vm, `_props`, key)
    }
  }
  observerState.shouldConvert = true
}
validateProp

validateProp 不仅验证 prop 中数据类型,而且 读取 props 中的默认 value,如果 value 是对象,则给这个对象添加 observer。
绑定在 value 的 __ob__ 属性上。

function validateProp (
  key,
  propOptions,
  propsData,
  vm
) {
  var prop = propOptions[key];
  var absent = !hasOwn(propsData, key);
  var value = propsData[key];
  // handle boolean props
  if (isType(Boolean, prop.type)) {
    if (absent && !hasOwn(prop, 'default')) {
      value = false;
    } else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) {
      value = true;
    }
  }
  // check default value
  if (value === undefined) {
    value = getPropDefaultValue(vm, prop, key);
    // since the default value is a fresh copy,
    // make sure to observe it.
    var prevShouldConvert = observerState.shouldConvert;
    observerState.shouldConvert = true;
    observe(value);
    observerState.shouldConvert = prevShouldConvert;
  }
  if (process.env.NODE_ENV !== 'production') {
    assertProp(prop, key, value, vm, absent);
  }
  return value
}

会检查 default value,, observe(value); 给 Object 对象 添加 观察者,绑定到 value 的 __ob__ 属性上。


/**
 * 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) {
  if (!isObject(value)) {
    return
  }
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}
defineReactive$$1(props, key, value, function () {}

给 value 设置 getter 和 setter。利用 Dep 完成数据的双向绑定。当数据更新时,dep 会通知订阅的 Watcher,进行更新。

proxy(vm, "_props", key);

可以看下 proxy 的实现, 这里的目的就是为了 能够直接通过 vm.[key] 来直接访问。

function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}
initState(2)initMethods(vm, opts.methods);

初始化 Components 中定义的 methods,生成一个 boundFn 绑定到 vm 上。

例如:methods:

onClick: function()

boundFun

function boundFn (a) {
    var l = arguments.length;
    return l
      ? l > 1
        ? fn.apply(ctx, arguments)
        : fn.call(ctx, a)
      : fn.call(ctx)
  }
initState(3)initData(vm)

读取 data 中定义的 属性 和 对应的值,并都代理到 vm 上。如果 某个 key 在 props 上已经定义过,则给出一个 warning。

这里面会调用一个函数,判断 key 不是 $ 和 _ 才进行 proxy。 $ 和 _ 主要是 Vue 自己定义的instance 属性。不允许 业务用这个做为 data 中 key 的名字。

 function isReserved (str) {
   var c = (str + '').charCodeAt(0);
   return c === 0x24 || c === 0x5F
 }

最后调用 observe(data, true) 给 data 添加 观察者,遍历 data 每一个属性,并设置 getter 和 setter。

initState(4)initComputed(vm, opts.computed)

computed 是 Vue 定义的计算属性,这里一般都是返回 一个 get 访方法。
所以这里 需要遍历 computed 中定义的 key,每一个都添加 Watcher,来订阅。

调用 defineComputed 把 key 都绑定到 vm 上,病添加 get 和 set 方法。

initState(4)initWatch(vm, opts.watch)

初始化 Vue 中的 watch 属性。

Vue.init(options)(3)callHook(vm, 'created')

执行 钩子函数 created。

Vue.init(options)(3)vm.$mount(vm.$options.el)

vm.$options.el 在 JSBundle 中定义 module.exports.el = 'true'

这里就开始 创建 Element、加载 组件了。

// wrap mount
Vue$2.prototype.$mount = function (
  el,
  hydrating
) {
  return mountComponent(
    this,
    el && query(el, this.$document),
    hydrating
  )
};

query 的作用就是 创建一个 Comment 类型的 Node,作为 doc 根节点的占位符。

function query (el, document) {
  // renderer is injected by weex factory wrapper
  var placeholder = new renderer.Comment('root');
  placeholder.hasAttribute = placeholder.removeAttribute = function () {}; // hack for patch
  document.documentElement.appendChild(placeholder);
  return placeholder
}

挂载组件的方法 mountComponent

function mountComponent (
  vm,
  el,
  hydrating
) {

    // 1. 保存 el
  vm.$el = el;
  // 2. 如果 render 不存在,创建一个 empty VNode
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode;
    。。。
  }

  // 3. 调用 mount 生命周期函数 beforeMount
  callHook(vm, 'beforeMount');

  var updateComponent;
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = function () {
      var name = vm._name;
      var id = vm._uid;
      var startTag = "vue-perf-start:" + id;
      var endTag = "vue-perf-end:" + id;

      mark(startTag);
      var vnode = vm._render();
      mark(endTag);
      measure((name + " render"), startTag, endTag);

      mark(startTag);
      vm._update(vnode, hydrating);
      mark(endTag);
      measure((name + " patch"), startTag, endTag);
    };
  } else {
  // 4. 设置 vm 更新后的执行函数。既 vm 有更新,则执行这个 updateComponents 函数
    updateComponent = function () {
      vm._update(vm._render(), hydrating);
    };
  }

    // 5. 给 vm 添加 Watcher。
    // 这里 Watcher 初始化中,最后一个方法会直接调用 Watcher 的 get() 函数。
    // 然后调用上面的 `updateComponents` 进行更新。
  vm._watcher = new Watcher(vm, updateComponent, noop);
  hydrating = false;

  // 6. 最后调用 钩子 函数 mounted。
  if (vm.$vnode == null) {
    vm._isMounted = true;
    callHook(vm, 'mounted');
  }
  return vm
}
mountComponent(1)Watcher
var Watcher = function Watcher (
  vm,
  expOrFn,
  cb,
  options
) {
    。。。。。

    this.getter = expOrFn;
    。。。。。
    this.value = this.lazy
        ? undefined
        : this.get();
}

Watcher.prototype.get = function get () {
  // 1. 设置 Dep.target = self
  pushTarget(this);
  var value;
  var vm = this.vm;
  // 2. 调用 new Watcher 时,定义的 getter 方法。
  if (this.user) {
    try {
      value = this.getter.call(vm, vm);
    } catch (e) {
      handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
    }
  } else {
    value = this.getter.call(vm, vm);
  }
  // "touch" every property so they are all tracked as
  // dependencies for deep watching
  if (this.deep) {
    traverse(value);
  }
  popTarget();
  this.cleanupDeps();
  return value
};

new Watcher 会直接执行 设置的 updateComponent 方法。

vm._update(vm._render(), hydrating);
  Vue.prototype._render = function () {
    var vm = this;
    var ref = vm.$options;
    var render = ref.render;
    var staticRenderFns = ref.staticRenderFns;
    var _parentVnode = ref._parentVnode;
    ......

    var vnode;
    try {
      vnode = render.call(vm._renderProxy, vm.$createElement);
    } catch (e) {
      。。。。。。
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        );
      }
      vnode = createEmptyVNode();
    }
    // set parent
    vnode.parent = _parentVnode;
    return vnode
  };

生成 VNode 树。

vm._render()(1)render.call

vnode = render.call(vm._renderProxy, vm.$createElement);

1、render 函数 就是 渲染的入口函数。在 JSBundle 中定义。

2、vm._renderProxy 就是在 vm 的 init 方法中调用 initProxy时 赋值的 vm._renderProxy = new Proxy(vm, handlers);
就是给 vm 代理一个 get 函数,来获取 vm 上 key 的值。

3、vm.$createElement 就是在 initRender中定义的,创建 Element。

render:例如

render:function (){
            var _vm=this;
            var _h=_vm.$createElement;
            var _c=_vm._self._c||_h;
        return _c('div', {
            staticClass: ["wrapper"],
            on: {
                "click": _vm.update
            }
        },
            [_c('image', {
            staticClass: ["logo"],
            attrs: {
                "src": _vm.logoUrl
            }
        }),
                _c('text', {
            staticClass: ["title"]
        },
                    [_vm._v("Hello " + _vm._s(_vm.title))]
                ),
                _c('text', {
            staticClass: ["detail"]
        },
                    [_vm._v(_vm._s(_vm.text))]
                )
            ])
    }

render 函数意思:

_c: createElement
vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };

function createElement (
  context,
  tag,
  data,
  children,
  normalizationType,
  alwaysNormalize
) {}

_v: createTextVNode,<element>TextNode</element>
_s: toString()

调用到 createElement

function _createElement (
  context,
  tag,
  data,
  children,
  normalizationType
) {
.....

// tag 是字符串,则 createVNode,如果是 组件,则 createComponent
if (typeof tag === 'string') {
    var Ctor;
    ns = config.getTagNamespace(tag);
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      );
    } else if ((Ctor = resolveAsset(context.$options, 'components', tag))) {
      // component
      vnode = createComponent(Ctor, data, context, children, tag);
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      );
    }
  } else {
  // 不是字符串,则 createComponent。
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children);
  }
  if (vnode) {
    if (ns) { applyNS(vnode, ns); }
    return vnode
  } else {
    return createEmptyVNode()
  }

}

以 text 节点为例:
context: Vue,tag:text,data:节点的属性,children:VNode,normalizationType:false。

data:

on: {click: function}

staticClass: ["title"] (1)

判断是否是 内部 components,则 new 一个 VNode,如果是 自定义组件,则 createComponent,否则创建一个 VNode。

这个 createComponent 方法中,会给该 VNode 添加一个 data 属性,里面有 hook 对象,这个 hook 就是后面在 update 的时候,创建 自定义组件的关键方法。

data:
    hook:
        init
        prepatch
        insert
        destroy

_createElement 根据 JSBundle 中 render 的节点数 调用顺序,调用完毕后 也是一个 树状结构。

vm._update

update 就是根据 前面创建的 VNode ,来创建 weex 中的 Element。

组件的 创建和刷新 都是调用的这个_update 方法。

Vue.prototype._update = function (vnode, hydrating) {
    var vm = this;
    // 1. 如果已经 mount 过的,说明是 update,这里调用一个 update 的钩子函数。
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate');
    }

    // 2. 根据 vm 的 _vnode 属性是否有值,来判断 是 创建 跟 Element,还是 更新组件。
    // vm.$el 就是前面创建的 value 为 root 的 comment 节点。
    var prevEl = vm.$el;
    var prevVnode = vm._vnode;
    var prevActiveInstance = activeInstance;
    activeInstance = vm;
    vm._vnode = vnode;
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.

    // 3. 调用 vm.__patch__ 来创建/更新组件。path 方法也是 vue 中 vdom 的核心算法了。 源码在 vue 仓库 `src/core/vdom/patch.js`。
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(
        vm.$el, vnode, hydrating, false /* removeOnly */,
        vm.$options._parentElm,
        vm.$options._refElm
      );
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode);
    }
    activeInstance = prevActiveInstance;
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null;
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm;
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el;
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  };
vm._update patch

patch 函数是在 Vue 初始化时,调用 createPatchFunction 方法创建的。

var patch = createPatchFunction({
  nodeOps: nodeOps,
  modules: modules,
  LONG_LIST_THRESHOLD: 10
});

1、modules 是 platform(weex,web) 的 modules 和 base 结合起来的。有

attrs,
   klass,
   events,
   style,
   transition,

   //base
   ref,
     directives

2、nodeOpssrc/platforms/weex/runtime/node-ops.js,web 平台对应在 platforms/web目录下的 node-ops.js

对 node 的操作 无非就是 增删改查 移动 这些,vue 对此定义了一组相同的方法,每一个平台个子负责实现自己的。
web 平台就是钓鱼 document来操作。weex平台就是调用 weex 定义的 node 来实现。

3、看 patch 方法内部:生成 Element 是调用 createElm 方法。

patch
function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {

    // 1. 是否销毁 老节点
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
      return
    }

    var isInitialPatch = false;
    var insertedVnodeQueue = [];
    // 2. 判断如果没有 oldVnode,则 是 创建新的。
    if (isUndef(oldVnode)) {
      // empty mount (likely as component), create new root element
      isInitialPatch = true;
      createElm(vnode, insertedVnodeQueue, parentElm, refElm);
    } else {
      var isRealElement = isDef(oldVnode.nodeType);
      if (!isRealElement && sameVnode(oldVnode, vnode)) {
        // patch existing root node
        patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
      } else {
        if (isRealElement) {
          // mounting to a real element
          // check if this is server-rendered content and if we can perform
          // a successful hydration.
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute('server-rendered')) {
            oldVnode.removeAttribute('server-rendered');
            hydrating = true;
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true);
              return oldVnode
            } else if (process.env.NODE_ENV !== 'production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                'full client-side render.'
              );
            }
          }
          // either not server-rendered, or hydration failed.
          // create an empty node and replace it
          oldVnode = emptyNodeAt(oldVnode);
        }
        // replacing existing element
        var oldElm = oldVnode.elm;
        var parentElm$1 = nodeOps.parentNode(oldElm);
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm$1,
          nodeOps.nextSibling(oldElm)
        );

        if (isDef(vnode.parent)) {
          // component root element replaced.
          // update parent placeholder node element, recursively
          var ancestor = vnode.parent;
          while (ancestor) {
            ancestor.elm = vnode.elm;
            ancestor = ancestor.parent;
          }
          if (isPatchable(vnode)) {
            for (var i = 0; i < cbs.create.length; ++i) {
              cbs.create[i](emptyNode, vnode.parent);
            }
          }
        }

        if (isDef(parentElm$1)) {
          removeVnodes(parentElm$1, [oldVnode], 0, 0);
        } else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode);
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
    return vnode.elm
  }

patch函数接收6个参数:

oldVnode: 旧的虚拟节点或旧的真实dom节点
vnode: 新的虚拟节点
hydrating: 是否要跟真是dom混合
removeOnly: 特殊flag,用于<transition-group>组件
parentElm: 父节点
refElm: 新节点将插入到refElm之前

patch的策略是:

  1. 如果vnode不存在但是oldVnode存在,说明意图是要销毁老节点,那么就调用invokeDestroyHook(oldVnode)来进行销毁
  2. 如果oldVnode不存在但是vnode存在,说明意图是要创建新节点,那么就调用createElm来创建新节点
  3. 当vnode和oldVnode都存在时

(1) 如果oldVnode和vnode是同一个节点,就调用patchVnode来进行patch

(2) 当vnode和oldVnode不是同一个节点时,如果oldVnode是真实dom节点或hydrating设置为true,需要用hydrate函数将虚拟dom和真是dom进行映射,然后将oldVnode设置为对应的虚拟dom,找到oldVnode.elm的父节点,根据vnode创建一个真实dom节点并插入到该父节点中oldVnode.elm的位置

这里是第一次创建,所以会调用到 createElm。 patchVnodeVDomUpdate

createElm
function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested) {

    vnode.isRootInsert = !nested; // for transition enter check

    // 1. 直接调用 `createComponent` 判断这个 vnode 是否是 组件。
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }

    var data = vnode.data;
    var children = vnode.children;
    var tag = vnode.tag;

    if (isDef(tag)) {

    。。。。。。

        // 2. 调用 nodeOps 创建 Element,这里 weex 平台的 nodeOps。
      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode);

        // 3. 样式仅限当前组件使用,才使用scoped。 style 的 作用域
      setScope(vnode);

      /* istanbul ignore if */
      {

      // weex 默认 Element 是先添加父组件, 如果 appendAsTree == true,则先创建子组件,再把当前组件添加到父组件。
        // in Weex, the default insertion order is parent-first.
        // List items can be optimized to use children-first insertion
        // with append="tree".
        var appendAsTree = isDef(data) && isTrue(data.appendAsTree);
        if (!appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue);
          }
          insert(parentElm, vnode.elm, refElm);
        }
        // 4. create 子节点
        createChildren(vnode, children, insertedVnodeQueue);
        if (appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue);
          }
          insert(parentElm, vnode.elm, refElm);
        }
      }

      if (process.env.NODE_ENV !== 'production' && data && data.pre) {
        inPre--;
      }
    } else if (isTrue(vnode.isComment)) {
    // create component 节点
      vnode.elm = nodeOps.createComment(vnode.text);
      insert(parentElm, vnode.elm, refElm);
    } else {
    // create 文本节点
      vnode.elm = nodeOps.createTextNode(vnode.text);
      insert(parentElm, vnode.elm, refElm);
    }
  }
createComponent
  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    var i = vnode.data;
    if (isDef(i)) {
      var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        i(vnode, false /* hydrating */, parentElm, refElm);
      }
      // after calling the init hook, if the vnode is a child component
      // it should've created a child instance and mounted it. the child
      // component also has set the placeholder vnode's elm.
      // in that case we can just return the element and be done.
      if (isDef(vnode.componentInstance)) {
        initComponent(vnode, insertedVnodeQueue);
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
        }
        return true
      }
    }
  }

如果是 自定义组件,则会在 createComponent 中,判断出来,调用 componentVNodeHooks 的 init 创建 component。

init: function init (
    vnode,
    hydrating,
    parentElm,
    refElm
  ) {
    if (!vnode.componentInstance || vnode.componentInstance._isDestroyed) {
    // 1. 创建 componentInstance。
      var child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance,
        parentElm,
        refElm
      );
      // 2. 挂载
      child.$mount(hydrating ? vnode.elm : undefined, hydrating);
    } else if (vnode.data.keepAlive) {
      // kept-alive components, treat as a patch
      var mountedNode = vnode; // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode);
    }
  },

createComponentInstanceForVnode 走的流程类似于 创建根节点的过程。

invokeCreateHooks

调用 create 的钩子函数。

  function invokeCreateHooks (vnode, insertedVnodeQueue) {
    for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
      cbs.create[i$1](emptyNode, vnode);
    }
    i = vnode.data.hook; // Reuse variable
    if (isDef(i)) {
      if (isDef(i.create)) { i.create(emptyNode, vnode); }
      if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
    }
  }

patch 的生命周期函数总共有

activate: Array (1)
    0 function enter(_, vnode) {}

create: Array (7)
    0 function updateAttrs(oldVnode, vnode) {}
    1 function updateClass(oldVnode, vnode) {}
    2 function updateDOMListeners(oldVnode, vnode) {}
    3 function createStyle(oldVnode, vnode) {}
    4 function enter(_, vnode) {}
    5 function create(_, vnode) { registerRef(vnode); }
    6 function updateDirectives(oldVnode, vnode) {}

destroy: Array (2)
    0 function destroy(vnode) { registerRef(vnode, true); }
    1 function unbindDirectives(vnode) { updateDirectives(vnode, emptyNode); }

remove: Array (1)
    0 function leave(vnode, rm) {}

update: Array (6)
    0 function updateAttrs(oldVnode, vnode) {}
    1 function updateClass(oldVnode, vnode) {}
    2 function updateDOMListeners(oldVnode, vnode) {}
    3 function updateStyle(oldVnode, vnode) {}
    4 function update(oldVnode, vnode) {}
    5 function updateDirectives(oldVnode, vnode) {}

调用 create 的钩子函数,更新 attrs,class,listeners 等等,这些属性更新最终都会调用到 weex 的 Element 对象上,然后组成 json 发给客户端。

updateDomListeners 的作用就是 绑定 on 事件到 Element 上。

insert(parentElm, vnode.elm, refElm);

把当前节点 插入到 父节点中。

appendChild->appendBody->sendBody


function sendBody (doc, node) {
  const body = node.toJSON()
  const children = body.children
  delete body.children
  let result = doc.taskCenter.send('dom', { action: 'createBody' }, [body])
  if (children) {
    children.forEach(child => {
      result = doc.taskCenter.send('dom', { action: 'addElement' }, [body.ref, child, -1])
    })
  }
  return result
}

node 转为 json,发送 createBody 事件给 native

{ref: "_root", type: "text", attr: {data-v-0f845315: ""}, style: {paddingTop: 40, paddingBottom: 40, fontSize: 48}, event: ["click"]}
createChildren

创建子节点,遍历子节点,依然调用 createElm 来创建 Element

  function createChildren (vnode, children, insertedVnodeQueue) {
    if (Array.isArray(children)) {
      for (let i = 0; i < children.length; ++i) {
        createElm(children[i], insertedVnodeQueue, vnode.elm, null, true)
      }
    } else if (isPrimitive(vnode.text)) {
      nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(vnode.text))
    }
  }

createInstance(7)发送 createFinish 事件

当所有的操作都做完之后,就发送 createFinish 事件,通知客户端 document 创建完成了。

instance.document.taskCenter.send('dom', { action: 'createFinish' }, []);

总结

native 调用 createInstance

renderURL

weex createInstance

create Vue function

执行 jsBundle

new Vue()

mount->

mountComponent

vm._render() createVNode

vm._update();

createElment <------
|
createChildren ----->

update native->

createFinish

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容