全面解析BOM,成为 JS 高手

1,369 阅读6分钟

各位看官如果觉得此文对自己有些帮助,可以动动小手点个赞,加快我创作进度哦,赠人玫瑰手有余香,谢谢。

1.window

  • 一个全局对象,全局变量和属性均是它的属性
  • 包含 DOM 文档的窗口,表示浏览器窗口和页面可见区域

window.isSecureContext

有些特殊情况也被认为是安全的,主要是为了本地的开发

window.open()

  • 可以打开一个指定地址的新窗口
  • 返回打开的新窗口对象的引用
  • 注意:如果是同源,可以直接调用新窗口的方法

语法:

let windowObjectReference = window.open(strUrl, strWindowName, [strWindowFeatures]);
strUrl: url地址
strWindowName: 新窗口的名称
strWindowFeatures:可选值,定义将要打开的窗口的一些特性(工具栏、位置)

点击打开新窗口开启新窗口,点击关闭可以关闭新窗口

<body>
    <div>
        <button type="button" id="btnOpen">打开新窗口</button>
        <button type="button" id="btnClose">关闭</button>
    </div>

    <script>
        let winRef;
        let strWindowFeatures = `
                    menubar=yes,
                    location=yes,
                    resizable=yes,
                    scrollbars=yes,
                    status=no,
                    left=100px,
                    top=100px,
                `;

        btnOpen.addEventListener("click", function () {
            winRef = window.open("https://juejin.cn/frontend", "掘金", strWindowFeatures);
        });

        btnClose.addEventListener("click", function () {
            winRef && winRef.close();
        });
    </script>
</body>

同源可以互相调用其窗口的方法

父窗口:

<body>
    <div>父窗口</div>

    <button id="btnOpen">打开子窗口</button>
    <button id="btnInvoke">调用子窗口方法</button>

    <script>
        let winRef;
        btnOpen.addEventListener("click", function () {
            let strWindowFeatures = `
                    menubar=yes,
                    location=yes,
                    resizable=yes,
                    scrollbars=yes,
                    status=no,
                    left=100px,
                    top=100px,
                `;

            winRef = window.open("./ifr.html", "新窗口", strWindowFeatures);
        });

        function parentMethod() {
            alert("父窗口方法执行调用");
        }

        btnInvoke.addEventListener("click", function () {
            winRef && winRef.childMethod();
        });
    </script>
</body>

子窗口:

<body>
    <div>子窗口</div>
    
    <button id="btnInvoke">调用父窗口方法</button>

    <script>
        function childMethod() {
            alert("子窗口方法执行调用");
        }

        btnInvoke.addEventListener("click", function () {
            window && window.opener && window.opener.parentMethod();
        });
    </script>
</body>

操作演示:

window.open

窗体可见性

document.hidden

  • 返回布尔值,表示页面是(true)否(false)隐藏

document.visibilityState

  • 返回document的可见性,由此可以知道当前页面显示隐藏和正在渲染
  • 可选值:"visible"、"hidden" 、 "prerender"

还有一个可搭配的事件 visibilitychange 监听当前窗口可见性是否更改

代码演示:

<script>
    console.log("visibilityState:", document.visibilityState, "document.hidden:", document.hidden);
    console.log("----------------------------------------");
    document.addEventListener("visibilitychange", function () {
        console.log("visibilityState:", document.visibilityState, " document.hidden:", document.hidden);
    });
</script>

滚动方法

方法名作用拥有此方法的对象
scroll滚动到指定的位置Window, Element
scrollTo滚动到指定的位置Window, Element
scrollBy滚动指定的偏移量Window, Element
scrolllnfoView滚动到可视区Element

除此之外还有一些方法

  • 设置scrollTop、scrollLeft等
  • 设置锚点

window.matchMedia()

  • 可被用于判定 Document 是否匹配媒体查询
  • 监控一个 document 来判定它匹配了或者停止匹配了此媒体查询
<body>
    <div>
        (min-width: 600px):
        <span style="color: red" id="mq-value"> </span>
    </div>
    <script>
        let mql = window.matchMedia("(min-width: 600px)");
        document.querySelector("#mq-value").innerText = mql.matches;

        mql.addEventListener("change", function () {
            mql = window.matchMedia("(min-width: 600px)");
            document.querySelector("#mq-value").innerText = mql.matches;
        });
    </script>
</body>

window.getSelection()

  • 等价方法:Document.getSelection()
  • 表示用户选择的文本范围或光标的当前位置
  • 可使用 Document.activeElement 来返回当前的焦点元素
