yanyue404 / blog

Just blog and not just blog.
https://yanyue404.github.io/blog/
Other
90 stars 13 forks source link

你不知道的 Web API #246

Open yanyue404 opened 1 year ago

yanyue404 commented 1 year ago

postMessage:H5 与 App 交互

H5 调 APP

  1. 由 APP 向 H5 注入一个全局 js 对象,然后在 H5 直接访问这个对象,如 JSBridge
  2. window 后面接的方法必须使用 APP 定义好的监听方法,如:postMessage
  3. 监听方法 postMessage 后面接具体逻辑调用的方法或直接传参 (参数必须和 APP 那边确定好,两边保持一致性,无论是参数个数还是参数类型)
  4. 向 app 发送指令,当 app 处理完指令后一般会回调全局约定好的回调方法。通过这种形式与 app 进行交互
const getUserInfo = (callback) => {
  let timestamp = +new Date(); //时间戳
  // 回调函数
  JSBridge.callback__userInfo.push({
    callback,
    timestamp,
  });
  JSBridge.postMessage({
    action: "userInfo",
    timestamp: +new Date(),
  });
};

/**
 * 向app发送指令,当app处理完指令后一般会回调全局的 fromApp()方法。通过这种形式与app进行交互
 * @param msg   指令(json对象)
 */
const postMessage = (msg) => {
  console.log("action:", JSON.stringify(msg));
  if (!this.isInApp()) {
    console.warn("异常提示:请在 App 内使用!");
    return; //为防止在非app环境下调用,如果发现不是app环境,则结束代码
  }
  try {
    let plat =
      window.navigator.platform.toLowerCase() || window.navigator.platform;
    if (plat == "iphone" || plat == "iPhone") {
      window.webkit.messageHandlers.iphone.postMessage(msg);
    } else {
      window.android.postMessage(msg);
    }
  } catch (err) {
    console.log(err);
  }
};

APP 调 H5

H5 这边实现要在全局 window 上定义好 APP 要回调的方法

function fromApp(action, msg) {
  let res = null; // msg转换后的json对象
  switch (action) {
    case "userInfo":
      res = eval("(" + msg + ")"); //转化为json对象
      JSBridge.userInfo = res;
      for (let i = 0; i < JSBridge.callback__userInfo.length; i++) {
        const task = JSBridge.callback__userInfo[i];
        if (task.callback && task.timestamp === res.reqTime) {
          task.callback(res);
          JSBridge.callback__userInfo.splice(i, 1); //将该位置上的元素移除
        }
      }
      break;
  }
}

navigator.sendBeacon: 页面关闭时,前端上传监控数据

通过 HTTP POST 请求,将少量数据使用异步的方式,发送到服务端。

function reportEvent() {
  const url = "http://api.wangxiaokai.vip/test";
  const data = JSON.stringify({
    time: performance.now(),
  });

  navigator.sendBeacon(url, data);
}

document.addEventListener("visibilitychange", function () {
  if (document.visiblityState === "hidden") {
    reportEvent();
  }
});

发送的时机

浏览器端自动判断合适的时机进行发送

是否会产生阻塞或影响页面性能

不会产生阻塞,影响当前页面的卸载。 不影响下个新页面的加载,不存在性能问题。 另外,数据传输可靠。

语法

navigator.sendBeacon(url, data);

参数解析

返回值

当浏览器将数据成功加入传输队列时,sendBeacon 方法会返回 true,否则返回 false。

注意返回值的时机:成功加入传输队列,而不是服务端的处理成功后的返回。

缺点

JS new Function 语法

创建一个函数的底层方法,支持函数体的数据格式是字符串,在模板引擎类似场景下有不可替代的作用,异常强大。

<body>
  <template id="template">
    <h2>${title}</h2>
    ${data.map(function (obj, index) { return `
    <p>文章:${obj.article}</p>
    <p>作者:${obj.author}</p>
    `; }).join('')}
  </template>
</body>
<script>
  String.prototype.interpolate = function (params) {
    console.log("innerHTML 内容:", this);

    const names = Object.keys(params);
    const vals = Object.values(params);

    return new Function(...names, `return \`${this}\`;`)(...vals);
  };

  const html = template.innerHTML.interpolate({
    data: [
      {
        article: "文章标题1",
        author: "张鑫旭",
      },
      {
        article: "文章标题2",
        author: "CSS新世界",
      },
    ],
    title: "例子演示",
  });

  console.log(html);
</script>

输出结果:

/* 
        <h2>例子演示</h2>

        <p>文章:文章标题1</p>
        <p>作者:张鑫旭</p>

        <p>文章:文章标题2</p>
        <p>作者:CSS新世界</p> */

IOS 返回不刷新


/**
 * 缓存静态方法的value

 * @param {*} fn  fn 的参数中不能使用 引用类型
 */
function cacheStaticFn(fn) {
  const cacheMap = new Map()
  return (...args) => {
    let cacheKey = args.join('-')
    if (!cacheMap.has(cacheKey)) {
      cacheMap.set(cacheKey, fn(...args))
    }
    return cacheMap.get(cacheKey)
  }
}

let isIos = cacheStaticFn(() => {
  let browserRule = /^.*((iPhone)|(iPad)|(Safari))+.*$/
  return browserRule.test(navigator.userAgent)
})

// 当ios后退刷新时
async function whenIosBack(callback) {
  if (isIos()) {
    window.onpageshow = (event) => {
      if (event.persisted) {
        callback()
      }
    }
    return true
  }
  return false
}

function handleRefresh(callback) {
  if (!whenIosBack(callback)) {
    document.addEventListener('visibilitychange', () => {
      if (!document.hidden && !document.webkitHidden && !document.mozHidden) {
        callback()
      }
    })
  }
}

参考链接