iOS 客户端视角扫盲:WKWebView 里 window、messageHandlers 与 Native 回调到底怎么工作?

WKWebView 里 H5 的 window 到底是什么?一文讲清 iOS 与 H5 的通信本质

最近在整理一套 WKWebView Demo,里面实现了一个很典型的双向通信场景:

  • H5 点击按钮
  • 调用 Native
  • Native 组装一份 JSON
  • 再把 JSON 回传给 H5
  • 最后 H5 把 JSON 字符串展示到页面上

代码跑通后,我发现一个很适合给客户端同学"扫盲"的问题是:

H5 里经常出现的 window,到底是什么?为什么它能调到 Native?

这篇文章就从 iOS 客户端视角,把这件事讲清楚。


一、先说结论:window 是网页 JS 的全局对象

如果你是客户端同学,可以先记住一句话:

window = 当前网页 JavaScript 运行时的全局对象。

你可以把它理解成:

  • JS 世界里的"最外层对象"
  • 当前网页运行环境的全局上下文
  • 网页里很多能力的统一入口

比如这些前端常见能力,很多都挂在 window 上:

js 复制代码
window.location
window.alert()
window.setTimeout()
window.localStorage

而在 WKWebView 环境里,iOS 还会通过 WebKit 给 H5 注入额外能力,比如:

js 复制代码
window.webkit.messageHandlers

这就是 H5 能调用 Native 的关键入口。


二、客户端同学最常见的一句代码,到底是什么意思?

WKWebView 场景里,我们经常会看到这样的 H5 代码:

js 复制代码
window.webkit.messageHandlers.nativeBridge.postMessage({
  action: "getNativeJSON",
  from: "h5"
});

如果第一次看到,很容易一脸懵。

其实它可以拆成 5 层:

  • window
    • 当前网页的全局对象
  • webkit
    • WebKit 注入给当前页面的一层对象
  • messageHandlers
    • Native 注册给 H5 的消息通道集合
  • nativeBridge
    • 其中一条具体通道,名字由 Native 注册时决定
  • postMessage(...)
    • H5 发消息给 Native 的调用方式

所以整句话翻译成客户端语言就是:

"H5 通过 WebKit 提供的 nativeBridge 通道,把一段消息发给 iOS。"


三、为什么 H5 能直接写 window.webkit.messageHandlers

因为这个对象不是 H5 自己凭空写出来的,而是 Native 提前注册好的

在 iOS 侧,我们通常会这样注册:

swift 复制代码
userContentController.add(
    WeakScriptMessageHandler(delegate: self),
    name: pageConfiguration.jsBridgeName
)

如果 pageConfiguration.jsBridgeName = "nativeBridge",那么 H5 就能在 JS 环境里访问:

js 复制代码
window.webkit.messageHandlers.nativeBridge

也就是说:

  • Native 注册了一个名为 nativeBridge 的 handler
  • WebKit 把这个 handler 暴露给了 JS 环境
  • H5 就能通过 postMessage 调用 Native

这就是 H5 -> Native 的通信本质。


四、window 不是"桥",它只是"全局对象"

这里很容易有个误区:

很多客户端同学会把 window 直接理解成"JSBridge"。

这个说法不够准确。

更准确的理解应该是:

  • window 是网页全局对象
  • window.webkit.messageHandlers.xxx 才是 Native 暴露给 H5 的桥接入口

也就是说:

window 是容器,桥接能力只是挂在这个容器上的一部分属性。

就像你在 Swift 里写:

swift 复制代码
app.network.request()

你不会说 app 就等于网络层本身。

同理:

js 复制代码
window.webkit.messageHandlers.nativeBridge.postMessage(...)

也不是说 window 就等于 Native Bridge,而是说 Bridge 被挂在 window 这棵对象树下面。


五、从 iOS 客户端角度,怎么类比最好理解?

如果用客户端同学更熟悉的方式来类比,可以这么记:

1. window 类似 JS 世界的根对象

