浏览器原理 · 25/51
1. addEventListener 第三个参数 2. addEventListener 与 attachEvent 区别 3. 浏览器兼容性测试与内核 4. 浏览器兼容性问题 5. 浏览器内核与引擎 6. 浏览器图层创建条件 7. 浏览器多进程架构 8. 浏览器渲染机制 9. 浏览器存储方案 10. 浏览器版本检测方法 11. children 与 childNodes 区别 12. 常见浏览器兼容性问题 13. Chrome 页面进程数量 14. 坐标系统对比 15. 多标签页通讯方案 16. 删除 Cookie 17. 自定义事件 18. DOM 事件处理方式演进 19. 元素尺寸属性对比 20. DOM 节点操作 21. DOM 事件机制 22. addEventListener 与 attachEvent 的区别 23. 获取页面所有复选框 24. HTMLCollection 与 NodeList 区别 25. Hybrid 应用开发 26. 强缓存命中机制 27. 浏览器缓存机制 28. 页面编码与资源编码不一致处理 29. jQuery 事件绑定方法对比 30. Input 点击触发的事件顺序 31. JavaScript 浏览器兼容性问题 32. jQuery 多事件绑定实现 33. JSBridge 原理 34. 链接点击后 Hover 失效解决方案 35. 减少重绘和回流的性能优化 36. 移动端 300ms 点击延迟问题 37. 移动端视口配置 38. 移动端点击穿透问题解决 39. 移动端兼容性问题 40. JSBridge 原理与实现 41. 移动端 1px 像素问题解决方案 42. 浏览器渲染流程 43. 页面加载完成事件对比 44. Offset、Scroll、Client 属性对比 45. 同源策略与跨域解决方案 46. Script 标签位置对页面加载的影响 47. Service Worker 与 PWA 48. 存储方案对比:Cookie、Storage、IndexedDB 49. 强缓存默认时间 50. URL 到页面显示的完整过程 51. V8 引擎 JavaScript 执行过程

Hybrid 应用开发

Hybrid App 原理、JSBridge 通信机制及实现

问题

什么是 Hybrid App?前端如何与 Native 进行通信?

解答

什么是 Hybrid App

Hybrid App 是混合应用,结合了 Web 技术和 Native 技术。核心是通过 WebView 加载 H5 页面,并通过 JSBridge 实现 JS 与 Native 的双向通信。

┌─────────────────────────────┐
│         Native App          │
│  ┌───────────────────────┐  │
│  │       WebView         │  │
│  │  ┌─────────────────┐  │  │
│  │  │    H5 页面       │  │  │
│  │  └─────────────────┘  │  │
│  └───────────────────────┘  │
│            ↕                │
│        JSBridge             │
│            ↕                │
│      Native 能力            │
└─────────────────────────────┘

JS 调用 Native 的方式

1. URL Scheme 拦截

// H5 端发起请求
function callNative(action, params) {
  const url = `myapp://action=${action}&params=${encodeURIComponent(JSON.stringify(params))}`;
  
  // 方式一:创建 iframe
  const iframe = document.createElement('iframe');
  iframe.style.display = 'none';
  iframe.src = url;
  document.body.appendChild(iframe);
  setTimeout(() => document.body.removeChild(iframe), 100);
  
  // 方式二:直接跳转(不推荐,会中断页面)
  // location.href = url;
}

// 调用示例:打开相机
callNative('openCamera', { quality: 'high' });

2. 注入全局对象

Native 向 WebView 注入全局对象,JS 直接调用:

// Android 注入的对象名通常是 AndroidBridge
// iOS 注入的对象在 window.webkit.messageHandlers

// 封装统一调用方法
function callNative(action, params) {
  // Android
  if (window.AndroidBridge) {
    window.AndroidBridge.postMessage(JSON.stringify({ action, params }));
    return;
  }
  
  // iOS WKWebView
  if (window.webkit?.messageHandlers?.nativeBridge) {
    window.webkit.messageHandlers.nativeBridge.postMessage({ action, params });
    return;
  }
  
  console.warn('Not in Hybrid environment');
}

Native 调用 JS

