加載 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 "http:// { \"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閱讀 228,363評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內容