微信小程序之SelectorQuery
在开发小程序展开全文组件时需要用到节点查询API - [代码]wx.createSelectorQuery()[代码] 来查询全文内容的高度。
[图片]
[代码]wx.createSelectorQuery()[代码] 返回一个 [代码]SelectorQuery[代码] 对象实例。
[代码]SelectorQuery[代码] 有五个方法([代码]in[代码],[代码]select[代码],[代码]selectAll[代码],[代码]selectViewport[代码],[代码]exec[代码]),第一个返回 [代码]SelectorQuery[代码],后四个返回 [代码]NodesRef[代码]。
[代码]NodesRef[代码] 有四个方法([代码]fields[代码],[代码]boundingClientRect[代码],[代码]scrollOffset[代码],[代码]context[代码]),第一个返回 [代码]NodesRef[代码],后三个返回 [代码]SelectorQuery[代码]。
对照官方提供的示例代码来看
[代码]const query = wx.createSelectorQuery() // query 是 SelectorQuery 对象
query.select('#the-id').boundingClientRect() // select 后是 NodesRef 对象,然后 boundingClientRect 返回 SelectorQuery 对象
query.selectViewport().scrollOffset() // selectViewport 后是 NodesRef 对象,然后 scrollOffset 返回 SelectorQuery 对象
query.exec(function (res) { // exec 返回 NodesRef 对象
res[0].top // #the-id节点的上边界坐标
res[1].scrollTop // 显示区域的竖直滚动位置
})
[代码]
问题:每行执行返回的 [代码]SelectorQuery[代码] 对象是相同的吗?
答案:是的,都是同一个对象。
问题:直接执行 [代码]query.select('#the-id').boundingClientRect().exec[代码] 也可以吗?
答案:可以,[代码]boundingClientRect()[代码] 返回就是 [代码]query[代码]。
问题:这样连写 [代码]query.select('#the-id').boundingClientRect().selectViewport().scrollOffset()[代码] 算两条查询请求吗?
答案:是两条请求。
问题:[代码]query.exec[代码] 执行后会清空前面的查询请求吗?再次执行还能拿到结果吗?
答案:可以,[代码]query[代码] 不会清空请求。
问题:[代码]boundingClientRect[代码] 和 [代码]scrollOffset[代码] 可以接受 [代码]callback[代码] 参数,它与 [代码]query.exec[代码] 执行顺序是怎样,修改 [代码]res[代码] 结果会影响到后面的 [代码]callback[代码] 吗?
答案:先执行 [代码]boundingClientRect[代码] 和 [代码]scrollOffset[代码] 的 [代码]callback[代码],再执行 [代码]query.exec[代码] 的 [代码]callback[代码];修改 [代码]res[代码] 结果会影响到后面 [代码]exec[代码] 的结果。
上面的问题通过小程序开发者工具中的 [代码]WAService.js[代码] 源码简单美化还原后可以了解 [代码]SelectorQuery[代码] 的代码逻辑
SelectorQuery.js
[代码]import NodesRef from 'NodesRef';
import requestComponentInfo from 'requestComponentInfo';
export default function(pluginId) {
return class SelectorQuery {
constructor(plugin) {
if (plugin && plugin.page) {
this._component = this._defaultComponent = plugin.page;
this._webviewId = this._defaultComponent.__wxWebviewId__;
} else {
var pages = __internalGlobal__.getCurrentPagesByDomain('');
this._defaultComponent = pages[pages.length - 1],
this._component = null;
this._webviewId = null;
}
this._queue = [];
this._queueCb = [];
}
in(component) {
if (!this._webviewId) {
this._webviewId = component.__wxWebviewId__;
this._component = component;
} else if (this._webviewId !== component.__wxWebviewId__) {
console.error('A single SelectorQuery could not work in components in different pages. A SelectorQuery#in call has been ignored.');
} else {
this._component = component;
}
return this;
}
select(selector) {
return new NodesRef(this, this._component, selector, true);
}
selectAll(selector) {
return new NodesRef(this, this._component, selector, false);
}
selectViewport() {
return new NodesRef(this, 0, '', true);
}
_push(selector, component, single, fields, callback) {
if (!this._webviewId) {
this._webviewId = this._defaultComponent ? this._defaultComponent.__wxWebviewId__ : undefined;
}
const rootNodeId = pluginId ? '' : r.getRootNodeId(this._webviewId);
this._queue.push({
component: null != component ? (0 === component ? 0 : component.__wxExparserNodeId__) : rootNodeId,
selector,
single,
fields,
});
this._queueCb.push(callback || null);
}
exec(callback) {
requestComponentInfo(this._webviewId, {
pluginId,
queue: this._queue,
}, (results) => {
const queueCb = this._queueCb;
results.forEach((res, index) => {
if ('function' == typeof queueCb[index]) {
queueCb[index].call(this, res);
}
});
if ('function' == typeof callback) {
callback.call(this, results);
}
})
}
}
}
[代码]
requestComponentInfo.js
[代码]const subscribe = function(eventType, callback) {
const _callback = function(event, webviewId, nativeInfo = {}) {
const { data = {}, options } = event;
const startTime = options && options.timestamp || 0;
const endTime = Date.now();
if ('function' == typeof callback) {
callback(data, webviewId);
Reporter.speedReport({
key: 'webview2AppService',
data,
timeMark: {
startTime,
endTime,
nativeTime: nativeInfo.nativeTime || 0,
}
});
}
};
__safeway__.bridge.subscribe(eventType, _callback);
}
const publish = function(eventType, data, webviewIds) {
const event = {
data,
options: {
timestamp: Date.now(),
}
};
__safeway__.bridge.publish(eventType, event, webviewIds);
}
const requestCb = {};
let requestId = 1;
subscribe('responseComponentInfo', function(data) {
const reqId = data.reqId;
const callback = requestCb[reqId];
if (callback) {
delete requestCb[reqId];
callback(data.res);
}
});
export default function requestComponentInfo(webviewId, reqs, callback) {
const reqId = requestId++;
if (!webviewId) {
console.warn('An SelectorQuery call is ignored because no proper page or component is found. Please considering using `SelectorQuery.in` to specify a proper one.');
return;
}
requestCb[reqId] = callback,
publish('requestComponentInfo',
{
reqId,
reqs,
},
[webviewId],
);
}
[代码]
NodesRef.js
[代码]export default class NodesRef {
constructor(selectorQuery, component, selector, single) {
this._selectorQuery = selectorQuery;
this._component = component;
this._selector = selector;
this._single = single;
}
fields(fields, callback) {
this._selectorQuery._push(this._selector, this._component, this._single,
fields,
callback,
);
return this._selectorQuery;
}
boundingClientRect(callback) {
this._selectorQuery._push( this._selector, this._component, this._single,
{
id: true,
dataset: true,
rect: true,
size: true,
},
callback,
);
return this._selectorQuery;
}
scrollOffset(callback) {
this._selectorQuery._push( this._selector, this._component, this._single,
{
id: true,
dataset: true,
scrollOffset: true,
},
callback,
);
return this._selectorQuery;
}
}
[代码]