浏览器LocalStorage和SharedWorker跨标签页通信-连载2

浏览器LocalStorage和SharedWorker跨标签页通信-连载2

目录

浏览器LocalStorage和SharedWorker跨标签页通信-连载2

一、回过头来说“浏览器”的“多标签页”

1.1、什么是“多标签页”

1.2、“多标签页”的本质

二、LocalStorage及其跨标签页通信

2.1、localStorage的概念:

2.2、localStorage的上下文及其适用范围 :

2.3、localStorage及其父代存储对象Storage:

2.4、localStorage能和导航器的存储管理器StorageManager---navigator.storage通信 :

2.5、localStorage的事件类型StorageEvent : 

2.6、localStorage跨标签页通信的编程

2.6.1、页面中的localStorage相关脚本:

2.6.2、localStorage通信双方的相关脚本支持模块化导入

三、SharedWorker及其跨标签页通信

3.1、SharedWorker已共享的线程

3.2、SharedWorker线程跨标签页通信

3.3、SharedWorker线程的编程(略),参见此链接 


        继续上篇:

浏览器跨标签页通信BroadCast和ServiceWorker-连载1_pulledup的博客-CSDN博客

        边开发项目,就边深入学习、总结和运用:

一、回过头来说“浏览器”的“多标签页”

1.1、什么是“多标签页”

 

1.2、“多标签页”的本质

        之前,一直没有提及“浏览器”的“多标签页”的概念和本质。形象的多,多标签页长这样:

 ◆ 浏览器App实例的pid---浏览器主进程pid

 ◆ 浏览器主进程pid下---可被其产生浏览器子进程pid

 ◆ 浏览器子进程pid---分为后台“非GUI服务子进程”前台“GUI渲染子进程”

 ◆ 1个浏览器前台“GUI渲染子进程”---即:标签页,通常是图形用户界面的形式

 ◆ N个浏览器前台“GUI渲染子进程”的集合---即:多标签页

 ◆ “非GUI服务子进程”---那些个运行在后台,提供服务的子进程,比如各种worker等等、负责与操作系统通信的进程等等、一些必要的常驻内存的守护进程......

 ◆ 它是浏览器“多进程基础架构”的集中体现,提供了安全性和性能优势。

 ◆ 使用它,还可实现“读”、“写”存储的不同客户端操作,防止“共享冲突”

 ◆ 过去是单进程架构;Google最先发起“多进程基础架构”,微软、苹果等相继跟上。

 ◆ 关于多标签页的一些官方技术参考:

windows.WindowType - Mozilla | MDN

https://developer.chrome.com/docs/extensions/reference/windows/#type-WindowType

GitHub - GoogleChrome/chrome-extensions-samples: Chrome Extensions Samples

https://developer.chrome.com/docs/extensions/reference/tabs/#type-Tab

二、LocalStorage及其跨标签页通信

2.1、localStorage的概念:

◆ 顾名思义LocalStorage跨标签页通信,带存储,存储的分区为Storage下的Local Storage

◆ localStorage是原生DOM标准库lib.dom.d.ts中WindowLocalStorage的别名:

C:\Users\Administrator\.vscode\extensions\***\node_modules\typescript\lib\lib.dom.d.ts

interface WindowLocalStorage {
    readonly localStorage: Storage;
}

◆ 它的宿主为:Window

◆ 它是全局变量、拥有全局作用域:

declare var localStorage: Storage;

◆ devTool中的存储分区在:

2.2、localStorage的上下文及其适用范围 :