Native 直接执行 JS 代码:

// H5 端暴露全局方法供 Native 调用
window.JSBridge = {
  // Native 调用此方法传递数据
  onNativeCallback(data) {
    console.log('Received from Native:', data);
    // 触发对应的回调
    const { callbackId, result } = JSON.parse(data);
    if (this.callbacks[callbackId]) {
      this.callbacks[callbackId](result);
      delete this.callbacks[callbackId];
    }
  },
  
  callbacks: {},
  callbackId: 0,
  
  // 调用 Native 并注册回调
  call(action, params, callback) {
    const id = ++this.callbackId;
    this.callbacks[id] = callback;
    
    callNative(action, { ...params, callbackId: id });
  }
};

完整 JSBridge 封装

class JSBridge {
  constructor() {
    this.callbacks = {};
    this.callbackId = 0;
    this.handlers = {};
    
    // 暴露给 Native 调用的入口
    window._handleNativeMessage = this.handleNativeMessage.bind(this);
  }
  
  // JS 调用 Native
  callNative(action, params = {}) {
    return new Promise((resolve, reject) => {
      const callbackId = ++this.callbackId;
      
      // 设置超时
      const timer = setTimeout(() => {
        delete this.callbacks[callbackId];
        reject(new Error('JSBridge call timeout'));
      }, 10000);
      
      this.callbacks[callbackId] = (result) => {
        clearTimeout(timer);
        if (result.success) {
          resolve(result.data);
        } else {
          reject(new Error(result.message));
        }
      };
      
      const message = JSON.stringify({ action, params, callbackId });
      
      // 根据环境选择通信方式
      if (window.AndroidBridge) {
        window.AndroidBridge.postMessage(message);
      } else if (window.webkit?.messageHandlers?.nativeBridge) {
        window.webkit.messageHandlers.nativeBridge.postMessage(message);
      } else {
        // 降级到 URL Scheme
        this.callByScheme(action, params, callbackId);
      }
    });
  }
  
  // URL Scheme 方式
  callByScheme(action, params, callbackId) {
    const url = `myapp://${action}?params=${encodeURIComponent(
      JSON.stringify({ ...params, callbackId })
    )}`;
    const iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    iframe.src = url;
    document.body.appendChild(iframe);
    setTimeout(() => iframe.remove(), 100);
  }
  
  // 处理 Native 消息
  handleNativeMessage(messageStr) {
    try {
      const { type, callbackId, data } = JSON.parse(messageStr);
      
      if (type === 'callback' && this.callbacks[callbackId]) {
        // Native 回调 JS
        this.callbacks[callbackId](data);
        delete this.callbacks[callbackId];
      } else if (type === 'event' && this.handlers[data.event]) {
        // Native 主动推送事件
        this.handlers[data.event].forEach(fn => fn(data.payload));
      }
    } catch (e) {
      console.error('Parse native message error:', e);
    }
  }
  
  // 注册事件监听
  on(event, handler) {
    if (!this.handlers[event]) {
      this.handlers[event] = [];
    }
    this.handlers[event].push(handler);
  }
  
  off(event, handler) {
    if (this.handlers[event]) {
      this.handlers[event] = this.handlers[event].filter(fn => fn !== handler);
    }
  }
}

// 使用示例
const bridge = new JSBridge();

// 调用 Native 方法
bridge.callNative('getDeviceInfo').then(info => {
  console.log('设备信息:', info);
});

// 监听 Native 事件
bridge.on('networkChange', (status) => {
  console.log('网络状态变化:', status);
});

常见 Hybrid 方案对比

方案原理性能开发成本
WebView + JSBridge原生 WebView 加载 H5一般
React NativeJS 驱动原生组件较好
Flutter自绘引擎
小程序双线程架构较好

关键点

  • 通信方式:URL Scheme 拦截、注入全局对象、evaluateJavaScript
  • JSBridge:封装双向通信,支持回调和事件监听
  • 异步处理:通过 callbackId 匹配请求和响应
  • 兼容性:需要区分 Android/iOS,做好降级处理
  • 安全性:校验 URL Scheme 来源,防止恶意调用