<body>
    <div>云牧是个大帅锅</div>
    <div style="margin-top: 50px"></div>
    选中的内容:
    <div id="selectedContent"></div>
    <script>
        setInterval(function () {
            selectedContent.textContent = window.getSelection().toString();
        }, 1500);
    </script>
</body>

window.frameElement()

  • 定义∶返回嵌入当前window对象的元素(比如<iframe> 或者<object>)
  • 如果当前window对象已经是顶层窗口,则返回null
  • 例子: window.frameElement 获得 iframe 节点,然后设置其 src 属性,实现调整

index.html

<div>
    <iframe src="./ifr1.html"></iframe>
</div>

ifr1.html

iframe 1
<script>
    setTimeout(function () {
        window.frameElement.src = "./ifr2.html";
    }, 3000);
</script>

ifr2.html

iframe 2
<script>
    setTimeout(function () {
        window.frameElement.src = "./ifr.html";
    }, 3000);
</script>

网络状态

  • navigator.onLine
<body>
    <div id="message"></div>

    <script>
        message.innerHTML += `i am ${navigator.onLine ? "online" : "offline"}<br />`;
        window.onoffline = function () {
            message.innerHTML += "i am offline <br />";
        };
        window.ononline = function () {
            message.innerHTML += "i am online <br />";
        };
    </script>
</body>

window.print打印

  • 打开打印对话框打印当前文档

使用媒体查询,在浏览器打印的时候设置一些特殊样式

<style media="print">
</style>

@media print {
    .content1 {
        color: green;
        font-size:18px
    }
}

<link rel="stylesheet" href="./print.css" media="print">

@import url("print.css") print;

实现一个案例:打印的时候隐藏按钮和一些文字

<style media="print">
    .content1 {
        color: green;
        font-size: 18px;
    }
    .content2 {
        display: none;
    }
    #btnPrint {
        display: none;
    }
</style>
</head>

<body>
    <div class="content1">
        <h3>云牧大帅锅</h3>
    </div>
    <div class="content2">
        <h3>云牧小帅锅</h3>
    </div>

    <button id="btnPrint">打印</button>
    <script>
        btnPrint.addEventListener("click", function () {
            window.print();
        });
    </script>
</body>

2.不同窗口间如何传递消息

1.WebSocket

  • 思路:引入第三者进行中转
  • 缺点∶需要引入服务端

image

2.定时器+客户端存储

  • 思路:本地存储+本地轮询

本地存储的方式

  • cookie
  • localStorage/sessionStorage
  • indexDB
  • chrome的FileSystem

index.html iframe引入两个页面

<style>
    html,
    body,
    section {
        height: 100%;
    }

    section {
        display: flex;
    }

    iframe {
        flex: 1;
        height: 100%;
    }
</style>
</head>
<body>
    <section>
        <iframe src="./page1.html"></iframe>
        <iframe src="./page2.html"></iframe>
    </section>
</body>

page1.html

<body>
    <h3>Page 1</h3>
    <section style="margin-top: 50px; text-align: center">
        <input id="inputMessage" value="测试消息" />
        <input type="button" value="发送消息" id="btnSend" />
        <section id="messages">
            <p>收到的消息:</p>
        </section>
    </section>

    <script>
        let messagesEle = document.getElementById("messages");
        let messageEl = document.getElementById("inputMessage");
        let btnSend = document.getElementById("btnSend");
        let lastMessage = null;

        setInterval(() => {
            let message = sessionStorage.getItem("ls-message2");
            try {
                if (message) {
                    message = JSON.parse(message);
                    if (!lastMessage || lastMessage.date != message.date) {
                        appendMessage(message);
                        lastMessage = message;
                    }
                }
            } catch (err) {
                console.log(err);
            }
        }, 200);

        function appendMessage(data) {
            let msgEl = document.createElement("p");
            msgEl.innerText = data.date + " " + data.from + ":" + data.message;
            messagesEle.appendChild(msgEl);
        }

        btnSend.addEventListener("click", function () {
            let message = messageEl.value;

            sessionStorage.setItem(
                "ls-message1",
                JSON.stringify({
                    date: Date.now(),
                    message,
                    from: "page 1",
                })
            );
        });
    </script>
</body>

page2.html

