JSSDK源码分析

本篇文章仅用于学习目的。不可用于商业用途,若有侵权,请告知,立刻删除。

源码一览

去掉了一些与基础框架无关的接口、函数、变量

/**
 * 6.0.2
 * 关于 6.0.2 的相关文档说明:
 * - 为什么6.0.1版本config:ok,但是6.0.2版本之后不ok
 * (
 * 因为6.0.2版本之前没有做权限验证,所以config都是ok,
 * 但这并不意味着你config中的签名是OK的,
 * 请在6.0.2检验是否生成正确的签名以保证config在高版本中也ok。
 * )
 * - 是否需要对低版本自己做兼容
 * (
 * jssdk都是兼容低版本的,
 * 不需要第三方自己额外做更多工作,
 * 但有的接口是6.0.2新引入的,只有新版才可调用
 * )
 */
!(function (e, n) {
  "function" == typeof define && (define.amd || define.cmd)
    ? define(function () {
        return n(e);
      })
    : n(e, !0);
})(this, function (entryO, entryE) {
  if (!entryO.jWeixin) {
    var n;
    var global = {
      config: "preVerifyJSAPI",
    };
    /** 类似转码,把global中的 value 作为新obj的key,global中的 key 作为新 obj 的 value */
    var createObjectByGlobal = (function () {
      var tempObj = {};
      for (var n in global) {
        tempObj[global[n]] = n;
      }
      return tempObj;
    })();
    var oDocument = entryO.document;
    var userAgent = navigator.userAgent.toLowerCase();
    var platform = navigator.platform.toLowerCase();
    var isMacOrWin = !(!platform.match("mac") && !platform.match("win"));
    var isWxdebugger = -1 != userAgent.indexOf("wxdebugger");
    var isMicromessenger = -1 != userAgent.indexOf("micromessenger"); // 通过userAgent是否包含MicroMessenger来判断是否在微信内置浏览器打开网页
    var isAndroid = -1 != userAgent.indexOf("android");
    var isIphone =
      -1 != userAgent.indexOf("iphone") || -1 != userAgent.indexOf("ipad");
    var micromessengerVersion = (n =
      userAgent.match(/micromessenger\/(\d+\.\d+\.\d+)/) ||
      userAgent.match(/micromessenger\/(\d+\.\d+)/))
      ? n[1]
      : "";
    var help = {
      version: 1,
      appId: "",
      initTime: 0,
      preVerifyTime: 0,
      networkType: "",
      isPreVerifyOk: 1,
      systemType: isIphone ? 1 : isAndroid ? 2 : -1,
      clientVersion: micromessengerVersion,
      url: encodeURIComponent(location.href),
    };
    var vConfig = {};
    var SEvents = { _completes: [] };
    var stateSetting = { state: 0, data: {} };

    /** 要对外暴露的接口api对象 */
    var wechatJsEvents = {
      /**
       * 所有需要使用JS-SDK的页面必须先注入配置信息,
       * 否则将无法调用
       * (
       * 同一个url仅需调用一次,
       * 对于变化url的SPA的web app可在每次url变化时进行调用,
       * 目前Android微信客户端不支持pushState的H5新特性,
       * 所以使用pushState来实现web app的页面会导致签名失败,
       * 此问题会在Android6.2中修复
       * )。
       * @param {*} inputConfig
       */
      config: function (inputConfig) {
        emptyConfigParamWhenDebug("config", (vConfig = inputConfig));

        /**
         * vConfig.check !== false
         * 不允许转换,意思 truly 那一套失效,必须为 false 或其他任何值
         *
         * comment:
         * 目前来看 vConfig.check 均为 undefind,所以 isCheckOfConfig 为 true。
         * 因为没找到传递此参数的地方,且 config 文档中没有此变量
         *
         * true
         */
        var isCheckOfConfig = !1 !== vConfig.check;

        micromessengerAddEventListener(function () {
          if (isCheckOfConfig) {
            installWeixinJSBridge(
              global.config,
              {
                verifyJsApiList: getList(vConfig.jsApiList),
                verifyOpenTagList: getList(vConfig.openTagList),
              },
              // 立即执行函数IIFE
              (function () {
                /**
                 * 初始时:var SEvents = { _completes: [] };
                 *
                 * _complete
                 *
                 * success
                 * fail
                 * complete
                 */

                /** 声明 _complete 函数 */
                SEvents._complete = function (completeData) {
                  stateSetting.state = 1;
                  stateSetting.data = completeData;
                };
                /** 声明 success 函数 */
                SEvents.success = function (e) {
                  help.isPreVerifyOk = 0;
                };
                /** 声明 fail 函数 */
                SEvents.fail = function (e) {
                  SEvents._fail ? SEvents._fail(e) : (stateSetting.state = -1);
                };

                var seventCompletes = SEvents._completes;

                seventCompletes.push(function () {
                  // IIFE 立即执行函数
                  !(function () {
                    /**
                     * &&
                     * !isMacOrWin  非mac非win浏览器
                     * !isWxdebugger 不是微信开发者工具
                     * !vConfig.debug 未开启debug模式
                     * micromessengerVersion >= '6.0.2' 微信公众号版本号大于等于 6.0.2
                     * help.systemType >= 0 (苹果或者安卓手机)苹果手机:1;安卓手机:2;默认:-1
                     */
                    if (
                      !(
                        isMacOrWin ||
                        isWxdebugger ||
                        vConfig.debug ||
                        micromessengerVersion < "6.0.2" ||
                        help.systemType < 0
                      )
                    ) {
                      // 声明 image
                      var tempImage = new Image();
                      // 设置 appid
                      help.appId = vConfig.appId;

                      wechatJsEvents.getNetworkType({
                        isInnerInvoke: !0,
                        success: function (getNetworkTypeObj) {
                          help.networkType = getNetworkTypeObj.networkType;
                          tempImage.src =
                            "https://open.weixin.qq.com/sdk/report?v=" +
                            help.version +
                            "&o=" +
                            help.isPreVerifyOk +
                            "&s=" +
                            help.systemType +
                            "&c=" +
                            help.clientVersion +
                            "&a=" +
                            help.appId +
                            "&n=" +
                            help.networkType +
                            "&i=" +
                            help.initTime +
                            "&p=" +
                            help.preVerifyTime +
                            "&u=" +
                            help.url;
                        },
                      });
                    }
                  })();
                });

                // 接口调用完成时执行的回调函数,无论成功或失败都会执行。
                SEvents.complete = function () {
                  for (var index = 0; index < seventCompletes.length; ++index) {
                    seventCompletes[index](); // 顺序执行 _completes 数组中所有函数
                  }
                  SEvents._completes = []; // 执行完毕后,清空 _completes 数组
                };

                return SEvents;
              })()
            );
          } else {
            // 状态设置为完成
            stateSetting.state = 1;

            var sEventsCompletes = SEvents._completes;
            for (n = 0; n < sEventsCompletes.length; ++n) {
              // 顺次执行所有 complete 中事件
              sEventsCompletes[n]();
            }
            // 执行完complete之后清空complete数组
            SEvents._completes = [];
          }
        });

        // 要对外暴露的接口api对象 有 invoke 属性时不再执行,
        // 若无 invoke 属性时添加 invoke 和 on 两个属性
        wechatJsEvents.invoke ||
          ((wechatJsEvents.invoke = function (invokeFunc, invokeN, invokeI) {
            entryO.WeixinJSBridge &&
              WeixinJSBridge.invoke(invokeFunc, createXObj(invokeN), invokeI);
          }),
          (wechatJsEvents.on = function (onFunc, onN) {
            entryO.WeixinJSBridge && WeixinJSBridge.on(onFunc, onN);
          }));
      },

      getNetworkType: function (e) {
        installWeixinJSBridge(
          "getNetworkType",
          {},
          ((e._complete = function (e) {
            e = (function (e) {
              var n = e.errMsg;
              e.errMsg = "getNetworkType:ok";
              var eSubtype = e.subtype;
              if ((delete e.subtype, eSubtype)) e.networkType = eSubtype;
              else {
                var t = n.indexOf(":");
                var colonNextStr = n.substring(t + 1);
                switch (colonNextStr) {
                  case "wifi":
                  case "edge":
                  case "wwan":
                    e.networkType = colonNextStr;
                    break;
                  default:
                    e.errMsg = "getNetworkType:fail";
                }
              }
              return e;
            })(e);
          }),
          e)
        );
      },
      /**
       * config信息验证后会执行ready方法,
       * 所有接口调用都必须在config接口获得结果之后,
       * config是一个客户端的异步操作,
       * 所以如果需要在页面加载时就调用相关接口,
       * 则须把相关接口放在ready函数中调用来确保正确执行。
       * 对于用户触发时才调用的接口,
       * 则可以直接调用,
       * 不需要放在ready函数中。
       * @param {*} readyFunc
       */
      ready: function (readyFunc) {
        0 != stateSetting.state
          ? readyFunc() // 状态为完成状态,直接执行
          : (SEvents._completes.push(readyFunc),
            !isMicromessenger && vConfig.debug && readyFunc());
        // 1. 推送到 _completes 数组中
        // 2. 非微信公众号 且 开启debug 执行 e
      },
      error: function (e) {
        micromessengerVersion < "6.0.2" ||
          (-1 == stateSetting.state
            ? e(stateSetting.data)
            : (SEvents._fail = e));
      },
    };
    var T = 1;
    var k = {};

    return (
      // document.addEventListener
      oDocument.addEventListener(
        "error",
        function (e) {
          if (!isAndroid) {
            var n = e.target;
            var eTargetTagName = n.tagName;
            var t = n.src;
            if (
              "IMG" == eTargetTagName ||
              "VIDEO" == eTargetTagName ||
              "AUDIO" == eTargetTagName ||
              "SOURCE" == eTargetTagName
            )
              if (-1 != t.indexOf("wxlocalresource://")) {
                e.preventDefault(), e.stopPropagation();
                var o = n["wx-id"]; // e.target['wx-id']
                if ((o || ((o = T++), (n["wx-id"] = o)), k[o])) return;
                (k[o] = !0),
                  wx.ready(function () {
                    wx.getLocalImgData({
                      localId: t,
                      success: function (e) {
                        n.src = e.localData;
                      },
                    });
                  });
              }
          }
        },
        !0
      ),
      oDocument.addEventListener(
        "load",
        function (e) {
          if (!isAndroid) {
            var n = e.target,
              eTargetTagName = n.tagName; // e.target.tagName
            n.src;
            if (
              "IMG" == eTargetTagName ||
              "VIDEO" == eTargetTagName ||
              "AUDIO" == eTargetTagName ||
              "SOURCE" == eTargetTagName
            ) {
              var t = n["wx-id"]; // e.target['wx-id']
              t && (k[t] = !1); // k[e.target['wx-id']] = false
            }
          }
        },
        !0 // true
      ),
      entryE && (entryO.wx = entryO.jWeixin = wechatJsEvents),
      wechatJsEvents
    );
  }

  /** 挂载部分函数到微信浏览器内置函数 WeixinJSBridge 上 */
  function installWeixinJSBridge(
    jsbridgeParamName,
    jsbridgeObj,
    jsbridgeParam
  ) {
    entryO.WeixinJSBridge
      ? WeixinJSBridge.invoke(
          jsbridgeParamName,
          createXObj(jsbridgeObj),
          function (jsbridgeInvokeObj) {
            main(jsbridgeParamName, jsbridgeInvokeObj, jsbridgeParam);
          }
        )
      : emptyConfigParamWhenDebug(jsbridgeParamName, jsbridgeParam);
  }

  /** 设置一个 createXObj 对象 */
  function createXObj(xObj) {
    (xObj = xObj || {}).appId = vConfig.appId;
    xObj.verifyAppId = vConfig.appId;
    xObj.verifySignType = "sha1";
    xObj.verifyTimestamp = vConfig.timestamp + "";
    xObj.verifyNonceStr = vConfig.nonceStr;
    xObj.verifySignature = vConfig.signature;

    return xObj;
  }

  /**
   * SEvents
   */
  function main(paramName, obj, jsbridgeParam) {
    ("openEnterpriseChat" != paramName && "openBusinessView" !== paramName) ||
      (obj.errCode = obj.err_code),
      delete obj.err_code,
      delete obj.err_desc,
      delete obj.err_detail;

    var objErrMsg = obj.errMsg;

    /**
     * objErrMsg
     * - 存在   后面不执行
     * - 不存在 后面执行
     */
    objErrMsg ||
      // #1
      // #1-1
      ((objErrMsg = obj.err_msg),
      // #1-2
      delete obj.err_msg,
      // #1-3
      (objErrMsg = (function (paramName, objErrMsgParam) {
        var errMsgFuncFirstParam = paramName,
          t = createObjectByGlobal[errMsgFuncFirstParam];
        t && (errMsgFuncFirstParam = t);

        var okParam = "ok";

        if (objErrMsgParam) {
          var colonIndex = objErrMsgParam.indexOf(":");
          "confirm" == (okParam = objErrMsgParam.substring(colonIndex + 1)) &&
            (okParam = "ok"),
            "failed" == okParam && (okParam = "fail"),
            -1 != okParam.indexOf("failed_") &&
              (okParam = okParam.substring(7)),
            -1 != okParam.indexOf("fail_") && (okParam = okParam.substring(5)),
            ("access denied" !=
              (okParam = (okParam = okParam.replace(
                /_/g,
                " "
              )).toLowerCase()) &&
              "no permission to execute" != okParam) ||
              (okParam = "permission denied"),
            "config" == errMsgFuncFirstParam &&
              "function not exist" == okParam &&
              (okParam = "ok"),
            "" == okParam && (okParam = "fail");
        }

        return (objErrMsgParam = errMsgFuncFirstParam + ":" + okParam);
      })(paramName, objErrMsg)), // 立即执行函数
      // #1-4
      (obj.errMsg = objErrMsg)), // 设置 errMsg 为 objErrMsg
      // #2
      (jsbridgeParam = jsbridgeParam || {})._complete &&
        (jsbridgeParam._complete(obj), delete jsbridgeParam._complete), // 执行了(1: 设置 state 为 1)
      // #3
      (objErrMsg = obj.errMsg || ""),
      // #4
      vConfig.debug &&
        !jsbridgeParam.isInnerInvoke &&
        alert(JSON.stringify(obj));

    var colonIndex = objErrMsg.indexOf(":");

    switch (objErrMsg.substring(colonIndex + 1)) {
      case "ok":
        jsbridgeParam.success && jsbridgeParam.success(obj);
        break;
      case "cancel":
        jsbridgeParam.cancel && jsbridgeParam.cancel(obj);
        break;
      default:
        jsbridgeParam.fail && jsbridgeParam.fail(obj);
    }

    // ✨✨✨✨✨✨✨✨✨✨ 关键 ✨✨✨✨✨✨✨✨✨✨
    jsbridgeParam.complete && jsbridgeParam.complete(obj);
    // ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
  }

  /**
   * 获取列表
   * verifyJsApiList: getList(vConfig.jsApiList)
   * verifyOpenTagList: getList(vConfig.openTagList)
   */
  function getList(list) {
    if (list) {
      for (var index = 0; index < list.length; ++index) {
        var t = list[index];
        var o = global[t];

        o && (list[index] = o);
      }
      return list;
    }
  }

  /**
   * ("config", (vConfig = e));
   *
   * function installWeixinJSBridge(n, e, i)
   * (n, i)
   * if (vConfig.debug && (!obj || !obj.isInnerInvoke))
   * debug模式下调用config却未传入参数时触发,设置一个默认的config函数,不执行所有ready函数
   */
  function emptyConfigParamWhenDebug(paramKey, obj) {
    if (!(!vConfig.debug || (obj && obj.isInnerInvoke))) {
      var temp = createObjectByGlobal[paramKey];

      temp && (paramKey = temp);

      obj && obj._complete && delete obj._complete;

      console.log('"' + paramKey + '",', obj || "");
    }
  }

  function micromessengerAddEventListener(e) {
    isMicromessenger &&
      (entryO.WeixinJSBridge
        ? e()
        : oDocument.addEventListener &&
          oDocument.addEventListener("WeixinJSBridgeReady", e, !1)); // !1 (false)
  }
});

