jquery.load 方法详解和一些思考

2 阅读5分钟

jquery.load 方法详解和一些思考

本文以最新版本的 jquery@3.7.1 作为示例

函数介绍

jquery 中有一个 load 方法,它能够通过 ajax 的方式,加载远程的 html 代码,并插入到指定的 DOM 里。

它的函数签名为:

.load( url [, data ] [, complete ] )

其中它的必须参数为 url, 可选参数为 datacomplete

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 页面 idcontainer 的元素,然后把它里面的内容插入当前调用页面的 #result DOM 中,这点从语义上很好理解。

data

第二个参数为 data,用来发送一些参数,假如传入的是一个对象,那么这个 ajax 方法会从 HTTP GET 变成 POST 的同时。 data 会被包裹成 FormData 类型被发送到服务端。

Image

Image

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.htmljQuery.appendjQuery.parseHTML

方法详解

ajax

首先 jQuery.ajax 就是对原生对象 XMLHttpRequest 的封装罢了,源代码在 /src/ajax.js,从远程加载 html 字符串如下所示:

Image

html

self.html 源代码在 /src/manipulation.js 位置。

html 这个函数功能比较多,既可以获取某个元素的 html 也可以利用 append 方法去添加元素等等的,

在这里调用,主要的用途为把服务端返回的 responseText 字符串,使用 elem.appendChild 方法给塞到目标元素中去。

什么?

responseText 不是 html 字符串嘛?而 appendChild 接受的参数是一个 Node 对象嘛?字符串怎么能直接 appendChild 呢?

这就不得不提到 html 的内部方法 domManipbuildFragment 了。

2 个方法负责解析 html 字符串,并最终返回一个 DocumentFragment 对象。

Image

然后 DOM 的原生方法 appendChild 就可以接收这个对象作为参数,然后再添加到我们的文档中去。

domManip 会对 html 字符串进行一定的剥壳,即去除 <!DOCTYPE html>,html,head,body 这些元素,只保留它们内部的部分。其实 innerHTML 也会做这样的处理。

Image

append & parseHTML

这么一讲,其实把 jQuery.appendjQuery.parseHTML2 个函数的功能也讲到了。

因为 jQuery.append 其实就是对 appendChild 方法的封装。

jQuery.parseHTML 也是将 html 字符串转化成 Node 数组的方法。


值得一提的是在 html 实现里面,有一段核心代码:

if (elem) {
  this.empty().append(value);
}

在添加元素之前,它会先调用 empty 这个清除方法,清除之前所有的元素,以及他们所有绑定的事件(防止内存泄露),然后再去添加 DocumentFragment 的。

所以我们反复的 jquery.load 实际上就是会不断清除的清除之前的元素,然后再添加进新的元素,这点很重要!

副作用

jquery.load 加载的 html 会带有一定程度上的副作用。

抛开它的性能,限制,安全问题不谈。它从远程 html 中加载的 cssscript 极有可能造成污染。

因为它们是以标签的形式直接加载进我们的文档流中,共享一个作用域,并没有经过任何的隔离处理。

比如我们有 2 个页面,hometab

2 个页面都对 h1 存在样式,同时都有一个 showMsg 方法。

这时候,本来 home 运行的好好的,然后 jquery.loadtab 页面,结果发现 tab 页面的 h1 样式覆盖了 home 页面的样式,而 showMsg 方法也被 tab 里的 showMsg 通过声明的方式给替换掉了。

这导致我们在使用 jquery.load 一定要小心,要建立一定的规范防止这种情况的发生。

比如我们使用重复的 const 关键字去声明同一个变量就会报错:

Image

加载 webpack chunk

通常情况下,jquery.load 加载 webpack chunk 是没有问题的。

因为默认情况下 webpack chunk 都是一个个自带闭包的 iife 函数。

它大体上由 2 部分组成: webpack_modulesruntime

optimization.runtimeChunk 默认值为 false 的情况下,

每一个 webpack entrychunk 内部都被嵌入了它所需的运行时。

而我们改变 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 这套体系中呢?