一、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];
- create WXRootView 作為 instance 的跟 view
- 確保 components,module 注冊。
- 是否需要替換 使用 CoreText 和 Slider
- 調用 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' }, []);
}
- create Document
- create instance
- create 獲取 Module 對象的函數,需要 instance 來獲取 instance.document.taskCenter。
- create timerAPIs module
- create weex 對象,Vue 中寫的 weex.requireModue 就是調用這里的 genModuleGetter 的這個方法
- 給 instance 創建 Vue module。
- create instanceVars,把 上面創建的對象,打包傳遞給 callFunction, 生成 執行 JSBundle 的匿名函數。
- callFunction(instanceVars, appCode),生成 執行 JSBundle 的匿名函數
- 發送 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
才能工作。body
的 type
必須是一個 div
,list
或 scroller
。
documentElement
另外添加了 2 個 函數,appendChild
和insertBefore
。
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
}
- create Vue 函數
- 創建一個函數,判斷 element type 是否保留 element
- 給 Vue 添加 instanceID 和 document 屬性
- 給 Vue 添加 requireModule 屬性
- 添加一個 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 代碼中的 template
、script
和style
。具體可在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')
beforeCreate
在 createVueModuleInstance
方法中進行了設置,主要是為了 把 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)
provide
和inject
主要為高階插件/組件庫提供用例。并不推薦直接用于應用程序代碼中。
這對選項需要一起使用,以允許一個祖先組件向其所有子孫后代注入一個依賴,不論組件層次有多深,并在起上下游關系成立的時間里始終生效。如果你熟悉 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、nodeOps
在 src/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的策略是:
- 如果vnode不存在但是oldVnode存在,說明意圖是要銷毀老節點,那么就調用invokeDestroyHook(oldVnode)來進行銷毀
- 如果oldVnode不存在但是vnode存在,說明意圖是要創建新節點,那么就調用createElm來創建新節點
- 當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。 patchVnode
見 VDomUpdate
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