类似于你有一个全局入口对象:

swift 复制代码
app

然后一层层往下找:

swift 复制代码
app.webkit.messageHandlers.nativeBridge.postMessage(...)

只是 JS 里这个根对象叫 window

2. messageHandlers 类似 Native 暴露的能力列表

你可以把它想成一个"功能注册中心"。

比如 Native 注册了:

  • nativeBridge
  • loginBridge
  • routerBridge

那么 H5 就可以访问:

js 复制代码
window.webkit.messageHandlers.nativeBridge
window.webkit.messageHandlers.loginBridge
window.webkit.messageHandlers.routerBridge

3. postMessage 类似一次 RPC 调用

H5 通过 postMessage 发送一个消息对象,Native 收到后再根据 action 去分发:

js 复制代码
{ action: "getNativeJSON", from: "h5" }

这跟客户端里常见的"路由分发"或者"协议分发"其实很像。


六、H5 调 Native 的本质,其实就是"发消息"

我们这次 Demo 里,H5 请求 Native JSON 的代码大概是这样:

js 复制代码
function requestNativeJSON() {
  window.webkit.messageHandlers.nativeBridge.postMessage({
    action: "getNativeJSON",
    from: "h5"
  });
}

这里不是"直接调用 Swift 方法"。

H5 做的事情本质上是:

  • 往 Native 发一条消息
  • 消息里带上 action 和参数
  • Native 收到后自己决定怎么处理

iOS 侧会在 WKScriptMessageHandler 里接收:

swift 复制代码
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    guard message.name == pageConfiguration.jsBridgeName else { return }

    if let body = message.body as? [String: Any], let action = body["action"] as? String {
        switch action {
        case "getNativeJSON":
            sendNativeJSONToH5()
        default:
            break
        }
    }
}

所以从本质上说:

H5 -> Native 不是"函数直调",而是"消息通信 + Native 分发处理"。


七、那 Native 又是怎么把结果回传给 H5 的?

这就是另一条方向:

Native -> H5

在 iOS 里最常见的方式是:

swift 复制代码
webView.evaluateJavaScript(script)

比如这次 Demo 里,Native 会执行这样一段脚本:

swift 复制代码
let script = "window.renderNativeJSONFromNative(\(jsonObjectString));"
webView.evaluateJavaScript(script)

它的含义是:

  • Native 主动执行一段 JS
  • 调用页面里的 window.renderNativeJSONFromNative(...)
  • 把 JSON 数据作为参数传给 H5

而 H5 这边提前定义了这个函数:

js 复制代码
function renderNativeJSONFromNative(payload) {
  var resultNode = document.getElementById('nativeJSONResult');
  resultNode.textContent = JSON.stringify(payload, null, 2);
}

这就形成了完整闭环:

  • H5 发消息给 Native
  • Native 收到并处理
  • Native 再执行 JS 回调 H5
  • H5 更新页面展示结果

八、所以双向通信可以记成两句话

这个特别适合团队分享时说:

H5 调 Native

js 复制代码
window.webkit.messageHandlers.xxx.postMessage(...)

意思是:

H5 通过 WebKit 注入的消息通道,把消息发给 Native。

Native 回调 H5

swift 复制代码
webView.evaluateJavaScript("window.xxx(...)")

意思是:

Native 主动执行页面里的 JS 方法,把结果传回给 H5。

如果再浓缩一下,就是:

  • postMessage:H5 调 Native
  • evaluateJavaScript:Native 调 H5

九、为什么很多 JSBridge 设计都会围绕 window 展开?

因为 window 是网页运行时最自然的全局入口。

前端代码如果想暴露一个全局函数,最简单的方式就是挂到 window 上:

js 复制代码
window.renderNativeJSONFromNative = function(payload) { ... }

而 Native 想让 H5 使用某些能力,也通常会借助 WebKit 注入到这个全局环境里。