<body>
    <h3>Page 2</h3>
    <section style="margin-top: 50px; text-align: center">
        <input id="inputMessage" value="测试消息" />
        <input type="button" value="发送消息" id="btnSend" />
        <section id="messages">
            <p>收到的消息:</p>
        </section>
    </section>

    <script>
        let messagesEle = document.getElementById("messages");
        let messageEl = document.getElementById("inputMessage");
        let btnSend = document.getElementById("btnSend");
        let lastMessage = null;

        setInterval(() => {
            let message = sessionStorage.getItem("ls-message1");
            try {
                if (message) {
                    message = JSON.parse(message);
                    if (!lastMessage || lastMessage.date != message.date) {
                        appendMessage(message);
                        lastMessage = message;
                    }
                }
            } catch (err) {
                console.log(err);
            }
        }, 200);

        function appendMessage(data) {
            let msgEl = document.createElement("p");
            msgEl.innerText = data.date + " " + data.from + ":" + data.message;
            messagesEle.appendChild(msgEl);
        }

        btnSend.addEventListener("click", function () {
            let message = messageEl.value;

            sessionStorage.setItem(
                "ls-message2",
                JSON.stringify({
                    date: Date.now(),
                    message,
                    from: "page 2",
                })
            );
        });
    </script>
</body>

setInterval

3.postMessage

  • 思路:用某种手段建立窗口间的联系,通过 postMessage 进行跨窗体通讯
  • 优点:不受同源策略的限制

index.html

<body>
    <div>
        <iframe src="./ifr.html" id="ifr" style="width: 600px; height: 300px"></iframe>
    </div>

    index.html
    <div>
        <div>Message:</div>
        <div id="messages"></div>
    </div>
    <script>
        window.addEventListener("message", function (event) {
            messages.innerHTML += `
                <div>${event.data}</div>  
            `;
        });

        setInterval(() => {
            ifr.contentWindow.postMessage(`message from index.html: ${Date.now()}`);
        }, 2000);
    </script>
</body>

ifr.html

<body>
    ifr.html
    <div>
        <div>Message:</div>
        <div id="messages"></div>
    </div>
    <script>
        window.addEventListener("message", function (event) {
            messages.innerHTML += `
                <div>${event.data}</div>  
            `;
        });

        setInterval(() => {
            window.parent.postMessage(`message from ifr.html: ${Date.now()}`);
        }, 2000);
    </script>
</body>

postMessage

4.storage

index.html

<body>
    <form>
        <div>
            <button id="btnSend" type="button">发送消息</button>
        </div>
        <div>消息:</div>
        <div id="message"></div>

        <script>
            btnSend.addEventListener("click", function () {
                localStorage.setItem(
                    "key",
                    JSON.stringify({
                        key: "key",
                        data: Math.random(),
                    })
                );
            });

            window.addEventListener("storage", function (ev) {
                console.log("ev:", ev);
                message.textContent = JSON.stringify({
                    oldValue: ev.oldValue,
                    newValue: ev.newValue,
                });
            });
        </script>
    </form>
</body>

其他页面

<body>
    <div>消息:</div>
    <div id="message"></div>

    <script>
        window.addEventListener("storage", function (ev) {
            console.log("ev:", ev);
            message.textContent = JSON.stringify({
                oldValue: ev.oldValue,
                newValue: ev.newValue,
            });
        });
    </script>
</body>

上面 index 页面自身无法监测 storage 事件来感知修改,我们需要拦截重写一下对应的设置方法

<script>
    // 拦截,监听事件
    const oriSetItem = localStorage.setItem;
    Object.defineProperty(localStorage.__proto__, "setItem", {
        value: function (key, value) {
            let oldValue = localStorage.getItem(key);
            let event = new StorageEvent("storage", {
                key,
                newValue: value,
                oldValue,
                url: document.URL,
                storageArea: localStorage,
            });
            window.dispatchEvent(event);
            oriSetItem.apply(this, arguments);
        },
    });
</script>

storage

5.Broadcast Channel

  • 允许同源的不同浏览器窗口,Tab页,frame或者 iframe 下的不同文档之间相互通信
  • 缺点:需要同源

image

index.html

<style>
    html,
    body,
    section {
        height: 100%;
    }

    * {
        font-size: 28px;
    }
    section {
        display: flex;
    }

    iframe {
        flex: 1;
        height: 100%;
    }
</style>
</head>
<body>
    <section>
        <iframe src="./page1.html"></iframe>
        <iframe src="./page2.html"></iframe>
    </section>
</body>

page1.html

<body>
    <h3>Page 1</h3>
    <section style="margin-top: 50px; text-align: center">
        <input id="inputMessage" value="测试消息" />
        <input type="button" value="发送消息" id="btnSend" />
        <section id="messages">
            <p>收到的消息:</p>
        </section>
    </section>

    <script>
        let messagesEle = document.getElementById("messages");
        let messageEl = document.getElementById("inputMessage");
        let btnSend = document.getElementById("btnSend");

        let channel = new BroadcastChannel("channel-BroadcastChannel");
        channel.addEventListener("message", function (ev) {
            let msgEl = document.createElement("p");
            msgEl.innerText = ev.data.date + " " + ev.data.from + ":" + ev.data.message;
            messagesEle.appendChild(msgEl);
        });

        btnSend.addEventListener("click", function () {
            let message = messageEl.value;
            channel.postMessage({
                date: new Date().toLocaleString(),
                message,
                from: "page 1",
            });
        });
    </script>