源码梳理

/**
 * 模仿JSSDK框架结构
 *
 */

/** 状态设置 */
let stateSetting = { state: 0, data: {} };
/** complete对象 */
let oriComplete = { _completes: [] };

/** 获取 complete 对象 */
function _getComplete() {
  /** 声明 _complete 函数 */
  oriComplete._complete = function (completeData) {
    stateY.state = 1;
    stateY.data = completeData;
  };

  const seventCompletes = oriComplete._completes;
  // ✨✨✨ 接口调用完成时执行的回调函数,无论成功或失败都会执行。
  oriComplete.complete = function () {
    for (var index = 0; index < seventCompletes.length; ++index) {
      seventCompletes[index](); // 顺序执行 _completes 数组中所有函数
    }
    oriComplete._completes = []; // 执行完毕后,清空 _completes 数组
  };

  return oriComplete;
}

function config() {
  const completeData = {};

  let complete = _getComplete();

  // complete = complete || {} 相当于先判断complete是否为undefined,
  // 若为则重新将complete赋值为{},避免后面 a.b 因为 a 为 undefined 报错
  const _complete = (complete = complete || {})._complete;
  if (_complete) {
    complete._complete(completeData);
    delete complete._complete;
  }

  // ✨✨✨ 在这里执行了 complete 事件
  complete && complete(completeData);
}