所以你会看到很多 JSBridge 方案,不管名字怎么变化,底层都绕不开两件事:

  • H5 侧通过 window 找到桥接入口
  • Native 侧通过执行 JS 调用 window 上的方法

window 不是桥本身,但它往往是桥接能力的"落点"。


十、客户端同学在理解 window 时,最容易踩的几个误区

误区 1:window 就是 iOS 注入的对象

不是。

window 本来就是浏览器 / WebView 里的全局对象。

iOS 注入的是它下面的某些属性,例如:

js 复制代码
window.webkit.messageHandlers

误区 2:H5 是在直接调 Swift 方法

也不是。

H5 只是通过 postMessage 发消息给 Native,Native 收到之后再自己分发。

误区 3:Native 回调 H5 只能传字符串

不完全对。

本质上是 Native 拼一段 JS 代码给 WebView 执行。你可以传字符串,也可以把 JSON 序列化后拼进 JS 调用里。

不过在工程实践里,要特别注意:

  • 转义
  • 注入安全
  • 数据格式统一
  • 大对象传输成本

十一、如果站在工程实践角度,这套机制还能怎么升级?

这次 Demo 是一个"扫盲版最小闭环",但线上业务一般会继续升级成标准协议。

比如从:

js 复制代码
{ action: "getNativeJSON" }

升级成:

js 复制代码
{
  module: "user",
  method: "getProfile",
  params: { userId: "10001" },
  callbackId: "cb_123"
}

然后 Native 统一返回:

json 复制代码
{
  "callbackId": "cb_123",
  "code": 0,
  "message": "success",
  "data": {
    "id": "10001",
    "name": "Oliver"
  }
}

这样就能支持:

  • 多模块分发
  • 异步回调
  • Promise 封装
  • 错误码体系
  • 统一埋点与日志

这时候你再回头看 window,就会更清楚:

它只是 JS 世界的入口,真正的重点是桥接协议怎么定义。


十二、最后用一句最容易记住的话收尾

如果你是客户端同学,只要记住下面这三句,基本就不会再被 window 绕晕:

  • window 是网页 JS 的全局对象
  • window.webkit.messageHandlers.xxx.postMessage(...) 是 H5 调 Native
  • evaluateJavaScript("window.xxx(...)") 是 Native 回调 H5

结尾

以前我第一次看 window.webkit.messageHandlers.nativeBridge.postMessage(...) 的时候,也会觉得这玩意儿像"黑魔法"。

但把它拆开看,本质并不复杂:

  • window 是 JS 全局对象
  • webkit.messageHandlers 是 Native 暴露给 H5 的消息通道
  • postMessage 是 H5 发消息
  • evaluateJavaScript 是 Native 回调

一旦把这四层关系理顺,WKWebView 里的绝大多数桥接代码,读起来都会清晰很多。

如果你最近也在整理 WKWebViewJSBridge 或者混合栈相关知识,希望这篇扫盲随笔能帮你少绕几个弯。

源码

相关推荐
妙码生花15 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(四十五):前端远程下拉输入组件
前端·javascript·vue.js
Listen·Rain17 小时前
AGENTS.md — Vue 3 Frontend Development
前端·javascript·vue.js
BioRunYiXue18 小时前
技术干货 | LiP-MS全流程解析:从实验设计到数据分析
大数据·前端·javascript·人工智能·算法·数据挖掘·数据分析
南风知我意啊19 小时前
Vue3图片缩放拖拽组件全攻略
前端·javascript·vue.js
布兰妮甜20 小时前
暗黑模式一键切换完整方案(CSS 变量 + 本地存储)
javascript·css·web开发·用户体验·前端工程化
早睡早起身体好12320 小时前
Vue 3 路由与懒加载入门:从零完成一个迷你学习中心
javascript·vue.js·学习
猫猫不是喵喵.21 小时前
Vue 3 项目创建指南:基于 Vue CLI 与 Vite 的两种方式
前端·javascript·vue.js
张元清21 小时前
React useThrottle Hook:节流值与回调(2026)
javascript·react.js