</body>

page2.html

<body>
    <h3>Page 2</h3>

    <section style="margin-top: 50px; text-align: center">
        <input id="inputMessage" value="测试消息" />
        <input type="button" value="发送消息" id="btnSend" />
        <section id="messages">
            <p>收到的消息:</p>
        </section>
    </section>

    <script>
        let messagesEle = document.getElementById("messages");
        let messageEl = document.getElementById("inputMessage");
        let btnSend = document.getElementById("btnSend");

        let channel = new BroadcastChannel("channel-BroadcastChannel");
        channel.addEventListener("message", function (ev) {
            let msgEl = document.createElement("p");
            msgEl.innerText = ev.data.date + " " + ev.data.from + ":" + ev.data.message;
            messagesEle.appendChild(msgEl);
        });

        btnSend.addEventListener("click", function () {
            let message = messageEl.value;
            channel.postMessage({
                date: new Date().toLocaleString(),
                message,
                from: "page 2",
            });
        });
    </script>
</body>

6.MessageChannel

  • Channel Messaging API的 MessageChannel 接口允许我们创建一个新的消息通道,并通过它的两个MessagePort属性发送数据
  • 缺点:需要先建立联系
  • 此方式有点别扭

index.html

<body>
    <iframe id="ifr" src="./ifr.html" style="width: 600px; height: 300px"></iframe>

    <div>index.html</div>

    <div>
        <div>Message:</div>
        <div id="messages"></div>
    </div>

    <script>
        const channel = new MessageChannel();

        // iframe加载完成建立联系
        ifr.onload = function () {
            ifr.contentWindow.postMessage("__init__", "*", [channel.port2]);
        };
        // 监听ifr传递过来的消息
        channel.port1.onmessage = onMessage;
        function onMessage(e) {
            messages.innerHTML += `
                  <div>${event.data}</div>
              `;
        }
        // 发送消息
        setInterval(function () {
            channel.port1.postMessage(`message from index.html, ${Date.now()}`);
        }, 3000);
    </script>
</body>

ifr.html

<body>
    ifr.html
    <div>
        <div>Message:</div>
        <div id="messages"></div>
    </div>
    <script>
        // 监听消息传递
        window.addEventListener("message", function (event) {
            if (event.data === "__init__") {
                initChannel(event.ports[0]);
            }
        });
        function initChannel(port) {
            port.onmessage = function (event) {
                messages.innerHTML += `
                <div>${event.data}</div>  
            `;
                port.postMessage(`message from the iframe, ${Date.now()}`);
            };
        }
    </script>
</body>

7.SharedWorker

  • 这是 Web Worker 之后出来的共享的Worker,不同页面可以共享这个Worker
  • 缺点∶兼容性、同源策略

index.html

<style>
    html,
    body,
    section {
        height: 100%;
    }

    section {
        display: flex;
    }

    iframe {
        flex: 1;
        height: 100%;
    }
</style>
</head>
<body>
    <section>
        <iframe src="./page1.html"></iframe>
        <iframe src="./page2.html"></iframe>
    </section>
    <script>
        //mdn demo: https://github.com/mdn/simple-shared-worker
    </script>
</body>

page1.html

<body>
    <h3>Page 1</h3>
    <section style="margin-top: 50px; text-align: center">
        <input id="inputMessage" value="测试消息" />
        <input type="button" value="发送消息" id="btnSend" />
        <section id="messages">
            <p>收到的消息:</p>
        </section>
    </section>

    <script src="./worker.js"></script>
    <script>
        let messagesEle = document.getElementById("messages");
        let messageEl = document.getElementById("inputMessage");
        let btnSend = document.getElementById("btnSend");

        if (!window.SharedWorker) {
            alert("浏览器不支持SharedWorkder!");
        } else {
            let myWorker = new SharedWorker("./worker.js");

            myWorker.port.onmessage = function (e) {
                let msgEl = document.createElement("p");
                let data = e.data;
                msgEl.innerText = data.date + " " + data.from + ":" + data.message;
                messagesEle.appendChild(msgEl);
            };

            btnSend.addEventListener("click", function () {
                let message = messageEl.value;

                myWorker.port.postMessage({
                    date: new Date().toLocaleString(),
                    message,
                    from: "page 1",
                });
            });

            myWorker.port.start();
        }
    </script>