/**
 * config信息验证后会执行ready方法,
 * 所有接口调用都必须在config接口获得结果之后,
 * config是一个客户端的异步操作,
 * 所以如果需要在页面加载时就调用相关接口,
 * 则须把相关接口放在ready函数中调用来确保正确执行。
 * 对于用户触发时才调用的接口,
 * 则可以直接调用,
 * 不需要放在ready函数中。
 * @param {*} readyFunc
 */
function ready(readyFunc) {
  // 以下为原始代码,难以理解
  // 0 != stateSetting.state
  //   ? readyFunc() // 状态为完成状态,直接执行
  //   : (oriComplete._completes.push(readyFunc),
  //     !isMicromessenger && vConfig.debug && readyFunc());

  // 重新写作 if-else 形式
  if (stateSetting.state != 0) {
    // config注入成功,直接执行 readyFunc
    readyFunc();
  } else {
    // ✨✨✨ config未注入成功,推送入complete._completes数组中,待config执行完毕,再执行数组中的事件
    oriComplete._completes.push(readyFunc);

    if (!isMicromessenger && vConfig.debug) {
      // 非微信内置浏览器 且 开启debug,直接执行
      readyFunc();
    }
  }
}

源码抽离

/** complete对象 */
let oriComplete: Record<string, any> = { _completes: [] as Function[] };

/** 获取 complete 对象 */
function _getComplete() {
  const seventCompletes = oriComplete._completes;
  // ✨✨✨ 接口调用完成时执行的回调函数,无论成功或失败都会执行。
  oriComplete.complete = function () {
    for (var index = 0; index < seventCompletes.length; ++index) {
      seventCompletes[index](); // 顺序执行 _completes 数组中所有函数
    }
    oriComplete._completes = []; // 执行完毕后,清空 _completes 数组
  };
  return oriComplete;
}

function config() {
  let complete = _getComplete();
  SDK.initialized = true;
  // ✨✨✨ 在这里执行了 complete 事件
  complete && complete.complete && complete.complete();
}

function readyCallback(func: Function) {
  SDK.initialized ? func && func() : oriComplete._completes.push(func);
}

// use
Sdk.readyCallback(() => {
  console.log('readyCallback: before config');
});
Sdk.config();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值