wepy小程序怎么在获取别的页面的talkingdata官网

2.6k 次阅读
标签:至少1个,最多5个
小程序不提供类似于vue一样监听数据(),当数据改变时触发回调检测改变数据类型是否符合要求。
现在,使用wach即可扩展类似vm.watch的功能
引入watch库
import Watch from '../../libs/watch';
配置初始化检察对象
import Watch from '../../libs/watch';
e: [1, 2, [3, 4]]
a: function(val, oldVal) {
console.log('new: %s, old: %s', val, oldVal);
'b.c.d': function(val, oldVal) {
console.log('new: %s, old: %s', val, oldVal);
'b.e[2][0]': function(val, oldVal) {
console.log('new: %s, old: %s', val, oldVal);
'b.e[3][4]': function(val, oldVal) {
console.log('new: %s, old: %s', val, oldVal);
可以将需要监听的数据放入watch里面,当数据改变时推送相应的订阅事件(注:不在data里面的数据项不会放入观察者列表,比如上面的'b.e[3][4]')
实例化watch
watch = new Watch(this);
当watch创建示例初始化时会把监听数据项放入观察者列表里面
watch.setData({
'b.c.d': 3,
'b.e[2][0]': 444,
watch.data('b.e[2][0]', 555);
watch.setData(obj)
等价于原生小程序this.setData,会改变数据并更新视图,也会触发回调(假如有配置)
watch.setData({
'b.c.d': 3,
'b.e[2][0]': 444,
// this.setData({
'b.c.d': 3,
'b.e[2][0]': 444,
watch.data(key, val)
等价于原生小程序this.data.a = 3,之后改变数据不更新视图,也会触发回调(假如有配置)
watch.data('b.e[2][0]', 555);
//等价于this.b.e[2][0] = 555
watch.getter(data, path)
能根据提供的路径深度获取数据
watch.get({
e: [1, 2, [3, 4]]
}, 'b.e[2][0]');
watch.unSubscribe(key)
删除观察者,改变数据不触发回调
watch.unSubscribe('b.e[2][0]');
git clone https://github.com/jayZOU/watch.git
打开小程序开发工具,新建项目,定位目录到watch/src
7 收藏&&|&&5
很棒!赞一个!
很棒!赞一个!
分享到微博?
我要该,理由是:
在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。新人专享好礼凡未购买过小册的用户,均可领取三张 5 折新人专享券,购买小册时自动使用专享券,最高可节省 45 元。小册新人 5 折券最高可省 15 元小册新人 5 折券最高可省 15 元小册新人 5 折券最高可省 15 元注:专享券的使用期限在领券的七天内。一键领取购买小册时自动使用专享券前往小册首页本活动仅适用于小册新用户知道了深入wepy小程序组件化框架wepy是一个优秀的微信小程序组件化框架,突破了小程序的限制,支持了npm包加载以及组件化方案,并且在性能优化方面也下了功夫,不管之后项目是否会使用到,该框架组件化编译方案都是值得学习和深入的。
本文同步于个人博客 wepy文档: github地址:
我们先提出几个问题,让下面的分析更有针对性:
wepy如何实现单文件组件.wpy编译?
如何隔离组件作用域?
如何实现组件通信?
如何实现加载外部npm包?
我们可以先clone下
docs目录下是开发文档以及使用docsify生成的文档网站
packages目录下就是核心代码了,wepy是小程序端的框架,wepy-cli是脚手架(负责组件化编译等等),其他就是一些预处理编译插件、还有一些压缩插件等等
scripts是一些shell脚本,负责test等等一些操作
gulpfile.js主要负责将package中的src开发目录中js文件babel转换至lib目录供使用,并支持watch compile
我们就重点看wepy以及wepy-cli,接下来的文章也是围绕这两个展开
上面的bin/wepy.js是入口文件,其实就一句话引入lib中经过babel转换的文件
require('../lib/wepy');复制代码
下图简单画出了整体wepy-cli的编译构建流程,忽略了部分细节接下来答大体说下build的流程(可以跟着图看)
wepy-cli使用commander作为命令行工具,build命令执行调用compile build方法
如果没有指定文件build,则获取src目录下所有文件,寻找没有引用的(指的是这种&script src=&&&),调用compile方法开始编译,如果指定文件,则相应判断寻找父组件或者寻找引用去编译
compile方法根据文件后缀判断,调用不同文件的方法(下面就说wpy单文件组件)
compile-wpy调用resolveWpy方法(核心)
替换内容中的attr(比如@tap =& bindtap等等)
使用xmldom放入内容,操作节点
获取wpy中的包裹的config,放入rst.config
将样式放入rst.style
提前编译wxml,如果是jade/pug之类的
获取文件中的import和components放入rst.template.components
获取代码中components的属性,获取props以及events放入rst.script.code中rst.script.code = rst.script.code.replace(/[\s\r\n]components\s*=[\s\r\n]*/, (match, item, index) =& {
return `$props = ${JSON.stringify(props)};\r\n$events = ${JSON.stringify(events)};\r\n${match}`;
});复制代码
最后拆解style、template、script构成一个rst文件let rst = {
moduleId: moduleId,
style: [],
template: {
components: {},
config: {},
};复制代码
rst构建完成,开始逐个操作
rst.config写入xxx.json
rst.template
经过compiler后再次使用xmldom
updateSlot,替换内容
updateBind, 将{{}}以及attr中的加入组件前缀($prefix)
将组件替换成相应xml
寻找require再进入compile-style
compiler预编译处理,并且置入依赖(@import)
rst.script
compiler处理
假如不是npm包,则置入wepy框架初始化代码
if (type !== 'npm') {
if (type === 'page' || type === 'app') {
code = code.replace(/exports\.default\s*=\s*(\w+);/ig, function (m, defaultExport) {
if (defaultExport === 'undefined') {
return '';
if (type === 'page') {
let pagePath = path.join(path.relative(appPath.dir, opath.dir), opath.name).replace(/\\/ig, '/');
return `\nPage(require('wepy').default.$createPage(${defaultExport} , '${pagePath}'));\n`;
let appConfig = JSON.stringify(config.appConfig);
return `\nApp(require('wepy').default.$createApp(${defaultExport}, ${appConfig}));\n`;
resolveDeps(核心),根据require的形式,模仿node require加载机制,将文件拷贝到相应目录,修改require内容,这里包括外部npm包的拷贝加载
假如是npm包,特殊处理部分代码
均通过相应plugins顺序通过一遍,最后输出到dist目录
大体就像开发文档的图一样,现在看就很清晰了
这个方法用于生成rst,拆分wpy单文件组件,上面流程讲了大部分,这里就详细讲下props和event的提取其实也不是很复杂,就是遍历元素,取出相应attributes,放入events[comid][attr.name]以及props[comid][attr.name]放入代码中
elems.forEach((elem) =& {
if (calculatedComs.indexOf(elem) === -1) {
let comid = util.getComId(elem);
[].slice.call(elem.attributes || []).forEach((attr) =& {
if (attr.name !== 'id' && attr.name !== 'path') {
if (/v-on:/.test(attr.name)) {
if (!events[comid])
events[comid] = {};
events[comid][attr.name] = attr.
if (!props[comid])
props[comid] = {};
if (['hidden', 'wx:if', 'wx:elif', 'wx:else'].indexOf(attr.name) === -1) {
props[comid][attr.name] = attr.
if (Object.keys(props).length) {
rst.script.code =rst.script.code.replace(/[\s\r\n]components\s*=[\s\r\n]*/, (match, item, index) =& {
return `$props = ${JSON.stringify(props)};\r\n$events = ${JSON.stringify(events)};\r\n${match}`;
getComId(elem) {
let tagName = elem.nodeN
let path = elem.getAttribute('path');
let id = elem.getAttribute('id');
if (tagName !== 'component')
return tagN
if (path && !id)
},复制代码
updateBind遍历调用parseExp,并且遇到子元素调用自身传入前面的prefix,最后可以生成$parent$child$xxx这种一样的数据
parseExp就是用于替换添加prefix
下面精简了下代码,易于理解
updateBind(node, prefix, ignores = {}, mapping = {}) {
let comid =
if (node.nodeName === '#text' && prefix) {
if (node.data && node.data.indexOf('{{') & -1) {
node.replaceData(0, node.data.length, this.parseExp(node.data, prefix, ignores, mapping));
[].slice.call(node.attributes || []).forEach((attr) =& {
if (prefix) {
if (attr.value.indexOf('{{') & -1) {
attr.value = this.parseExp(attr.value, prefix, ignores, mapping);
if (attr.name.indexOf('bind') === 0 || attr.name.indexOf('catch') === 0) {
if (prefix) {
attr.value = `$${comid}$${attr.value}`;
[].slice.call(node.childNodes || []).forEach((child) =& {
this.updateBind(child, prefix, ignores, mapping);
},复制代码
parseExp(content, prefix, ignores, mapping) {
let comid =
return content.replace(/\{\{([^}]+)\}\}/ig, (matchs, words) =& {
return matchs.replace(/[^\.\w'"](\.{0}|\.{3})([a-z_\$][\w\d\._\$]*)/ig, (match, expand, word, n) =& {
let char = match[0];
let tmp = word.match(/^([\w\$]+)(.*)/);
let w = tmp[1];
let rest = tmp[2];
if (ignores[w] || this.isInQuote(matchs, n)) {
if (mapping.items && mapping.items[w]) {
let upper = comid.split(PREFIX);
upper.pop();
upper = upper.join(PREFIX);
upper = upper ? `${PREFIX}${upper}${JOIN}` : '';
return `${char}${expand}${upper}${mapping.items[w].mapping}${rest}`;
return `${char}${expand}${PREFIX}${comid}${JOIN}${word}`;
},复制代码
这个方法用于wpy框架的加载机制将require部分替换成正确的编译后的路径npm包通过读取相应package.json中的main部分去寻找文件,寻找npm文件会再继续resolveDeps获取依赖,最后写入npm中
resolveDeps (code, type, opath) {
let params = cache.getParams();
let wpyExt = params.wpyE
return code.replace(/require\(['"]([\w\d_\-\.\/@]+)['"]\)/ig, (match, lib) =& {
let resolved =
let target = '', source = '', ext = '', needCopy = false;
if (lib[0] === '.') {
source = path.join(opath.dir, lib);
if (type === 'npm') {
target = path.join(npmPath, path.relative(modulesPath, source));
needCopy = true;
target = util.getDistPath(source);
needCopy = false;
} else if (lib.indexOf('/') === -1 ||
lib.indexOf('/') === lib.length - 1 ||
(lib[0] === '@' && lib.indexOf('/') !== -1 && lib.lastIndexOf('/') === lib.indexOf('/'))
let pkg = this.getPkgConfig(lib);
if (!pkg) {
throw Error('找不到模块: ' + lib);
let main = pkg.main || 'index.js';
if (pkg.browser && typeof pkg.browser === 'string') {
main = pkg.
source = path.join(modulesPath, lib, main);
target = path.join(npmPath, lib, main);
lib += path.sep +
needCopy = true;
source = path.join(modulesPath, lib);
target = path.join(npmPath, lib);
needCopy = true;
if (util.isFile(source + wpyExt)) {
ext = '.js';
} else if (util.isFile(source + '.js')) {
ext = '.js';
} else if (util.isDir(source) && util.isFile(source + path.sep + 'index.js')) {
ext = path.sep + 'index.js';
}else if (util.isFile(source)) {
throw ('找不到文件: ' + source);
resolved =
if (/\.wpy$/.test(resolved)) {
target = target.replace(/\.wpy$/, '') + '.js';
resolved = resolved.replace(/\.wpy$/, '') + '.js';
if (needCopy) {
if (!cache.checkBuildCache(source)) {
cache.setBuildCache(source);
util.log('依赖: ' + path.relative(process.cwd(), target), '拷贝');
this.compile('js', null, 'npm', path.parse(source));
if (type === 'npm') {
if (lib[0] !== '.') {
resolved = path.join('..' + path.sep, path.relative(opath.dir, modulesPath), lib);
if (lib[0] === '.' && lib[1] === '.')
resolved = './' +
resolved = path.relative(util.getDistPath(opath, opath.ext, src, dist), target);
resolved = resolved.replace(/\\/g, '/').replace(/^\.\.\//, './');
return `require('${resolved}')`;
},复制代码
在代码中会常看到以下PluginHelper再进行写入,我们可以看看如何实现plugin一个一个运用到content中的
let plg = new loader.PluginHelper(config.plugins, {
type: 'wxml',
code: util.decode(node.toString()),
file: target,
output (p) {
util.output(p.action, p.file);
done (rst) {
util.output('写入', rst.file);
rst.code = self.replaceBooleanAttr(rst.code);
util.writeFile(target, rst.code);
});复制代码核心代码如下,其实跟koa/express中间的compose类似,通过next方法,调用完一个调用下一个next(),next()不断,最后done(),next方法在框架内部实现,done方法有我们配置即可,当然在插件中(就像中间件)需要在最后调用next
class PluginHelper {
constructor (plugins, op) {
this.applyPlugin(0, op);
return true;
applyPlugin (index, op) {
let plg = loadedPlugins[index];
if (!plg) {
op.done && op.done(op);
op.next = () =& {
this.applyPlugin(index + 1, op);
op.catch = () =& {
op.error && op.error(op);
plg.apply(op);
这里的wepy是wepy框架的前端部分,需要在小程序中import的
主要职责就是让框架中props和events能成功使用,就是需要setData一些加prefix的内容,并且实现组件之间的通信,以及部分性能调优
wepy.js: 暴露$createApp、$createPage等接口
base.js: $createApp、$createPage逻辑,bindExt为组件以及method添加prefix
app.js: promisifyAPI以及intercept拦截接口逻辑
page.js: 继承component,route、page一些性能调优逻辑
component.js: 组件逻辑,props构建,computed计算,脏值检查,组件通信($invoke、$broadcast、$emit)
native.js: 空,代码里面用于app.js中重新定义wx自带接口
event.js: 用于传入method第一参数e,可以获取组件通信的来源等
mixin.js: 将混合的数据,事件以及方法注入到组件之中
util.js:工具包
针对前端wepy部分,也画了个流程图方便理解,也略去大量细节部分,后面分析可以跟着图来
上一节wepy-cli编译时往代码中注入了以下代码
Page(require('wepy').default.$createPage(${defaultExport} , '${pagePath}'));
App(require('wepy').default.$createApp(${defaultExport}, ${appConfig}));复制代码这也是入口所在,从这里开始入手分析
$createApp在App包裹中,正常小程序应该是App({}),所以这里$createApp返回config,这里new class extends wepy.app, 通过调用app.js中$initAPI实现接口promise化以及实现拦截器定义接口使用
Object.defineProperty(native, key, {
get () { return (...args) =& wx[key].apply(wx, args) }
wepy[key] = native[key];复制代码success时候reoslve,fail时候reject实现promise化,在其中查询拦截器调用
if (self.$addons.promisify) {
return new Promise((resolve, reject) =& {
let bak = {};
['fail', 'success', 'complete'].forEach((k) =& {
bak[k] = obj[k];
obj[k] = (res) =& {
if (self.$interceptors[key] && self.$interceptors[key][k]) {
res = self.$interceptors[key][k].call(self, res);
if (k === 'success')
resolve(res)
else if (k === 'fail')
reject(res);
if (self.$addons.requestfix && key === 'request') {
RequestMQ.request(obj);
wx[key](obj);
$createPage在Page包裹中,同样返回config{},构造page实例,来自new class extends wepy.page,page class又继承于component
$bindEvt方法:
遍历com.components,如果com还有子组件则递归调用,new class extend componeng后放入com.$coms
递归设置com.$prefix,第一层没有,接下去就是$one$,再有子级,就是$one$two$,以此类推...
这个方法名叫$bindEvt,主要也是跟方法有关的,将所有methods中的方法放入config中并添加当前组件的$prefix,即是$prefix+method,并且方法最后都会调用com.$mixin,调用com.$apply
onload方法:
调用$init并且调用super.$init,即class component的$init
根据$props(这个是编译注入的)生成$mappingProps,mapping双向绑定的部分
Props.build(this.props);(注意这个props是前端编写)构建props,并寻找父级的$props(编译注入),获取值以后放在this.data[key]里,如果有props设定为twoWay,同样放入$mappingProps中
初始化数据(注意把prefix加上去了),并将defaultData setData到页面
defaultData[`${this.$prefix}${k}`] = this.data[k];
this[k] = this.data[k];复制代码
计算computed的值放入this[k]中
获取this.$com(在base.js中的bindEvt根据components绑定的),让组件一个一个继续$init、onLoad、$mixins、$apply
$init结束,调用page的onload方法,调用$mixins, 最后再page.$apply
$apply方法需要特别提一下,它于component中的$digest配合,是wepy框架里比较核心的脏值检查setData机制
下图是官网的图我们可以看下$apply方法带fn去调用,则调用结束再调用自身,然后假如当前阶段$$phase为无,则设为$apply阶段,假如调用时候之前已经标记过apply,则调用 this.$digest();进入脏检查阶段
$apply (fn) {
if (typeof(fn) === 'function') {
fn.call(this);
this.$apply();
if (this.$$phase) {
this.$$phase = '$apply';
this.$digest();
}复制代码$digest方法就是脏值检查了,顺便再讲之前的$createPage我们可以看到data放在好几个地方,this[k],this.data[k],this.$data,这里来区分以下它们
this[k]是作为当前的数据,没有set上去的新数据,这也是wepy框架的一个特点,它将setData简化,this.xxx = yyy 替代之前的setData({xxx: yyy})
this.data就是刚开始用到的初始化数据,放入defaultData中set,但是作为this.data里的,小程序更新数据setData也会更新到这里的数据
this.$data是作为set上去的数据,wepy框架重写setData方法,会操作这个this.$data
分析完上面,脏值检查就很明了了,拿this.$data跟this中比较,不等的话放入readyToSet中,然后再setData,更新this.$data即可,还需注意上面官网图下两个tips,注意只会有一个脏数据检查流程
至于组件通信方面,有了一棵组件树,理好层级父级的关系就不复杂了,举一个$emit触发父级事件例子分析一下
假如是用event传入的,寻找父级组件$events(编译时注入的,在attr里遍历收集的),然后apply相应的方法即可
假如不是,则一层一层找上级方法emit即可
$emit (evtName, ...args) {
let com = this;
let source = this;
let $evt = new event(evtName, source, 'emit');
if (this.$parent.$events && this.$parent.$events[this.$name]) {
let method = this.$parent.$events[this.$name]['v-on:' + evtName];
if (method && this.$parent.methods) {
let fn = this.$parent.methods[method];
if (typeof(fn) === 'function') {
this.$parent.$apply(() =& {
fn.apply(this.$parent, args.concat($evt));
throw new Error(`Invalid method from emit, component is ${this.$parent.$name}, method is ${method}. Make sure you defined it already.\n`);
while(com && com.$isComponent !== undefined && $evt.active) {
let comContext =
let fn = getEventsFn(comContext, evtName);
fn && comContext.$apply(() =& {
fn.apply(comContext, args.concat($evt));
com = comContext.$
其他的invoke和broadcast不具体讲了,只要构建出组件树,问题就很好解决(构建就是在每次new的时候记住它的$parent就行了)
到这里,整体流程大致讲完了,没有涵盖所有细节,作者的这个小程序框架很强大,并且作者还在积极地解决issue和更新,值得我们点赞~接下来来回答下文首提出的问题,应该就迎刃而解了1. wepy如何实现单文件组件.wpy编译?答:wepy框架通过wepy-cli对.wpy编译,拆解为style,script(+config),template几部分,再分别处理,生成到dist文件对应xxx.wxss,xxx.script,xxx.json,xxx.wxml
2. 如何隔离组件作用域?答:通过组件在不同page的命名作为前缀,并且以父级为起点,依次为$child,再子级就是$child$chind,依次类推。。。不同组件在不同的component实例下,data set到page就是带上前缀,同样的method也是加入前缀放在Page({})中
3. 如何实现组件通信?答:通过编译获取component的路径注入代码,在小程序代码运行时,根据逐层require获取,new component,并记下父级$parent,构建组件树。如果向子组件传props和events?编译时就会收集在template中传入的props和events注入到代码中$props和$events,然后子组件init的时候获取父级$parent的$props并加入前缀$prefix去setData(子组件的在page中的元素表现已经在编译的时候被替换成了$prefix$data的样子),这样就实现了传值。调用$emit触发父组件event,直接寻找父级$parent apply调用相应方法即可。广播事件broadcast就是直接广度优先去遍历组件树就行了。
4. 如何实现加载外部npm包?答:wepy-cli在处理script部分,根据require的内容判断是否是npm内容或者带有npm标识,如果是require('xxx') require('xxx/yyy')的形式获取package.json中的main部分找到引用文件,就去compile该文件(带上npm标识继续去resolveDeps),如果判断不是npm内容修正require即可,带有npm标识最后会打包到npm文件夹。
其他可以参考阅读的相关介绍文章
谢谢阅读~欢迎follow我哈哈欢迎继续观光我的~(近期可能迁移)
加入掘金和开发者一起成长。发送简历到 hr@xitu.io,期待你的加入!分享在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。
问题对人有帮助,内容完整,我也想知道答案
问题没有实际价值,缺少关键内容,没有改进余地
我在某一页面的onLoad中调用了wepy.request,此时我的作用域是在app.wpy上,我想调用另外一个页面method中的函数该怎么做,如果是写在外面的函数又该如何调用,求大神解答
同步到新浪微博
分享到微博?
关闭理由:
删除理由:
忽略理由:
推广(招聘、广告、SEO 等)方面的内容
与已有问题重复(请编辑该提问指向已有相同问题)
答非所问,不符合答题要求
宜作评论而非答案
带有人身攻击、辱骂、仇恨等违反条款的内容
无法获得确切结果的问题
非开发直接相关的问题
非技术提问的讨论型问题
其他原因(请补充说明)
我要该,理由是:
在 SegmentFault,学习技能、解决问题
每个月,我们帮助 1000 万的开发者解决各种各样的技术问题。并助力他们在技术能力、职业生涯、影响力上获得提升。不忘初心,方得始终
微信小程序页面间的通信
最近在做微信小程序时,涉及到了把值从一个页面传递到另一个页面的问题,在网上查阅了一些资料,在这里总结一下常用的方法。
1、页面跳转时,在跳转的url中传递,比如:
wx.navigateTo({
url: '../InfoContent/InfoContent?id=1'
在InfoContent页面:
onLoad: function (options) {
//页面初始化 options为页面跳转所带来的参数
var id=options.//获取值
2、全局变量的形式
app.js代码:
globalData:{
赋值代码:
var app = getApp();
app.globalData.id=2
取值代码:
var app = getApp();
var id=app.globalData.
没有更多推荐了,微信小程序 中 a页面跳转到b页面
然后在b页面进行操作后 怎么将b页面的数据返回给a页面? - 知乎有问题,上知乎。知乎作为中文互联网最大的知识分享平台,以「知识连接一切」为愿景,致力于构建一个人人都可以便捷接入的知识分享网络,让人们便捷地与世界分享知识、经验和见解,发现更大的世界。3被浏览453分享邀请回答赞同 添加评论分享收藏感谢收起}

我要回帖

更多关于 talkingdata有几轮面试 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信