</body>

page2.html

<body>
    <h3>Page 2</h3>
    <section style="margin-top: 50px; text-align: center">
        <input id="inputMessage" value="测试消息" />
        <input type="button" value="发送消息" id="btnSend" />
        <section id="messages">
            <p>收到的消息:</p>
        </section>
    </section>

    <script src="./worker.js"></script>
    <script>
        let messagesEle = document.getElementById("messages");
        let messageEl = document.getElementById("inputMessage");
        let btnSend = document.getElementById("btnSend");

        if (!window.SharedWorker) {
            alert("浏览器不支持SharedWorkder!");
        } else {
            let myWorker = new SharedWorker("./worker.js");

            myWorker.port.onmessage = function (e) {
                let msgEl = document.createElement("p");
                let data = e.data;
                msgEl.innerText = data.date + " " + data.from + ":" + data.message;
                messagesEle.appendChild(msgEl);
            };

            btnSend.addEventListener("click", function () {
                let message = messageEl.value;

                myWorker.port.postMessage({
                    date: new Date().toLocaleString(),
                    message,
                    from: "page 2",
                });
            });

            myWorker.port.start();
        }
    </script>
</body>

worker.js

let portList = [];

onconnect = function (e) {
    let port = e.ports[0];
    ensurePorts(port);
    port.onmessage = function (e) {
        let data = e.data;
        disptach(port, data);
    };
    port.start();
};

function ensurePorts(port) {
    if (portList.indexOf(port) < 0) {
        portList.push(port);
    }
}

function disptach(selfPort, data) {
    portList.filter((port) => selfPort !== port).forEach((port) => port.postMessage(data));
}

SharedWorker

总结

方法方式是否需要有强关联遵守同源策略web worker可用
WebScoket
定时器+客户端存储indexedDB
postMessage自己的postMessage
StorageEvent×
Broadcast Channel
MessageChannel
SharedWorker

3.location

url

  • host 包含端口号,hostname 返回域名
  • 没有端口号的 url,host 和 hostname 相同

image

修改 location 属性值

  • 可使用 location 对应属性替换地址栏对应的值
  • 注意:location.origin属性是只读的,存在兼容问题(IE11以下不存在)
  • 除hash,修改其他属性值改都会以新 URL 重新加载,并会生成一条新的历史记录在浏览器中
  • 修改 pathtname 可以不用传开头/,修改 search 可以不用传?,修改 hash 可以不用传#
window.location.protocol = "https";
window.location.host = "127.0.0.1:5500";
window.location.hostname = "127.0.0.1";
window.location.port = "5500";
window.location.pathname = "test/path";
window.location.search = "wd=ff";
window.location.hash = "/home";
window.location.href = "http://127.0.0.1:5500/liveSeverTest.html";

访问 location对象方式

  • window.location
  • document.location
  • window.document.location
  • location(不推荐)
console.log(window.location);
console.log(window.document.location);
console.log(document.location);
console.log(location);

window.location.reload

  • 定义:重新加载当前文档
  • 参数: false或者不传,浏览器可能从缓存中读取页面
  • 参数:true,强制从服务器重新下载文档

assign VS href VS replace

属性/方法名增加新纪录
href
assign
replace
<div>
    <button type="button" id="btnHref">href</button>
    <button type="button" id="btnAssign">assign</button>
    <button type="button" id="btnReplace">replace</button>
</div>

<script>
    btnHref.onclick = function () {
        window.location.href = "https://juejin.cn";
    };
    btnAssign.onclick = function () {
        window.location.assign("https://juejin.cn");
    };
    btnReplace.onclick = function () {
        window.location.replace("https://juejin.cn");
    };
</script>

window.location.href VS window.open

  • window.location.href是用新的域名页调换当前页,不会开新窗口
  • window.open用来打开新窗口或者查找已命名的窗口,打开新窗口可能会被浏览器拦截

hash监听方式

  • window.onhashchange = funcRef
  • window.addEventListener("hashchange", funcRef, false)
<body>
    <button id="home">#/home</button>
    <button id="detail">#/detail</button>
    <script>
        const homeBtn = document.getElementById("home");
        const detailBtn = document.getElementById("detail");
        homeBtn.addEventListener("click", function () {
            window.location.hash = "home";
        });

        detailBtn.addEventListener("click", function () {
            window.location.hash = "test";
        });

        window.onhashchange = function hashChangeListener(e) {
            console.log("window.onhashchange: onhashchange:");
            console.log("oldUrl:", e.oldURL);
            console.log("newUrl:", e.newURL);
        };

        window.addEventListener(
            "hashchange",
            function hashChangeListener(e) {
                console.log("window.addEventListener: hashchange:");
            },
            false
        );
    </script>
