思路
我们要从weex的runtime源码出发理解weex的实现原理。
runtime
entries
看文件夹的名字也知道,entries文件夹就是入口文件定义的位置了,我们点进去看他的index.js的定义,发现它是各种前端框架的入口文件,以index.js为入口会编译所有框架的版本。
import * as Vanilla from './vanilla/index'
import * as Vue from 'weex-vue-framework'
import * as Weex from './legacy/index'
import Rax from 'weex-rax-framework'
export default {
Vanilla,
Vue,
Rax,
Weex
}
比如vue.js:
import setup from './setup'
import * as Vue from 'weex-vue-framework'
setup({ Vue })
api
api文件夹主要是暴露api给注入vue的前端框架。
做的事情包括,初始化第三方框架,传入weex提供的config参数中的一些对象;返回全局api供第三方前端框架。
const config = {
Document, Element, Comment, Listener,
TaskCenter,
sendTasks (...args) {//调用原生代码
if (typeof callNative === 'function') {
return callNative(...args)
}
return (global.callNative || (() => {}))(...args)
}
}
Document.handler = config.sendTasks
第三方前端框架可以根据config中参数进行初始化。 暴露的全局api供第三方前端框架调用
createInstance,//创建实例
createInstanceContext,//创建实力上下文对象
getRoot,//获得根实例
getJSFMVersion,//获得jsm版本
getDocument: getDoc,//获得当前的document全局对象
registerService: register,//注册服务
unregisterService: unregister,//取消注册
callJS (id, tasks) {//callJS,native通过这个方法统一调用js方法
const framework = frameworks[getFrameworkType(id)]
if (framework && typeof framework.receiveTasks === 'function') {
return framework.receiveTasks(id, tasks)
}
return receiveTasks(id, tasks)
}
adaptMethod('registerComponents', registerComponents)//注册组件
adaptMethod('registerModules', registerModules)//注册模块
adaptMethod('registerMethods');
['destroyInstance', 'refreshInstance'].forEach(genInstance)//注册生命周期函数
在init函数中我们发现还调用了initHandler函数,这个函数初始化任务处理中心的方法,是brige/TaskCenter.js中的init函数,这个函数为TaskCenter类的原型上挂载了诸多对外的方法,这些方法最终是调用的原生方法处理。
const DOM_METHODS = {
createFinish: global.callCreateFinish,
updateFinish: global.callUpdateFinish,
refreshFinish: global.callRefreshFinish,
createBody: global.callCreateBody,
addElement: global.callAddElement,
removeElement: global.callRemoveElement,
moveElement: global.callMoveElement,
updateAttrs: global.callUpdateAttrs,
updateStyle: global.callUpdateStyle,
addEvent: global.callAddEvent,
removeEvent: global.callRemoveEvent,
__updateComponentData: global.__updateComponentData
}
WeexInstance 是暴露给开发者使用的weex的对外界接口,我们可以从其构造函数概览可以全局访问的接口:
constructor (id, config, data) {
setId(this, String(id))
this.config = config || {}//全局配置,包括环境变量,设备信息等
this._nativeData = data || {}//原生初始化传递过来的数据
this.document = new Document(id, this.config.bundleUrl)//模拟dom的document对象
this.requireModule = this.requireModule.bind(this)//引入模块
this.importScript = this.importScript.bind(this)//引入js代码,立即执行
this.isRegisteredModule = isRegisteredModule //检测模块是否存在
this.isRegisteredComponent = isRegisteredComponent //检测组件是否存在
}
这些是我们开发weex界面可以直接调用的。
brige
是js与native的连接器,用于js调用native代码,处理管理事件回调函数 CallbackManager.js:回调函数管理器,包括组件hook函数 Handler:任务处理器,用于调用原生方法
//可调用的原生方法
const handlerMap = {
createBody: 'callCreateBody',
addElement: 'callAddElement',
removeElement: 'callRemoveElement',
moveElement: 'callMoveElement',
updateAttrs: 'callUpdateAttrs',
updateStyle: 'callUpdateStyle',
addEvent: 'callAddEvent',
removeEvent: 'callRemoveEvent'
}
Listener:Handler对外的访问接口(封装),为调用native的方法或者说发送调用信号提供可分批的操作。 Receiver.js:接收原生发送的事件和组件回调
export function receiveTasks (id, tasks) {
const document = getDoc(id)
if (!document) {
return new Error(`[JS Framework] Failed to receiveTasks, `
+ `instance (${id}) is not available.`)
}
if (Array.isArray(tasks)) {
return tasks.map(task => {
switch (task.method) {
case 'callback': return callback(document, ...task.args)
case 'fireEventSync':
case 'fireEvent': return fireEvent(document, ...task.args)
case 'componentHook': return componentHook(document, ...task.args)
}
})
}
}
TaskCenter: 是任务处理中心,处理以下事务:
- 通过CallbackManager管理回调函数
- 通过CallbackManager管理hook回调
- 处理dom操作(通过向原生发送指令)
- 处理component组件方法调用
- 处理module模块方法调用
其中回调相关即回调函数和hook回调都是通过callbackManager管理,dom操作通过全局config对象上的sendTasks调用global.callNative向原生发送指令,component和module通过各自的handler处理,分别为定义到global上的callNativeComponent和callNativeModule方法,不存在的话就同样调用sendTasks方法。
vdom
index.js:入口文件
Commen.js:注释结点
Document:对应浏览器document的vdom实现
Element:元素结点的vdom实现
Node.js:vdom的基础实现,基类
WeexElement.js:注册weex组件的文件,有一个map集合保存所有注册了的组件,并为组件添加组件对应的方法。
operation.js:所有的dom操作方法定义在此文件中
思维导图
根据我们上面的整理和理解,接下来把知识点通过思维导图的形式进行梳理,使得知识点关联起来,脉络更清晰,方便我们理解和记忆。
上面就是我们对runtime-jsFramework的梳理和理解了,接下来我们通过阅读weex-vue-framework的原理进一步理解这部分代码的使用。
weex-vue-framework
首先我们发现weex-vue-framework主要做了如下几件事:
-
createInstanceContext
创建实例,处理weex实例
-
createVueModuleInstance
创建Vue实例,并做对weex的适配
-
挂载weex提供的api到Vue实例上,如document,taskCenter等
通过上面的理解我们发现,weex的js运行时框架实际上就是为前端框架提供了一系列api,包括dom操作、事件处理等。
通过封装好的vdom操作、事件管理系统、service服务等初始化诸如Vue的前端框架。
weex原生初始化时weexjs运行时在v8引擎中运行,相当于我们的js运行环境中全局有了一个weexjs运行时声明的Vue构造函数(Vue前端实例初始化),weexjs运行时中的Vue实例与web端的不同,它是经过特殊处理的,主要区别是它的vdom操作不是浏览器的dom操作,而是原生封装的通过weexjs运行时暴露给它的对应的view操作;事件体系是基于原生封装暴露给它的事件体系。
实际上,我们可以编写自己的前端框架或者改写已有的其他前端框架,对weexjs运行时框架进行适配(主要是vdom和事件系统)。需要在框架上定义init(config),createInstance(config,weexInstanceContext),receiveTasks(id,task)等原型方法基于weexjs运行时暴露的api进行一些初始化的处理。
进一步理解
根据这个思路我们看一下Vue编译之后的源码加以理解,我们编写的Vue文件被weex-loader或者weex-builder编译后是如下形式:
(function(modules){
var installedModules={};
function _webpack_require_(moduleId){
// Check if module is in cache
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
id: moduleId,
loaded: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.loaded = true;
// Return the exports of the module
return module.exports;
}
//省略无关代码......
return __webpack_require__(__webpack_require__.s = 647);
})(
//省略无关代码......
647:(function(module, exports, __webpack_require__) {
var __vue_exports__, __vue_options__;
var __vue_styles__ = [];
/* styles 生成样式相关的数据*/
__vue_styles__.push(__webpack_require__(648)
);
/* script 生成vue的options */
__vue_exports__ = __webpack_require__(649);
/* template 生成的渲染函数 */
var __vue_template__ = __webpack_require__(651);
__vue_options__ = __vue_exports__ = __vue_exports__ || {};
//下面对__vue_options__的操作就相当于与操作__vue_exports__了
__vue_options__.render = __vue_template__.render;
__vue_options__._scopeId = 'data-v-07574e69';
__vue_options__.style = __vue_options__.style || {};
__vue_styles__.forEach(function (module) {
for (var name in module) {
__vue_options__.style[name] = module[name];
}
});
module.exports = __vue_exports__;
module.exports.el = 'true';
//传入的options是一个把template编译成render函数的选项对象
//下面就是Vue的初始化了,数据监听,挂载vdom结点创建真实视图
new Vue(module.exports);
}),
//样式
648:(function(module, exports) {
module.exports = {
'deleteImg': {
'position': 'absolute',
'width': '36',
'height': '36',
'top': '37',
'right': '20'
},
'deleteImg1': {
'position': 'absolute',
'width': '36',
'height': '36',
'top': '37',
'right': '65'
},
'deleteImg2': {
'position': 'absolute',
'width': '36',
'height': '36',
'top': '37',
'right': '20'
},
'bgt': {
'width': '750'
},
'img': {
'width': '100',
'height': '100'
},
'topimg': {
'width': '520',
'height': '160',
'position': 'absolute',
'top': '180',
'left': '115'
},
'bgimg': {
'width': '750',
'marginTop': '148',
'alignItems': 'center',
'textAlign': 'center',
'flexDirection': 'column',
'justifyContent': 'center'
},
'inputdiv': {
'marginLeft': '50',
'width': '650',
'height': '100',
'flexDirection': 'row',
'borderBottomWidth': '1',
'borderBottomColor': '#d3d3d3',
'borderBottomStyle': 'solid',
'paddingTop': '30'
},
'inputimg': {
'fontSize': '30',
'color': '#333333',
'width': '100',
'lineHeight': '50'
},
'content': {
'marginTop': '140'
},
'input': {
'width': '500',
'height': '50',
'marginLeft': '30'
},
'textchar': {
'fontSize': '29',
'color': '#ffffff',
'fontWeight': 'bold'
},
'btn': {
'marginTop': '100',
'marginLeft': '40',
'backgroundColor': '#2250FF',
'width': '670',
'height': '86',
'justifyContent': 'center',
'alignItems': 'center',
'borderRadius': '43'
},
'btns': {
'marginTop': '30',
'marginLeft': '40',
'width': '670',
'flexDirection': 'row',
'justifyContent': 'space-between'
},
'lichar': {
'color': '#2250FF',
'fontSize': '26'
}
};
}),
//js代码导出vue选项
/***/ 649:
/***/ (function(module, exports, __webpack_require__) {
'use strict';
var _updateDialog = __webpack_require__(39);
var _updateDialog2 = _interopRequireDefault(_updateDialog);
var _type_net = __webpack_require__(17);
var _type_net2 = _interopRequireDefault(_type_net);
var _configEnv = __webpack_require__(2);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var notify = weex.requireModule('notify');
var nav = weex.requireModule('navigator');
var pref = weex.requireModule('pref');
var utils = weex.requireModule('utils');
var st = weex.requireModule('static');
var modal = weex.requireModule('modal');
var updater = weex.requireModule('updater');
var config = __webpack_require__(650);
exports.default = {
components: {
UpdateDialog: _updateDialog2.default
},
data: {
userInfo: {},
remember: false,
userName: '',
password: '',
dialogShow: false,
pogressShow: false,
nowVertion: '',
must: false,
updateInfo: {},
value: 0,
progress: 0,
showPwd: false
},
computed: {
showName: function showName() {
if (this.userName == '') {
return false;
} else {
return true;
}
},
delPwd: function delPwd() {
if (this.password == '') {
return false;
} else {
return true;
}
}
},
methods: {
// thirdLogin(){
// modal.toast({message:'正在开发~~'})
// },
// ksdl(){
// modal.toast({message:'正在开发~~'})
// },
deleteImg: function deleteImg(index) {
if (index == 1) {
this.userName = '';
} else {
this.password = '';
}
},
cencels: function cencels() {
this.dialogShow = false;
},
confirms: function confirms() {
var self = this;
self.update();
this.dialogShow = false;
},
login: function login() {
//记住密码
var self = this;
utils.getPhoneInfo(function (res) {
self.netLogin(res);
});
// var info={
// brand:'111',
// phoneModel:'1',
// osName:'1',
// osVersion:'1'
// };
// self.netLogin(info);
},
netLogin: function netLogin(deviceInfo) {
var self = this;
var nav = weex.requireModule('navigator');
var rsaAuthInfo = utils.encryptAuthInfo(self.userName, self.password).authInfo;
_type_net2.default.post(_configEnv.env.login, { authorization: rsaAuthInfo, clientCode: 'mb-app',
equipmentBrand: deviceInfo.brand, equipment: deviceInfo.phoneModel, systemName: deviceInfo.osName,
systemVersion: deviceInfo.osVersion }, function (res) {
var data = res.res;
if (data.code == 0) {
modal.toast({ message: '登陆成功' });
pref.set('userInfo', data.data.userContext);
st.set('userInfo', data.data.userContext);
pref.set('key', { key: rsaAuthInfo });
var token = '';
if (weex.config.env.platform === 'iOS') {
token = res.headers['Authorization'];
} else {
token = res.token;
}
//掌上工作台扫码调试掌上工作台需要添加此代码
// utils.setSchema('assets');
st.setPUserInfo({ key: rsaAuthInfo, token: token, data: data.data.userContext.currentUser });
nav.pushParam('host.js', { from: 'login' });
utils.JPushLogin(_configEnv.env.env + '_' + 'mobile_' + data.data.userContext.currentUser.userCode);
} else {
modal.toast({ message: data.msg });
}
}, 'login');
}
},
created: function created() {
var self = this;
if (weex.config.env.platform != 'iOS') {
notify.regist('login_dismiss', function (res) {
nav.dismiss();
});
}
var globalEvent = weex.requireModule('globalEvent');
globalEvent.addEventListener('onPageInit', function (e) {
self.userInfo = pref.get('userInfo');
var notify = weex.requireModule('notify');
notify.send('guide_dismiss', { a: '1' });
notify.send('index_dismiss', { a: '1' });
});
}
};
}),
//根据template模板生成的渲染函数
/***/ 651:
/***/ (function(module, exports) {
module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return _c('div', {
staticClass: ['bgt']
}, [_vm._m(0), _c('div', {
staticClass: ['content']
}, [_c('div', {
staticClass: ['inputdiv']
}, [_c('text', {
staticClass: ['inputimg']
}, [_vm._v('账号')]), _c('input', {
staticClass: ['input'],
attrs: {
'placeholder': '输入账号',
'value': (_vm.userName)
},
on: {
'input': function($event) {
_vm.userName = $event.target.attr.value;
}
}
}), (_vm.showName) ? _c('image', {
staticClass: ['deleteImg'],
attrs: {
'src': 'root:img/delete.png'
},
on: {
'click': function($event) {
_vm.deleteImg(1);
}
}
}) : _vm._e()]), _c('div', {
staticClass: ['inputdiv']
}, [_c('text', {
staticClass: ['inputimg']
}, [_vm._v('密码')]), (!_vm.showPwd) ? _c('input', {
staticClass: ['input'],
attrs: {
'placeholder': '输入密码',
'type': 'password',
'value': (_vm.password)
},
on: {
'input': function($event) {
_vm.password = $event.target.attr.value;
}
}
}) : _c('input', {
staticClass: ['input'],
attrs: {
'placeholder': '输入密码',
'value': (_vm.password)
},
on: {
'input': function($event) {
_vm.password = $event.target.attr.value;
}
}
}), (_vm.showPwd && _vm.delPwd) ? _c('image', {
staticClass: ['deleteImg1'],
attrs: {
'src': 'root:img/user/see.png'
},
on: {
'click': function($event) {
_vm.showPwd = !_vm.showPwd;
}
}
}) : (!_vm.showPwd && _vm.delPwd) ? _c('image', {
staticClass: ['deleteImg1'],
attrs: {
'src': 'root:img/user/noSee.png'
},
on: {
'click': function($event) {
_vm.showPwd = !_vm.showPwd;
}
}
}) : _vm._e(), (_vm.delPwd) ? _c('image', {
staticClass: ['deleteImg'],
attrs: {
'src': 'root:img/delete.png'
},
on: {
'click': function($event) {
_vm.deleteImg(2);
}
}
}) : _vm._e()])]), _c('div', {
staticClass: ['btn'],
on: {
'click': _vm.login
}
}, [_c('text', {
staticClass: ['textchar']
}, [_vm._v('登录')])])]);
},staticRenderFns: [function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
return _c('div', {
staticClass: ['bgimg']
}, [_c('image', {
staticClass: ['img'],
attrs: {
'src': 'root:img/logo1.png'
}
}), _c('text', {
staticStyle: {
fontSize: '34px',
fontWeight: 'bold',
marginTop: '30px'
}
}, [_vm._v('掌上工作台')])]);
}]};
module.exports.render._withStripped = true;
})
//省略无关代码......
)
此端编译之后的js代码首先是一个模块加载的实现,关于模块加载相关的理解之前在理解webpack模块加载原理
中介绍过,这里不再赘述,这里着重说weex原理相关的。
我们看到自执行函数的最后一行执行了__webpack_require__(__webpack_require__.s = 647)
,即加载了传入参数中id为647的对象,我们找到它:
647:(function(module, exports, __webpack_require__) {
var __vue_exports__, __vue_options__;
var __vue_styles__ = [];
/* styles 生成样式相关的数据*/
__vue_styles__.push(__webpack_require__(648)
);
/* script 生成vue的options */
__vue_exports__ = __webpack_require__(649);
/* template 生成的渲染函数 */
var __vue_template__ = __webpack_require__(651);
__vue_options__ = __vue_exports__ = __vue_exports__ || {};
//下面对__vue_options__的操作就相当于与操作__vue_exports__了
__vue_options__.render = __vue_template__.render;
__vue_options__._scopeId = 'data-v-07574e69';
__vue_options__.style = __vue_options__.style || {};
__vue_styles__.forEach(function (module) {
for (var name in module) {
__vue_options__.style[name] = module[name];
}
});
module.exports = __vue_exports__;
module.exports.el = 'true';
//传入的options是一个把template编译成render函数的选项对象
//下面就是Vue的初始化了,数据监听,挂载vdom结点创建真实视图
new Vue(module.exports);
}),
所以这个jsbundle首先会执行这块儿代码,这段代码核心是为vue选项上挂上render函数,然后通过options创建一个Vue实例。 就是Vue的初始化了,数据监听,挂载vdom结点创建真实视图等vue的内部操作了,只是这里的vdom操作创建视图不是浏览器的dom操作而是原生封装的原生创建视图的操作。
weex框架分层理解
-
Vue.js、Rax.js:前端框架
-
weex-js-frameworlk:调用C++native接口,通过JSBrige调用Android和IOS,封装了一系列的dom操作,taskCenter管理回调、模块和组件方法。
-
weexsdk(c++):weex的c++引擎,用于连接Android/ios,封装了统一的dom体系,回调事件管理,模块、组件管理和调用。
-
Android/ios:原生UI、组件,功能模块等。
总结
通过上面的学习和理解,我们可以这样理解weex的实现原理:
- 首先我们通过weexsdk(c++)把Android和IOS的UI封装成统一的vdom和事件体系;
- runtime-jsFramework则是对这个vdom和事件体系的进一步封装和实现,它可以直接调用native代码与Android和IOS等平台交互,我们会在weexSdk初始化时运行jsframework的运行时,相当于初始化js执行环境,全局添加weex框架api。
- 最后是诸如Vue.js和Rax.js的前端框架,它们可以使用runtime-jsFramework封装的vdom和事件体系,就像我们在浏览器端一样使用。
以上就是自己在阅读了weex的runtime-jsFramework和weex-vue-framework的源码后对weex实现原理的总结,如果对您有帮助,还望不吝点赞,如有错误,还望指正,多谢~~~