這是我參與更文挑戰的第3天,活動詳情查看: 更文挑戰html
從運行環境方面開看,H5 的宿主環境是瀏覽器,只要有瀏覽器,就可使用,包括APP中的 web-view 組件,以及小程序提供的 web-view 組件小程序就不同了,它運行於特定的移動軟件平臺 (Wechat / 支付寶 / 字節跳動 / 百度 / QQ 等)。react
拿微信小程序來講,它是基於瀏覽器內核重構的內置解析器,它並非一個完整的瀏覽器,官方文檔中重點強調了腳本內沒法使用瀏覽器中經常使用的 window 對象和 document 對象,就是沒有 DOM 和 BOM 的相關的 API,這一條就幹掉了 JQ 和一些依賴於 BOM 和 DOM 的NPM包。android
H5 的運行就是一個網頁的運行,這裏不過多敘述,小程序仍是以微信小程序舉例。ios
若是用戶已經打開過某小程序,在必定時間內再次打開該小程序,此時無需從新啓動,只需將後臺態的小程序切換到前臺,整個過程就是所謂的 熱啓動。若是用戶首次打開或小程序被微信主動銷燬後再次打開的狀況,此時小程序須要從新加載啓動,就是 冷啓動
。web
當小程序進入後臺必定時間,或系統資源佔用太高,或者是你手動銷燬,纔算真正的銷燬正則表達式
H5最被詬病的地方在哪?系統權限不夠,好比網絡通訊狀態、數據緩存能力、通信錄、或調用硬件的,如藍牙功能等等一些APP有的功能,H5就沒有這些系統權限,由於它重度依賴瀏覽器能力,依舊是微信小程序舉例,微信客戶端的這些系統級權限均可以和微信小程序無縫銜接,官方宣稱擁有 Native App 的流暢性能。json
H5 開發你們都知道,標準的 HTML、CSS、JavaScript ,萬變不離其三劍客小程序不一樣, (Wechat / 支付寶 / 字節跳動 / 百度 / QQ 等)不一樣的小程序都有本身定義獨特的語言最經常使用的微信小程序,自定義的 WXML、WXSS,WXML 中所有是微信自定義的標籤,WXSS、JSON 和 JS 文件中的寫法都稍有限制,官方文檔中都有明確的使用介紹,雖容易上手,但仍是有區別的。小程序
H5 的話想怎麼更新就怎麼更新,更新後拋開CDN/瀏覽器緩存啥的,基本上更新結束刷新就能夠看到效果 小程序不一樣,仍是微信舉例,嘿嘿,微信小程序更新啥的是須要經過審覈的。並且開發者在發佈新版本以後,沒法馬上影響到全部現網用戶,要在發佈以後 24 小時以內才下發新版本信息到用戶 小程序每次 冷啓動 時,都會檢查有無更新版本,若是發現有新版本,會異步下載新版本代碼包,並同時用客戶端本地包進行啓動,因此新版本的小程序須要等下一次 冷啓動 纔會應用上,固然微信也有 wx.getUpdateManager 能夠作檢查更新windows
H5就是 web 渲染,瀏覽器渲染。微信小程序的宿主環境是微信,宿主環境爲了執行小程序的各類文件:wxml文件、wxss文件、js文件,提供了雙線模型。後端
小程序的渲染層和邏輯層分別由兩個線程管理:
這兩個線程間的通訊經由小程序 Native 側中轉,邏輯層發送網絡請求也經由 Native 側轉發。如此設計的初衷是爲了管控和安全,微信小程序阻止開發者使用一些瀏覽器提供的,諸如跳轉頁面、操做 DOM、動態執行腳本的開放性接口。將邏輯層與視圖層進行分離,視圖層和邏輯層之間只有數據的通訊,能夠防止開發者隨意操做界面,更好的保證了用戶數據安全。
三端的腳本執行環境以及用於渲染非原生組件的環境是各不相同的:
運行環境 | 邏輯層 | 渲染層 |
---|---|---|
Android | V8 | Chromium 定製內核 |
IOS | JavaScriptCore | WKWebView |
小程序開發者工具 | NWJS | Chrome WebView |
運行環境邏輯層渲染層 AndroidV8Chromium
定製內核IOS JavaScriptCoreWKWebView
小程序開發者工具NWJSChrome WebView
小程序的視圖是在WebView裏渲染的,那搭建視圖的方式天然就須要用到HTML語言。可是HTML語言標籤衆多,增長了理解成本,並且直接使用HTML語言,開發者能夠利用<a>
標籤實現跳轉到其餘在線網頁,也能夠動畫執行 JAVAScript
,前面所提到的爲解決管控與安全而創建的雙線程模型就成擺設了。
所以,小程序設計一套組件框架—— Exparser 。基於這個框架,內置了一套組件,以涵蓋小程序的基礎功能,便於開發者快速搭建出任何界面。同時也提供了自定義組件的能力,開發者能夠自行擴展更多的組件,以實現代碼複用。值得一提的是,內置組件有一部分較複雜組件是用客戶端原生渲染的,以提供更好的性能。
你們都知道,瀏覽器緩存是個很是有用的特性,它可以提高性能、減小延遲,還能夠減小帶寬、下降網絡負荷。關於瀏覽器的緩存機制能夠總結成下面 2 句話:
更進一步,咱們能夠粗略瞭解一下強制緩存和協商緩存的運行機理。若強制緩存(Expires 和 Cache-Control)生效則直接使用緩存,若不生效則進行協商緩存(Last-Modified/If-Modified-Since 和 Etag/If-None-Match),協商緩存由服務器決定是否使用緩存,若協商緩存失效,那麼表明該請求的緩存失效,返回 200,從新返回資源和緩存標識,再存入瀏覽器緩存中;生效則返回 304,繼續使用緩存。這段文字是想讓讀者拓展一下知識面,若是想要更輸入瞭解,能夠經過上面的一些關鍵字(強緩存、協商緩存、Expire、Cache-Control 等)去查找更詳細的資料。 微信的 web-view 組件就是一個嵌在小程序裏的瀏覽器,它在緩存上並無徹底遵守上述的規則,也即它的緩存並不能及時獲得清理。想必下面的操做你們都有嘗試過:
沒法及時刷新緩存會致使發佈了最新的頁面,而小程序裏仍然是之前的頁面,從而會帶來許多問題,如先後端的數據不一致,新的特性沒法及時起做用,修改的問題沒有獲得解決等等。這裏須要說明一下:web-view 在過一段時間(時間不定,一天或者幾小時,無明顯規律)是能夠進行緩存刷新的,而本 Chat 要解決的是及時刷新的問題。
首先想到的就是onShow方法的實現,以前有人提議用visibilitychange來實現onShow方法。但調研事後,這種方式在ios中表現符合預期,可是在安卓手機裏,是不能按預期觸發的。因而就有了下面的方案,這個方案須要h5和小程序的webview都作處理。
核心思想:利用webview的hash特性
爲何要執行window.history.go(-1) ? 由於hash變動會致使webview歷史棧長度+1,用戶須要多一次返回操做。但這一步明顯是多餘的。同時window.history.go(-1)後,會把webview在hash中添加的參數去掉,還能保證和以前的url一致。
出於平滑接入的考慮,不能上來搞一刀切,要保證現有頁面再不作任何修改的狀況下繼續訪問。新能力要經過額外參數區分,如:檢測url中的query部分,帶有__isonshowpro=1
再進行經過hash方式傳參。改造原有邏輯,讓__isonshowpro=1時,hash處理邏輯優先級最高參數定義,在前面加入了兩個下劃線,目的是爲了分區url中正常的參數。咱們來看看h5端的sdk是怎麼實現的
import util from './util';
class WASDK {
/** * Create a instance. * @ignore */
constructor(){
// hashchang事件處理
if('onhashchange' in window && window.addEventListener && !WASDK.hashInfo.isInit){
// 更新標誌位
WASDK.hashInfo.isInit = true;
// 綁定hashchange
window.addEventListener('hashchange', ()=>{
// 若是小程序webview修改的hash,才進行處理
if (util.getHash(window.location.href, '__wachangehash') === '1') {
// 這塊有個坑:
// ios小程序webview在修改完url的hash以後,頁面hashchange和更新均可以正常觸發
// 可是:h5調用部分小程序能力會失敗(如:ios在設置完hash後,調用wx.uploadImg會失敗,須要從新設置wx.config)
// 由於ios小程序的邏輯是,url只要發生變化,wx.config中的appId就找不到了
// 因此須要從新進行wx.config配置
// 這一步是獲取以前設置wx.config的參數(須要從服務端拿,由於以前已經獲取過了,這裏從緩存直接取)
const jsticket = window.native && window.native.adapter && window.native.adapter.jsticket || null;
const ua = navigator.userAgent;
// 非安卓系統要從新設置wx.config
if (jsticket && !(ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1)) {
window.wx.config({
debug: false,
appId: jsticket.appId,
timestamp: jsticket.timestamp,
nonceStr: jsticket.noncestr,
signature: jsticket.signature,
jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ',
'onMenuShareQZone', 'onMenuShareWeibo', 'scanQRCode', 'chooseImage', 'uploadImage', 'previewImage', 'getLocation', 'openLocation']
})
}
// 觸發緩存數組的回調
WASDK.hashInfo.callbackArr.forEach(callback=>{
callback();
})
// 執行返回操做(這一步是重點!!)
// 由於webview設置完hash參數後,會使webview歷史棧+1
// 而實際並不須要此次多餘的歷史記錄,因此須要執行返回操做把它去掉
// 即使是返回操做,也僅僅是hash層面的變動,因此不會觸發頁面刷新
// 用setTimeout表示在下一次事件循環進行返回操做。若是後面有對dom操做能夠在當前次事件循環完成
setTimeout(()=>{
window.history.go(-1);
}, 0);
}
}, false)
}
}
/** * hash相關信息 */
static hashInfo = {
// 是否已經初始化
isInit: false,
// hash回調數組
callbackArr: []
}
/** * 頁面再次展現時鉤子方法 * @param {Function} callback - 必填, callback回調方法, 回傳參數爲hash部分問號後面的參數解析對象 */
@execLog
onShow(callback){
if (typeof callback === 'function') {
// 對回調方法進行onshow邏輯包裝,並推入緩存數組
WASDK.hashInfo.callbackArr.push(function(){
// 檢查是不是指定參數發生變化
if(util.getHash(window.location.href, '__isonshow') === '1'){
// 觸發onShow回調
callback();
}
})
} else {
util.console.error(`參數錯誤,調用onShow請傳入正確callback回調`);
}
}
/** * 業務處理完成併發送消息 * @param {Object} obj - 必填項,消息對象 * @param {String} obj.key - 必填項,消息名稱 * @param {String} obj.content - 可選項,消息內容,默認空串,若是是內容對象,請轉換成字符串 * @param {String|Number} condition - 可選項,默認僅進行postMessage * String - 能夠傳指定url的路徑,當小程序webview打開指定的url或者onshow時,會觸發該消息 * 也可傳小程序path,這個爲之後預留 * Number - 返回到指定的測試,相似history.go(-1),如: -1,-2 */
@execLog
serviceDone(obj, condition){
if(obj && obj.key){
// 消息體
const message = {
// 消息名稱
key: obj.key,
// 消息體
content: obj.content || '',
// 觸發條件
trigger: {
// 類型 'immediately'在下一次onshow中馬上觸發, 'url',在找到指定h5連接時觸發,'path'在打開指定小程序路徑時觸發
type: 'immediately',
// 條件內容,immediately是爲空,url是爲h5連接地址,path是爲小程序路徑
content: ''
}
};
// 解析觸發條件
condition = condition || 0;
// 若是是路徑
if(typeof condition === 'string' && (condition.indexOf('http') > -1 || condition.indexOf('pages/') > -1)){
// 設置消息觸發條件
message.trigger = {
type: condition.indexOf('http') > -1 ? 'url' : 'path',
content: condition
}
}
// 發送消息
wx.miniProgram.postMessage({
data: {
messageData: message
}
});
// 若是不是url或者path觸發,則對conditon是否須要返回進行判斷
if(message.trigger.type === 'immediately'){
// 查看是否須要返回指定的層級,兼容傳入'-1'字符串這種類型的場景
try{
condition = parseInt(condition, 10);
}catch(e){}
// 保證返回級數的正確性
if(condition && typeof condition === 'number' && !isNaN(condition)){
this.handler.navigateBack({delta: Math.abs(condition)});
}
}
}else{
util.console.error(`參數錯誤,調用serviceDone方法,傳入的對象中不包含key值`);
}
}
...
}
window.native = new Native();
export default native;
複製代碼
這個看着也挺多,總結下來是兩點:
綁定一個hashchange事件(這裏作了防止重複綁定事件的處理),將傳入的onShow自定義事件緩存在一個數組中,hashchange觸發時,根據特有的標誌位__isonshow和__wachangehash肯定是否觸發
處理傳過來的數據,處理該數據的觸發條件:immediately表示最近的一次onShow觸發,或者本身指定url經過wx.miniProgram.postMessage發送數據
瀏覽器訪問資源是經過 URL 地址,若是內嵌 H5 的地址不發生變化,那麼 web-view 訪問資源會從緩存裏取,而緩存裏並無最新的數據,這就致使了服務端的最新資源根本沒法到達瀏覽器,這也就解釋了爲何修改 Nginx 的 Cache-Control 配置也沒法生效的緣由。因此,要想完全解決及時刷新,必須讓 web-view 去訪問新的地址。咱們假定小程序訪問的 URL 地址爲:https://www.yourdomain.com/101/#/index
其中 101 就是構建的一個版本號,每次遞增,保證次次不一樣便可。
這部分須要在H5頁面種下一個sdk,好比名字就叫bridge.js,下面是我作了幾年小程序總結出來的經常使用方法:
// bridge.js
let ua = window.navigator.userAgent.toLowerCase();
const globalObj = {
testDataArr: [],
doJSReadyFuncExecuted: false,
errorInfo: '',
miniappSDK: null,
miniappType: '',
actionQueue: [],
MINIAPP_TYPE: {
WECHATMINIAPP: 'WECHATMINIAPP',// miniprogram
WECHATAPP: 'WECHATAPP', // miniprogram + offiaccount
OLDQUICKAPP: 'OLDQUICKAPP', // old
NEWQUICKAPP: 'NEWQUICKAPP', // new
ALIPAYAPP: 'ALIPAYAPP',
BAIDUAPP: 'BAIDUAPP',
TOUTIAOAPP: 'TOUTIAOAPP',
QQAPP: 'QQAPP' // No longer maintained
},
JSSDK_URL_OBJ: {
WECHATMINIAPP: 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js',
WECHATAPP: 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js',
OLDQUICKAPP: 'https://xxxxxxxx/amsweb/quickApp/mixBridge.js',
NEWQUICKAPP: 'https://quickapp/jssdk.webview.min.js',
ALIPAYAPP: 'https://appx/web-view.min.js',
BAIDUAPP: 'https://b.bdstatic.com/searchbox/icms/searchbox/js/swan-2.0.21.js',
TOUTIAOAPP: 'https://s3.pstatp.com/toutiao/tmajssdk/jssdk-1.0.1.js',
QQAPP: 'https://qqq.gtimg.cn/miniprogram/webview_jssdk/qqjssdk-1.0.0.js'
},
bversion: '1.0.0'
}
if(typeof window['__bfi'] == 'undefined') {
window['__bfi'] = [];
};
window['__bfi'].push([
'_tracklog',
'174537',
`ua=${ua}&pageId=\${page_id}`
]);
function isAndroid () {
return ua.includes('android');
}
function isWechatMiniapp () {
// @source https://developers.weixin.qq.com/community/develop/doc/00022e37c78b802f186750b5751000
// in wechat && (in android || in ios)
return isWechat() && (ua.includes('miniprogram') || window.__wxjs_environment === 'miniprogram');
}
function isWechat () {
// in wechat-web-browser
// https://blog.csdn.net/daipianpian/article/details/86543080
// @source blog ( https://www.jianshu.com/p/6a10f833b099 )
return /micromessenger/i.test(ua) || /windows phone/i.test(ua);
}
function isOldQuickapp () {
return (/(hap|OPPO\/Hybrid)\/\d/i.test(ua)) && !isNewQuickapp();
}
function isNewQuickapp () {
// @source 2020.04.10, Vivo( Li Chunjiao ) has confirmed that this method is feasible
return ua.includes('mode-quickapp');
}
function isAlipay () {
// @source 2020.06.15, Alipay has confirmed that this method is feasible
let isAli = (/APXWebView/i.test(ua)) || (/MiniProgram/i.test(ua) && !ua.includes('micromessenger'));
// @source 2020.11.17, https://www.yuque.com/books/share/6d822c34-9121-47d8-a805-4c57b0b2d2f0/hiv1tn
let isUCKuake = ua.includes('aliapp') && (ua.includes('uc') || ua.includes('quark'));
// @source 2021.03.26
let isGaode = ua.includes('aliapp') && ua.includes('amapclient');
return isAli || isUCKuake || isGaode;
}
function isBaidu () {
// @source 2020.11.05, baidu's doc ( https://smartprogram.baidu.com/docs/develop/component/open_web-view/ )
return /swan\//.test(ua) || /^webswan-/.test(window.name);
}
function isToutiao () {
// @source 2020.11.05, toutiao's doc ( https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/component/open-capacity/web-view/ )
return ua.includes("toutiaomicroapp");
}
function isQQ () {
// @source 2021.04.21, add ua.includes('miniprogram'), qq's doc ( https://q.qq.com/wiki/develop/miniprogram/component/open-ability/web-view.html )
return ua.includes('qq') && ua.includes('miniprogram');
}
// return miniapp type of the environment
function isMiniProgram () {
let appType = false;
let typeNameObj = globalObj.MINIAPP_TYPE
try {
if (isWechatMiniapp()) {
appType = typeNameObj.WECHATMINIAPP;
} else if (isOldQuickapp()) {
appType = typeNameObj.OLDQUICKAPP;
} else if (isNewQuickapp()) {
appType = typeNameObj.NEWQUICKAPP;
} else if (isAlipay()) {
appType = typeNameObj.ALIPAYAPP;
} else if (isBaidu()) {
appType = typeNameObj.BAIDUAPP;
} else if (isToutiao()) {
appType = typeNameObj.TOUTIAOAPP;
} else if (isQQ()) {
appType = typeNameObj.QQAPP;
}
console.log('判斷所處環境,isMiniProgram 返回值: ', appType);
window['__bfi'].push([
'_tracklog',
'174537',
`api_name=isMiniProgram&miniappType=${appType}&pageId=\${page_id}`
]);
return appType;
} catch (e) {
window['__bfi'].push([
'_tracklog',
'174537',
`api_name=isMiniProgram&err_msg=${e.message}&err_stack=${e.stack}`
]);
return false; // 'catch error'
}
}
export {
isAndroid, // 判斷H5頁面是否處於安卓系統
isWechatMiniapp, // 判斷H5頁面是否處於微信小程序環境
isWechat, // 判斷H5頁面是否處於微信環境
isOldQuickapp, // 判斷H5頁面是否處於【老版快應用】小程序環境
isNewQuickapp, // 判斷H5頁面是否處於【新版快應用】小程序環境
isAlipay, // 判斷H5頁面是否處於支付寶小程序環境
isBaidu, // 判斷H5頁面是否處於百度小程序環境
isToutiao, // 判斷H5頁面是否處於頭條小程序環境
isQQ, // 判斷H5頁面是否處於QQ小程序環境
isMiniProgram // 返回H5頁面所處環境的應用名
}
複製代碼
使用前,最好查閱相應小程序的文檔,由於各個小程序對API的支持程度不一樣。js文件的引用不能放在裏,bridge.js 裏面對當前頁面的head進行操做了。由於 bridge.js 引入JSSDK的方式是 爲 head標籤添加 script標籤,若在 head標籤中引入bridge.js,就會報錯。
若打開h5,顯示「頁面訪問受限」之類的提示信息,可嘗試下方的操做:(這種狀況,通常是打開測試環境的h5 url 時出現)勾選IDE中的「忽略webview域名合法性檢查」 和 「忽略request域名合法性檢查」。
【快應用相關】 目前Vivo,Oppo,華爲三家廠商已支持新版快應用,VivoOPPO已上線,小米不支持。對於新版快應用,若H5頁面須要調用新版快應用JS-SDK中提供的API,須要提早將該H5連接的域名配置到可信任的網址裏(應寫成正則表達式的形式進行配置)。
【頭條相關】 頭條小程序的redirectTo、navigateTo 等頁面跳轉的 api 只支持 url 爲 / 開始的絕對路徑
【支付寶相關】 目前的1.0.73版 bridge.js 判斷是否處於支付寶小程序的方法,會將h5處於支付寶小程序、h5處於支付寶內置瀏覽器都判斷爲處於支付寶小程序內。所以,在調my.XXXX以前,須要先調判斷環境工具函數 判斷一下,確保確實是處於支付寶小程序內,而非支付寶內置瀏覽器內。
在小程序中,咱們利用 app 的 onShow 鉤子函數來完成最新的 URL 獲取,同時還要保證只有獲取了版本號以後才能加載其餘的頁面,所以這裏要用到同步接口調用。請參考下面代碼:
//這裏加入同步請求到服務器獲取最新路徑
onShow: function (options) {
this.getFEVersion()
},
getFEVersion: function () {
//下面是利用Promise進行同步調用的寫法
return new Promise(function (resolve, reject) {
wx.request({
//下面是本機調試的一個地址,上線時請改爲本身服務端的地址
url: 'http://192.168.0.168:8090/getFEVersion',
data: {},
method: 'POST',
header: {
'content-type': 'application/json',
},
success: function (res) {
if (res.data.success) {
const app = getApp();
//res.data.version 是從服務端返回的最新fe的版本號,即上面的數字101
app.globalData.feUrl = 'https://www.yourdomain.com/' + res.data.version + '/#/index'
}
resolve();
},
fail: function (error) {
console.log(error);
reject();
}
})
});
},
webview動態處理
/** * @file 根據入參的小程序類型,動態加載相應的 JavaScript文件 * 指定<script>元素的src屬性,指定事件處理程序(onload事件 onerror事件) */
const globalObj = {
testDataArr: [],
doJSReadyFuncExecuted: false,
errorInfo: '',
miniappSDK: null,
miniappType: '',
actionQueue: [],
MINIAPP_TYPE: {
WECHATMINIAPP: 'WECHATMINIAPP',// miniprogram
WECHATAPP: 'WECHATAPP', // miniprogram + offiaccount
OLDQUICKAPP: 'OLDQUICKAPP', // old
NEWQUICKAPP: 'NEWQUICKAPP', // new
ALIPAYAPP: 'ALIPAYAPP',
BAIDUAPP: 'BAIDUAPP',
TOUTIAOAPP: 'TOUTIAOAPP',
QQAPP: 'QQAPP' // No longer maintained
},
JSSDK_URL_OBJ: {
WECHATMINIAPP: 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js',
WECHATAPP: 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js',
OLDQUICKAPP:'https://xxxxxxxxx/amsweb/quickApp/mixBridge.js',
NEWQUICKAPP: 'https://quickapp/jssdk.webview.min.js',
ALIPAYAPP: 'https://appx/web-view.min.js',
BAIDUAPP: 'https://b.bdstatic.com/searchbox/icms/searchbox/js/swan-2.0.21.js',
TOUTIAOAPP: 'https://s3.pstatp.com/toutiao/tmajssdk/jssdk-1.0.1.js',
QQAPP: 'https://qqq.gtimg.cn/miniprogram/webview_jssdk/qqjssdk-1.0.0.js'
},
bversion: '1.0.0'
}
let n = 0;
function loadListener (type) {
// 先執行一次,再進入setTimeout
// 多加幾個埋點,記錄不一樣類型的信息
console.log(`====== 重試次數:${n} ======`);
if(n === 0) {
processAddRes(type);
} else {
setTimeout(function () {
processAddRes(type);
}, 200)
}
}
function processAddRes(type) {
let curMiniappType = globalObj.miniappType;
let curLoadJsUrl = globalObj.JSSDK_URL_OBJ[curMiniappType];
if(!addJSSDKToGlobalObj()){
n++;
loadListener();
if(n % 10 === 0) {
const msg = `重試達到【${n}】次`
console.log(msg);
console.log(globalObj.errorInfo || '======');
}
return;
}
let actionQueue = globalObj.actionQueue;
if (actionQueue && actionQueue.length) {
let aItem = null;
while (aItem = actionQueue.shift()) {
try {
globalObj.miniappSDK[aItem.apiName].apply(globalObj.miniappSDK, aItem.args)
} catch (e) {
//
}
}
}
}
// 將JSSDK提供的方法保存到global
function addJSSDKToGlobalObj () {
let curMiniappType = globalObj.miniappType;
try{
let _miniappSDK = null;
switch(curMiniappType) {
case 'WECHATMINIAPP':
case 'WECHATAPP':
case 'OLDQUICKAPP':
_miniappSDK = typeof wx !== 'undefined' && wx.miniProgram;
break;
case 'NEWQUICKAPP':
_miniappSDK = qa;
break;
case 'ALIPAYAPP':
_miniappSDK = my;
break;
case 'BAIDUAPP':
_miniappSDK = typeof swan !== 'undefined' && swan.webView;
break;
case 'TOUTIAOAPP':
_miniappSDK = typeof tt !== 'undefined' && tt.miniProgram;
break;
case 'QQAPP':
_miniappSDK = typeof qq !== 'undefined' && qq.miniProgram;
break;
}
if(_miniappSDK) {
globalObj.miniappSDK = _miniappSDK
}
if (!globalObj.miniappSDK || !globalObj.miniappSDK.navigateTo) {
console.log(globalObj)
let g_errmsg = (!globalObj.miniappSDK ? 'miniappSDK_is_undefined' : 'API_is_undefined');
let g_errstack = 'none'
globalObj.errorInfo = 'g_errmsg=' + g_errmsg + '&g_errstack=' + g_errstack;
return false;
}
} catch (e) {
// 記錄下是什麼緣由return的false: 在return false 的地方,將緣由掛到全局變量上,loadListener觸發埋點時,記錄下來
globalObj.errorInfo = 'g_errmsg=' + e.message + '_have_catch_error' + '&g_errstack=' + e.stack;
return false;
}
globalObj.errorInfo = 'g_errmsg=outof_try-catch_return_true';
return true;
}
function parseQuery(url) {
let query = {};
let idx = url.indexOf("?");
let str = url.substr(idx + 1);
if (str == "" || idx == -1) {
return {};
}
let pairs = str.split('&');
for (let i = 0; i < pairs.length; i++) {
let pair = pairs[i].split('=');
// 當根據 = 號分割後有多條數據時,從數組第1位起以後的要所有保留。
// 好比 src=/issue/create?type=1752,要處理成爲:src: '/issue/create?type=1752',而不是 src: '/issue/create?type'
if (pair.length > 2) {
pair[1] = pair.slice(1).join('=');
}
query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
}
return query;
};
function loadScript() {
// 埋點信息,增長加載的jssdk-url,後續可能能夠從url中獲取到微信的版本號
// 設備品牌、設備型號、微信版本號、操做系統及版本、客戶端平臺、客戶端基礎庫版本 Object wx.getSystemInfoSync()
let curMiniappType = globalObj.miniappType;
let curLoadJsUrl = globalObj.JSSDK_URL_OBJ[curMiniappType];
let jSBridgeReady = function(type) {
console.log('jSBridgeReady, event type: ', type);
// 保證後續邏輯只會執行一次
if (globalObj.doJSReadyFuncExecuted) {
return;
}
globalObj.doJSReadyFuncExecuted = true;
console.log('script is onload, doJSReadyFuncExecuted')
loadListener(type);
}
if (curMiniappType === "WECHATMINIAPP" || curMiniappType === "WECHATAPP" || curMiniappType === "OLDQUICKAPP") {
// 監聽WeixinJSBridgeReady 和 onload 前,發個埋點,看下當前是否已經有wx 和 wx.miniProgram(由於目前nfes 只引入了微信jssdk)
document.addEventListener('WeixinJSBridgeReady', function() {
console.log('WeixinJSBridgeReady ======');
jSBridgeReady('WeixinJSBridgeReady')
}, false)
}
if (curMiniappType === "NEWQUICKAPP") {
document.addEventListener('QaJSBridgeReady', function() {
console.log('QaJSBridgeReady ======');
jSBridgeReady('QaJSBridgeReady')
}, false)
}
let script = document.createElement("script");
script.src = curLoadJsUrl;
script.async = false; // 註釋掉,由於添加async的話,執行順序沒法保證
let scriptArr = document.getElementsByTagName('script');
console.log(scriptArr);
for(let i = 0; i < scriptArr.length; i++) {
let item = scriptArr[i];
if(item.src.includes('/ares2/market/mixappBridge/') && item.src.includes('/default/bridge')) {
// 取參數,動態設置async
let queryObj = parseQuery(item.src); // 兜底的值爲 {}
console.log('queryObj: ', queryObj);
if(typeof queryObj.bridgeAsync !== 'undefined') {
script.async = queryObj.bridgeAsync === '1' ? true : false;
}
}
}
console.log('最終,script.async: ', script.async);
script.onload = function(e) {
console.log('script is onload ======')
jSBridgeReady('onload')
}
script.onerror = function(e) {
console.log('script is onerror')
}
window.onerror = function(message, source, lineNo, columnNo, error) {
// to do track
}
document.getElementsByTagName('head')[0].appendChild(script)
}
export {
loadScript
}
複製代碼
微信提供了一個環境變量,加載h5之後第一個頁面能夠及時拿到,但後續的頁面都須要在微信的sdk加載完成之後才能拿到,所以建議你們在wx.ready或者是weixinjsbridgeready事件裏面去判斷,區別就在於前者須要加載jweixin.js纔有,但這裏有坑,坑在於h5的開發者可能並不知道你這個檢測過程須要時間,是一個異步的過程,他們可能頁面一加載就須要調用一些api,這時候就可能會出錯,所以你必定要提供一個api調用的隊列和等待機制。具體作法見上面代碼。
第二個常見問題是支付,由於小程序webview裏面不支持直接調起微信支付,因此基本上須要支付的時候,都須要來到小程序裏面,支付完再回去。上面作好了之後,在h5這塊調用就一句話就能夠了。針對產品有大量內嵌H5頁面的狀況下,最好根據業務分兩種支付頁面,一是有的業務h5有本身完善的交易體系,下單動做在h5裏面就能夠完成,他們只須要小程序付款,所以咱們有一個精簡的支付頁,進來直接就拉起微信支付,還有一種狀況是業務須要小程序提供完整的下單支付流程,那麼久能夠直接進入咱們小程序的收銀臺來,圖上就是sdk裏面的基本邏輯,咱們經過payOnly這個參數來決定進到哪一個頁面。
咱們再看下小程序裏面精簡支付怎麼實現的,就是onload以後直接調用api拉起微信支付,支付成功之後根據h5傳回來的參數,若是是個小程序頁面,那直接跳轉過去,不然就刷新上一個webview頁面,而後返回回去。
那怎麼解決這種流失呢,咱們加了一個左上角返回的功能。首先進入的是一個空白的中轉頁,而後進入h5頁面,這樣左上角就會出現返回按鈕了,當用戶按左上角的返回按鈕時候,頁面會被重載到小程序首頁去,這個看似簡單又微小的動做,對業務其實有很大的影響,咱們看兩個數字,通過咱們的數據統計發現,左上角返回按鈕點擊率高達70%以上,由於這種落地頁通常是被用戶分享出來的,之前純h5的時候只能經過左上角返回,因此在小程序裏用戶也習慣如此,第二個數字,重載到首頁之後,後續頁面訪問率有10%以上,這兩個數字對業務提高其實蠻大的。其實現原理很簡單,都是經過第二次觸發onShow時進行處理。
Q: 可能出現的登陸登出同步問題
A: 跳到我的頁登陸完成,此時是新開的webview同步兩端登陸態,點返回,到上一個webview,此時這個webview嵌套的首頁,沒有觸發react-imvc onshow事件。這個頁面是老的,退出登陸也是同樣,因此在首頁會去跳h5的登陸而不是小程序登陸,致使登陸態不一樣步。 解決思路:須要返回首頁刷一下h5頁面。
誤區:直接在我的登陸以後,relaunch到首頁,會致使沒有直接調用註銷webview把token置換,沒法退出 解決方案:判段從我的頁返回的時候,設置webview的url加個參數,從新刷一下。