</body>

动态执行脚本

<body>
    <textarea id="textareaScript" cols="30" rows="20"></textarea>

    <div>
        <button type="button" id="btnCreateScript">创建脚本</button>
    </div>

    <script>
        textareaScript.value = `;(function(){
            console.log("location.href:", location.href)
        })()`;

        btnCreateScript.onclick = function () {
            const scriptEL = document.createElement("script");
            // 使用脚本内容初始化blob最终创建出一个url地址
            const scriptSrc = URL.createObjectURL(new Blob([textareaScript.value]));
            scriptEL.src = scriptSrc;
            document.body.appendChild(scriptEL);
        };
    </script>
</body>

URL.searchParams

const urlObj = new URL(window.location.href);
function getQueryString(key) {
    // 获取地址栏的查询字符串
    return urlObj.searchParams.get(key);
}

其他方法属性:

  • keys() 返回iterator,此对象包含所有搜索的键名
  • values() 返回iterator,此对象包含所有的value
  • entries() 返回一个iterator,可以遍历所有的键值对的对象
  • set() 设置一个搜索参数新值,原来有多个值将删除其他所有值
  • get() 获取指定搜索参数的值
  • has() 判断是否有指定的搜索参数
  • getAll() 获取指定搜索参数的所有值,返回一个数组
  • delete() 从搜索参数列表里删除指定的键和值
  • append() 插入一个指定的键/值
  • toString() 返回搜索参数组成的字符串
  • sort() 按键名排序

encodeURL 和 encodeURLComponent

  • 都是编码URL,唯一的区别在于编码的字符范围不同,encodeURLComponent相对更广
const test1 = ";,/?:@&=+$"; // 保留字符
const test2 = "-_.!~*'()"; // 不转义字符
const test3 = "#"; // 数字标志
const test4 = "ABC abc 123"; // 字母数字字符和空格

console.log("-----------encodeURI------------");

console.log(encodeURI(test1));
console.log(encodeURI(test2));
console.log(encodeURI(test3));
console.log(encodeURI(test4));

console.log("-----------encodeURIComponent------------");

console.log(encodeURIComponent(test1));
console.log(encodeURIComponent(test2));
console.log(encodeURIComponent(test3));
console.log(encodeURIComponent(test4));

执行结果如下:

image

  • 使用场景:
    • 如果要编码整个URL,使用encodeURL
    • 如果要编码 URL 中的后面的参数,使用 encodeURLComponent 最好

例如 www.baidu.com/index.html?…

4.navigator(不至于识别设备)

navigator.userAgent

  • 识别是否微信内置浏览器
//android
//mozilla/5.0 (linux; u; android 4.1.2; zh-cn; mi-one plus build/jzo54k) applewebkit/534.30 (khtml, like gecko) version/4.0 mobile safari/534.30 micromessenger/5.0.1.352

//ios
//mozilla/5.0 (iphone; cpu iphone os 5_1_1 like mac os x) applewebkit/534.46 (khtml, like gecko) mobile/9b206 micromessenger/5.0

function isWX() {
  const ua = window.navigator.userAgent.toLowerCase();
  return ua.match(/MicroMessenger/i) == "micromessenger";
}

if ("serviceWorker" in navigator) {
  // Supported!
}
  • 解析userAgent的库 : ua-parser-js

navigator.onLine(在线状态)

  • 返回 true | false 表示浏览器的在线状态
  • 使用:结合 document.ononline 与 document.onoffline 网络变化
<body>
    <div id="net-change"></div>
    <script>
        function netChangeStatus(online) {
            const netChangeEl = document.getElementById("net-change");

            if (online) {
                netChangeEl.innerText = `你是在线状态`;
            } else {
                netChangeEl.innerText = `哥们你掉线了`;
            }
        }
        
        netChangeStatus(navigator.onLine);

        window.addEventListener("online", () => {
            netChangeStatus(true);
        });

        window.addEventListener("offline", () => {
            netChangeStatus(false);
        });
    </script>

navigator.clipboard(剪切板)

  • 返回剪切板对象
  • 注意∶必须是安全上下文(local,https,wss )
  • 注意:window.isSecureContext 检测安全上下文
<button id="btnCopy">复制</button>

