《JavaScript 高级程序设计》第10-16章

2024年03月13日

10.浏览器对象模型(BOM)

window 对象

BOM 的核心是 window 对象,表示浏览器的实例。

  • 因为 window 对象被复用为 ECMAScript 中的 Global 对象,所以通过 var 声明的所有全局变量和函数会变成 window 对象的属性和方法
  • top 对象始终指向最外层窗口,即浏览器窗口本身。parent 对象始终指向当前窗口的父窗口。self 对象始终指向 window
  • 现代浏览器提供了 screenLeft 和 screenTop 属性表示窗口相对于屏幕左侧和顶部的位置,可以使用 moveTo 和 moveBy 方法移动窗口
  • outerWidth 和 outerHeight 返回浏览器窗口自身大小,innerWidth 和 innerHeight 返回页面视口大小
  • 度量文档相对于视口的滚动距离可以使用:window.pageXoffset/window.scrollX 和 window.pageYoffset/window.scrollY 。使用 scroll、scrollTo 和 scrollBy 方法滚动页面,前两个方法接收滚动坐标,最后一个方法接收滚动距离
  • window.open 方法可以导航到指定 URL,也可以用于打开新浏览器窗口
  • setTimeout 用于指定在一段时间后执行某些代码,接收两个参数:要执行的代码和等待的时间。
    • 注意:由于 JavaScript 是单线程的,为了调度不同代码的执行,JavaScript 维护了一个任务队列,其中的任务会按照添加到队列的先后顺序执行。setTimeout 第二个参数只是告诉 JavaScript 引擎在指定毫秒数后把任务添加到这个队列,并非立即执行
    • 调用 setTimeout 返回表示该超时排期的数值ID,使用 clearTimeout 并传入该 ID 可以取消等待中的排期任务
  • setInterval 用于指定每隔一段时间执行某些代码,接收两个参数:要执行的代码以及把下一次执行定时代码的任务添加到队列要等待的时间
    • setInterval 也会返回一个循环定时 ID,使用 clearInterval 可以取消循环定时
  • 使用 alert、confirm 和 prompt 方法可以调用浏览器系统对话框展示信息

location 对象

location 对象是最有用的 BOM 对象之一,提供当前窗口中加载文档的信息及常用导航功能。它既是 window 属性又是 document 属性。

  • URLSearchParams 提供了一组标准 API 方法用于检查和修改查询字符串,包括 get、set 和 delete 等,大部分支持的浏览器也可以将 URLSearchParams 实例作为可迭代对象
  • 可以通过设置 location.href 或 window.location 为一个 URL 修改浏览器的地址,它们都通过以该 URL 的值调用 assign 方法启动导航到新 URL,并在浏览器历史记录中增加一条新的记录。如果不希望增加记录可以使用 replace 方法。reload 方法可以重新加载当前页面,传入 true 可以强制从服务器重新加载

navigator 对象的属性通常用于确定浏览器的类型。

  • 可以通过 plugins 属性获取浏览器插件数组
  • 使用 registerProtocolHandler 方法可以把一个网站注册为处理某种特定类型信息的应用程序

screen 对象

screen 对象保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器信息

history 对象

history 对象表示当前窗口首次使用以来用户的导航历史记录,该对象并不会暴露用户访问过的 URL,但可以在不知道实际 URL 的情况下实现前进和后退。

导航

使用 go 方法可以在用户历史记录中沿任何方向导航,接收一个参数表示前进或后退多少步。可以使用 back 和 forward 方法简写,模拟浏览器的前进后退按钮。history 对象还有一个 length 属性表示历史记录中有多少条目。

历史状态管理

history.pushState 方法可以让开发者改变浏览器 URL 而不会加载新页面,该方法接收3个参数:一个 state 对象、一个新状态的标题和一个(可选)相对 URL。

javascript 复制代码
let stateObject = {foo:'bar'}
history.pushState(stateObject, 'My Title', 'baz.html')

pushState 方法执行后,状态信息会被推到历史记录中,浏览器地址也会改变以反映新的相对 URL。虽然 location.href 返回的是地址栏中的新内容但是浏览器页不会向服务器发送请求。
可以通过 history.state 获取当前状态对象,使用 replaceState 覆盖当前状态。

11.文档对象模型(DOM)

节点层级

任何 HTML 和 XML 文档都可以用 DOM 表示为一个由节点组成的层级结构

Node 类型

  • DOM Level1 描述了名为 Node 的接口,所有 DOM 节点类型都必须实现该接口。Node 接口在 JavaScript 中被实现为 Node 类型,所有节点类型都继承 Node 类型
  • 节点的 nodeType 属性表示该节点的类型
  • 节点的 childNodes 属性包含一个 NodeList 实例,用于存储可以按位置存取的有序节点
    • NodeList 对象可以动态的反应 DOM 结构的变化
    • childNodes 列表中的每个节点都是同一列表中其他节点的同胞节点。使用 previousSibling 和 nextSibling 可以在列表的节点间导航
    • 父节点可以使用专门属性 firstChild 和 lastChild 获取 childNodes 中第一个和最后一个节点
    • hasChildNodes 方法用于判断节点是否有子节点
  • 节点的 parentNode 属性指向其 DOM 树中的父元素
  • DOM 提供了一些操纵节点的方法
    • appendChild 用于在 childNodes 列表末尾添加节点,如果把文档中存在的节点传给该方法,则这个节点会从原位置转移到新位置
    • insertBefore 方法用于把节点放置在 childNodes 指定位置,接收两个参数:要插入的节点和参照节点
    • replaceChild 方法接受两个参数:要插入的节点和要替换的节点。要替换的节点会被返回并从文档树中完全移除
    • removeChild 方法用于移除节点
    • cloneNode 方法会返回与调用它的节点一模一样的节点,接收一个布尔值参数表示是否深复制
    • normalize 方法用于处理文档子树中的文本节点,删除空文本节点,将同胞文本节点合并

