本文以最新版本的
jquery@3.7.1
作为示例
在 jquery
中有一个 load
方法,它能够通过 ajax
的方式,加载远程的 html
代码,并插入到指定的 DOM
里。
它的函数签名为:
.load( url [, data ] [, complete ] )
其中它的必须参数为 url
, 可选参数为 data
和 complete
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
,用来发送一些参数,假如传入的是一个对象,那么这个 ajax
方法会从 HTTP GET
变成 POST
的同时。 data
会被包裹成 FormData
类型被发送到服务端。
第三个参数 complete
就传入一个正常的回调方法,返回 responseText
,textStatus
,jqXHR
对象
这些官方 API 文档地址: https://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
首先 jQuery.ajax
就是对原生对象 XMLHttpRequest
的封装罢了,源代码在 /src/ajax.js
,从远程加载 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
也会做这样的处理。
这么一讲,其实把 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
关键字去声明同一个变量就会报错:
通常情况下,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
这套体系中呢?