<body>
    <script>
        // 需要人为的触发
        function copyToClipboard(textToCopy) {
            if (navigator.clipboard && window.isSecureContext) {
                return navigator.clipboard.writeText(textToCopy);
            } else {
                console.log("其他方式复制");
            }
        }

        btnCopy.onclick = function () {
            copyToClipboard("需要拷贝的内容")
                .then(() => console.log("拷贝成功"))
                .catch(() => console.log("拷贝失败"));
        };
    </script>

navigator.cookieEnabled(cookie)

  • 返回当前页面是否启用了cookie

navigator.serviceWorker(Service Worker)

  • 返回关联文件的 ServiceWorkerContainer 对象,提供对 ServiceWorker 的注册,删除,升级和通信访问
  • 只能在安全上下文使用

应用场景:

  • 后台数据同步
  • 集中处理计算成本高的数据更新
  • 性能增强,用于预获取用户需要的资源

navigator.mediaDevices(媒体设备)

  • 返回一个MdeiaDevices对象,用户获取媒体信息设备
  • 应用场景:H5调用摄像头识别二维码,共享屏幕等

获取媒体设备

if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
    navigator.mediaDevices
        .enumerateDevices()
        .then(function (devices) {
        console.log("devices==", devices);
    })
        .catch(function (err) {
        console.log(err.name + ": " + err.message);
    });
}

共享媒体使用

<body>
    <video id="video" style="height: 800px; width: 800px" autoplay></video>
    <button id="start">开始共享屏幕</button>
    <button id="stop">停止共享屏幕</button>

    <script>
        const mediaOptions = {
            video: {
                cursor: "always",
            },
            audio: false,
        };

        let videoElem = document.getElementById("video");

        document.getElementById("start").addEventListener("click", function () {
            startShareScreen();
        });

        document.getElementById("stop").addEventListener("click", function () {
            stopShareScreen();
        });

        async function startShareScreen() {
            try {
                videoElem.srcObject = await navigator.mediaDevices.getDisplayMedia(mediaOptions);
            } catch (err) {
                console.error("Error: " + err);
            }
        }

        function stopShareScreen(evt) {
            let tracks = videoElem.srcObject.getTracks();
            tracks.forEach((track) => track.stop());
            videoElem.srcObject = null;
        }
    </script>
</body>

navigator.storage(存储)

  • 返回 StorageManager 对象,用于访问浏览器的整体存储能力
  • 注意∶必须安全上下文
  • 应用:获取 storage 的存储大小以及可分配大小
navigator.storage.estimate().then(function (estimate) {
  console.log("使用:", estimate.usage);
  console.log("总量:", estimate.quota);
});

navigator.sendBeacon(上报数据)

  • 作用:通过 httpPost 将少量的数据异步传输到web服务器
  • 应用:它主要用于将统计数据发送到 Web 服务器,同时避免了用传统技术(如:XMLHttpRequest )发送分析数据的一些问题

客户端代码:

function log() {
    let xhr = new XMLHttpRequest();
    xhr.open("post", "http://127.0.0.1:3000/report/xhr", true);
    xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    xhr.send("xhr=1");
}

function sendBeacon() {
    const data = new FormData();
    data.append("sendB", 2);
    navigator.sendBeacon("http://127.0.0.1:3000/report/bean", data);
}

window.addEventListener("unload", function (event) {
    sendBeacon();
});

window.addEventListener("unload", function (event) {
    log();
});

服务端代码:

const express = require("express");

const app = express();

app.use("/report/xhr", function (req, res, next) {
    console.log("收到上报请求, from xhr");
});
app.use("/report/bean", function (req, res, next) {
    console.log("收到上报请求, from bean");
});

app.listen(3000, function () {
    console.log("listening port 3000");
});

navigator.connection(网络信息)-- 实验

  • 返回一个NetworkInformation 对象,其包含网络信息
  • 应用:获取当前用户的宽带信息,如网络类型,下载速度等

navigator.permissions(权限对象)-- 实验

  • 返回一个 Permissions 对象
  • 应用:获取权限信息
<body>
    <button id="btnQuery">查询位置权限</button>
    <button id="btnGetLocation">获取位置信息</button>
    <script>
        btnQuery.onclick = function () {
            navigator.permissions
                .query({ name: "geolocation" })
                .then(function (result) {
                if (result.state === "granted") {
                    console.log("位置权限 granted");
                } else if (result.state === "prompt") {
                    console.log("位置权限 prompt");
                }
                console.log("位置权限:", result.state);
            })
                .catch((err) => {
                console.log("err:", err);
            });
        };
        let options = {
            enableHighAccuracy: true,
            timeout: 1200,
            maximumAge: 0,
        };

        btnGetLocation.onclick = function () {
            navigator.geolocation.getCurrentPosition(
                function (pos) {
                    let crd = pos.coords;
                    console.log(`Latitude : ${crd.latitude}`);
                    console.log(`Longitude: ${crd.longitude}`);
                },
                function (err) {
                    console.log("error", err);
                },
                options
            );
        };
    </script>