// ●  来超 : 
// ◆ localStorage及其父代Storage(Storage存储对象) :  
//    隶属“浏览器的”全局Window上下文,因而对所有标签页均适用 : 
interface WindowEventHandlers {
    onafterprint: ((this: WindowEventHandlers, ev: Event) => any) | null;
    onbeforeprint: ((this: WindowEventHandlers, ev: Event) => any) | null;
    onbeforeunload: ((this: WindowEventHandlers, ev: BeforeUnloadEvent) => any) | null;
    ongamepadconnected: ((this: WindowEventHandlers, ev: GamepadEvent) => any) | null;
    ongamepaddisconnected: ((this: WindowEventHandlers, ev: GamepadEvent) => any) | null;
    onhashchange: ((this: WindowEventHandlers, ev: HashChangeEvent) => any) | null;
    onlanguagechange: ((this: WindowEventHandlers, ev: Event) => any) | null;
    onmessage: ((this: WindowEventHandlers, ev: MessageEvent) => any) | null;
    onmessageerror: ((this: WindowEventHandlers, ev: MessageEvent) => any) | null;
    onoffline: ((this: WindowEventHandlers, ev: Event) => any) | null;
    ononline: ((this: WindowEventHandlers, ev: Event) => any) | null;
    onpagehide: ((this: WindowEventHandlers, ev: PageTransitionEvent) => any) | null;
    onpageshow: ((this: WindowEventHandlers, ev: PageTransitionEvent) => any) | null;
    onpopstate: ((this: WindowEventHandlers, ev: PopStateEvent) => any) | null;
    onrejectionhandled: ((this: WindowEventHandlers, ev: PromiseRejectionEvent) => any) | null;
    onunhandledrejection: ((this: WindowEventHandlers, ev: PromiseRejectionEvent) => any) | null;
    onunload: ((this: WindowEventHandlers, ev: Event) => any) | null;
    addEventListener<K extends keyof WindowEventHandlersEventMap>(type: K, listener: (this: WindowEventHandlers, ev: WindowEventHandlersEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
    addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
    removeEventListener<K extends keyof WindowEventHandlersEventMap>(type: K, listener: (this: WindowEventHandlers, ev: WindowEventHandlersEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
    removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
    // ● storage事件(:StorageEvent类型) : 
    onstorage: ((this: WindowEventHandlers, ev: StorageEvent) => any) | null;        
}
interface WindowLocalStorage {
    readonly localStorage: Storage;
}
//

2.3、localStorage及其父代存储对象Storage:


// ◆ Storage存储对象 : ● 此Web存储API接口提供对特定"域/源"的会话(session)或本地存储(local storage)的访问。例如,它允许"添、删、改"存储的数据项
//    :This Web Storage API interface provides access to a particular domain's session or local storage. It allows, for example, the addition, modification, or deletion of stored data items. 
interface Storage {
    // ◇ Returns the number of key/value pairs. 
    readonly length: number;
    //
    // ◇ Removes all key/value pairs, if there are any.
    //
    //    Dispatches a storage event on Window objects holding an equivalent Storage object.
    //
    clear(): void;
    // ◇ Returns the current value associated with the given key, or null if the given key does not exist. 
    getItem(key: string): string | null;
    // ◇ Returns the name of the nth key, or null if n is greater than or equal to the number of key/value pairs.  
    key(index: number): string | null;
    //
    // ◇ Removes the key/value pair with the given key, if a key/value pair with the given key exists.
    //
    //    Dispatches a storage event on Window objects holding an equivalent Storage object.
    //
    removeItem(key: string): void;
    //
    // ◇ Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
    //
    //    Throws a "QuotaExceededError" DOMException exception if the new value couldn't be set. (Setting could fail if, e.g., the user has disabled storage for the site, or if the quota has been exceeded.)
    //
    //    Dispatches a storage event on Window objects holding an equivalent Storage object.
    // 
    setItem(key: string, value: string): void;
    [name: string]: any;
}
declare var Storage: {
    prototype: Storage;
    new(): Storage;
};
*/

2.4、localStorage能和导航器的存储管理器StorageManager---navigator.storage通信 :

// ◆ 存储管理器StorageManager---navigator.storage :
navigator.storage.estimate().then((storageEstimate) => console.log(storageEstimate));
navigator.storage.getDirectory().then((fileSystemDirectoryHandle) => {
    console.log(fileSystemDirectoryHandle);
});
navigator.storage.persist().then((boolean) => console.log('可以持久化吗: ', boolean));
navigator.storage.persisted().then((boolean) => console.log('是否已持久化: ', boolean));
// ●  安全上下文中有效 : Available only in secure contexts. 
//    ◇ 存储管理器StorageManager属于●导航器的存储: navigator.storage:StorageManager :
//
interface StorageManager {
    estimate(): Promise<StorageEstimate>;
    getDirectory(): Promise<FileSystemDirectoryHandle>;
    persist(): Promise<boolean>;
    persisted(): Promise<boolean>;
}
//

2.5、localStorage的事件类型StorageEvent : 

// ◆ StorageEvent事件类型 : 
//   ● 当StorageEvent事件"有权访问的存储区域(storageArea: Storage对象)"在另一个文档(document)的上下文中发生"增删改"(即:变化)时,会将其发送到窗口(即: 会触发该事件) : 
//     : A StorageEvent is sent to a window when a storage area it has access to is changed within the context of another document. 
interface StorageEvent extends Event {
    // ◇ Returns the key of the storage item being changed. 
    readonly key: string | null;
    // ◇ Returns the new value of the key of the storage item whose value is being changed. 
    readonly newValue: string | null;
    // ◇ Returns the old value of the key of the storage item whose value is being changed. 
    readonly oldValue: string | null;
    // ◇ Returns the Storage object that was affected. 
    readonly storageArea: Storage | null;
    // ◇ Returns the URL of the document whose storage item changed. 
    readonly url: string;
    // @deprecated // 弃用的
    initStorageEvent(type: string, bubbles?: boolean, cancelable?: boolean, key?: string | null, oldValue?: string | null, newValue?: string | null, url?: string | URL, storageArea?: Storage | null): void;
}
declare var StorageEvent: {
    prototype: StorageEvent;
    new(type: string, eventInitDict?: StorageEventInit): StorageEvent; // :type: string---对应map映射常量WindowEventHandlersEventMap : 
};     
interface WindowEventHandlersEventMap { // : map映射常量 :
    // StorageEvent事件类型映射常量字符串type === "storage" :
    "storage": StorageEvent;
    // 
    "afterprint": Event;
    "beforeprint": Event;
    "beforeunload": BeforeUnloadEvent;
    "gamepadconnected": GamepadEvent;
    "gamepaddisconnected": GamepadEvent;
    "hashchange": HashChangeEvent;
    "languagechange": Event;
    "message": MessageEvent;
    "messageerror": MessageEvent;
    "offline": Event;
    "online": Event;
    "pagehide": PageTransitionEvent;
    "pageshow": PageTransitionEvent;
    "popstate": PopStateEvent;
    "rejectionhandled": PromiseRejectionEvent;
    "unhandledrejection": PromiseRejectionEvent;
    "unload": Event;
}

2.6、localStorage跨标签页通信的编程

2.6.1、页面中的localStorage相关脚本:

        如上,因为localStorage的宿主为全局Window,所以对所有“标签页”均适用,代码可以直接与DOM互动。

        通信的发射方,写“存储”触发storage事件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>页面一</title>
</head>
<body>
    <script>
        localStorage.name = "谢杰";
        localStorage.age = 20 + Math.ceil(Math.random() * 10); 
        // :来超:否则一旦缓存之后会“http 304”
        console.log(`localStorage的键值对已经设置: ${localStorage.length}组`);
    </script>
</body>
</html>

        通信的接收方,监听storage事件:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>页面二</title>
</head>

<body>
    <script>
        // 
        window.onstorage = function(e) {
            console.log("修改的键为:", e.key);
            console.log("之前的值为:", e.oldValue);
            console.log("修改后的值为:", e.newValue);
            console.log(e.storageArea);
            console.log(e.url);
        }
    </script>
</body>

</html>

2.6.2、localStorage通信双方的相关脚本支持模块化导入

三、SharedWorker及其跨标签页通信

3.1、SharedWorker已共享的线程

◆ 谁在“共享”(或它是谁的属性、或它的宿主是谁):浏览器的App(或称其主程进窗体)

◆ 共享什么(或随被共享了):消息的服务(即SharedWorker),或简单的理解为消息收发的服务代码的js模块文件

◆ 共享的消息的介质(对谁“读和写”操作):是内存,它没有磁盘存储

◆ 共享的目标对象:浏览器当前App进程下的各种“窗口”(或称其GUI子进程),现在主流的就是上面提到的“标签页”Tabs.tab

◆ 可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker。它们实现一个不同于普通“专用”worker的接口,具有不同的全局作用域,SharedWorkerGlobalScope

3.2、SharedWorker线程跨标签页通信

◆ SharedWorker线程作为注册它的通信双方的“中间件”

◆ 通信的双方,都必须注册SharedWorker线程,才能互通“消息”

◆ 它是基于“消息”message的通信,通过它共享的MessagePort使得注册双方建立connect连接事件后,由它来“代理”互通消息,(不像BroadCast Channel,是通过BroadcastChannel通道的通信,通过MessageChannel通道的两端,通过MessageChannel消息通道来监听MessageEvent消息事件,让双方来互通),都是基于MessageEvent的事件模型

◆ 客户端connect连接(一旦接入)即触发事件

◆ 可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker。它们实现一个不同于普通“专用”worker的接口,具有不同的全局作用域,SharedWorkerGlobalScope

◆ 浏览器支持不太好(特别在移动端):◇ 在浏览器的App主进程上“争用”内存; ◇ 又不支持严格的MIME type检测; ◇ 又不贡献“存储”; ◇ 还容易一起“读写的共享冲突” ; ◇ 权限又过大,可能会影响到其它脚本的正常作业;等等......

◆ 一般不建议使用

◆ 使用Worker的话:一般建议使用:

        “专用Worker线程”(拥有DedicatedWorkerGlobalScope专用的全局作用域)或“服务serviceWorker线程”

        关于“线程安全”和“内容安全策略CSP”详见:此链接 

3.3、SharedWorker线程的编程(略),参见此链接 

喜欢的,就收藏并点个赞,鼓励我继续技术的原创写作及经验分享:

浏览器Disk Cache磁盘缓存及其协商缓存、及原生App和浏览器实现缓存的差异_pulledup的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/pulledup/article/details/127557778