jquery.load 方法详解和一些思考
jquery.load 方法详解和一些思考
本文以最新版本的
jquery@3.7.1作为示例
函数介绍
在 jquery 中有一个 load 方法,它能够通过 ajax 的方式,加载远程的 html 代码,并插入到指定的 DOM 里。
它的函数签名为:
.load( url [, data ] [, complete ] )
其中它的必须参数为 url, 可选参数为 data 和 complete
url
url 顾名思义为远程资源的 url 地址,比如你有另外一个页面在你服务器的 ajax/test.html 路径。那么直接在当前页面里调用,去选中某个元素,插入远程的 HTML 就行,代码类似于:
$("#result").load("ajax/test.html");
更厉害的是,而且这个 url 参数还接受 jquery 选择器,来选中远程的 html 代码中指定的 html 片段,来填充进指定的元素中:
$("#result").load("ajax/test.html #container");
比如上面这段代码,就是只选中 ajax/test.html 页面 id 为 container 的元素,然后把它里面的内容插入当前调用页面的 #result DOM 中,这点从语义上很好理解。
data
第二个参数为 data,用来发送一些参数,假如传入的是一个对象,那么这个 ajax 方法会从 HTTP GET 变成 POST 的同时。 data 会被包裹成 FormData 类型被发送到服务端。


complete
第三个参数 complete 就传入一个正常的回调方法,返回 responseText,textStatus,jqXHR 对象
这些官方 API 文档地址: api.jquery.com/load/ 上都有,接下来我们来探寻 jquery.load 的本质。
源代码解析
我们对它的源代码进行简单分析,可以看到对应它的源代码位置在 jquery Github 仓库的 /src/ajax/load.js 位置。
首先我们跳过前期大量的函数重载部分,可以看到它的本质如下所示:
jQuery
.ajax({
url: url,
type: type || "GET",
dataType: "html",
data: params,
})
.done(function (responseText) {
response = arguments;
self.html(
selector
? jQuery("<div>").append(jQuery.parseHTML(responseText)).find(selector)
: responseText
);
})
.always(
callback &&
function (jqXHR, status) {
self.each(function () {
callback.apply(this, response || [jqXHR.responseText, status, jqXHR]);
});
}
);
主要由四个函数组成: jQuery.ajax, self.html 和 jQuery.append 和 jQuery.parseHTML
方法详解
ajax
首先 jQuery.ajax 就是对原生对象 XMLHttpRequest 的封装罢了,源代码在 /src/ajax.js,从远程加载 html 字符串如下所示:

html
self.html 源代码在 /src/manipulation.js 位置。
html 这个函数功能比较多,既可以获取某个元素的 html 也可以利用 append 方法去添加元素等等的,
在这里调用,主要的用途为把服务端返回的 responseText 字符串,使用 elem.appendChild 方法给塞到目标元素中去。
什么?
responseText 不是 html 字符串嘛?而 appendChild 接受的参数是一个 Node 对象嘛?字符串怎么能直接 appendChild 呢?
这就不得不提到 html 的内部方法 domManip 和 buildFragment 了。
这 2 个方法负责解析 html 字符串,并最终返回一个 DocumentFragment 对象。

然后 DOM 的原生方法 appendChild 就可以接收这个对象作为参数,然后再添加到我们的文档中去。
domManip会对html字符串进行一定的剥壳,即去除<!DOCTYPE html>,html,head,body这些元素,只保留它们内部的部分。其实innerHTML也会做这样的处理。

append & parseHTML
这么一讲,其实把 jQuery.append 和 jQuery.parseHTML 这 2 个函数的功能也讲到了。
因为 jQuery.append 其实就是对 appendChild 方法的封装。
而 jQuery.parseHTML 也是将 html 字符串转化成 Node 数组的方法。
值得一提的是在 html 实现里面,有一段核心代码:
if (elem) {
this.empty().append(value);
}
在添加元素之前,它会先调用 empty 这个清除方法,清除之前所有的元素,以及他们所有绑定的事件(防止内存泄露),然后再去添加 DocumentFragment 的。
所以我们反复的 jquery.load 实际上就是会不断清除的清除之前的元素,然后再添加进新的元素,这点很重要!
副作用
jquery.load 加载的 html 会带有一定程度上的副作用。
抛开它的性能,限制,安全问题不谈。它从远程 html 中加载的 css 和 script 极有可能造成污染。
因为它们是以标签的形式直接加载进我们的文档流中,共享一个作用域,并没有经过任何的隔离处理。
比如我们有 2 个页面,home 和 tab。
这 2 个页面都对 h1 存在样式,同时都有一个 showMsg 方法。
这时候,本来 home 运行的好好的,然后 jquery.load 了 tab 页面,结果发现 tab 页面的 h1 样式覆盖了 home 页面的样式,而 showMsg 方法也被 tab 里的 showMsg 通过声明的方式给替换掉了。
这导致我们在使用 jquery.load 一定要小心,要建立一定的规范防止这种情况的发生。
比如我们使用重复的 const 关键字去声明同一个变量就会报错:

加载 webpack chunk
通常情况下,jquery.load 加载 webpack chunk 是没有问题的。
因为默认情况下 webpack chunk 都是一个个自带闭包的 iife 函数。
它大体上由 2 部分组成: webpack_modules 和 runtime。
在 optimization.runtimeChunk 默认值为 false 的情况下,
每一个 webpack entry 的 chunk 内部都被嵌入了它所需的运行时。
而我们改变 optimization.runtimeChunk 的值为 single 的配置会让所有的 chunk 共用一个运行时。multiple 的配置会让每个 entry 所关联的那些 chunk 共用一个运行时。
在 html 加载的时候,由于各个的 chunk 的运行时被抽出,所以需要优先加载运行时文件,再加载其他的 chunk。
同时为了让运行时中的,__webpack_modules__ 和 __webpack_module_cache__ 能够收集到其他 chunk 的内容,在 webpack runtime chunk 里还会在全局的 self 对象上挂载一个数组,
例如 self["webpackChunkmy_webpack_project"],然后这时候由于其他的 chunk 被剥离了 runtime,此时就只需要留下 webpack_modules 代码,并使用 push 方法,放入 self["webpackChunkmy_webpack_project"] 数组中。
所以我们看到很多被抽出 runtime 开头的 chunk 代码头都是:
(self["webpackChunkmy_webpack_project"] = self["webpackChunkmy_webpack_project"] || []).push
不过这种抽离 runtime 的方式,虽然最终减小了许多打包产物的体积,不过也可能造成一些隐藏的 bug,比如利用 jquery.load 加载的某个页面,由于加载后运行时缺失,或者 chunk 依赖的运行时方法,没有被注入,造成页面崩溃。
这种现象不由得让我深思,如何将 jquery.load 融入 webpack 这套体系中呢?
转载自:https://juejin.cn/post/7347210988258885642