</body>

navigator.mediaSession(共享媒体信息)-- 实验

  • 返回一个 MediaSeeion 对象,用来与浏览器共享媒体信息。比如播放状态,标题,封面等
  • 应用:通知栏自定义媒体信息

5.history ,网页端的方向盘

历史记录本质也是一个栈

image

旧四样

API说明
history.back向后移动一页
history.forward向前移动一页
history.go向前或者向后移动指定页数
history.length当前会话中的历史页面数

back & forward & length

  • back:会话历史记录中向后移动一页。如果没有上一页,则此方法调用不执行任何操作
  • forward:在会话历史中向前移动一页。如果没有下一页,则此方法调用不执行任何操作
  • length:返回当前会话中的历史页面数,包含当前页面在内。对于新开一个 tab 加载的页面当前属性返回值

go

  • 在会话历史中向前或者向后移动指定页数
  • 负值表示向后移动,正值表示向前移动
  • 如果未向该函数传参或等于0,则该函数与调用 location.reload() 具有相同的效果
  • 如果需要移动的页面数,大于可以移动的页面数,不进行任何移动
<body>
    <div>当前:首页</div>
    <div>
        <a href="./page1.html">去page1.html</a><br />
        <a href="./page2.html">去page2.html</a><br />
        <a href="./page3.html">去page3.html</a><br />
    </div>
    <div>历史记录长度:<span id="hlength"></span></div>
    <div>
        <button type="button" onclick="history.back()">Back</button>
        <button type="button" onclick="history.forward()">Forward</button>
        <button type="button" onclick="history.go(1)">Go(1)</button>
        <button type="button" onclick="history.go(2)">Go(2)</button>
        <button type="button" onclick="history.go(-1)">Go(-1)</button>
        <button type="button" onclick="history.go(-2)">Go(-2)</button>
    </div>
    <script>
        hlength.textContent = history.length;
    </script>
</body>

新四样

API说明
history.pushState向当前浏览器会话的历史堆栈中添加一个状态
history.replacestate修改当前历史记录状态
history.state返回在会话栈页的状态值的拷贝
window.onpopstate当活动历史记录条目更改时,将触发 popstate 事件

history.pushState

  • 语法: history.pushState(state, title[, url])
  • 其会增加历史访问记录(即使url为空),但不会改变页面的内容
  • 改变的新的 URL 跟当前的 URL 必须是同源

history.replacestate

  • 语法:history.replaceState(stateObj, title,[url])
  • 是替换浏览记录栈顶部的记录,不会增加栈的深度
  • 新的URL跟当前的URL必须是同源

window.onpopstate

  • 当活动历史记录条目更改时,将触发 popstate 事件
  • 调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件
  • popstate事件只会在浏览器某些行为下触发,比如点击后退、前进按钮(或者在 JavaScript 中调用history.back()、history.forward()、history.go()方法)
  • a标签的锚点也会触发该事件
<body>
    <div>
        state:
        <div id="stateValue"></div>
        <div>历史记录长度:<span id="hlength"></span></div>
    </div>
    <div>
        <br />
        <a href="#index">#index</a><br />
        <button type="button" id="btnPushState">pushState</button><br />
        <button type="button" id="btnReplaceState">replaceState</button><br />
    </div>

    <script>
        let index = 0;
        hlength.textContent = history.length;
        btnPushState.onclick = function (ev) {
            index++;
            history.pushState({ data: "pushState" + index }, "", `/pushState${index}.html`);
            hlength.textContent = history.length;
        };

        btnReplaceState.onclick = function (ev) {
            history.replaceState({ data: "replaceState" }, "", "/replaceState.html");
            hlength.textContent = history.length;
        };

        window.onpopstate = function (ev) {
            console.log("onpopstate trigger");
            stateValue.textContent = JSON.stringify(ev.state);
            hlength.textContent = history.length;
        };
    </script>
</body>
  • 刷新的时候需要与服务端配合
  • 方案:不管我们访问路由是否,服务端都返回同一份index.html
const express = require("express");
const path = require("path");

const app = express();

app.get("*", function (req, res, next) {
  res.sendFile(path.join(__dirname, "./static/index.html"));
});

app.listen(8086, function () {
  console.log("listening on port 8086");
});