这篇文章讨论的是为什么Web模块化是很有用的,并介绍了现在可以用来实现Web模块化的一些机制。这里有另一篇文章介绍了RequireJS使用的函数包装格式的设计理念。 问题§1
解决方案§2 前端开发者需要这样的解决方案:
脚本载入API § 3首先梳理出脚本载入API。这里有几个选择:
所有的都映射到载入 some/path/some/module.js。理想情况下,我们可以选择CommonJS的语法,因为它很可能会变得更加常见,而且我们想要重用代码。
当前我们也希望一些语法能够载入已存在的纯文本JavaScript文件,因此开发者不用重写所有的JavaScript来从脚本载入中获益。
但是,我们需要一些能在浏览器中更好的工作的事物。CommonJS 的require()是一个同步调用,它期望能够立即返回那个模块。不过这在浏览器中工作的不是很好。 异步与同步§ 4下面这个例子说明了浏览器的基本问题。假设我们有一个Employee对象,我们想要一个派生自Employee对象的Manager对象。获取该例子,我们可能会用我们的脚步载入API来这样编码:
如上面注释中所示,如果require()是异步的,这段代码不会工作。但是,在浏览器中同步载入脚步将会抹杀性能。那么,怎么办? 脚本载入:XHR§ 5使用XMLHttpRequest(XHR)载入脚本是很有吸引力的。如果使用XHR,我们就可以触摸上面的文本,也就是可以通过正则表达式来查找require()调用,以确保我们载入了这些脚本,然后再用eval()或script元素将文本内容传给使用XHR载入的脚本。
使用带文本内容的script标签来设置为文件文本也不太好:
XHR 在跨域请求的时候还有问题。一些浏览器现在有跨域XHR的支持,但并不是全部。并且 IE 决定创建一个不同的API对象:XDomainRequest来实现跨域请求。出现了更多的需要改动的地方,更容易出错。特别是,你需要确定不发送任何不标准的HTTPheader或者还需要另外一个"预检"的请求来保证这次跨域的请求是被允许的。 Dojo 通过eval()使用基于XHR的loader,但是,虽然它能用,但是一直是困扰开发者的源头。Dojo 有一个 xdomain loader但是它需要通过使用一个函数wrapper来修改require的模块,所以script src=""标签可以用来加载模块了。还有很多边界情况和变化的地方来给程序员增加困难。 如果我们创建一个新的脚本加载器,我们可以做的更好。 脚本载入:Web Workers § 6web worker可能是另一个加载脚本的方法,但是:
脚本载入:document.write()§ 7document.write()可以用来载入脚本,它可以从其他的域载入脚本并且映射了浏览器通常是如何使用脚本的,因此它可以用来进行简单的调试。 但是,在异步VS同步的例子中,我们不能直接执行脚本。理想情况下,在执行脚本前我们能够通过require()知道相关依赖项,并且确保这些依赖项被首先载入。但是我们不能在脚本执行前访问它。 而且,document.write()在页面载入后就不工作了。对于你的网站,一个好的方法是在用户需要进行下一步操作时来载入脚本。 最后,通过document.write()载入脚本或阻塞页面的渲染。要让你的网站有最佳表现,这个方法是不可取的。 脚本载入:head.appendChild(script)§ 8我们可以在需要时创建脚本并将它们添加到头部:
上面的脚本片段多了一点东西,不过那正是基本的思想。这种方法比document.write要好,因为它不会阻塞页面的渲染并且在页面载入后仍能工作。 但是,它仍然有同步VS异步例子的问题:理想情况下,在执行脚本前我们能够通过require()知道相关依赖项,并且确保这些依赖项被首先载入。 函数封装 § 9在执行我们的脚本前,我们需要知道相关依赖项并确保已经将其载入。做这件事的最好方法是通过函数封装来构造我们的模块载入API。像这样:
这是ReguireJS的句法。如果你想载入没有定义成模块的纯文本的JavaScript的话,有一种简单的句法:
选择这种句法是因为,它足够简洁并且允许载入者使用head.appendChild(script)载入类型。 出于在浏览器中良好工作的需要,它有不同于普通的CommonJS句法。有建议说普通的CommonJS句法可以使用head.appendChild(script)的载入类型,如果服务器进程有封装的函数可以将模块转换成传输格式的话。 我相信不强制使用一个运行时服务器进程来转换代码是很重要的事:
关于设计的力量和功能封装格式的使用案例的更多细节,被叫做异步模块定义(Asynchronous Module Definition (AMD)),请前往为什么是AMD? |