Document 类型

  • Document 类型是 JavaScript 中表示文档节点的类型。在浏览器中文档对象 document 是 HTMLDocuemnt 的实例,表示整个 HTML 页面
  • document 对象的 documentElement、firstChild 和 child Nodes[0] 都指向同一个值,即 元素。document 对象的 body 属性指向 元素
  • document 对象包含一些常用属性:
    • title 包含 元素中的文本</li> <li data-line="87">URL 包含当前页面完整的 URL</li> <li data-line="88">domain 包含页面的域名</li> <li data-line="89">referrer 包含链接到当前页面的 URL</li> </ul> </li> <li data-line="90">获取元素的引用可以使用 getElementById 、getElementsByTagName 和 getElementsByName 方法</li> </ul> <h3 data-line="92" id="Element 类型">Element 类型</h3> <ul data-line="94"> <li data-line="94">Element 表示 XML 或 HTML 元素,对外暴露出访问元素标签名、子节点和属性的能力</li> <li data-line="95">可以通过 nodeName 或 tagName 属性获取元素的标签名</li> <li data-line="96">所有的 HTML 元素都通过 HTMLElement 类型表示,并具有以下标准属性: <ul data-line="97"> <li data-line="97">id 元素在文档中的唯一标识符</li> <li data-line="98">title 元素的额外信息</li> <li data-line="99">lang 元素内容的语言代码</li> <li data-line="100">dir 语言的书写方向</li> <li data-line="101">className 相当于 class 属性,指定元素的 CSS 类</li> </ul> </li> <li data-line="102">与属性相关的 DOM 方法主要有: <ul data-line="103"> <li data-line="103">getAttribute 获取属性的值(由于通过 DOM 对象和 getAttribute 方法在获取 style 属性和事件处理程序时表现不同,开发者通常使用对象属性进行 DOM 编程,使用 getAttribute 获取自定义属性的值)</li> <li data-line="104">setAttribute 设置属性的值</li> <li data-line="105">removeAttribute 删除属性</li> </ul> </li> <li data-line="106">Element 类型包含 attributes 属性。属性包含一个 NamedNodeMap 实例,每个属性表示为一个 Attr 节点。每个节点的 nodeName 是对应属性的名字,nodeValue 是对应属性的值。NamedNodeMap 对象包含下列方法: <ul data-line="107"> <li data-line="107">getNamedItem(name) 返回 nodeName 属性等于 name 的节点</li> <li data-line="108">removeNamedItem(name) 删除 nodeName 等于 name 的节点</li> <li data-line="109">setNamedItem(node) 像列表中添加 node 节点,以其 nodeName 为索引</li> <li data-line="110">item(pos) 返回索引位置 pos 处的节点</li> </ul> </li> <li data-line="111">可以使用 document.createElement 创建新元素,接收一个参数为要创建的元素的标签名</li> </ul> <h3 data-line="113" id="Text 类型">Text 类型</h3> <ul data-line="115"> <li data-line="115">Text 节点由 Text 类型表示,包含按字面解释的纯文本,亦可能包含转义后的 HTML 字符</li> <li data-line="116">Text 节点中包含的文本可以通过 nodeValue 或 data 属性访问,还可以通过 length 属性获取文本节点中包含的字符数量</li> <li data-line="117">文本节点暴露了下列操作方法: <ul data-line="118"> <li data-line="118">appendData(text)</li> <li data-line="119">deleteData(offset, count)</li> <li data-line="120">insertData(offset, text)</li> <li data-line="121">replaceData(offset, count, text)</li> <li data-line="122">splitText(offset)</li> <li data-line="123">substringData(offset, count) 提取从位置 offset 到 offset + count 的文本</li> </ul> </li> <li data-line="124">document.createTextNode 方法可以用来创建新文本节点</li> </ul> <h1 data-line="126" id="12.DOM 扩展">12.DOM 扩展</h1> <h2 data-line="128" id="Selectors API">Selectors API</h2> <ul data-line="130"> <li data-line="130">querySelector 方法接收 CSS 选择符参数,返回匹配该模式的第一个后代元素,如果没有匹配项则返回 null</li> <li data-line="131">querySelectorAll 方法接收一个用于查询的参数并返回所有匹配的节点。返回值是一个 NodeList 的静态实例。(并非动态)</li> <li data-line="132">matches 方法接收一个 CSS 选择符参数,如果元素匹配则返回 true 否则返回 false。使用该方法可以检测某个元素会不会被上述两种方法返回</li> </ul> <h2 data-line="134" id="元素遍历">元素遍历</h2> <p data-line="136">DOM元素新增的5个属性:</p> <ul data-line="138"> <li data-line="138">childElementCount 子元素数量(不包括文本节点和注释)</li> <li data-line="139">firstElementChild</li> <li data-line="140">lastElementChild</li> <li data-line="141">previousElementSibling</li> <li data-line="142">nextElementSibling</li> </ul> <h2 data-line="144" id="HTML5">HTML5</h2> <ul data-line="146"> <li data-line="146">getElementsByClassName 方法接受一个参数,即包含一个或多个类名的字符串,返回包含相应类的元素的 NodeList</li> <li data-line="147">HTML5 给所有元素添加了 classList 属性。classList 是一个新的集合类型 DOMTokenList 的实例,拥有 length 属性和 item 方法以及中括号获取元素。此外 DOMTokenList 还增加了以下方法: <ul data-line="148"> <li data-line="148">add(value)</li> <li data-line="149">contains(value)</li> <li data-line="150">remove(value)</li> <li data-line="151">toggle(value) 如果类名列表存在 value 则删除,如果不存在则添加</li> </ul> </li> <li data-line="152">HTML5 增加了辅助 DOM 焦点管理的方法: <ul data-line="153"> <li data-line="153">document.activeElement 包含当前拥有焦点的 DOM 元素。默认情况下页面加载完成前为 null,加载完成后设置为 document.body</li> <li data-line="154">document.hasFocus 方法返回布尔值表示文档是否拥有焦点</li> </ul> </li> <li data-line="155">HTML5 拓展了 HTMLDocument 类型: <ul data-line="156"> <li data-line="156">document.readyState 属性 <ul data-line="157"> <li data-line="157">loading 表示正在加载</li> <li data-line="158">complete 表示文档加载完成</li> </ul> </li> <li data-line="159">document.compatMode 属性表示页面渲染模式 <ul data-line="160"> <li data-line="160">CSS1Compat 标准模式</li> <li data-line="161">BackCompat 混杂模式</li> </ul> </li> <li data-line="162">document.head 属性指向 <head> 元素</li> </ul> </li> <li data-line="163">HTML5 新增了 document.characterSet 属性表示文档实际使用的字符集,也可以用来指定新字符集</li> <li data-line="164">HTML5 允许为元素指定非标准属性,使用 data- 前缀表示这些属性不包含与渲染有关的信息,也不包含元素的语义信息 <ul data-line="165"> <li data-line="165">可以通过元素的 dataset 属性访问自定义数据属性。元素的每个 data-name 属性在 dataset 中都可以通过 data- 前缀后面的字符串作为键来访问</li> </ul> </li> <li data-line="166">innerHTML 属性会返回元素所有后代的 HTML 字符串,写入 innerHTML 时会根据提供的字符串以新的 DOM 子树替换元素中原来包含的所有节点</li> <li data-line="167">outerHTML 属性与 innterHTML 类似,但是包含了调用它的元素</li> <li data-line="168">insertAdjacentHTML 和 insertAdjacentText 方法用于插入标签,接收两个参数:要插入标记的位置和要插入的 HTML 或文本。其中,第一个参数必须为下列值中的一个: <ul data-line="169"> <li data-line="169">"beforebegin" 插入当前元素前面,作为前一个同胞节点</li> <li data-line="170">"afterbegin" 插入当前元素内部,作为新的子节点或放在第一个子节点前面</li> <li data-line="171">"beforeend" 插入当前元素内部,作为新的子节点或放在最后一个子节点后面</li> <li data-line="172">"afterend" 插入当前元素后面,作为下一个同胞节点</li> </ul> </li> <li data-line="173">scrollIntoView 方法用于滚动浏览器窗口或容器元素以便包含元素进入视口。参数如下: <ul data-line="174"> <li data-line="174">alignToTop <ul data-line="175"> <li data-line="175">true 窗口滚动后元素顶部与视口顶部对齐</li> <li data-line="176">false 窗口滚动后元素底部与视口底部对齐</li> </ul> </li> <li data-line="177">scrollIntoViewOptions 是一个选项对象 <ul data-line="178"> <li data-line="178">behavior 过渡动画,smooth 或 auto,默认为 auto</li> <li data-line="179">block 定义垂直方向的对齐,start、center、end 或 nearest,默认为 start</li> <li data-line="180">inline 定义水平方向的对齐, start、center、end 或 nearest,默认为 nearest</li> </ul> </li> <li data-line="181">不传参数等同于 alignToTop 为 true</li> </ul> </li> </ul> <h1 data-line="183" id="13.事件">13.事件</h1> <h2 data-line="185" id="事件流">事件流</h2> <p data-line="187">事件流描述了页面接收时间的顺序。<br> <strong>事件冒泡</strong>的事件被定义为从最具体的元素开始触发,然后向上传播到没那么具体的元素。现代浏览器中的事件会一直冒泡到 windows 对象。<br> <strong>事件捕获</strong>的意思是最不具体的元素最先收到事件,最具体的节点最后收到事件。事件捕获是为了在事件到达最终目标前拦截事件。<br> DOM 事件流规定事件流分为3个阶段:事件捕获、到达目标和事件冒泡。</p> <h2 data-line="192" id="事件处理程序">事件处理程序</h2> <p data-line="194">为响应事件而调用的函数称为事件处理程序(或事件监听器)。<br> JavaScript 中传统的事件处理程序是把一个函数赋值给元素的一个事件处理程序属性。例如:</p> <details data-line="197" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">let btn = document.getElementById('myBtn'); btn.onClick = function() { console.log('Clicked'); };</span><span rn-wrapper aria-hidden="true"><span></span><span></span><span></span><span></span></span></code></pre> </details> <p data-line="204">DOM2 Events 为事件处理程序的赋值和移除定义了两个方法:addEventListener 和 removeEventListener。方法接收3个参数:事件名、事件处理函数和一个布尔值(true 表示在捕获阶段调用事件处理程序,false 表示在冒泡阶段调用事件处理程序)。使用 DOM2 方式的主要优势是可以为同一个事件添加多个事件处理程序。</p> <h2 data-line="206" id="事件对象">事件对象</h2> <p data-line="208">在 DOM 中发生事件时,所有相关信息会存储在 event 对象中,event 对象也是传给事件处理程序的唯一参数。</p> <ul data-line="210"> <li data-line="210">在事件处理程序内部,this 对象始终等于 currentTarget 的值,而 target 只包含事件的实际目标</li> <li data-line="211">preventDefault 方法用于阻止特定事件的默认动作</li> <li data-line="212">stopPropagation 方法用于阻止事件流在 DOM 结构中的传播,取消后续事件捕获或冒泡</li> <li data-line="213">eventPhase 属性可用于确定事件流所处阶段 <ul data-line="214"> <li data-line="214">1 捕获阶段</li> <li data-line="215">2 在目标上被调用</li> <li data-line="216">3 冒泡阶段</li> </ul> </li> </ul> <h2 data-line="218" id="事件类型">事件类型</h2> <h3 data-line="220" id="用户界面事件">用户界面事件</h3> <ul data-line="222"> <li data-line="222">load 在 window 上当页面加载完成后触发</li> <li data-line="223">unload 在 window 上当页面完全卸载后触发</li> <li data-line="224">abort 在<object>元素上当相应对象加载完成前被用户提前终止下载时触发</li> <li data-line="225">error 在 window 上当 JavaScript 报错时触发;在<img>元素上当无法加载指定图片时触发;在<object>元素上当无法加载相应对象时触发</li> <li data-line="226">select 在文本框上当用户选择了一个或多个字符时触发</li> <li data-line="227">resize 在 window 或窗格上当窗口或窗格被缩放时触发</li> <li data-line="228">scroll 当用户滚动包含滚动条的元素时在元素上触发</li> </ul> <h3 data-line="230" id="焦点事件">焦点事件</h3> <p data-line="232">焦点事件在页面元素获得或失去焦点时触发。常用焦点事件包括:</p> <ul data-line="234"> <li data-line="234">blur 元素失去焦点时触发</li> <li data-line="235">focus 当元素获得焦点时触发,该事件不冒泡</li> <li data-line="236">focusin 当元素获得焦点时触发,这个事件是 focus 的冒泡版</li> <li data-line="237">focusout 当元素失去焦点时触发,这个事件是 blur 的通用版</li> </ul> <h3 data-line="239" id="鼠标和滚轮事件">鼠标和滚轮事件</h3> <ul data-line="241"> <li data-line="241">click 当用户点击鼠标主键或按键盘回车触发</li> <li data-line="242">dbclick 当用户双击鼠标主键触发</li> <li data-line="243">mousedown 按下鼠标任意键触发</li> <li data-line="244">mouseenter 鼠标光标从元素外部移入内部触发</li> <li data-line="245">mouseleave 鼠标光标从元素内部移到外部触发</li> <li data-line="246">mousemove 鼠标在元素上移动时<strong>反复</strong>触发</li> <li data-line="247">mouseout 鼠标光标从一个元素移动到另一个元素时触发</li> <li data-line="248">mouseover 鼠标光标从元素外部移到内部触发</li> <li data-line="249">mouseup 用户释放鼠标键时触发</li> <li data-line="250">mousewheel 滚轮事件</li> <li data-line="251">鼠标事件的 event 对象可以获取光标的坐标: <ul data-line="252"> <li data-line="252">clientX & clientY 相对于浏览器视口的坐标</li> <li data-line="253">pageX & pageY 相对于页面的坐标</li> <li data-line="254">screenX & screenY 相对于屏幕的坐标</li> </ul> </li> <li data-line="255">event 对象可以通过 shiftKey、ctrlKey、altKey 和 metaKey 属性获取修饰键状态,返回一个布尔值</li> </ul> <h3 data-line="257" id="键盘输入事件">键盘输入事件</h3> <ul data-line="259"> <li data-line="259">keydown 按下键盘某个键触发,持续按住会重复触发</li> <li data-line="260">keyup 释放某个键触发</li> <li data-line="261">textInput 输入事件</li> </ul> <h3 data-line="263" id="HTML5 事件">HTML5 事件</h3> <ul data-line="265"> <li data-line="265">contextmenu 用于控制上下文菜单</li> <li data-line="266">beforeunload 在 window 上触发,给开发者提供组织页面被卸载的机会</li> <li data-line="267">DOMContentLoaded 会在 DOM 树构建完成后触发,不用等待图片、JavaScript文件、CSS文件或其他资源加载完成</li> <li data-line="268">haschange 在URL散列值(#后的内容)发生变化时触发</li> </ul> <h2 data-line="270" id="内存与性能">内存与性能</h2> <ul data-line="272"> <li data-line="272">使用<strong>事件委托</strong>处理过多事件处理程序。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件</li> <li data-line="273">及时删除不用的事件处理程序来提升页面性能</li> </ul> <h2 data-line="275" id="模拟事件">模拟事件</h2> <ul data-line="277"> <li data-line="277">模拟鼠标事件:createEvent 方法并传入 MouseEvents 参数会返回一个 event 对象,使用该对象的 initMouseEvent 方法指定信息</li> <li data-line="278">模拟键盘事件:createEvent 方法并传入 KeyboardEvent 参数会返回一个 event 对象,使用该对象的 initKeyboardEvent 方法指定信息</li> <li data-line="279">模拟其他事件:createEvent 方法并传入 HTMLEvents 参数会返回一个 event 对象,使用该对象的 initEvent 方法指定信息</li> <li data-line="280">自定义 DOM 事件:createEvent 方法并传入 CustomEvent 参数会返回一个 event 对象,使用该对象的 initCustomEvent 方法指定信息</li> </ul> <h1 data-line="282" id="14.错误处理与调试">14.错误处理与调试</h1> <ul data-line="284"> <li data-line="284">ECMA-262 第3版新增了 try/catch 语句作为 JavaScript 中处理异常的一种方式</li> <li data-line="285">try/catch 语句可以使用可选的 finally 子句,finally 中的代码始终运行</li> <li data-line="286">ECMA-262 定义了8种错误类型: <ul data-line="287"> <li data-line="287">Error 基类型,其他错误类型继承该类型</li> <li data-line="288">InternalError 底层 JavaScript 引擎抛出异常时由浏览器抛出</li> <li data-line="289">EvalError eval 函数发生异常时抛出</li> <li data-line="290">RangeError 数值越界时抛出</li> <li data-line="291">ReferenceError 找不到对象时抛出</li> <li data-line="292">SyntaxError 给 eval 传入的字符串包含 JavaScript 语法错误时抛出</li> <li data-line="293">TypeError 变量不是预期类型或者访问不存在的方法时</li> <li data-line="294">URIError encodeURI 和 decodeURI 传入格式错误的 URI 时发生</li> </ul> </li> <li data-line="295">throw 操作符用于抛出自定义错误,必须有一个类型不限的值。使用 throw 操作符时,代码立即停止执行,除非 try/catch 语句捕获了抛出的值</li> </ul> <h1 data-line="297" id="15.JSON">15.JSON</h1> <ul data-line="299"> <li data-line="299">JSON 解析与序列化的两个方法: <ul data-line="300"> <li data-line="300">stringify 方法接收一个要序列化的对象和两个可选参数。第一个可选参数是过滤器,可以是数组或函数;第二个参数是用于缩进结果的 JSON 字符串的选项。 <ul data-line="301"> <li data-line="301">如果过滤器参数是一个数组,那么返回结果中只会包含该数组中列出的属性;如果过滤器参数是一个函数,该函数接收 key 和 value 两个属性,可以根据 key 返回自定义的结果。如果返回值为 undefined,则这个 key 会被忽略</li> <li data-line="302">字符串缩进参数为数值时,表示每一级缩进的空格数;如果参数为字符串,那么 JSON 字符串会使用这个字符串来缩进</li> <li data-line="303">可以在要序列化的对象上添加 toJSON 方法自定义序列化</li> </ul> </li> <li data-line="304">parse 方法可以接受一个额外的还原函数参数,类似于 stringify 的替代函数。还原函数会针对每个 key/value 调用一次。如果还原函数返回 undefined 则结果中会删除相应的 key,如果返回了其他值,则该值会作为相应 key 的 value 插入到结果中</li> </ul> </li> </ul> <h1 data-line="306" id="16.网络请求与远程资源">16.网络请求与远程资源</h1> <h2 data-line="308" id="XMLHttpRequest 对象">XMLHttpRequest 对象</h2> <p data-line="310">XHR 对象通过 XMLHttpRequest 构造函数创建,使用 open 方法配置并使用 send 方法发送。open 方法接收3个参数:请求类型,URL 以及 请求是否异步的布尔值。send 方法接收一个参数,作为请求体发送的数据。如果不需要请求体则必须传 null。</p> <details data-line="312" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">let xhr = new XMLHttpRequest() xhr.open('get', 'example.php', false) xhr.send(null)</span><span rn-wrapper aria-hidden="true"><span></span><span></span><span></span></span></code></pre> </details> <p data-line="318">收到服务器响应后,XHR 对象的以下属性会被填充数据:</p> <ul data-line="320"> <li data-line="320">responseText</li> <li data-line="321">responseXML</li> <li data-line="322">status</li> <li data-line="323">statusText</li> </ul> <p data-line="325">此外 XHR 对象有一个 readyState 属性,表示当前处在请求/响应过程的哪个阶段。常用的值为 4,表示收到所有响应。readyState 改变时会触发 readystatechange 事件,可以为 XHR 对象添加事件处理程序。</p> <details data-line="327" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">let xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { if (xhr.readyState == 4) { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { alert(xhr.responseText) } else { alert('request was unsuccessful:' + xhr.status) } } } xhr.open('get', 'example.txt', true) xhr.send(null)</span><span rn-wrapper aria-hidden="true"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre> </details> <p data-line="342">可以调用 abort 方法在收到响应前取消异步请求。<br> XHR 对象可以使用 setRequestHeader 方法设置额外的请求头,使用 getResponseHeader 方法传入想要获取的头部名称获取响应头,使用 getAllResponseHeaders 方法获取所有响应头的字符串。<br> FormData 类型用于表单序列化,也便于创建与表单类似格式的数据通过 XHR 对象发送。</p> <ul data-line="346"> <li data-line="346">append 方法用于填充数据,接收两个参数:键和值,相当于表单字段名称和该字段的值。</li> </ul> <h2 data-line="348" id="进度事件">进度事件</h2> <ul data-line="350"> <li data-line="350">loadstart 接收到响应的第一个字节触发</li> <li data-line="351">progress 接收响应期间反复触发</li> <li data-line="352">error 请求出错时触发</li> <li data-line="353">abort 调用 abort 方法终止链接时触发</li> <li data-line="354">load 成功接收响应时触发</li> <li data-line="355">loadend 通信完成后触发(包括 error、abort)</li> </ul> <h2 data-line="357" id="跨源资源共享">跨源资源共享</h2> <p data-line="359">跨源资源共享(CORS)定义了浏览器与服务器如何实现跨源通信。其基本思路是使用自定义 HTTP 头部允许浏览器和服务器相互了解以确认请求或响应应该成功还是失败。<br> 对于简单的请求,比如:GET 或 POST 类型,没有自定义头部且请求体是 text/plain 类型,这样的请求会在发送时有一个<strong>额外的头部</strong> Origin,包含发送请求的页面的源(协议、域名和端口)。如果服务器决定响应请求,应该发送 Access-Control-Allow-Origin 头部包含相同的源或 *(表示资源公开)。如果没有这个头部或源不匹配则表明不会响应浏览器请求。<strong>无论请求还是响应都不会包含 cookie 信息</strong>。<br> 对于上述简单请求外的情况,CORS 通过一种叫<strong>预检请求</strong>的服务器验证机制实现跨源。在发送自定义头部、GET 和 POST 外的方法以及不同请求体内容类型时,会先向服务器发送一个“预检”请求,这个请求使用 OPTIONS 方法发送并包含以下头部:</p> <ul data-line="363"> <li data-line="363">Origin:与简单请求相同</li> <li data-line="364">Access-Control-Request-Method:请求希望使用的方法</li> <li data-line="365">Access-Control-Request-Headers:(可选)要使用的逗号分隔的自定义头部列表</li> </ul> <p data-line="367">服务器在这个请求发送后可以确定是否允许这种类型的请求并在响应中发送如下头部:</p> <ul data-line="369"> <li data-line="369">Access-Control-Allow-Origin:与简单请求相同</li> <li data-line="370">Access-Control-Allow-Methods:允许的方法(逗号分隔)</li> <li data-line="371">Access-Control-Allow-Headers:服务器允许的头部(逗号分隔)</li> <li data-line="372">Access-Control-Max-Age:缓存预检请求的秒数</li> </ul> <p data-line="374"><strong>预检请求返回后,结果会按照响应指定时间缓存一段时间。</strong><br> 默认情况下,跨源请求不提供凭据(cookie、HTTP 认证和客户端 SSL 证书)。可以通过 WithCredentials 属性设置为 true 来表明请求会发送凭据。如果服务器允许带凭据的请求,可以在响应中包含头部:Access-Control-Allow-Credentials: true。</p> <h2 data-line="377" id="替代性跨源技术">替代性跨源技术</h2> <ul data-line="379"> <li data-line="379">图片探测是利用<img>标签实现跨域通信最早的一种技术</li> <li data-line="380">JSONP 调用通过动态创建<script>元素并为 src 属性指定跨域 URL 实现通信</li> </ul> <h2 data-line="382" id="Fetch API">Fetch API</h2> <p data-line="384">Fetch API 能够执行 XMLHttpRequest 对象的所有任务,但更易使用且接口更现代化,并且能在 Web 工作线程等现代 Web 工具中使用。</p> <h3 data-line="386" id="基本用法">基本用法</h3> <p data-line="388">fetch 方法只有一个必须的参数 input。多数情况下这个参数是要获取资源的 URL。这个方法返回一个期约:</p> <details data-line="390" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">let r = fetch('/bar') .then((response) => { console.log(response) })</span><span rn-wrapper aria-hidden="true"><span></span><span></span><span></span><span></span></span></code></pre> </details> <p data-line="397">读取 response 最简单的方式是取得文本格式的内容,使用 text 方法。<br> Fetch API 支持通过 Response 的 status 和 statusText 属性检查响应状态。跟随重定向时,响应对象的 redirected 属性会被设置为 true,状态码仍然是 200。<br> 只使用 URL 时,fetch 方法会发送 GET 请求,只包含最低限度的请求头。进一步配置请求可以传入第二个参数 init 对象。init 对象的可选配置包括:</p> <ul data-line="401"> <li data-line="401">body</li> <li data-line="402">cache</li> <li data-line="403">credentials</li> <li data-line="404">headers</li> <li data-line="405">integrity:用于强制子资源完整性</li> <li data-line="406">keepalive:用于知识浏览器允许请求存在时间超出页面生命周期</li> <li data-line="407">method</li> <li data-line="408">mode:用于指定请求模式。这个模式决定来自跨源请求的响应是否有效</li> <li data-line="409">redirect:用于指定如何处理重定向响应</li> <li data-line="410">referrer:用于指定 HTTP 的 Referer 头部的内容</li> <li data-line="411">referrerPolicy:用于指定 HTTP 的 Referer 头部</li> <li data-line="412">signal:用于支持通过 AbortController 中断进行的 fetch 请求</li> </ul> <details data-line="414" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">let abortController = new AbortController() fetch('wikipedia.zip', {signal: abortController.signal}) .catch(() => console.log('aborted!')) // 10毫秒后中断请求 setTimeout(() => abortController.abort(), 10)</span><span rn-wrapper aria-hidden="true"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre> </details> <h3 data-line="424" id="Headers 对象">Headers 对象</h3> <p data-line="426">Headers 对象是所有外发请求和入站响应头部的容器。Headers 对象与 Map 对象极为相似,都有 get、set、has 和 delete 等实例方法。此外,在初始化 Headers 对象的时候也可以使用键值对的形式。<br> Headers 对象通过 append 方法添加多个值。在 Headers 实例中还不存在的头部上调用 append 方法相当于调用 set,后续调用会以逗号为分隔符拼接多个值。</p> <details data-line="429" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">let h = new Headers() h.append('foo', 'bar') console.log(h.get('foo')) // 'bar' h.append('foo', 'baz') console.log(h.get('foo')) //'bar, baz'</span><span rn-wrapper aria-hidden="true"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre> </details> <h3 data-line="439" id="Request 对象">Request 对象</h3> <p data-line="441">Request 对象是获取资源请求的接口,暴露请求相关信息和请求体的不同方式。<br> Request 构造函数接收 input 参数和 init 对象,其中 input 参数一般为 URL,init 参数与 fetch 方法一样。<br> Fetch API 提供了两种创建 Request 对象副本的方式:</p> <ol data-line="445"> <li data-line="445">使用 Request 构造函数</li> </ol> <details data-line="447" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">let r1 = new Request('https://foo.com') let r2 = new Request(r1) console.log(r2.url) // https://foo.com</span><span rn-wrapper aria-hidden="true"><span></span><span></span><span></span><span></span></span></code></pre> </details> <p data-line="454">如果传入 init 对象则会覆盖源对象中同名的值</p> <details data-line="456" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">let r1 = new Request('https://foo.com') let r2 = new Request(r1, {method: 'post'}) console.log(r1.method) // GET console.log(r2.method) // POST</span><span rn-wrapper aria-hidden="true"><span></span><span></span><span></span><span></span><span></span></span></code></pre> </details> <p data-line="464"><strong>这种克隆方式并不总能得到一模一样的副本</strong>。第一个请求的请求体会被标记为“已使用”(bodyUsed 属性为 true)。如果源对象与创建的新对象不同源,则 referrer 属性会被清除。如果源对象的 mode 为navigate,则会被转换为 same-origin。</p> <ol start="2" data-line="466"> <li data-line="466">使用 clone 方法可以创建一模一样的副本,并且请求体不会被标记为“已使用”</li> </ol> <details data-line="468" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">let r1 = new Request('https://foo.com', {method: 'POST', body: 'foobar'}) let r2 = r1.clone() console.log(r1.url) // https://foo.com console.log(r2.url) // https://foo.com console.log(r1.bodyUsed) // false console.log(r2.bodyUsed) // false</span><span rn-wrapper aria-hidden="true"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre> </details> <p data-line="479"><strong>如果请求对象的 bodyUsed 属性为 true,上述任何一种方式都不能用来创建这个对象的副本</strong>。<br> fetch 方法可以直接传入创建好的 Request 实例。</p> <h3 data-line="482" id="Response 对象">Response 对象</h3> <p data-line="484">大多数情况下,产生 Response 对象的主要方式是调用 fetch 方法,它会返回一个最后会解决为 Response 对象的期约。<br> Response 类还有两个生成 Response 对象的静态方法:</p> <ol data-line="487"> <li data-line="487">Response.redirect:接收一个 URL 和一个重定向状态码并返回重定向的 Response 对象</li> <li data-line="488">Response.error:产生表示网络错误的 Response 对象</li> </ol> <p data-line="490">Response 对象也有 clone 方法,用法与 Request 对象类似。</p> <h3 data-line="492" id="Request、Response 及 Body 混入">Request、Response 及 Body 混入</h3> <p data-line="494">Request 和 Response 都使用了 Fetch API 的 Body 混入,为上述两种类型提供了只读的 body 属性(实现为 ReadableStream)、只读的 bodyUsed 布尔值(表示 body 流是否已读)和一组方法。<br> Body 混入提供了5个方法,用于将 ReadableStream 转存到缓存区的内存里,将缓冲区转换为某种 JavaScript 对象类型,以及通过期约来产生结果。</p> <ol data-line="497"> <li data-line="497">Body.text 方法返回期约,解决为 UTF-8 格式字符串</li> <li data-line="498">Body.json 方法返回期约,解决为 JSON</li> <li data-line="499">Body.formData 方法返回期约,解决为 FormData 实例</li> <li data-line="500">Body.arrayBuffer 方法返回期约,解决为 ArrayBuffer 实例</li> <li data-line="501">Body.blob 方法返回期约,解决为 Blob 实例</li> </ol> <h3 data-line="503" id="Web Socket">Web Socket</h3> <p data-line="505">Web Socket(套接字)的目标是通过一个长时连接实现与服务器全双工、双向的通信。因为 Web Socket 使用了自定义协议,所以 URL 方案(scheme)要使用 ws:// 和 wss://。<br> 创建一个新的 Web Socket 需要实例化一个 WebSocket 对象并传入提供连接的 URL:</p> <details data-line="508" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">let socket = new WebSocket('ws://www.example.com/server.php')</span><span rn-wrapper aria-hidden="true"><span></span></span></code></pre> </details> <p data-line="512"><strong>必须给 WebSocket 构造函数传入一个绝对 URL。同源策略不适用与 WebSocket</strong>。浏览器会在初始化 WebSocket 对象后立即创建连接。使用 close 方法可以关闭 Web Socket 连接。<br> 使用 send 方法向服务器发送数据,包括字符串、ArrayBuffer 或 Blob:</p> <details data-line="515" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">let socket = new WebSocket('ws://www.example.com/server.php') let stringData = 'Hello World!' let arrayBufferData = Unit8Array.from(['f', 'o', 'o']) let blobData = new Blob(['f', 'o', 'o']) socket.send(stringData) socket.send(arrayBufferData.buffer) socket.send(blobData)</span><span rn-wrapper aria-hidden="true"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre> </details> <p data-line="527">服务器向客户端发送消息时,WebSocket 对象上会触发 message 事件。可以通过事件处理函数的 event.data 属性访问到有效载荷。返回的数据也可能是 blob 或 arraybuffer。</p> <details data-line="529" class="md-editor-code" open=""> <summary class="md-editor-code-head"> <div class="md-editor-code-flag"><span></span><span></span><span></span></div> <div class="md-editor-code-action"> <span class="md-editor-code-lang">javascript</span> <span class="md-editor-copy-button" data-tips="复制代码">复制代码</span> <span class="md-editor-collapse-tips"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-chevron-left md-editor-icon"><circle cx="12" cy="12" r="10"/><path d="m14 16-4-4 4-4"/></svg></span> </div> </summary> <pre><code class="language-javascript" language=javascript><span class="md-editor-code-block">socket.onmessage = function (event) { let data = event.data }</span><span rn-wrapper aria-hidden="true"><span></span><span></span><span></span></span></code></pre> </details> <p data-line="535">WebSocket 对象在连接生命周期中还有可能触发 3 个其他事件:</p> <ol data-line="537"> <li data-line="537">open:连接成功建立时触发</li> <li data-line="538">error:发生错误时触发</li> <li data-line="539">close:连接关闭时触发</li> </ol> </div><!----><!--]--></div><div class="mt-6"><!--[--><!--]--></div><div class="relative py-4 md:py-8 flex items-center"><div class="flex-1 border-t border-dashed border-gray-100 dark:border-gray-800 md:border-t-2"></div><div class="relative px-3 md:px-6"><div class="flex items-center gap-1.5 md:gap-2 px-3 md:px-4 py-1 md:py-1.5 rounded-full border border-dashed border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 md:border-2"><span class="relative flex h-1.5 w-1.5 md:h-2 md:w-2"><span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary-400 opacity-75"></span><span class="relative inline-flex rounded-full h-1.5 w-1.5 md:h-2 md:w-2 bg-primary-500"></span></span><span class="text-xs font-bold uppercase tracking-[0.1em] md:tracking-[0.2em] text-gray-500 dark:text-gray-400 whitespace-nowrap">相关文章</span></div></div><div class="flex-1 border-t border-dashed border-gray-100 dark:border-gray-800 md:border-t-2"></div></div><div><div data-orientation="horizontal" class="flex flex-col gap-8 lg:gap-y-16 sm:grid sm:grid-cols-2 lg:grid-cols-3"><!--[--><!--[--><article data-orientation="vertical" data-slot="root" class="relative group/blog-post rounded-lg overflow-hidden flex flex-col bg-default ring ring-default has-focus-visible:ring-2 has-focus-visible:ring-primary transition hover:bg-elevated/50"><!----><div data-slot="body" class="min-w-0 flex-1 flex flex-col p-4 sm:p-6"><a href="/posts/17" aria-label="Vite项目配置本地HTTPS" class="focus:outline-none peer"><!--[--><!--[--><span class="absolute inset-0" aria-hidden="true"></span><!--]--><!--]--></a><!--[--><div data-slot="meta" class="flex items-center gap-2 mb-2"><!--[--><!----><!--]--><time datetime="2025-10-13T07:54:22.000Z" data-slot="date" class="text-sm text-toned"><!--[-->2025年10月13日<!--]--></time></div><h2 data-slot="title" class="text-pretty font-semibold text-highlighted text-lg"><!--[-->Vite项目配置本地HTTPS<!--]--></h2><!----><!----><!--]--></div><!----></article><article data-orientation="vertical" data-slot="root" class="relative group/blog-post rounded-lg overflow-hidden flex flex-col bg-default ring ring-default has-focus-visible:ring-2 has-focus-visible:ring-primary transition hover:bg-elevated/50"><!----><div data-slot="body" class="min-w-0 flex-1 flex flex-col p-4 sm:p-6"><a href="/posts/22" aria-label="React Native 开发环境安装踩坑" class="focus:outline-none peer"><!--[--><!--[--><span class="absolute inset-0" aria-hidden="true"></span><!--]--><!--]--></a><!--[--><div data-slot="meta" class="flex items-center gap-2 mb-2"><!--[--><!----><!--]--><time datetime="2024-06-25T11:43:18.000Z" data-slot="date" class="text-sm text-toned"><!--[-->2024年6月25日<!--]--></time></div><h2 data-slot="title" class="text-pretty font-semibold text-highlighted text-lg"><!--[-->React Native 开发环境安装踩坑<!--]--></h2><!----><!----><!--]--></div><!----></article><article data-orientation="vertical" data-slot="root" class="relative group/blog-post rounded-lg overflow-hidden flex flex-col bg-default ring ring-default has-focus-visible:ring-2 has-focus-visible:ring-primary transition hover:bg-elevated/50"><!----><div data-slot="body" class="min-w-0 flex-1 flex flex-col p-4 sm:p-6"><a href="/posts/14" aria-label="《JavaScript 高级程序设计》第8-9章 函数&期约与异步函数" class="focus:outline-none peer"><!--[--><!--[--><span class="absolute inset-0" aria-hidden="true"></span><!--]--><!--]--></a><!--[--><div data-slot="meta" class="flex items-center gap-2 mb-2"><!--[--><!----><!--]--><time datetime="2024-03-12T11:41:56.000Z" data-slot="date" class="text-sm text-toned"><!--[-->2024年3月12日<!--]--></time></div><h2 data-slot="title" class="text-pretty font-semibold text-highlighted text-lg"><!--[-->《JavaScript 高级程序设计》第8-9章 函数&期约与异步函数<!--]--></h2><!----><!----><!--]--></div><!----></article><!--]--><!--]--></div></div></div><!--]--></div><!--]--></div><!----></div><!--]--></div><!--]--></div><footer data-slot="root" class="h-20 flex items-center"><!----><div class="w-full max-w-(--ui-container) mx-auto px-4 sm:px-6 lg:px-8 py-8 lg:py-4 lg:flex lg:items-center lg:justify-between lg:gap-x-3" data-slot="container"><!--[--><div data-slot="right" class="lg:flex-1 flex items-center justify-center lg:justify-end gap-x-1.5 lg:order-3"><!--[--><p class="text-muted text-sm"> Powered by <a href="https://nuxt.com" target="_blank" class="font-semibold"> Nuxt.js </a></p><!--]--></div><div data-slot="center" class="mt-3 lg:mt-0 lg:order-2 flex items-center justify-center"><!--[--><!--]--></div><div data-slot="left" class="flex items-center justify-center lg:justify-start lg:flex-1 gap-x-1.5 mt-3 lg:mt-0 lg:order-1"><!--[--><p class="text-muted text-sm"> Neekko33's Blog © 2026</p><!--]--></div><!--]--></div><!----></footer></div><!--]--><!--]--><!--[--><!--]--><!--v-if--><!--]--><!--[--><!--]--><!--]--><!--]--></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/",buildId:"055a609c-7d3f-4e80-9502-4b6496359fb9",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="true" id="__NUXT_DATA__">[["ShallowReactive",1],{"data":2,"state":46,"once":53,"_errors":54,"serverRendered":50,"path":56},["ShallowReactive",3],{"$ftAoTn5b7VeExj4j9SfFdEJyXhbOtPlPs2jkIEMdYIXo":4,"$fu76y50jFRS9ch7--s7LBimGWGLndEcSJS1PouKmugJU":16},{"status":5,"message":6,"data":7},"success","Success",{"id":8,"user_id":9,"category_id":10,"title":11,"content":12,"created_at":13,"updated_at":14,"status":9,"deployed_at":15},16,1,2,"《JavaScript 高级程序设计》第10-16章","# 10.浏览器对象模型(BOM)\n\n## window 对象\n\nBOM 的核心是 window 对象,表示浏览器的实例。\n\n- 因为 window 对象被复用为 ECMAScript 中的 Global 对象,所以通过 var 声明的所有全局变量和函数会变成 window 对象的属性和方法\n- top 对象始终指向最外层窗口,即浏览器窗口本身。parent 对象始终指向当前窗口的父窗口。self 对象始终指向 window \n- 现代浏览器提供了 screenLeft 和 screenTop 属性表示窗口相对于屏幕左侧和顶部的位置,可以使用 moveTo 和 moveBy 方法移动窗口\n- outerWidth 和 outerHeight 返回浏览器窗口自身大小,innerWidth 和 innerHeight 返回页面视口大小\n- 度量文档相对于视口的滚动距离可以使用:window.pageXoffset/window.scrollX 和 window.pageYoffset/window.scrollY 。使用 scroll、scrollTo 和 scrollBy 方法滚动页面,前两个方法接收滚动坐标,最后一个方法接收滚动距离\n- window.open 方法可以导航到指定 URL,也可以用于打开新浏览器窗口\n- setTimeout 用于指定在一段时间后执行某些代码,接收两个参数:要执行的代码和等待的时间。\n - **注意**:由于 JavaScript 是单线程的,为了调度不同代码的执行,JavaScript 维护了一个任务队列,其中的任务会按照添加到队列的先后顺序执行。setTimeout 第二个参数只是告诉 JavaScript 引擎在指定毫秒数后把任务添加到这个队列,并非立即执行\n - 调用 setTimeout 返回表示该超时排期的数值ID,使用 clearTimeout 并传入该 ID 可以取消等待中的排期任务\n- setInterval 用于指定每隔一段时间执行某些代码,接收两个参数:要执行的代码以及把下一次执行定时代码的任务添加到队列要等待的时间\n - setInterval 也会返回一个循环定时 ID,使用 clearInterval 可以取消循环定时\n- 使用 alert、confirm 和 prompt 方法可以调用浏览器系统对话框展示信息\n\n## location 对象\n\nlocation 对象是最有用的 BOM 对象之一,提供当前窗口中加载文档的信息及常用导航功能。它既是 window 属性又是 document 属性。\n\n- URLSearchParams 提供了一组标准 API 方法用于检查和修改查询字符串,包括 get、set 和 delete 等,大部分支持的浏览器也可以将 URLSearchParams 实例作为可迭代对象\n- 可以通过设置 location.href 或 window.location 为一个 URL 修改浏览器的地址,它们都通过以该 URL 的值调用 assign 方法启动导航到新 URL,并在浏览器历史记录中增加一条新的记录。如果不希望增加记录可以使用 replace 方法。reload 方法可以重新加载当前页面,传入 true 可以强制从服务器重新加载\n\n## navigator 对象\n\nnavigator 对象的属性通常用于确定浏览器的类型。\n\n- 可以通过 plugins 属性获取浏览器插件数组\n- 使用 registerProtocolHandler 方法可以把一个网站注册为处理某种特定类型信息的应用程序\n\n## screen 对象\n\nscreen 对象保存的纯粹是客户端能力信息,也就是浏览器窗口外面的客户端显示器信息\n\n## history 对象\n\nhistory 对象表示当前窗口首次使用以来用户的导航历史记录,该对象并不会暴露用户访问过的 URL,但可以在不知道实际 URL 的情况下实现前进和后退。\n\n### 导航\n\n使用 go 方法可以在用户历史记录中沿任何方向导航,接收一个参数表示前进或后退多少步。可以使用 back 和 forward 方法简写,模拟浏览器的前进后退按钮。history 对象还有一个 length 属性表示历史记录中有多少条目。\n\n### 历史状态管理\n\nhistory.pushState 方法可以让开发者改变浏览器 URL 而不会加载新页面,该方法接收3个参数:一个 state 对象、一个新状态的标题和一个(可选)相对 URL。\n\n```javascript\nlet stateObject = {foo:'bar'}\nhistory.pushState(stateObject, 'My Title', 'baz.html')\n```\n\npushState 方法执行后,状态信息会被推到历史记录中,浏览器地址也会改变以反映新的相对 URL。虽然 location.href 返回的是地址栏中的新内容但是浏览器页不会向服务器发送请求。\n可以通过 history.state 获取当前状态对象,使用 replaceState 覆盖当前状态。\n\n# 11.文档对象模型(DOM)\n\n## 节点层级\n\n任何 HTML 和 XML 文档都可以用 DOM 表示为一个由节点组成的层级结构\n\n### Node 类型\n\n- DOM Level1 描述了名为 Node 的接口,所有 DOM 节点类型都必须实现该接口。Node 接口在 JavaScript 中被实现为 Node 类型,所有节点类型都继承 Node 类型\n- 节点的 nodeType 属性表示该节点的类型\n- 节点的 childNodes 属性包含一个 NodeList 实例,用于存储可以按位置存取的有序节点\n - NodeList 对象可以动态的反应 DOM 结构的变化\n - childNodes 列表中的每个节点都是同一列表中其他节点的同胞节点。使用 previousSibling 和 nextSibling 可以在列表的节点间导航\n - 父节点可以使用专门属性 firstChild 和 lastChild 获取 childNodes 中第一个和最后一个节点\n - hasChildNodes 方法用于判断节点是否有子节点\n- 节点的 parentNode 属性指向其 DOM 树中的父元素\n- DOM 提供了一些操纵节点的方法\n - appendChild 用于在 childNodes 列表末尾添加节点,如果把文档中存在的节点传给该方法,则这个节点会从原位置转移到新位置\n - insertBefore 方法用于把节点放置在 childNodes 指定位置,接收两个参数:要插入的节点和参照节点\n - replaceChild 方法接受两个参数:要插入的节点和要替换的节点。要替换的节点会被返回并从文档树中完全移除\n - removeChild 方法用于移除节点\n - cloneNode 方法会返回与调用它的节点一模一样的节点,接收一个布尔值参数表示是否深复制\n - normalize 方法用于处理文档子树中的文本节点,删除空文本节点,将同胞文本节点合并\n\n### Document 类型\n\n- Document 类型是 JavaScript 中表示文档节点的类型。在浏览器中文档对象 document 是 HTMLDocuemnt 的实例,表示整个 HTML 页面\n- document 对象的 documentElement、firstChild 和 child Nodes[0] 都指向同一个值,即 \u003Chtml>元素。document 对象的 body 属性指向 \u003Cbody> 元素\n- document 对象包含一些常用属性:\n - title 包含 \u003Ctitle> 元素中的文本\n - URL 包含当前页面完整的 URL\n - domain 包含页面的域名\n - referrer 包含链接到当前页面的 URL\n- 获取元素的引用可以使用 getElementById 、getElementsByTagName 和 getElementsByName 方法\n\n### Element 类型\n\n- Element 表示 XML 或 HTML 元素,对外暴露出访问元素标签名、子节点和属性的能力\n- 可以通过 nodeName 或 tagName 属性获取元素的标签名\n- 所有的 HTML 元素都通过 HTMLElement 类型表示,并具有以下标准属性:\n - id 元素在文档中的唯一标识符\n - title 元素的额外信息\n - lang 元素内容的语言代码\n - dir 语言的书写方向\n - className 相当于 class 属性,指定元素的 CSS 类\n- 与属性相关的 DOM 方法主要有:\n - getAttribute 获取属性的值(由于通过 DOM 对象和 getAttribute 方法在获取 style 属性和事件处理程序时表现不同,开发者通常使用对象属性进行 DOM 编程,使用 getAttribute 获取自定义属性的值)\n - setAttribute 设置属性的值\n - removeAttribute 删除属性\n- Element 类型包含 attributes 属性。属性包含一个 NamedNodeMap 实例,每个属性表示为一个 Attr 节点。每个节点的 nodeName 是对应属性的名字,nodeValue 是对应属性的值。NamedNodeMap 对象包含下列方法:\n - getNamedItem(name) 返回 nodeName 属性等于 name 的节点\n - removeNamedItem(name) 删除 nodeName 等于 name 的节点\n - setNamedItem(node) 像列表中添加 node 节点,以其 nodeName 为索引\n - item(pos) 返回索引位置 pos 处的节点\n- 可以使用 document.createElement 创建新元素,接收一个参数为要创建的元素的标签名\n\n### Text 类型\n\n- Text 节点由 Text 类型表示,包含按字面解释的纯文本,亦可能包含转义后的 HTML 字符\n- Text 节点中包含的文本可以通过 nodeValue 或 data 属性访问,还可以通过 length 属性获取文本节点中包含的字符数量\n- 文本节点暴露了下列操作方法:\n - appendData(text)\n - deleteData(offset, count)\n - insertData(offset, text)\n - replaceData(offset, count, text)\n - splitText(offset)\n - substringData(offset, count) 提取从位置 offset 到 offset + count 的文本\n- document.createTextNode 方法可以用来创建新文本节点\n\n# 12.DOM 扩展\n\n## Selectors API\n\n- querySelector 方法接收 CSS 选择符参数,返回匹配该模式的第一个后代元素,如果没有匹配项则返回 null\n- querySelectorAll 方法接收一个用于查询的参数并返回所有匹配的节点。返回值是一个 NodeList 的静态实例。(并非动态)\n- matches 方法接收一个 CSS 选择符参数,如果元素匹配则返回 true 否则返回 false。使用该方法可以检测某个元素会不会被上述两种方法返回\n\n## 元素遍历\n\nDOM元素新增的5个属性:\n\n- childElementCount 子元素数量(不包括文本节点和注释)\n- firstElementChild\n- lastElementChild\n- previousElementSibling\n- nextElementSibling\n\n## HTML5\n\n- getElementsByClassName 方法接受一个参数,即包含一个或多个类名的字符串,返回包含相应类的元素的 NodeList\n- HTML5 给所有元素添加了 classList 属性。classList 是一个新的集合类型 DOMTokenList 的实例,拥有 length 属性和 item 方法以及中括号获取元素。此外 DOMTokenList 还增加了以下方法:\n - add(value)\n - contains(value)\n - remove(value)\n - toggle(value) 如果类名列表存在 value 则删除,如果不存在则添加\n- HTML5 增加了辅助 DOM 焦点管理的方法:\n - document.activeElement 包含当前拥有焦点的 DOM 元素。默认情况下页面加载完成前为 null,加载完成后设置为 document.body\n - document.hasFocus 方法返回布尔值表示文档是否拥有焦点\n- HTML5 拓展了 HTMLDocument 类型:\n - document.readyState 属性\n - loading 表示正在加载\n - complete 表示文档加载完成\n - document.compatMode 属性表示页面渲染模式\n - CSS1Compat 标准模式\n - BackCompat 混杂模式\n - document.head 属性指向 \u003Chead> 元素\n- HTML5 新增了 document.characterSet 属性表示文档实际使用的字符集,也可以用来指定新字符集\n- HTML5 允许为元素指定非标准属性,使用 data- 前缀表示这些属性不包含与渲染有关的信息,也不包含元素的语义信息\n - 可以通过元素的 dataset 属性访问自定义数据属性。元素的每个 data-name 属性在 dataset 中都可以通过 data- 前缀后面的字符串作为键来访问\n- innerHTML 属性会返回元素所有后代的 HTML 字符串,写入 innerHTML 时会根据提供的字符串以新的 DOM 子树替换元素中原来包含的所有节点\n- outerHTML 属性与 innterHTML 类似,但是包含了调用它的元素\n- insertAdjacentHTML 和 insertAdjacentText 方法用于插入标签,接收两个参数:要插入标记的位置和要插入的 HTML 或文本。其中,第一个参数必须为下列值中的一个:\n - \"beforebegin\" 插入当前元素前面,作为前一个同胞节点\n - \"afterbegin\" 插入当前元素内部,作为新的子节点或放在第一个子节点前面\n - \"beforeend\" 插入当前元素内部,作为新的子节点或放在最后一个子节点后面\n - \"afterend\" 插入当前元素后面,作为下一个同胞节点\n- scrollIntoView 方法用于滚动浏览器窗口或容器元素以便包含元素进入视口。参数如下:\n - alignToTop\n - true 窗口滚动后元素顶部与视口顶部对齐\n - false 窗口滚动后元素底部与视口底部对齐\n - scrollIntoViewOptions 是一个选项对象\n - behavior 过渡动画,smooth 或 auto,默认为 auto\n - block 定义垂直方向的对齐,start、center、end 或 nearest,默认为 start\n - inline 定义水平方向的对齐, start、center、end 或 nearest,默认为 nearest\n - 不传参数等同于 alignToTop 为 true\n\n# 13.事件\n\n## 事件流\n\n事件流描述了页面接收时间的顺序。\n**事件冒泡**的事件被定义为从最具体的元素开始触发,然后向上传播到没那么具体的元素。现代浏览器中的事件会一直冒泡到 windows 对象。\n**事件捕获**的意思是最不具体的元素最先收到事件,最具体的节点最后收到事件。事件捕获是为了在事件到达最终目标前拦截事件。\nDOM 事件流规定事件流分为3个阶段:事件捕获、到达目标和事件冒泡。\n\n## 事件处理程序\n\n为响应事件而调用的函数称为事件处理程序(或事件监听器)。\nJavaScript 中传统的事件处理程序是把一个函数赋值给元素的一个事件处理程序属性。例如:\n\n```javascript\nlet btn = document.getElementById('myBtn');\nbtn.onClick = function() {\n console.log('Clicked');\n};\n```\n\nDOM2 Events 为事件处理程序的赋值和移除定义了两个方法:addEventListener 和 removeEventListener。方法接收3个参数:事件名、事件处理函数和一个布尔值(true 表示在捕获阶段调用事件处理程序,false 表示在冒泡阶段调用事件处理程序)。使用 DOM2 方式的主要优势是可以为同一个事件添加多个事件处理程序。\n\n## 事件对象\n\n在 DOM 中发生事件时,所有相关信息会存储在 event 对象中,event 对象也是传给事件处理程序的唯一参数。\n\n- 在事件处理程序内部,this 对象始终等于 currentTarget 的值,而 target 只包含事件的实际目标\n- preventDefault 方法用于阻止特定事件的默认动作\n- stopPropagation 方法用于阻止事件流在 DOM 结构中的传播,取消后续事件捕获或冒泡\n- eventPhase 属性可用于确定事件流所处阶段\n - 1 捕获阶段\n - 2 在目标上被调用\n - 3 冒泡阶段\n\n## 事件类型\n\n### 用户界面事件\n\n- load 在 window 上当页面加载完成后触发\n- unload 在 window 上当页面完全卸载后触发\n- abort 在\u003Cobject>元素上当相应对象加载完成前被用户提前终止下载时触发\n- error 在 window 上当 JavaScript 报错时触发;在\u003Cimg>元素上当无法加载指定图片时触发;在\u003Cobject>元素上当无法加载相应对象时触发\n- select 在文本框上当用户选择了一个或多个字符时触发\n- resize 在 window 或窗格上当窗口或窗格被缩放时触发 \n- scroll 当用户滚动包含滚动条的元素时在元素上触发\n\n### 焦点事件\n\n焦点事件在页面元素获得或失去焦点时触发。常用焦点事件包括:\n\n- blur 元素失去焦点时触发\n- focus 当元素获得焦点时触发,该事件不冒泡\n- focusin 当元素获得焦点时触发,这个事件是 focus 的冒泡版\n- focusout 当元素失去焦点时触发,这个事件是 blur 的通用版\n\n### 鼠标和滚轮事件\n\n- click 当用户点击鼠标主键或按键盘回车触发\n- dbclick 当用户双击鼠标主键触发\n- mousedown 按下鼠标任意键触发\n- mouseenter 鼠标光标从元素外部移入内部触发\n- mouseleave 鼠标光标从元素内部移到外部触发\n- mousemove 鼠标在元素上移动时**反复**触发\n- mouseout 鼠标光标从一个元素移动到另一个元素时触发\n- mouseover 鼠标光标从元素外部移到内部触发\n- mouseup 用户释放鼠标键时触发\n- mousewheel 滚轮事件\n- 鼠标事件的 event 对象可以获取光标的坐标:\n - clientX & clientY 相对于浏览器视口的坐标\n - pageX & pageY 相对于页面的坐标\n - screenX & screenY 相对于屏幕的坐标\n- event 对象可以通过 shiftKey、ctrlKey、altKey 和 metaKey 属性获取修饰键状态,返回一个布尔值\n\n### 键盘输入事件\n\n- keydown 按下键盘某个键触发,持续按住会重复触发\n- keyup 释放某个键触发\n- textInput 输入事件\n\n### HTML5 事件\n\n- contextmenu 用于控制上下文菜单\n- beforeunload 在 window 上触发,给开发者提供组织页面被卸载的机会\n- DOMContentLoaded 会在 DOM 树构建完成后触发,不用等待图片、JavaScript文件、CSS文件或其他资源加载完成\n- haschange 在URL散列值(#后的内容)发生变化时触发\n\n## 内存与性能\n\n- 使用**事件委托**处理过多事件处理程序。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件\n- 及时删除不用的事件处理程序来提升页面性能\n\n## 模拟事件\n\n- 模拟鼠标事件:createEvent 方法并传入 MouseEvents 参数会返回一个 event 对象,使用该对象的 initMouseEvent 方法指定信息\n- 模拟键盘事件:createEvent 方法并传入 KeyboardEvent 参数会返回一个 event 对象,使用该对象的 initKeyboardEvent 方法指定信息\n- 模拟其他事件:createEvent 方法并传入 HTMLEvents 参数会返回一个 event 对象,使用该对象的 initEvent 方法指定信息\n- 自定义 DOM 事件:createEvent 方法并传入 CustomEvent 参数会返回一个 event 对象,使用该对象的 initCustomEvent 方法指定信息\n\n# 14.错误处理与调试\n\n- ECMA-262 第3版新增了 try/catch 语句作为 JavaScript 中处理异常的一种方式\n- try/catch 语句可以使用可选的 finally 子句,finally 中的代码始终运行\n- ECMA-262 定义了8种错误类型:\n - Error 基类型,其他错误类型继承该类型\n - InternalError 底层 JavaScript 引擎抛出异常时由浏览器抛出\n - EvalError eval 函数发生异常时抛出\n - RangeError 数值越界时抛出\n - ReferenceError 找不到对象时抛出\n - SyntaxError 给 eval 传入的字符串包含 JavaScript 语法错误时抛出\n - TypeError 变量不是预期类型或者访问不存在的方法时\n - URIError encodeURI 和 decodeURI 传入格式错误的 URI 时发生\n- throw 操作符用于抛出自定义错误,必须有一个类型不限的值。使用 throw 操作符时,代码立即停止执行,除非 try/catch 语句捕获了抛出的值\n\n# 15.JSON\n\n- JSON 解析与序列化的两个方法:\n - stringify 方法接收一个要序列化的对象和两个可选参数。第一个可选参数是过滤器,可以是数组或函数;第二个参数是用于缩进结果的 JSON 字符串的选项。\n - 如果过滤器参数是一个数组,那么返回结果中只会包含该数组中列出的属性;如果过滤器参数是一个函数,该函数接收 key 和 value 两个属性,可以根据 key 返回自定义的结果。如果返回值为 undefined,则这个 key 会被忽略\n - 字符串缩进参数为数值时,表示每一级缩进的空格数;如果参数为字符串,那么 JSON 字符串会使用这个字符串来缩进\n - 可以在要序列化的对象上添加 toJSON 方法自定义序列化\n - parse 方法可以接受一个额外的还原函数参数,类似于 stringify 的替代函数。还原函数会针对每个 key/value 调用一次。如果还原函数返回 undefined 则结果中会删除相应的 key,如果返回了其他值,则该值会作为相应 key 的 value 插入到结果中\n\n# 16.网络请求与远程资源\n\n## XMLHttpRequest 对象\n\nXHR 对象通过 XMLHttpRequest 构造函数创建,使用 open 方法配置并使用 send 方法发送。open 方法接收3个参数:请求类型,URL 以及 请求是否异步的布尔值。send 方法接收一个参数,作为请求体发送的数据。如果不需要请求体则必须传 null。\n\n```javascript\nlet xhr = new XMLHttpRequest()\nxhr.open('get', 'example.php', false)\nxhr.send(null)\n```\n\n收到服务器响应后,XHR 对象的以下属性会被填充数据:\n\n- responseText\n- responseXML\n- status\n- statusText\n\n此外 XHR 对象有一个 readyState 属性,表示当前处在请求/响应过程的哪个阶段。常用的值为 4,表示收到所有响应。readyState 改变时会触发 readystatechange 事件,可以为 XHR 对象添加事件处理程序。 \n\n```javascript\nlet xhr = new XMLHttpRequest()\nxhr.onreadystatechange = function () {\n if (xhr.readyState == 4) {\n if ((xhr.status >= 200 && xhr.status \u003C 300) || xhr.status == 304) {\n alert(xhr.responseText)\n } else {\n alert('request was unsuccessful:' + xhr.status)\n }\n }\n}\nxhr.open('get', 'example.txt', true)\nxhr.send(null)\n```\n\n可以调用 abort 方法在收到响应前取消异步请求。\nXHR 对象可以使用 setRequestHeader 方法设置额外的请求头,使用 getResponseHeader 方法传入想要获取的头部名称获取响应头,使用 getAllResponseHeaders 方法获取所有响应头的字符串。\nFormData 类型用于表单序列化,也便于创建与表单类似格式的数据通过 XHR 对象发送。\n\n- append 方法用于填充数据,接收两个参数:键和值,相当于表单字段名称和该字段的值。\n\n## 进度事件\n\n- loadstart 接收到响应的第一个字节触发\n- progress 接收响应期间反复触发\n- error 请求出错时触发\n- abort 调用 abort 方法终止链接时触发\n- load 成功接收响应时触发\n- loadend 通信完成后触发(包括 error、abort)\n\n## 跨源资源共享\n\n跨源资源共享(CORS)定义了浏览器与服务器如何实现跨源通信。其基本思路是使用自定义 HTTP 头部允许浏览器和服务器相互了解以确认请求或响应应该成功还是失败。\n对于简单的请求,比如:GET 或 POST 类型,没有自定义头部且请求体是 text/plain 类型,这样的请求会在发送时有一个**额外的头部** Origin,包含发送请求的页面的源(协议、域名和端口)。如果服务器决定响应请求,应该发送 Access-Control-Allow-Origin 头部包含相同的源或 *(表示资源公开)。如果没有这个头部或源不匹配则表明不会响应浏览器请求。**无论请求还是响应都不会包含 cookie 信息**。\n对于上述简单请求外的情况,CORS 通过一种叫**预检请求**的服务器验证机制实现跨源。在发送自定义头部、GET 和 POST 外的方法以及不同请求体内容类型时,会先向服务器发送一个“预检”请求,这个请求使用 OPTIONS 方法发送并包含以下头部:\n\n- Origin:与简单请求相同\n- Access-Control-Request-Method:请求希望使用的方法\n- Access-Control-Request-Headers:(可选)要使用的逗号分隔的自定义头部列表\n\n服务器在这个请求发送后可以确定是否允许这种类型的请求并在响应中发送如下头部:\n\n- Access-Control-Allow-Origin:与简单请求相同\n- Access-Control-Allow-Methods:允许的方法(逗号分隔)\n- Access-Control-Allow-Headers:服务器允许的头部(逗号分隔)\n- Access-Control-Max-Age:缓存预检请求的秒数\n\n**预检请求返回后,结果会按照响应指定时间缓存一段时间。**\n默认情况下,跨源请求不提供凭据(cookie、HTTP 认证和客户端 SSL 证书)。可以通过 WithCredentials 属性设置为 true 来表明请求会发送凭据。如果服务器允许带凭据的请求,可以在响应中包含头部:Access-Control-Allow-Credentials: true。\n\n## 替代性跨源技术\n\n- 图片探测是利用\u003Cimg>标签实现跨域通信最早的一种技术\n- JSONP 调用通过动态创建\u003Cscript>元素并为 src 属性指定跨域 URL 实现通信\n\n## Fetch API\n\nFetch API 能够执行 XMLHttpRequest 对象的所有任务,但更易使用且接口更现代化,并且能在 Web 工作线程等现代 Web 工具中使用。\n\n### 基本用法\n\nfetch 方法只有一个必须的参数 input。多数情况下这个参数是要获取资源的 URL。这个方法返回一个期约:\n\n```javascript\nlet r = fetch('/bar')\n .then((response) => {\n console.log(response)\n })\n```\n\n读取 response 最简单的方式是取得文本格式的内容,使用 text 方法。\nFetch API 支持通过 Response 的 status 和 statusText 属性检查响应状态。跟随重定向时,响应对象的 redirected 属性会被设置为 true,状态码仍然是 200。\n只使用 URL 时,fetch 方法会发送 GET 请求,只包含最低限度的请求头。进一步配置请求可以传入第二个参数 init 对象。init 对象的可选配置包括:\n\n- body\n- cache\n- credentials\n- headers\n- integrity:用于强制子资源完整性\n- keepalive:用于知识浏览器允许请求存在时间超出页面生命周期\n- method\n- mode:用于指定请求模式。这个模式决定来自跨源请求的响应是否有效\n- redirect:用于指定如何处理重定向响应\n- referrer:用于指定 HTTP 的 Referer 头部的内容\n- referrerPolicy:用于指定 HTTP 的 Referer 头部\n- signal:用于支持通过 AbortController 中断进行的 fetch 请求\n\n```javascript\nlet abortController = new AbortController()\n\nfetch('wikipedia.zip', {signal: abortController.signal})\n .catch(() => console.log('aborted!'))\n\n// 10毫秒后中断请求\nsetTimeout(() => abortController.abort(), 10)\n```\n\n### Headers 对象\n\nHeaders 对象是所有外发请求和入站响应头部的容器。Headers 对象与 Map 对象极为相似,都有 get、set、has 和 delete 等实例方法。此外,在初始化 Headers 对象的时候也可以使用键值对的形式。\nHeaders 对象通过 append 方法添加多个值。在 Headers 实例中还不存在的头部上调用 append 方法相当于调用 set,后续调用会以逗号为分隔符拼接多个值。\n\n```javascript\nlet h = new Headers()\n\nh.append('foo', 'bar')\nconsole.log(h.get('foo')) // 'bar'\n\nh.append('foo', 'baz')\nconsole.log(h.get('foo')) //'bar, baz'\n```\n\n### Request 对象\n\nRequest 对象是获取资源请求的接口,暴露请求相关信息和请求体的不同方式。\nRequest 构造函数接收 input 参数和 init 对象,其中 input 参数一般为 URL,init 参数与 fetch 方法一样。\nFetch API 提供了两种创建 Request 对象副本的方式:\n\n1. 使用 Request 构造函数\n\n```javascript\nlet r1 = new Request('https://foo.com')\nlet r2 = new Request(r1)\n\nconsole.log(r2.url) // https://foo.com\n```\n\n如果传入 init 对象则会覆盖源对象中同名的值\n\n```javascript\nlet r1 = new Request('https://foo.com')\nlet r2 = new Request(r1, {method: 'post'})\n\nconsole.log(r1.method) // GET\nconsole.log(r2.method) // POST\n```\n\n**这种克隆方式并不总能得到一模一样的副本**。第一个请求的请求体会被标记为“已使用”(bodyUsed 属性为 true)。如果源对象与创建的新对象不同源,则 referrer 属性会被清除。如果源对象的 mode 为navigate,则会被转换为 same-origin。\n\n2. 使用 clone 方法可以创建一模一样的副本,并且请求体不会被标记为“已使用”\n\n```javascript\nlet r1 = new Request('https://foo.com', {method: 'POST', body: 'foobar'})\nlet r2 = r1.clone()\n\nconsole.log(r1.url) // https://foo.com\nconsole.log(r2.url) // https://foo.com\n\nconsole.log(r1.bodyUsed) // false\nconsole.log(r2.bodyUsed) // false\n```\n\n**如果请求对象的 bodyUsed 属性为 true,上述任何一种方式都不能用来创建这个对象的副本**。\nfetch 方法可以直接传入创建好的 Request 实例。\n\n### Response 对象\n\n大多数情况下,产生 Response 对象的主要方式是调用 fetch 方法,它会返回一个最后会解决为 Response 对象的期约。\nResponse 类还有两个生成 Response 对象的静态方法:\n\n1. Response.redirect:接收一个 URL 和一个重定向状态码并返回重定向的 Response 对象\n2. Response.error:产生表示网络错误的 Response 对象\n\nResponse 对象也有 clone 方法,用法与 Request 对象类似。\n\n### Request、Response 及 Body 混入\n\nRequest 和 Response 都使用了 Fetch API 的 Body 混入,为上述两种类型提供了只读的 body 属性(实现为 ReadableStream)、只读的 bodyUsed 布尔值(表示 body 流是否已读)和一组方法。\nBody 混入提供了5个方法,用于将 ReadableStream 转存到缓存区的内存里,将缓冲区转换为某种 JavaScript 对象类型,以及通过期约来产生结果。\n\n1. Body.text 方法返回期约,解决为 UTF-8 格式字符串\n2. Body.json 方法返回期约,解决为 JSON\n3. Body.formData 方法返回期约,解决为 FormData 实例\n4. Body.arrayBuffer 方法返回期约,解决为 ArrayBuffer 实例\n5. Body.blob 方法返回期约,解决为 Blob 实例\n\n### Web Socket\n\nWeb Socket(套接字)的目标是通过一个长时连接实现与服务器全双工、双向的通信。因为 Web Socket 使用了自定义协议,所以 URL 方案(scheme)要使用 ws:// 和 wss://。\n创建一个新的 Web Socket 需要实例化一个 WebSocket 对象并传入提供连接的 URL:\n\n```javascript\nlet socket = new WebSocket('ws://www.example.com/server.php')\n```\n\n**必须给 WebSocket 构造函数传入一个绝对 URL。同源策略不适用与 WebSocket**。浏览器会在初始化 WebSocket 对象后立即创建连接。使用 close 方法可以关闭 Web Socket 连接。\n使用 send 方法向服务器发送数据,包括字符串、ArrayBuffer 或 Blob:\n\n```javascript\nlet socket = new WebSocket('ws://www.example.com/server.php')\n\nlet stringData = 'Hello World!'\nlet arrayBufferData = Unit8Array.from(['f', 'o', 'o'])\nlet blobData = new Blob(['f', 'o', 'o'])\n\nsocket.send(stringData)\nsocket.send(arrayBufferData.buffer)\nsocket.send(blobData)\n```\n\n服务器向客户端发送消息时,WebSocket 对象上会触发 message 事件。可以通过事件处理函数的 event.data 属性访问到有效载荷。返回的数据也可能是 blob 或 arraybuffer。\n\n```javascript\nsocket.onmessage = function (event) {\n let data = event.data\n}\n```\n\nWebSocket 对象在连接生命周期中还有可能触发 3 个其他事件:\n\n1. open:连接成功建立时触发\n2. error:发生错误时触发\n3. close:连接关闭时触发",null,"2026-01-12T16:25:08.000000Z","2024-03-13 19:42:26",{"status":5,"message":6,"data":17},[18,29,37],{"id":19,"title":20,"deployed_at":21,"category":22,"tags":25},17,"Vite项目配置本地HTTPS","2025-10-13 15:54:22",{"id":9,"user_id":9,"category_name":23,"created_at":24,"updated_at":24},"开发","2026-01-12T15:38:21.000000Z",[26],{"id":9,"user_id":9,"tag_name":27,"created_at":13,"updated_at":13,"pivot":28},"JavaScript",{"post_id":19,"tag_id":9},{"id":30,"title":31,"deployed_at":32,"category":33,"tags":34},22,"React Native 开发环境安装踩坑","2024-06-25 19:43:18",{"id":9,"user_id":9,"category_name":23,"created_at":24,"updated_at":24},[35],{"id":9,"user_id":9,"tag_name":27,"created_at":13,"updated_at":13,"pivot":36},{"post_id":30,"tag_id":9},{"id":38,"title":39,"deployed_at":40,"category":41,"tags":43},14,"《JavaScript 高级程序设计》第8-9章 函数&期约与异步函数","2024-03-12 19:41:56",{"id":10,"user_id":9,"category_name":42,"created_at":24,"updated_at":24},"笔记",[44],{"id":9,"user_id":9,"tag_name":27,"created_at":13,"updated_at":13,"pivot":45},{"post_id":38,"tag_id":9},["Reactive",47],{"$scolor-mode":48,"$stoasts":52},{"preference":49,"value":49,"unknown":50,"forced":51},"system",true,false,[],["Set"],["ShallowReactive",55],{"$ftAoTn5b7VeExj4j9SfFdEJyXhbOtPlPs2jkIEMdYIXo":-1,"$fu76y50jFRS9ch7--s7LBimGWGLndEcSJS1PouKmugJU":-1},"/posts/16"]</script></body></html>