Google I/O 2019 extend 成都站
DOM 与 BOM
延迟加载
<img src="path/to/image" loading="lazy" />
<iframe src="path/to/page" loading="lazy"></iframe>
loading
属性值
lazy
延迟加载eager
立即加载auto
浏览器自主判断加载策略(默认值)
loading
属性是早期提案lazyload
属性的代替- 将在 Chrome 76 发布
门户框架
主页面
<portal src="path/to/page"></portal>
const portal = document.querySelector('portal');
portal
.activate({ data: { key: 'value' } })
.then(() => portal.postMessage({ hello: 'portal' }));
子页面
window.addEventListener('portalactivate', event => {
console.log(event.data); // {key: 'value'}
const portal = event.adoptPredecessor(); // 主页面
document.body.append(portal);
});
window.addEventListener('message', event =>
console.log(
event.data, // {hello: 'portal'}
window.portalHost // 主页面 window 对象
)
);
分享网页
document.querySelector('button.share').addEventListener('click', async () => {
const data = { title: 'Hello', text: 'World', url: 'https://web.dev/' };
if (!navigator.canShare(data)) return;
await navigator.share(data);
console.log('Done!');
});
- 网页由 HTTPS 打开
- Android Chrome 61+
- 分享调用在用户操作回调栈中发起
- polyfill
在 PWA 的 manifest.json
中多加一段
即可成为分享目标(Chrome 71+)
{
"share_target": {
"action": "/path/to/share-target/",
"method": "GET",
"enctype": "application/x-www-form-urlencoded",
"params": {
"title": "your_title_key",
"text": "your_text_key",
"url": "you_url_key"
}
}
}
形状检测
文字
const textDetector = new TextDetector();
const texts = await textDetector.detect(image);
texts.forEach(text => console.log(text));
条码
const barcodeDetector = new BarcodeDetector({
formats: [
'aztec',
'code_128',
'code_39',
'code_93',
'codabar',
'data_matrix',
'ean_13',
'ean_8',
'itf',
'pdf417',
'qr_code',
'upc_a',
'upc_e'
]
});
const barcodes = await barcodeDetector.detect(image);
barcodes.forEach(barcode => console.log(barcode));
人脸
const faceDetector = new FaceDetector({
maxDetectedFaces: 5,
fastMode: false
});
const faces = await faceDetector.detect(image);
faces.forEach(face => console.log(face));
更多草案 API
HTTP
同站 Cookie
# 站内跳转带 cookie
Set-Cookie: a=1; SameSite=Strict
# 跳转到本站带 cookie
Set-Cookie: b=2; SameSite=Lax
# 不限制 cookie
Set-Cookie: c=3; SameSite=None
Web 包协议
JavaScript
V8 提速、降费
原生类型增强
const integer = 14_0000_0000; // 1400000000
const bigInt = BigInt(Number.MAX_SAFE_INTEGER) + 1n; // 9007199254740992n
[
[1, 2],
[3, 4]
].flat(); // [1, 2, 3, 4]
Object.fromEntries([
['a', 1],
['b', 2]
]); // {a: 1, b: 2}
console.log(...'FCC成都社区,FCC中文社区'.matchAll(/\w+([^,]+)/g));
// ['FCC成都社区', '成都社区'] ['FCC中文社区', '中文社区']
统一全局对象
const globalThis = (() => {
// 浏览器主线程
if (typeof window !== 'undefined') return window;
// Web worker 线程
if (typeof self !== 'undefined') return self;
// Node.JS
if (typeof global !== 'undefined') return global;
// 其它运行时的非严格模式
if (typeof this !== 'undefined') return this;
})();
- Stage-3
- Chrome 71、Firefox 65、Safari 12.1
国际化 API
相对时间
const formatter = new Intl.RelativeTimeFormat('zh-CN');
console.log(formatter.format(-1, 'day')); // 1天前
console.log(formatter.formatToParts(-1, 'day'));
/*
[
{
"type": "integer",
"value": "1",
"unit": "day"
},
{
"type": "literal",
"value": "天前"
}
]
*/
- Stage-3
- Chrome 71、Firefox 65
短语列表
const and = new Intl.ListFormat('zh-CN', { type: 'conjunction' }),
or = new Intl.ListFormat('zh-CN', { type: 'disjunction' });
const list = ['GDG', 'FCC', 'ThoughtWorks'];
console.log(
and.format(list), // 'GDG、FCC和ThoughtWorks'
or.format(list) // 'GDG、FCC或ThoughtWorks'
);
- Stage-3
- Chrome 72、Opera 60
Promise
.allSettled()
await Promise.allSettled([
fetch('https://fcc-cd.dev/'),
fetch('https://google.com/') // 没有科学上网
]);
console.log('所有请求已响应,部分可能失败');
.any()
const fastest = await Promise.any([
fetch('https://cdn.jsdelivr.net/npm/web-cell/dist/web-cell.min.js'),
fetch('https://unpkg.com/web-cell/dist/web-cell.min.js')
]);
console.log(fastest.url);
类实例成员
class Example {
publicField = 1;
#privateField = 2;
publicMethod = () =>
console.log(
this.publicField, // 1
this.#privateField // 2
);
}
- Stage-3
- Babel 7
- Node 12
- Chrome 72 / V8 7.2(公开成员)
- Chrome 74 / V8 7.4(私有成员)
垃圾回收 API
提案来源
- Python 2.1+ 标准库 weakref 模块
- JS Rhino 引擎可借用 Java WeakReference 类
- 动态检测工具 Frida 的 JS API
WeakRef
const URI = 'path/to/file';
const file = await (await fetch(URI)).blob();
const ref = new WeakRef(file),
cache = {};
cache[URI] = ref;
console.log(ref.deref()); // Blob {}
FinalizationGroup
const cleaner = new FinalizationGroup(iterator => {
for (const URI of iterator) delete cache[URI];
});
cleaner.register(image, URI);
Search How to
{
"@context": "http://schema.org",
"@type": "HowTo",
"image": {
"@type": "ImageObject",
"url": "https://example.com/1x1/photo.jpg"
},
"name": "How to tie a tie",
"description": "...",
"totalTime": "PT2M",
"video": {
"@type": "VideoObject",
"name": "Tie a Tie",
"description": "...",
"thumbnailUrl": "https://example.com/photos/photo.jpg",
"contentUrl": "http://www.example.com/videos/123_600x400.mp4",
"embedUrl": "http://www.example.com/videoplayer?id=123",
"uploadDate": "2019-01-05T08:00:00+08:00",
"duration": "P1MT10S"
},
"supply": [
{
"@type": "HowToSupply",
"name": "A tie"
},
{
"@type": "HowToSupply",
"name": "A collared shirt"
}
],
"tool": [
{
"@type": "HowToTool",
"name": "A mirror"
}
],
"step": [
{
"@type": "HowToStep",
"name": "Preparations",
"text": "...",
"image": "https://example.com/1x1/step1.jpg",
"url": "https://example.com/tie#step1"
},
{
"@type": "HowToStep",
"name": "Crossing once",
"text": "...",
"image": "https://example.com/1x1/step2.jpg",
"url": "https://example.com/tie#step2"
},
{
"@type": "HowToStep",
"name": "Second crossing",
"text": "...",
"image": "https://example.com/1x1/step3.jpg",
"url": "https://example.com/tie#step3"
},
{
"@type": "HowToStep",
"name": "Loop in",
"text": "...",
"image": "https://example.com/1x1/step4.jpg",
"url": "https://example.com/tie#step4"
},
{
"@type": "HowToStep",
"name": "Pull and tighten",
"text": "...",
"image": "https://example.com/1x1/step5.jpg",
"url": "https://example.com/tie#step5"
}
]
}
结构化元数据
Material Design 夜间模式
性感谷歌,在线教学
https://developers.google.cn/web/
https://events.google.com/io/codelabs/
https://codelabs.developers.google.com/?cat=Web