Web NFC API 让浏览器可以直接与NFC标签交换NDEF消息,而不依赖原生App。不过它的事件驱动模型比较松散,如果不做二次封装,读写逻辑很容易散落在页面代码里。用 jQuery 的 Deferred 把扫描、错误回调和写入流程统一成 Promise 风格,可以显著降低维护成本。下面先分析原生API的工作方式,再给出封装实现。

NDEFReader 的工作模型与封装切入点
Web NFC API 的核心是 NDEFReader 对象。它同时承担扫描和写入两类职责。读取时调用 scan() 方法启动标签监听,之后通过 reading 事件拿到 NFC 消息;写入时直接调用 write() 方法向靠近的标签写入 NDEF 记录。两个流程都依赖 Secure Context,也就是页面必须通过 HTTPS 或 localhost 访问。
在原生写法中,scan() 返回的 Promise 只表示扫描器是否成功启动,真正的标签数据在后续事件里到达。这种两阶段模型如果用传统回调写,会出现回调嵌套和状态不同步的问题。jQuery 的 Deferred 可以把事件转换成可以被 then、done、fail 订阅的结果。例如在 reading 事件触发时 resolve,在 readingerror 或权限拒绝时 reject,调用方就能像处理普通 Ajax 请求一样处理 NFC 读取。
另一个值得封装的地方是 AbortController。原生 scan() 可以接收 signal 参数,用于主动停止扫描。如果不主动 abort,Android Chrome 的 NFC 扫描会持续占用系统资源,有时还会导致页面切换后再次触发旧监听。封装时应该把 controller 暴露给调用方,确保页面 onHide 或 beforeunload 时能清理。
读取NFC标签:封装 scan 与 reading 事件
读取封装的思路是:每次调用创建一个新的 NDEFReader 和 AbortController,启动扫描后监听 reading 事件,解析 NDEFMessage 中的 records。每个 record 包含 recordType、mediaType、data 等字段,data 可能是字符串、ArrayBuffer 或 URL,需要根据类型做一次统一处理。
下面是一个基于 jQuery Deferred 的读取封装。它返回一个 Promise 对象,调用方可以用 done 接收标签内容,用 fail 捕获权限错误。
(function($) {
function parseRecord(record) {
var recordData = record.data;
if (typeof recordData === 'string') {
return recordData;
}
if (recordData instanceof ArrayBuffer) {
return new TextDecoder().decode(recordData);
}
if (recordData && recordData.buffer) {
return new TextDecoder().decode(recordData.buffer);
}
return String(recordData);
}
$.nfcRead = function(options) {
options = options || {};
var dfd = $.Deferred();
if (!('NDEFReader' in window)) {
dfd.reject(new Error('当前浏览器不支持 Web NFC API'));
return dfd.promise();
}
var reader = new NDEFReader();
var controller = new AbortController();
function cleanup() {
controller.abort();
}
reader.scan({ signal: controller.signal }).then(function() {
dfd.notify({ status: 'scanning' });
reader.addEventListener('reading', function(event) {
var serial = event.serialNumber || '';
var records = Array.from(event.message.records).map(function(record) {
return {
recordType: record.recordType,
mediaType: record.mediaType,
data: parseRecord(record)
};
});
cleanup();
dfd.resolve({
serialNumber: serial,
records: records
});
});
reader.addEventListener('readingerror', function() {
cleanup();
dfd.reject(new Error('读取标签时发生错误'));
});
}).catch(function(err) {
dfd.reject(err);
});
return dfd.promise();
};
})(jQuery);
调用方式也非常直接。页面上放一个 <button id="readNfc">读取 NFC 标签</button>,然后通过 click 事件触发 $.nfcRead。progress 回调可以用来更新界面状态,例如提示用户把标签贴近设备背面。
$('#readNfc').on('click', function() {
var $status = $('#status');
$status.text('请将 NFC 标签贴近手机背面');
$.nfcRead()
.progress(function(info) {
$status.text('正在扫描...');
})
.done(function(result) {
console.log('序列号:' + result.serialNumber);
result.records.forEach(function(item) {
console.log(item.recordType + ': ' + item.data);
});
$status.text('读取成功,共 ' + result.records.length + ' 条记录');
})
.fail(function(err) {
$status.text('读取失败:' + err.message);
});
});
这里要特别注意 reading 事件并不携带标签的唯一 ID,仅提供 serialNumber 时也要看设备实现。部分 Android 设备对序列号做了随机化处理,不能作为稳定的标签身份凭证。如果业务需要防伪或唯一标识,应该把业务 ID 写入 NDEF 数据区,而不是依赖硬件序列号。
写入NFC标签:用 jQuery Deferred 包装 write 方法
相比读取,写入操作的封装更接近普通异步方法。NDEFReader 的 write() 接受 NDEFMessageSource 参数,可以是一串 NDEFRecord 或完整的 NDEFMessage。写入过程中标签必须保持在感应区域内,一旦距离过远或写入失败,Promise 会进入 rejected 状态。
封装写入时需要考虑 NDEF 记录的构造方式。最简单的文本记录可以直接写成 { records: [{ recordType: 'text', data: 'hello nfc' }] }。如果希望写入 URL 类型,可以使用 recordType: 'url',data 写成 https://ipipp.com。此外,write() 支持 overwrite 选项,默认会覆盖已有标签内容;如果标签是只读的,写入会抛错。
(function($) {
$.nfcWrite = function(message, options) {
options = options || {};
var dfd = $.Deferred();
if (!('NDEFReader' in window)) {
dfd.reject(new Error('当前浏览器不支持 Web NFC API'));
return dfd.promise();
}
var reader = new NDEFReader();
var writeOptions = {
overwrite: options.overwrite !== false
};
if (options.signal) {
writeOptions.signal = options.signal;
}
reader.write(message, writeOptions).then(function() {
dfd.resolve({ success: true });
}).catch(function(err) {
dfd.reject(err);
});
return dfd.promise();
};
})(jQuery);
写入按钮的调用逻辑一般会先构造一条文本记录,再调用 $.nfcWrite。为了让用户知道需要持续贴近设备,可以在调用前显示提示,并在 done 回调里提示写入完成。
$('#writeNfc').on('click', function() {
var message = {
records: [
{ recordType: 'text', data: 'jQuery NFC demo' }
]
};
$('#status').text('请将 NFC 标签贴近设备,写入完成前不要移开');
$.nfcWrite(message)
.done(function() {
$('#status').text('写入成功');
})
.fail(function(err) {
$('#status').text('写入失败:' + err.message);
});
});
实际测试中,写入 URL 类型比写入 text 类型更容易触发系统对标签格式的校验。如果写入文本没有问题但写入 URL 失败,可以检查标签的容量和是否被厂商锁定。Web NFC API 目前无法解除标签写保护,也无法格式化空白标签为 NDEF 格式,这些限制需要在产品设计时提前评估。
权限、兼容性与工程实践
Web NFC API 在 Chrome for Android 上启用,桌面端和 iOS Safari 暂不支持。调用 scan() 或 write() 前应该先检测 window.NDEFReader 是否存在,同时捕获 NotAllowedError 和 NotSupportedError。权限拒绝通常发生在用户关闭了 NFC 权限或页面不是顶层浏览上下文时,错误消息因版本而异,建议用错误 name 而不是 message 做分支判断。
在工程化封装时,建议把 NFC 能力抽象成一个独立模块,jQuery 只是统一 Promise 风格的壳。模块内部维护扫描状态、事件监听和 AbortController,避免每次点击都创建不可控的监听器。例如可以在页面上只实例化一个 NDEFReader,通过状态机管理 idle、scanning、reading、writing 四种状态,这样多个按钮共享一个读取器,不会出现多个 scan 同时运行的冲突。
var NfcService = (function() {
var state = 'idle';
var reader = null;
var controller = null;
function ensureReader() {
if (!('NDEFReader' in window)) {
throw new Error('浏览器不支持 Web NFC API');
}
if (!reader) {
reader = new NDEFReader();
}
return reader;
}
function read() {
if (state !== 'idle') {
return $.Deferred().reject(new Error('当前有 NFC 操作正在进行')).promise();
}
state = 'scanning';
controller = new AbortController();
var dfd = $.Deferred();
var r = ensureReader();
r.scan({ signal: controller.signal }).catch(function(err) {
state = 'idle';
dfd.reject(err);
});
r.addEventListener('reading', function(event) {
state = 'idle';
dfd.resolve(event.message);
});
return dfd.promise();
}
function write(message) {
if (state !== 'idle') {
return $.Deferred().reject(new Error('当前有 NFC 操作正在进行')).promise();
}
state = 'writing';
var dfd = $.Deferred();
var r = ensureReader();
r.write(message).then(function() {
state = 'idle';
dfd.resolve({ success: true });
}).catch(function(err) {
state = 'idle';
dfd.reject(err);
});
return dfd.promise();
}
function stop() {
if (controller) {
controller.abort();
controller = null;
}
state = 'idle';
}
return { read: read, write: write, stop: stop };
})();
上面的服务层示例展示了状态机如何阻止重复扫描。这种结构比把逻辑全部塞进 $.fn 插件更清晰,jQuery 层只负责 DOM 事件绑定和 UI 反馈。另一个必须处理的是页面可见性变化,用户按 Home 键或切到其他 App 后,Web NFC 扫描可能被系统挂起,回到页面时旧监听可能失效。可以在 visibilitychange 事件里调用 NfcService.stop(),保证状态干净。
安全方面,Web NFC API 只允许读取和写入 NDEF 格式数据,无法访问银行卡支付、身份证件等底层协议。对敏感写入操作,建议在服务端生成签名或校验值,前端只负责传输 NDEF 数据,避免把核心逻辑暴露在客户端脚本里。
最终把上述封装应用在设备管理、现场巡检、智能标签展示等场景中,Web 应用可以直接配合低成本 NFC 标签完成数据采集。相比原生开发,jQuery + Web NFC 方案牺牲了部分底层能力,但换来了更快的迭代速度和跨平台部署能力,适合对实时性要求不高的内部工具和原型验证。
Web NFC APIjQueryNFC标签读写修改时间:2026-10-05 21:30:48