一、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 "// { \"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