js中如何测试xss漏洞加载html内容 – 在路上

&figure&&img src=&https://pic3.zhimg.com/v2-fc4ab2b0e8ed4b722eefb_b.jpg& data-rawwidth=&800& data-rawheight=&212& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic3.zhimg.com/v2-fc4ab2b0e8ed4b722eefb_r.jpg&&&/figure&&p&最近我开始思考React应用的状态管理。我已经取得一些有趣的结论,并且在这篇文章里我会向你展示我们所谓的状态管理并不是真的在管理状态。&/p&&blockquote&译者:阿里云前端-也树&/blockquote&&p&原文链接:&a href=&http://link.zhihu.com/?target=http%3A//krasimirtsonev.com/blog/article/managing-state-in-javascript-with-state-machines-stent& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&managing-state-in-javascript-with-state-machines-stent&/a&&/p&&h2&我们避而不谈的是什么(The elephant in the room)&/h2&&p&我们来看一个简单的例子。想象这是一个展示用户名称、密码和一个按钮的表单组件。用户会在填写表单后点击提交。如果一切顺利,我们完成了登录,并且有必要展示欢迎信息和一些链接:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-05738fae7eaf95ab3c8c3eb_b.jpg& data-caption=&& data-rawwidth=&800& data-rawheight=&302& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic4.zhimg.com/v2-05738fae7eaf95ab3c8c3eb_r.jpg&&&/figure&&p&我们假定这个组件有两个展示状态。一个是未登录状态,另一个是用户登录后的状态。所以从管理这两种状态开始,我们用一个布尔值的标志位来描述用户的状态。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&var isLoggedIn;
isLoggedIn = // 展示表单
isLoggedIn = // 展示欢迎信息和链接
&/code&&/pre&&/div&&p&但是这样还不够。如果我们点击提交按钮后触发的HTTP请求需要一些时间来响应,我们不能把表单孤零零的放在屏幕上,而需要更多的UI元素来展示这样的中间状态,因此我们不得不在组件中引入另一个状态。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-868ce67e92e81da617672b_b.jpg& data-caption=&& data-rawwidth=&800& data-rawheight=&214& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic4.zhimg.com/v2-868ce67e92e81da617672b_r.jpg&&&/figure&&p&现在我们有了第三种展示状态,仅仅用一个 &code&isLoggedIn&/code& 变量已经不能解决了。不走运的是我们不能设置变量值为 &code&false-ish&/code&,它不是 &code&true&/code& 也不是 &code&false&/code&。当然,我们可以引入另一个变量比如说 &code&isInProgress&/code&。一旦我们发送请求就会把这个变量的值置为 &code&true&/code&。这个变量会告诉我们是处于请求的过程中并且用户应该看到加载中的展示状态。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&var isLoggedIn;
// 展示表单
isLoggedIn =
isInProgress =
// 请求过程中
isLoggedIn =
isInProgress =
// 展示欢迎信息和链接
isLoggedIn =
isInProgress =
&/code&&/pre&&/div&&p&非常棒!我们用到两个变量并且需要记住这三种情况对应的变量值。看起来我们解决了问题。但另外的问题是,我们维护了太多状态。如果我们需要展示一个请求成功的信息,或者一切顺利的时候我们需要告知用户:“Yep, 你成功登录了”,并且两秒后信息伴随着华丽的动画隐藏起来,接着展示出最终的界面,要怎么办?&/p&&figure&&img src=&https://pic4.zhimg.com/v2-65a4a5ced4eed1a0287aafc_b.jpg& data-caption=&& data-rawwidth=&800& data-rawheight=&302& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic4.zhimg.com/v2-65a4a5ced4eed1a0287aafc_r.jpg&&&/figure&&p&现在情况变得有些复杂。我们有了 &code&isLoggedIn&/code& 和 &code&isInProgress&/code&,但是看起来仅仅使用它们还不够。&code&isInProgress&/code& 在请求结束后确实是 &code&false&/code&,但是他的默认值同样是 &code&false&/code&。我觉得我们需要第三个变量 - &code&isSuccessful&/code&。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&var isLoggedIn, isInProgress, isS
// 展示表单
isLoggedIn =
isInProgress =
isSuccessful =
// 请求过程中
isLoggedIn =
isInProgress =
isSuccessful =
// 展示成功状态
isLoggedIn =
isInProgress =
isSuccessful =
// 展示欢迎信息和链接
isLoggedIn =
isInProgress =
isSuccessful =
&/code&&/pre&&/div&&p&我们简单的状态管理一步步变成了由 if-else 组成的巨大的条件网,很难去理解和维护。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&if (isInProgress) {
// 请求过程中
} else if (isLoggedIn) {
if (isSuccessful) {
// 展示请求成功信息
// 展示欢迎信息和链接
// 等待输入,展示表单
&/code&&/pre&&/div&&p&我们还有一个问题会让这个情景变得更糟:如果请求失败我们要怎么做?我们需要展示一个错误信息和一个重试链接,如果点击重试我们会重复一次请求的过程。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-119f25c17e2e3b4a8ff639b76dd77cdc_b.jpg& data-caption=&& data-rawwidth=&800& data-rawheight=&302& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic1.zhimg.com/v2-119f25c17e2e3b4a8ff639b76dd77cdc_r.jpg&&&/figure&&p&现在我们的代码已经没有任何可维护性。我们有非常多的场景需要满足,仅仅依赖引入新的变量是不可接受的。让我们想想是否可以通过更好的命名方式来解决,同时可能还需要引入一个新的条件声明。&/p&&p&&code&isInProgress&/code& 仅仅在请求的过程中被用到。我们现在还关心请求结束之后的过程。&/p&&p&&code&isLoggedIn&/code& 有一点误导的含义,因为我们只要请求结束就把它置为 &code&true&/code&。而如果请求出错,用户并没有真正登入。所以我们把它重命名为 &code&isRequestFinished&/code&。虽然看起来好些了,但是它仅仅代表我们从服务器获得了响应,并不能用它来判断响应是否为错误。&/p&&p&&code&isSuccessful&/code& 是一个最终状态合适的候选变量。如果请求出错我们可以把它设置为 &code&false&/code&,但是等等,它的默认值也是 &code&false&/code&。所以它也不能作为代表错误状态的变量。&/p&&p&我们需要第四个变量,&code&isFailed&/code& 怎么样?&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&var isRequestFinished, isInProgress, isSuccessful, isF
if (isInProgress) {
// 请求过程中
} else if (isRequestFinished) {
if (isSuccessful) {
// 展示请求成功信息
} else if (isFailed) {
// 展示请求失败信息和重试链接
// 展示欢迎信息和链接
// 等待输入,展示表单
&/code&&/pre&&/div&&p&这四个变量描述了一个看似简单但实际并不简单的过程,这个过程包含了许多边界情况。当项目进一步迭代时,最终可能会由于已有变量的组合不能满足新的需求,而定义更多的变量。这就是构建用户界面十分困难的原因。&/p&&p&我们需要更好的状态管理方式。也许可以使用更现代和更流行的概念。&/p&&h2&Flux 或者 Redux 怎么样?&/h2&&p&最近我在思考 Flux 架构和 Redux 库在状态管理中的定位。即使这些工具和状态管理有关,但是它们本质上不是解决这类问题的。&/p&&blockquote&Flux 是 Facebook 用来构建客户端 web 应用的架构。它利用单向数据流补足了 React 的视图组件的组织方式。&br&Redux 是一个可预测的状态容器,用来构建 JavaScript 应用。&/blockquote&&p&它们是 “单向数据流” 和 “状态容器”,而不是 “状态管理”。Flux 和 Redux 背后的概念是非常实用和讨巧的。我认为它们是适合构建用户界面的方式。单向数据流让数据拥有可预测性,改进了前端开发。Redux 中的 reducer 拥有的不可变特性,提供了一种可以减少 bug 的数据传送方式。&br&就我的感受来说,这些模式更适用于数据管理和数据流管理。它们提供了完善的 API 来交换改变我们应用数据的信息,但是并不能解决我们状态管理的问题。这也因为这些问题是跟项目强相关的,问题的上下文取决于我们正在做的事情。&br&当然像处理 HTTP 请求我们可以通过某个库来解决,但是对其它相关的业务逻辑我们仍然需要自己编写代码来实现。问题在于我们如何用一种合适的方式去组织这些代码,而不至于每两年就把整个应用重写一遍。&/p&&p&几个月之前我开始寻找可以解决状态管理问题的模式,最终我发现了状态机的概念。事实上我们一直都在构建状态机,只不过我们不知道。&/p&&h2&什么是状态机?&/h2&&figure&&img src=&https://pic4.zhimg.com/v2-fc4ab2b0e8ed4b722eefb_b.jpg& data-caption=&& data-rawwidth=&800& data-rawheight=&212& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic4.zhimg.com/v2-fc4ab2b0e8ed4b722eefb_r.jpg&&&/figure&&p&状态机的数学定义是一个计算模型,我的理解是:状态机就是保存你的状态和状态变化的一个盒子。这里有一些不同种类的状态机,适用于我们这个案例的是有限状态机。像它的名字一样,有限状态机包含有限的几种状态。它接收一个输入并且基于这个输入和当前的状态决定下一个状态,可能会有多种情况输出。当状态机改变了状态,我们就称为它过渡到一个新的状态。&/p&&h2&实战状态机&/h2&&p&为了使用状态机我们或多或少需要定义两件事 - 状态和可能的过渡方法。让我们来尝试实现上面提到的表单需求。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-dff16253c22ace645da07a0ebd9c3922_b.jpg& data-caption=&& data-rawwidth=&800& data-rawheight=&302& class=&origin_image zh-lightbox-thumb& width=&800& data-original=&https://pic3.zhimg.com/v2-dff16253c22ace645da07a0ebd9c3922_r.jpg&&&/figure&&p&在这个表格中我们可以清楚的看到所有状态和他们可能的输出情况。我们同样定义了如果输入被传递进状态机后的下一个状态。编写这样的表格对你的开发周期大有裨益,因为他会回答你以下问题:&/p&&ul&&li&用户界面可能出现的所有状态有哪些?&/li&&li&每种状态之间会发生什么?&/li&&li&如果某种状态改变,结果是什么?&/li&&/ul&&p&这三个问题可以解决非常多的难题。想象一下当我们改变内容展示的时候有一个动画效果,当动画开始时,UI 仍然处于之前的状态并且用户仍然可以产生交互。举个例子,用户非常快速地点击了两次提交按钮。如果不适用状态机,我们需要使用if语句通过标志变量来防止代码的执行。但是如果回到上面那个表格,我们会看到 loading 状态不接受 Submit 状态的输入。所以如果我们在第一次点击按钮后把状态机转变为 loading 状态,我们就会处于一个安全的位置。即使 Submit 输入/动作被分发过来,状态机也会忽略它,当然也不会再向后端发出一个请求。&/p&&p&状态机模式对我来说是适用的。以下有三个理由支撑我在我的应用中使用状态机:&/p&&ul&&li&状态机模式免去了很多可能出现的 bug 和奇怪的清洁,因为它不会让 UI 变化为我们不知道的状态。&/li&&li&状态机不接受没有明确定义的输入作为当前的状态。这会免去我们对其它代码执行的部分容错处理。&/li&&li&状态机强制开发者以声明式的方式思考。因为我们大部分的逻辑需要提前定义。&/li&&/ul&&h2&在 JavaScript 里实现状态机&/h2&&p&现在,既然我们知道什么是状态机,那就让我们来实现一个并且解决我们一开始的问题。用一些嵌套的属性定义一个简单的对象字面量。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&const machine = {
currentState: 'login form',
'login form': {
submit: 'loading'
'loading': {
success: 'profile',
failure: 'error'
'profile': {
viewProfile: 'profile',
logout: 'login form'
'error': {
tryAgain: 'loading'
&/code&&/pre&&/div&&p&这个状态机对象使用我们上面表格中的内容定义了状态。像示例中那样,当我们在 &code&login form&/code& 状态时,我们用 &code&submit&/code& 作为一个输入并且应该以 &code&loading&/code& 状态结束。现在我们需要一个接收输入的函数。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&const input = function (name) {
const state = machine.currentS
if (machine.states[state][name]) {
machine.currentState = machine.states[state][name];
console.log(`${ state } + ${ name } --& ${ machine.currentState }`);
&/code&&/pre&&/div&&p&我们获得了当前状态并且检查提供的input是否合法,如果通过检查,我们就改变当前的状态,或者换句话说,将状态机过渡到一个新的状态。我们提供了一个日志输出用来输入、当前状态和新的状态(如果有变化的话)。下面是如何去使用我们的状态机:&/p&&div class=&highlight&&&pre&&code class=&language-zephir&&&span&&/span&&span class=&nx&&input&/span&&span class=&p&&(&/span&&span class=&s1&&'tryAgain'&/span&&span class=&p&&);&/span&
&span class=&c1&&// login form + tryAgain --& login form&/span&
&span class=&nx&&input&/span&&span class=&p&&(&/span&&span class=&s1&&'submit'&/span&&span class=&p&&);&/span&
&span class=&c1&&// login form + submit --& loading&/span&
&span class=&nx&&input&/span&&span class=&p&&(&/span&&span class=&s1&&'submit'&/span&&span class=&p&&);&/span&
&span class=&c1&&// loading + submit --& loading&/span&
&span class=&nx&&input&/span&&span class=&p&&(&/span&&span class=&s1&&'failure'&/span&&span class=&p&&);&/span&
&span class=&c1&&// loading + failure --& error&/span&
&span class=&nx&&input&/span&&span class=&p&&(&/span&&span class=&s1&&'submit'&/span&&span class=&p&&);&/span&
&span class=&c1&&// error + submit --& error&/span&
&span class=&nx&&input&/span&&span class=&p&&(&/span&&span class=&s1&&'tryAgain'&/span&&span class=&p&&);&/span&
&span class=&c1&&// error + tryAgain --& loading&/span&
&span class=&nx&&input&/span&&span class=&p&&(&/span&&span class=&s1&&'success'&/span&&span class=&p&&);&/span&
&span class=&c1&&// loading + success --& profile&/span&
&span class=&nx&&input&/span&&span class=&p&&(&/span&&span class=&s1&&'viewProfile'&/span&&span class=&p&&);&/span&
&span class=&c1&&// profile + viewProfile --& profile&/span&
&span class=&nx&&input&/span&&span class=&p&&(&/span&&span class=&s1&&'logout'&/span&&span class=&p&&);&/span&
&span class=&c1&&// profile + logout --& login form&/span&
&/code&&/pre&&/div&&p&注意我们尝试通过在 &code&login form&/code& 状态的时候发送 &code&tryAgain&/code& 状态来打破状态机的运转或者是重复发送提交请求。在这些场景下,当前的状态没有被改变并且状态机会忽略这些输入。&/p&&h2&最后的话&/h2&&p&我不知道状态机的概念是否适用于你自己的场景,但是对我来说非常适用。我仅仅改变了我处理状态管理的方式。我建议去尝试一下,绝对是值得的。&/p&
最近我开始思考React应用的状态管理。我已经取得一些有趣的结论,并且在这篇文章里我会向你展示我们所谓的状态管理并不是真的在管理状态。译者:阿里云前端-也树原文链接:我们避而不谈的是什么(Th…
&p&最近金拱门比较火,我们先戳开它的&a href=&https://link.zhihu.com/?target=https%3A//link.juejin.im/%3Ftarget%3Dhttps%253A%252F%252Fwww.mcdonalds.com.cn%252F& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&官网&/a&看看。&/p&&p&看完后,如果你老板要是让你做这么一个网站,一定要seo,一定要兼容IE,你会怎么去做呢?&/p&&p&用vue/react吧,单页应用满足不了seo,而且IE兼容性不好。上node中间层做服务端渲染又把事情搞麻烦了。只能用JQuery干,但是又该怎么做工程化呢?好像也不是很容易。因为目前大家的工程化方案多是一整套单页应用全家桶,如vue-cli的webpack模板。&/p&&p&而前端到如今这个阶段,再让大家接手一个没有工程化的项目,肯定内心非常抵触了。试想这么一个项目,手动link资源,不能写less/sass,不能写ES6,不能依赖管理,不能编译打包...,哦天,想都不敢想。可是工程化这事在实际业务中却没有大家想象中的那么顺利。比如刚刚金拱门的官网,页面很多,要求满足SEO,IE兼容。而且遇到这些项目,往往还会有这些问题:&/p&&ol&&li&由于页面是后端渲染,需要部署后端程序(php,java之类),各种环境配置相当麻烦。&/li&&li&前端的html代码依托于服务端,导致前端做工程化时,很难对接前后端项目。&/li&&/ol&&p&也就是说,我们需要做一个非单页应用的工程化项目。这个项目在线上时是前后端耦合的,但是在开发时,我们又不想前后端耦合。再整理一下,我们需要解决的问题有:&/p&&ol&&li&前后端分离,前端开发不能依赖于后端环境。&/li&&li&前端工程化。诸如静态资源的打包编译、依赖的管理、组件化等等。&/li&&/ol&&p&明确了要解决的问题后,我们就可以开始了。我们可以用webpack搭建一个项目,帮我们做一些打包、编译、文件处理这些工程化工作。webpack从零配置比较繁琐,我们可以选择修改一个轮子,比如把&a href=&https://link.zhihu.com/?target=https%3A//link.juejin.im/%3Ftarget%3Dhttps%253A%252F%252Fgithub.com%252Fvuejs%252Fvue-cli& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&vue-cli&/a&的webpack模板改造一下,删了没必要的vue-loader,给它增加一下多页面入口就好了。&/p&&h2&修改轮子&/h2&&h2&第一步:理解 &a href=&https://link.zhihu.com/?target=https%3A//link.juejin.im/%3Ftarget%3Dhttps%253A%252F%252Fgithub.com%252Fvuejs-templates%252Fwebpack& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&vuejs-templates/webpack&/a&&/h2&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&npm install -g vue-cli
vue init webpack my-project
&/code&&/pre&&/div&&p&既然要改人家的模板,先得理解人家都做了什么。这里就不带大家读代码了,根据package.json的命令一个个文件的代码看过去就知道了,很直接很暴力。&/p&&h2&第二步:删&/h2&&p&既然我们不需要用vue,那么对于vue文件处理的相关逻辑我们就不需要了。根据刚刚对这个模板的了解,我们知道&code&vue-loader&/code&跟&code&vue-style-loader&/code&是不需要的。所以删除对应的代码跟package.json里面的包就好了。&/p&&p&多提一点的是,&code&vue-style-loader&/code&虽然不需要,&code&style-loader&/code&还是需要的,所以需要用后者替换前者。&/p&&h2&第三步:加&/h2&&p&做减法容易,做加法就没这么轻松了。根据我们刚刚的需求,我们应该给它加个多页面入口。网上有非常多的webpack多入口配置教程。然而他们不一定就能满足我们的需求。他们普遍存在如下问题:&/p&&ol&&li&&b&入口文件需要自己配置&/b&。在一个页面较多的项目中,入口文件应当从约定的目录中自动读取,也更符合约定优于配置。&/li&&li&&b&多入口是针对js的&/b&。由于业界普遍是在用单页应用,页面由js生成,故多页面只要多个js入口就好,不需要直接写html。而我的需求不是,我希望的多入口是针对html文件而言的。&/li&&/ol&&p&不过当我们解决了上述两个问题后,我们还会有一个新的问题。我们不同的html文件,其实又是有公共的部分的。比如都有 header,footer。也就是说,我们需要给这些html文件增加一个模板。我们可以通过webpack的loader来实现,但是没有现成的loader可以比较好的解决。那怎么办呢?可以参考我另外一篇文章。&a href=&https://link.zhihu.com/?target=https%3A//link.juejin.im/%3Ftarget%3Dhttps%253A%252F%252Fjuejin.im%252Fpost%252F59df06e6f265da430d5701d0& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&编写自己的Webpack Loader&/a&。&/p&&h2&静态资源的版本控制&/h2&&p&上述问题解决后,我们的工作并未完成。现在这个项目的静态资源是以文件哈希值来控制的。可惜有的项目的静态资源是要后端来更新时间戳控制的。虽然这不是个好方案,但有些工程却依旧是这样。没办法,为了适应他们,我们必须得去掉哈希值。可是这样的话,当我们想更新css内引用的图片时又没辙了,因为css内链的图片后端没法控制版本。&/p&&p&这个该怎么解决呢?感谢webpack,我们可以通过如下的配置来实现:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&p&&{&/span&
&span class=&nx&&test&/span&&span class=&o&&:&/span& &span class=&sr&&/\.(png|jpe?g|gif|svg)(\?.*)?$/&/span&&span class=&p&&,&/span&
&span class=&nx&&oneOf&/span&&span class=&o&&:&/span& &span class=&p&&[&/span&
&span class=&p&&{&/span&
&span class=&nx&&issuer&/span&&span class=&o&&:&/span& &span class=&sr&&/\.html$/&/span&&span class=&p&&,&/span&
&span class=&nx&&loader&/span&&span class=&o&&:&/span& &span class=&s1&&'url-loader'&/span&&span class=&p&&,&/span&
&span class=&nx&&options&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&limit&/span&&span class=&o&&:&/span& &span class=&mi&&10000&/span&&span class=&p&&,&/span&
&span class=&nx&&name&/span&&span class=&o&&:&/span& &span class=&nx&&utils&/span&&span class=&p&&.&/span&&span class=&nx&&assetsPath&/span&&span class=&p&&(&/span&&span class=&s1&&'img/[name].[ext]'&/span&&span class=&p&&,)&/span&
&span class=&p&&}&/span&
&span class=&p&&},&/span&
&span class=&p&&{&/span&
&span class=&nx&&issuer&/span&&span class=&o&&:&/span& &span class=&sr&&/\.(css|less)$/&/span&&span class=&p&&,&/span&
&span class=&nx&&loader&/span&&span class=&o&&:&/span& &span class=&s1&&'url-loader'&/span&&span class=&p&&,&/span&
&span class=&nx&&options&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&limit&/span&&span class=&o&&:&/span& &span class=&mi&&10000&/span&&span class=&p&&,&/span&
&span class=&nx&&name&/span&&span class=&o&&:&/span& &span class=&nx&&utils&/span&&span class=&p&&.&/span&&span class=&nx&&assetsPath&/span&&span class=&p&&(&/span&&span class=&s1&&'img/[name].[hash:7].[ext]'&/span&&span class=&p&&)&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&p&&]&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&意思就是如果图片是在html中引用的则不加哈希值,在css文件中引入的则加上。&/p&&h2&完工&/h2&&p&这样我们就完成了一个简单的项目架构。它能帮助我们实现文件的打包、编译,html的模板控制等功能。最终能build出一份html+静态资源的web页面直接发布cdn。当然也可以把它们直接扔给后端。&/p&&p&不过这个架子还不是非常的完善,应用场景也有限,比较适用于一些&b&交互较少、页面较多、看重seo或者传统后端套页面&/b&的网站。另外,作为工程化中非常重要的组件化与测试,由于没有任何框架的引入,这点也需要使用者自己再去摸索。&/p&&p&另外,如果还是想用vue,react或angular,又不想搞他们的服务端渲染,可以尝试下&a href=&https://link.zhihu.com/?target=https%3A//link.juejin.im/%3Ftarget%3Dhttps%253A%252F%252Fjuejin.im%252Fpost%252F59cf4eb3c36c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&变相的服务端渲染系统&/a&。&/p&&p&最后,如果这个架子对您有用,欢迎戳开&a href=&https://link.zhihu.com/?target=https%3A//link.juejin.im/%3Ftarget%3Dhttps%253A%252F%252Fgithub.com%252Fwuomzfx%252Fpure-webpage& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&github&/a&。&/p&&p&&br&&/p&&p&--&a href=&https://link.zhihu.com/?target=https%3A//link.juejin.im/%3Ftarget%3Dhttps%253A%252F%252Fgithub.com%252Fwuomzfx%252Fpure-webpage%252Fblob%252Fmaster%252Fabout.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&阅读原文&/a& --转载请先经过本人授权-&a href=&https://link.zhihu.com/?target=https%3A//link.juejin.im/%3Ftarget%3Dhttps%253A%252F%252Fzhuanlan.zhihu.com%252Fdxyf2e& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&丁香园F2E&/a& &a href=&https://link.zhihu.com/?target=https%3A//link.juejin.im/%3Ftarget%3Dhttps%253A%252F%252Fwww.zhihu.com%252Fpeople%252Fxiang-xue-zhang& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&@相学长&/a&。&/p&
最近金拱门比较火,我们先戳开它的看看。看完后,如果你老板要是让你做这么一个网站,一定要seo,一定要兼容IE,你会怎么去做呢?用vue/react吧,单页应用满足不了seo,而且IE兼容性不好。上node中间层做服务端渲染又把事情搞麻烦了。只能用JQuery干,…
&figure&&img src=&https://pic3.zhimg.com/v2-62d6fa71c5d0aacb5b1a0a7_b.jpg& data-rawwidth=&2000& data-rawheight=&1138& class=&origin_image zh-lightbox-thumb& width=&2000& data-original=&https://pic3.zhimg.com/v2-62d6fa71c5d0aacb5b1a0a7_r.jpg&&&/figure&&p&但凡稍微复杂一点的软件,总免不了与配置文件打交道,不管是服务应用还是客户端应用,如果我们打开其文件目录,常常会看到一大堆xxx.rc、xxx.conf、xxx.ini,一般情况下,这些都是配置文件。&/p&&p&&br&&/p&&p&而在Web前端,情况有点特殊。&/p&&ul&&li&首先,前端代码的打包方式决定,一个作为库来用的js模块,不太适合拥有外置的配置文件。&/li&&li&第二,前端代码的部署实际上有三方参与:前端、后端模板、用户。如果分配三者的配置职权,就成了个新问题。&/li&&/ul&&p&&br&&/p&&h2&第一步:先搞清楚我们要解决什么问题&/h2&&p&&br&&/p&&p&现实中我们会遇到哪些问题呢?我们先来看几个场景:&/p&&p&&br&&/p&&p&&b&场景一&/b&&/p&&p&假设有一个前端页面,各个模块之间的依赖关系是这样的:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&page -& A -& B -& C
&/code&&/pre&&/div&&p&&br&&/p&&p&假设A、B、C、D都有若干配置项,那在实际工程中应该怎么把配置项配进去呢?&/p&&p&&br&&/p&&p&一般有两种方案:&/p&&ul&&li&第一种,配置全部通过初始化参数来传入。&/li&&li&第二种,每个模块定义自己的全局配置变量名,通过html(模板)中赋值全局变量传入配置。&/li&&/ul&&p&&br&&/p&&p&第一种方案的问题在于,假设C、D有配置项,可是我在编写B的时候,我并不知道,或者并不关心C、D具体要配成什么样,要上层来决定。怎么办呢?只能靠参数一层一层的往下传。&/p&&p&&br&&/p&&p&第二种方案的问题在于,当项目中的package比较多的时候,我们就会看到这样的html模板代码:&/p&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span&&span class=&p&&&&/span&&span class=&nt&&script&/span&&span class=&p&&&&/span&
&span class=&nb&&window&/span&&span class=&p&&.&/span&&span class=&nx&&__a_config&/span& &span class=&o&&=&/span& &span class=&p&&{...}&/span&
&span class=&nb&&window&/span&&span class=&p&&.&/span&&span class=&nx&&BConf__&/span& &span class=&o&&=&/span& &span class=&p&&{...}&/span&
&span class=&nb&&window&/span&&span class=&p&&.&/span&&span class=&nx&&$settingsForC&/span& &span class=&o&&=&/span& &span class=&p&&{...}&/span&
&span class=&p&&...&/span&
&span class=&p&&&/&/span&&span class=&nt&&script&/span&&span class=&p&&&&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&不仅全局变量杂乱无章,更讨厌的是ABC这些模块处理配置项的策略往往是不一样的,有的有默认值,有的没有,有的可以输入正则表达式,有的只能输入字符串……要知道很多时候html模板是在后端工程中的,天天跟后端口述这些,真的很难不出错。&/p&&p&&br&&/p&&p&&b&场景二&/b&&/p&&p&为了调试方便,我们常常打开网页时临时修改配置项,可能你会写这样的代码:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nb&&window&/span&&span class=&p&&.&/span&&span class=&nx&&location&/span&&span class=&p&&.&/span&&span class=&nx&&search&/span&&span class=&p&&.&/span&&span class=&nx&&indexOf&/span&&span class=&p&&(&/span&&span class=&s1&&'isdev'&/span&&span class=&p&&))&/span& &span class=&p&&{&/span&
&span class=&nx&&urlPrefix&/span& &span class=&o&&=&/span& &span class=&s1&&'...'&/span&
&span class=&nx&&isDev&/span& &span class=&o&&=&/span& &span class=&kc&&true&/span&
&span class=&p&&}&/span&
&span class=&c1&&//...&/span&
&span class=&kd&&function&/span& &span class=&nx&&foobar&/span&&span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&isDev&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&debug&/span&&span class=&p&&(&/span&&span class=&sb&&`...`&/span&&span class=&p&&)&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&这种代码可能你在每个项目、每个模块中都要写一遍,而且如果有些模块是你同事写的,你会发现他用来区别开发模式的标志可能和你不一样——What the fuck!&/p&&p&&br&&/p&&p&&b&场景三&/b&&/p&&p&考虑如下代码:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nb&&window&/span&&span class=&p&&.&/span&&span class=&nx&&__config&/span&&span class=&p&&.&/span&&span class=&nx&&foo&/span&&span class=&p&&.&/span&&span class=&nx&&bar&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&//...&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&代码看起来没什么问题,但是假设window.__config没有foo字段,代码就崩了。这种情况如何应对呢?难道要每次使用都判断一下foo是否存在吗?&/p&&p&&br&&/p&&p&通过上面三个场景,我们基本上能看到一下问题了,经过梳理,大致是下面这些:&/p&&ul&&li&我的各个模块能否使用同一模式读取配置,什么时候写配置,什么时候改配置,能否规定好?&/li&&li&我的配置散落在js(开发期)、html(部署期)、url(运行期)中,能否统一管理?&/li&&li&我需要配置项,但是外部使用者没配怎么办?配的值错了怎么办?我不想把这些判断逻辑散落在业务代码中。&/li&&li&根据最小依赖原则,我不想拿到所有config,我只拿我感兴趣的。&/li&&li&配置项A和B之间有依赖怎么办,有没有简单的语法处理依赖?&/li&&/ul&&p&&br&&/p&&p&至此,我们的需求基本上就清楚了。&/p&&p&&br&&/p&&h2&第二步:梳理解决思路,确定方案&/h2&&p&&br&&/p&&p&下面我们要思考一下如何解决问题了。&/p&&p&&br&&/p&&p&上面的问题,通过编码规范、文档,都可以一定程度上解决,但都不是最好的选择。我们可以考虑一下,为什么会有那么多js框架和库?其实大多数时候,这些框架和库并不是封装了一坨所有人都看不懂、写不出的“高级代码”,而是提供一个模式,让开发者按照既定的路线编写应用。&/p&&p&&br&&/p&&p&也就是说,框架和库的意义在于——&b&通过代码引导行为&/b&。&/p&&p&&br&&/p&&p&再看回我们的问题,我们要做的恰恰是通过某种方法引导上层开发者的行为,从而将“前端应用配置”这个领域&b&治理&/b&好。那么,我们首先解决了&b&选型问题&/b&:我们通过提供一个js库来规范配置行为,辅以命名规范、文档等规定,使得配置问题得到解决。&/p&&p&&br&&/p&&p&下面就是具体的方案了,我们按照第一part中的提问逐条分析:&/p&&p&&br&&/p&&p&&b&我的各个模块能否使用同一模式读取配置,什么时候写配置,什么时候改配置,能否规定好?&/b& &/p&&p&答:可以,通过提供一个js库,就能提供一个统一的模式。上层模块统一使用这个库,所有的行为都是一样的。&/p&&p&&br&&/p&&p&&b&我的配置散落在js(开发期)、html(部署期)、url(运行期)中,能否统一管理?&/b& &/p&&p&答:我们首先要搞清楚,为什么配置会散落在各个阶段?&/p&&ul&&li&js中的配置,一般是默认值,或者不想暴露的私有配置。&/li&&li&html中的配置,一般是部署的时候由不同工况决定的(比如开发、测试、预发、线上),所以不写在js而是写在html中。&/li&&li&url中的配置,一般是开发人员在打开网页的时候,由于某种原因想临时改动一下配置项,覆盖原有的配置(比如是否使用mock)。&/li&&/ul&&p&综合以上三条,我们就可以编写一个统一的ConfigReader,网页打开之后,依次读取配置来源,输出一个按照优先级覆盖过的config表。&/p&&p&&br&&/p&&p&注意这里有个优先级的问题,即三种来源配置的值不同,谁说了算?按照使用逻辑,正确的优先级是:运行期最高,部署期次之,开发期最低。这里可以参考下传统的命令行程序是怎么写的,一定是命令行参数的优先级高于配置文件,道理是同样的。&/p&&p&&br&&/p&&p&&b&我需要配置项,但是外部使用者没配怎么办?配的值错了怎么办?我不想把这些判断逻辑散落在业务代码中。&/b&&/p&&p&&br&&/p&&p&答:第一个问题可以通过提供默认值接口解决,第二个问题可以通过提供后处理函数解决。关键点是,默认值和后处理函数统一在ConfigReader中设置,这样就避免了这些东西污染业务代码。&/p&&p&&br&&/p&&p&这里的思路就是一个典型的“抽取”思路——把与业务相对独立的模块或者“切面”抽取出来,是的业务代码更清爽,可读性更高。&/p&&p&&br&&/p&&p&&b&根据最小依赖原则,我不想拿到所有config,我只拿我感兴趣的。&/b&&/p&&p&&br&&/p&&p&答:没问题,我们把配置的编写和配置的读取分离,ConfigReader声明了哪些字段就读哪些,没有声明的一律忽略。&/p&&p&&br&&/p&&p&&b&配置项A和B之间有依赖怎么办,有没有简单的语法处理依赖?&/b&&/p&&p&&br&&/p&&p&答:可以,这里的思考过程我就不说了,直接说结论——仿照VUE的计算属性语法即可。&/p&&p&&br&&/p&&p&通过以上分析,我们基本上已经理清楚了功能点,思路清楚了,方案也就呼之欲出了,总结一下就是下面这张图:&/p&&p&&br&&/p&&figure&&img src=&https://pic3.zhimg.com/v2-62d6fa71c5d0aacb5b1a0a7_b.jpg& data-rawwidth=&2000& data-rawheight=&1138& class=&origin_image zh-lightbox-thumb& width=&2000& data-original=&https://pic3.zhimg.com/v2-62d6fa71c5d0aacb5b1a0a7_r.jpg&&&/figure&&p&&br&&/p&&h2&第三步:详细设计与实现细节&/h2&&p&&br&&/p&&p&对于技术障碍不大的项目,这一步反倒是最简单的,有前面的结论作为指导,按部就班的实现就好了。&/p&&p&&br&&/p&&p&首先我们确定如何写配置,按照刚才的分析,写配置主要是部署期和运行期(开发期的默认值放在Reader中)。&/p&&p&&br&&/p&&p&&b&URL search 中编写配置:&/b&&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&http://www.foobar.com/index.html?some-item=1
&/code&&/pre&&/div&&p&&br&&/p&&p&&b&全局变量中编写配置:&/b&&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nb&&window&/span&&span class=&p&&.&/span&&span class=&nx&&__Konph&/span& &span class=&o&&=&/span& &span class=&p&&{&/span&
&span class=&s1&&'some-item'&/span&&span class=&o&&:&/span& &span class=&mi&&1&/span&&span class=&p&&,&/span&
&span class=&s1&&'another-item'&/span&&span class=&o&&:&/span& &span class=&s1&&'222'&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&我们只提供这两个写配置的入口,保证所有模块的配置都只有这两个来源,并且行为都一致,再辅以配置命名规范(比如大小写、前缀),就初步结束了写配置的乱象。&/p&&p&&br&&/p&&p&然后再来确定如何读配置,同样根据刚才的分析,将默认值,后处理函数等特性添加之后,设计出这样的接口:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&// somepackage/config.js&/span&
&span class=&kr&&import&/span& &span class=&nx&&conf&/span& &span class=&nx&&from&/span& &span class=&s1&&'konph'&/span&
&span class=&kr&&export&/span& &span class=&k&&default&/span& &span class=&nx&&conf&/span&&span class=&p&&({&/span&
&span class=&s1&&'item-with-default-value'&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&c1&&// 默认值&/span&
&span class=&nx&&def&/span&&span class=&o&&:&/span& &span class=&mi&&1&/span&
&span class=&p&&},&/span&
&span class=&s1&&'item-with-fit-function'&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&def&/span&&span class=&o&&:&/span& &span class=&mi&&2&/span&&span class=&p&&,&/span&
&span class=&c1&&// 后处理函数&/span&
&span class=&nx&&fit&/span&&span class=&o&&:&/span& &span class=&nx&&value&/span& &span class=&o&&=&&/span& &span class=&nx&&value&/span& &span class=&o&&&&/span& &span class=&mi&&2&/span& &span class=&o&&?&/span& &span class=&mi&&2&/span& &span class=&o&&:&/span& &span class=&nx&&value&/span&
&span class=&p&&},&/span&
&span class=&s1&&'another-item'&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&c1&&// 配置间依赖第一种写法&/span&
&span class=&nx&&fit&/span&&span class=&o&&:&/span& &span class=&p&&(&/span&&span class=&nx&&value&/span&&span class=&p&&,&/span& &span class=&nx&&context&/span&&span class=&p&&)&/span& &span class=&o&&=&&/span& &span class=&nx&&value&/span& &span class=&o&&+&/span& &span class=&nx&&context&/span&&span class=&p&&[&/span&&span class=&s1&&'item-with-default-value'&/span&&span class=&p&&]&/span& &span class=&o&&+&/span& &span class=&mi&&1&/span&
&span class=&p&&},&/span&
&span class=&s1&&'another-item'&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&c1&&// 配置间依赖第二种写法&/span&
&span class=&nx&&fit&/span&&span class=&o&&:&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&value&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&value&/span& &span class=&o&&+&/span& &span class=&k&&this&/span&&span class=&p&&[&/span&&span class=&s1&&'item-with-default-value'&/span&&span class=&p&&]&/span& &span class=&o&&+&/span& &span class=&mi&&1&/span&
&span class=&p&&}&/span&
&span class=&p&&},&/span&
&span class=&c1&&// 私有配置, 不会被全局变量或者url参数覆盖&/span&
&span class=&s1&&'some-private-item'&/span&&span class=&o&&:&/span& &span class=&nx&&conf&/span&&span class=&p&&.&/span&&span class=&kr&&private&/span&&span class=&p&&(&/span&&span class=&s1&&'I\'m a private config item.'&/span&&span class=&p&&)&/span&
&span class=&p&&})&/span&
&span class=&c1&&// somepackage/index.js&/span&
&span class=&kr&&import&/span& &span class=&nx&&config&/span& &span class=&nx&&from&/span& &span class=&s1&&'./config'&/span&
&span class=&kr&&const&/span& &span class=&nx&&item1&/span& &span class=&o&&=&/span& &span class=&nx&&config&/span&&span class=&p&&[&/span&&span class=&s1&&'item-with-default-value'&/span&&span class=&p&&]&/span&
&span class=&c1&&//...&/span&
&/code&&/pre&&/div&&p&&br&&/p&&p&通过上面的接口形式,我们成功地将配置读取与写配置分离,同时添加了默认值、后处理函数、配置间依赖的支持,语法也比较简单。&/p&&p&&br&&/p&&p&至于实现细节,我给出一个参考实现:&a href=&https://link.zhihu.com/?target=https%3A//github.com/yusangeng/konph& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&yusangeng/konph&/a& 有兴趣可以参考下。&/p&&p&&br&&/p&&h2&第四步:反思 & 讨论&/h2&&p&&br&&/p&&p&下面我们来复盘一下整个事情。&/p&&p&&br&&/p&&p&首先是路径,本文提出的问题实际上是比较简单的,在技术上没有障碍,但是它体现了一个解决问题的常见思路。我们解决问题经历了哪些步骤呢?&/p&&ul&&li&首先,提出问题,或者说我们要先看到问题。如果看到任何问题都觉得很正常,那就谈不上解决了。&/li&&li&第二,分析问题,继而问题分解,把要解决的问题一条一条列清楚,把不要解决的问题也列清楚。搞清楚自己要面对的问题边界,该放弃的就放弃。&/li&&li&第三,回答每一个分解后的问题,提出新模型的运行流程,继而综合起来,提出解决方案。&/li&&li&第四,详细设计和具体实现,设计一个好用方便的接口,并且一定程度上考虑扩展性。根据上一条的方案加接口,再去思考如何实现的问题。设计归设计,实现归实现,设计的时候尽量追求简练优雅灵活,不清楚如何实现的到实现的时候再研究,而不是“当下会写成啥样”就写成啥样。&/li&&/ul&&p&这里想着重讨论的是第二条,哪些问题我们选择不解决呢?我举两个例子:&/p&&ul&&li&比如:要不要做运行时监听配置。配置改变时实时通知到各个模块?&br&答:不做!服务端有这种功能是因为服务端长期运行,需要不停机改动配置,你一个网页要这种功能干什么?况且实现起来又不容易。&/li&&li&比如:要不要做树形的配置列表,否则就一层命名空间,重名了咋办?&br&答:不做!树形的配置列表会导致ConfigReader逻辑变得很复杂,而且这个问题通过命名规范可以防止。况且你的网页有多复杂?连配置都需要用到树形的命名空间?&/li&&/ul&&p&&br&&/p&&p&当然以上只是我根据我的需求得出的结论,面对不同的实际项目,完全可能得出不同的结论。&/p&
但凡稍微复杂一点的软件,总免不了与配置文件打交道,不管是服务应用还是客户端应用,如果我们打开其文件目录,常常会看到一大堆xxx.rc、xxx.conf、xxx.ini,一般情况下,这些都是配置文件。 而在Web前端,情况有点特殊。首先,前端代码的打包方式决定,一…
&blockquote&主要谈应用ES6应用,至于替代jquery 这样的讨论这又能拉一篇文章了,jquery 某些时候还是挺方便的 : )&/blockquote&&p&&b&用数据和行业主流方案,说服上层降低对低版本IE适配的要求。&/b&&/p&&p&根据题主问题,兼容性要求感觉已经定死了,&b&虽然这样还是想提下这条路&/b&,毕竟BAT大部分公司已经在开始走降低此方面的要求了,如果完成一劳永逸,淘宝、天猫首页 IE8以下访问已经开始引导升级了,我们这块主站虽然没做到,在支付、金融等环境已经开始进行安全性升级提示。&/p&&p&另外数据方面可以拿 &a href=&//link.zhihu.com/?target=http%3A//tongji.baidu.com/data/browser& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&浏览器市场份额 - 百度统计流量研究院&/a& ,目前IE7在 3.35%,趋势上也在不断下降。&/p&&p&&b&回到正题,还需要兼容IE7。&/b&&/p&&p&&b&题主主要负责的是游戏官网和活动页面,&/b&此处有2条分支:1.负责项目view层数据渲染和业务逻辑以及UI交互构建;2.只负责UI交互构建(就是切个图)&/p&&br&&p&&b&负责项目view层数据渲染和业务逻辑以及UI交互构建:&/b&&/p&&p&可以使用 webpack 用
babel 来将ES6代码进行编译转化,记得使用 es5-shim 来解决下低版本编译的兼容性问题;&/p&&p&如果业务逻辑比较复杂可以考虑引入 avalon &a href=&//link.zhihu.com/?target=http%3A//avalonjs.coding.me/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&扉页 · GitBook&/a& 来实现 mvvm,线上有个项目用的是2.2.4 这个版本(兼容到IE7),因为avalon在实际项目开发中有不少踩坑点,如果需求可以单独列下(比较多 - =)&/p&&p&&b&只负责UI交互构建:&/b&&/p&&p&实话讲技术工具的应用要根据项目背景来评估,如果只是负责UI交互构建(切个图),建议不用在线上项目里面去折腾了,投入产出比不高而且可能会造成不必要的线上风险。&/p&&p&题主主要也是想跟上技术发展提升自己,这个精力可以投入到个人兴趣项目开发中,比如自己给自己设定一个小需求:仿一个新闻类单页应用,开发一个TodoList之类的。一定要从头到尾开发完,同时结合别人的示例来对比项目架构等。这样同样也得到了技术积累和提升,说不定找到有合适项目的还能搞点外快 : ) 。&/p&&br&&p&文章参考:&/p&&p&&a href=&//link.zhihu.com/?target=https%3A//segmentfault.com/q/5558& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&webpack - Babel转换es6怎么支持ie8? - SegmentFault&/a& ES6转换低版本的一些讨论&/p&&p&&a href=&//link.zhihu.com/?target=https%3A//github.com/brickspert/webpack-react-react-router-ie8& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&brickspert/webpack-react-react-router-ie8&/a& webpack+react+react-router+es6兼容ie8&/p&&a href=&//link.zhihu.com/?target=http%3A//blog.csdn.net/qq_/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&webpack 配合babel 将es6转成es5 超简单实例&/a&
主要谈应用ES6应用,至于替代jquery 这样的讨论这又能拉一篇文章了,jquery 某些时候还是挺方便的 : )用数据和行业主流方案,说服上层降低对低版本IE适配的要求。根据题主问题,兼容性要求感觉已经定死了,虽然这样还是想提下这条路,毕竟BAT大部分公司已经…
&p&以前对浏览器兼容性问题只是大概知道一些点,没想到这次真正着手去做的时候,还是碰到了很多问题。刚开始的时候一边解决问题,一边想着:用 IE8 的都是神经病,到后来,我发现完了,I LOVE IE。&/p&&h2&0x00 起源&/h2&&p&在这次做小蜜 PC 版的时候,由于早于 PC 版,无线版已经重新设计了全新版,做了很多架构上的优化调整。所以在做的时候把无线版的前端架构拿了过来,主要的考虑就是品牌和功能保持跟无线版统一的同时,技术上也可相互支持以及组件复用。&/p&&p&无线版整个架构设计是同事做的,技术上主要采用 ES6 + Webpack + Babel 的方式,由于项目的独特性和特殊需求,并没有使用任何框架,只引入 zepto 作为一个标准支撑库。&/p&&p&而 PC 版的架构跟无线版基本保持一致,主要是把 zepto 换成了 jQuery。&/p&&p&下面是一些基本的开发依赖:&/p&&div class=&highlight&&&pre&&code class=&language-json&&&span&&/span&&span class=&p&&{&/span&
&span class=&nt&&&devDependencies&&/span&&span class=&p&&:&/span& &span class=&p&&{&/span&
&span class=&nt&&&babel-core&&/span&&span class=&p&&:&/span& &span class=&s2&&&~6.3.15&&/span&&span class=&p&&,&/span&
&span class=&nt&&&babel-loader&&/span&&span class=&p&&:&/span& &span class=&s2&&&~6.2.0&&/span&&span class=&p&&,&/span&
&span class=&nt&&&babel-preset-es2015&&/span&&span class=&p&&:&/span& &span class=&s2&&&~6.3.13&&/span&&span class=&p&&,&/span&
&span class=&nt&&&babel-preset-stage-0&&/span&&span class=&p&&:&/span& &span class=&s2&&&~6.3.13&&/span&&span class=&p&&,&/span&
&span class=&nt&&&babel-runtime&&/span&&span class=&p&&:&/span& &span class=&s2&&&~6.3.13&&/span&&span class=&p&&,&/span&
&span class=&nt&&&extract-text-webpack-plugin&&/span&&span class=&p&&:&/span& &span class=&s2&&&~0.9.1&&/span&&span class=&p&&,&/span&
&span class=&nt&&&less-loader&&/span&&span class=&p&&:&/span& &span class=&s2&&&~2.2.1&&/span&&span class=&p&&,&/span&
&span class=&nt&&&nunjucks-loader&&/span&&span class=&p&&:&/span& &span class=&s2&&&~1.0.7&&/span&&span class=&p&&,&/span&
&span class=&nt&&&style-loader&&/span&&span class=&p&&:&/span& &span class=&s2&&&~0.10.2&&/span&&span class=&p&&,&/span&
&span class=&nt&&&webpack&&/span&&span class=&p&&:&/span& &span class=&s2&&&~1.12.9&&/span&&span class=&p&&,&/span&
&span class=&nt&&&webpack-dev-server&&/span&&span class=&p&&:&/span& &span class=&s2&&&^1.10.1&&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&h2&0x01 polyfill&/h2&&p&由于 Babel 默认只转换转各种 ES2015 语法,而不转换新的 API,比如 Promise,以及 Object.assign、Array.from 这些新方法,这时我们需要提供一些 ployfill 来模拟出这样一个提供原生支持功能的浏览器环境。&/p&&p&主要有两种方式:babel-runtime 和 babel-polyfill。&/p&&h3&babel-runtime&/h3&&p&babel-runtime 的作用是模拟 ES2015 环境,包含各种分散的 polyfill 模块,我们可以在自己的模块里单独引入,比如 promise:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import 'babel-runtime/core-js/promise'
&/code&&/pre&&/div&&p&它们不会在全局环境添加未实现的方法,只是这样手动引用每个 polyfill 会非常低效,我们可以借助 Runtime transform 插件来自动化处理这一切。&/p&&p&首先使用 npm 安装:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&npm install babel-plugin-transform-runtime --save-dev
&/code&&/pre&&/div&&p&然后在 webpack 配置文件的 babel-loader 增加选项:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&loader: [&babel-loader&],
plugins: [
&transform-runtime&
presets: ['es2015', 'stage-0']
&/code&&/pre&&/div&&h3&babel-polyfill&/h3&&p&而 babel-polyfill 是针对全局环境的,引入它浏览器就好像具备了规范里定义的完整的特性,一旦引入,就会跑一个 babel-polyfill 实例。用法如下:&/p&&p&1.安装 babel-polyfill&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&npm install babel-polyfill --save
&/code&&/pre&&/div&&p&2.在入口文件中引用:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import 'babel-polyfill'
&/code&&/pre&&/div&&h3&小结:&/h3&&p&其实做到这些,在大部分浏览器就可以正常跑了,但我们做的是一个用户环境很不确定的产品,对一些年代久远但又不容忽视的运行环境,比如 IE8,我们做的还不够。&/p&&p&接下来将开始讲述我们在兼容性方面遇到的一些问题,和解决方法。&/p&&h2&0x02 开始在 IE8 运行&/h2&&p&最开始做的时候并没有针对 IE 做一些兼容性方面的处理,结果在 IE8 上一跑一堆问题。&/p&&p&第一步,我们把 jQuery 换成 1.12.1 ,因为 2.X 已经不再支持 IE8。&/p&&p&但并没有像我们想象中的那样,只是简单换一下 jQuery 版本就可以正常运行了。&/p&&h2&0x03 default or catch&/h2&&p&这是遇到的第一个问题。在兼容性测试过程中,对下面的代码:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&function&/span& &span class=&nx&&_interopRequireDefault&/span&&span class=&p&&(&/span&&span class=&nx&&obj&/span&&span class=&p&&)&/span& &span class=&p&&{&/span& &span class=&k&&return&/span& &span class=&nx&&obj&/span& &span class=&o&&&&&/span& &span class=&nx&&obj&/span&&span class=&p&&.&/span&&span class=&nx&&__esModule&/span& &span class=&o&&?&/span& &span class=&nx&&obj&/span& &span class=&o&&:&/span& &span class=&p&&{&/span& &span class=&k&&default&/span&&span class=&o&&:&/span& &span class=&nx&&obj&/span& &span class=&p&&};&/span& &span class=&p&&}&/span&
&/code&&/pre&&/div&&p&或者这种:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&module.exports = _main2.
&/code&&/pre&&/div&&p&在 IE8 下会直接报”缺少标识符、字符串或数字”的错。&/p&&p&我们得在对象的属性上加 '' 才可以。就像下面这样:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&function&/span& &span class=&nx&&_interopRequireDefault&/span&&span class=&p&&(&/span&&span class=&nx&&obj&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&obj&/span& &span class=&o&&&&&/span& &span class=&nx&&obj&/span&&span class=&p&&.&/span&&span class=&nx&&__esModule&/span& &span class=&o&&?&/span& &span class=&nx&&obj&/span& &span class=&o&&:&/span& &span class=&p&&{&/span& &span class=&err&&‘&/span&&span class=&k&&default&/span&&span class=&err&&’&/span&&span class=&o&&:&/span& &span class=&nx&&obj&/span& &span class=&p&&};&/span&
&span class=&p&&}&/span&
&span class=&nx&&module&/span&&span class=&p&&.&/span&&span class=&nx&&exports&/span& &span class=&o&&=&/span& &span class=&nx&&_main2&/span&&span class=&p&&[&/span&&span class=&s1&&'default'&/span&&span class=&p&&];&/span&
&/code&&/pre&&/div&&p&至于原因,并不是 IE8 下对象的属性必须得加 '' 才行,而是 default 的问题,作为一个关键字,同样的问题还包括 catch。&/p&&p&这两种情况,可以通过使用 transform-es3-property-literals 和 transform-es3-member-expression-literals 这两个插件搞定。&/p&&p&总之,在平时写代码的时候避免使用关键字,或者保留字作为对象的属性值,尤其是在习惯不加引号的情况下。相关讨论:&a href=&https://link.zhihu.com/?target=https%3A//github.com/airbnb/javascript/issues/61& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Allow reserved words for properties&/a&&/p&&h2&0x04 es5-shim、es5-sham&/h2&&p&为了兼容像 IE8 这样的老版本浏览器,我们引入 es5-shim 作为 polyfill。&/p&&p&但在遇到 Object.defineProperty 仍提示 &对象不支持此操作&&/p&&blockquote&&p&As currently implemented, the Object.defineProperty shim will not install on IE8 because IE8 already has such a method. However, the built-in IE8 method only works when applied to DOM objects.&/p&&/blockquote&&p&其实 es5-shim 明确说明,这个方法的 polyfill 在 IE8 会失败,因为 IE8 已经有个同名的方法,但只是用于 DOM 对象。&/p&&p&同样的问题还包括 Object.create,上述问题可以再引入 es5-sham 解决.&/p&&h2&0x05 addEventListener&/h2&&p&项目中有部分代码直接使用 addEventListener 这个 API,但在 IE8 下的事件绑定并不是这个方法。&/p&&p&这个问题很容易解决,也无需去写额外的 polyfill。我们已经把 jQuery 换成 1.x,所以只需把代码中 addEventListener 换成 jQuery 的写法就 Okay 了。&/p&&p&jQuery 其实为我们封装了很多 API,并做了很多兼容性的封装,类似的只要使用封装好的就可以了。&/p&&h2&0x06 无法获取未定义或 null 引用的属性&/h2&&p&这个问题是在特定场景下【转人工】出现的,出现问题的不是 IE8,而是 IE9 和 IE10。&/p&&p&原因是 ocs 实例创建失败,因为没有调用父类的构造函数。&/p&&p&通过安装 transform-es2015-classes 和 transform-proto-to-assign 解决。&/p&&p&在配置项加上这两个插件的配置:&/p&&div class=&highlight&&&pre&&code class=&language-json&&&span&&/span&&span class=&p&&{&/span&
&span class=&nt&&&plugins&&/span&&span class=&p&&:&/span& &span class=&p&&[&/span&
&span class=&p&&[&/span&&span class=&s2&&&transform-es2015-classes&&/span&&span class=&p&&,&/span& &span class=&p&&{&/span& &span class=&nt&&&loose&&/span&&span class=&p&&:&/span& &span class=&kc&&true&/span& &span class=&p&&}],&/span&
&span class=&s2&&&transform-proto-to-assign&&/span&
&span class=&p&&]&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&h2&0x07 postMessage&/h2&&p&虽然 postMessage 是 HTML5 的特性,但 IE8 和 Firefox3 很早就实现了这个 API,当然,跟后来的标准并不一致。这其实也不能怪 IE8。&/p&&blockquote&&p&The postMessage method is supported in Internet Explorer from version 8, Firefox from version 3 and Opera from version 9.5.&/p&&/blockquote&&p&我们可能会这样去使用:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&parent&/span&&span class=&p&&.&/span&&span class=&nx&&postMessage&/span&&span class=&p&&({&/span&&span class=&nx&&success&/span&&span class=&o&&:&/span& &span class=&s1&&'ok'&/span&&span class=&p&&,&/span& &span class=&nx&&name&/span&&span class=&o&&:&/span& &span class=&err&&‘&/span&&span class=&nx&&mirreal&/span&&span class=&err&&’&/span&&span class=&p&&},&/span& &span class=&err&&‘&/span&&span class=&o&&*&/span&&span class=&err&&’&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&但是为了兼容 IE8,我们得转成字符串:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&parent&/span&&span class=&p&&.&/span&&span class=&nx&&postMessage&/span&&span class=&p&&(&/span&&span class=&nx&&JSON&/span&&span class=&p&&.&/span&&span class=&nx&&stringify&/span&&span class=&p&&({&/span&&span class=&nx&&success&/span&&span class=&o&&:&/span& &span class=&s1&&'ok'&/span&&span class=&p&&,&/span& &span class=&nx&&name&/span&&span class=&o&&:&/span& &span class=&s2&&&mirreal&&/span&&span class=&p&&}),&/span& &span class=&err&&‘&/span&&span class=&o&&*&/span&&span class=&err&&’&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&另外一个需要注意的点是:在 IE8 下 window.postMessage 是同步的。&/p&&blockquote&&p&window.postMessage is syncronouse in IE 8&/p&&/blockquote&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&syncronouse&/span& &span class=&o&&=&/span& &span class=&kc&&true&/span&&span class=&p&&;&/span&
&span class=&nb&&window&/span&&span class=&p&&.&/span&&span class=&nx&&onmessage&/span& &span class=&o&&=&/span& &span class=&kd&&function&/span& &span class=&p&&()&/span& &span class=&p&&{&/span&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&nx&&syncronouse&/span&&span class=&p&&);&/span& &span class=&c1&&// 在 IE8 下会在控制台打印 true&/span&
&span class=&p&&};&/span&
&span class=&nb&&window&/span&&span class=&p&&.&/span&&span class=&nx&&postMessage&/span&&span class=&p&&(&/span&&span class=&s1&&'test'&/span&&span class=&p&&,&/span& &span class=&s1&&'*'&/span&&span class=&p&&);&/span&
&span class=&nx&&syncronouse&/span& &span class=&o&&=&/span& &span class=&kc&&false&/span&&span class=&p&&;&/span&
&/code&&/pre&&/div&&h2&0x08 IE8/IE9 的控制台&/h2&&p&遇到一个奇怪的问题,在刚开始遇到的时候(其实搞清楚原因,好像也挺正常的),小蜜在 IE8 IE9 无法加载。在 IE8 那个古老浏览器的左下角,好像也是唯一会在页面提示脚本错误的浏览器,提示 script error。&/p&&p&第一反应就是应该又是某个函数在 IE 下不支持,准备打开控制台看看到底哪里报错,结果却什么事都没有了,页面竟然顺畅地加载出来了,这下该怎么调试好呢?&/p&&p&开始思考:什么东西是依赖控制台而存在的,到底会是什么呢。。。其实就是控制台本身。&/p&&p&原因就是我们在代码中添加了一些控制信息会打印在控制台,而 IE8/IE9 要开启 IE Dev Tools 才能使用 console 对象。&/p&&p&切忌把 IE8/9 想成 Chrome/Firefox,以为永远有 window.console 可用.终于,IE10 改邪归正,console 不再像段誉的六脉神剑时有时无。&/p&&blockquote&&p&console.log is there in IE8, but the console object isn't created until you open DevTools. Therefore, a call to console.log may result in an error, for example if it occurs on page load before you have a chance to open the dev tools.&/p&&/blockquote&&p&但只要 IE8/9 还在一天,console 检查还是不能少的&/p&&p&事实上,IE8/9 从未死去,所以&/p&&p&就像这样:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nb&&window&/span&&span class=&p&&.&/span&&span class=&nx&&console&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&s1&&'log here'&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&要是有一堆 console.log, console.count, console.error, console.time, console.profile,... 这样去写,那还不把人写到恶心死。&/p&&p&写个简单的 console polyfill 吧,检测是否存在 console,不存在可以常见一个同名的空方法达到不报错的目的。当然,生产环境的代码其实也不会有那么多奇奇怪怪的 console。&/p&&h2&0x09 定义文档兼容性&/h2&&p&X-UA-Compatible 当初是针对 IE8 新加的一个配置。用于为 IE8 指定不同的页面渲染模式,比如使用 IE7 兼容模式,或者是采用最新的引擎。&/p&&p&现在基本也不需要前者的降级模式,更多的是写入 IE=edge 支持最新特性。而 chrome=1则会激活 Google Chrome Frame,前提是你的 IE 安装过这个插件。&/p&&p&有什么用呢,当然有用,有些 API 是作为新特性存在于 IE8 中的,比如 JSON,不开启的话就用不了。&/p&&h3&为什么要用 X-UA-Compatible?&/h3&&p&在 IE8 刚推出的时候,很多网页由于重构的问题,无法适应较高级的浏览器,所以使用 X-UA-Compatible 强制 IE8 采用低版本方式渲染。&/p&&p&比如:使用下面这段代码后,开发者无需考虑网页是否兼容 IE8 浏览器,只要确保网页在 IE6、IE7 下的表现就可以了。&/p&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span&&span class=&p&&&&/span&&span class=&nt&&meta&/span& &span class=&na&&http-equiv&/span&&span class=&o&&=&/span&&span class=&s&&&X-UA-Compatible&&/span& &span class=&na&&content&/span&&span class=&o&&=&/span&&span class=&s&&&IE=EmulateIE7&&/span& &span class=&p&&/&&/span&
&/code&&/pre&&/div&&p&而这段代码:&/p&&div class=&highlight&&&pre&&code class=&language-html&&&span&&/span&&span class=&p&&&&/span&&span class=&nt&&meta&/span& &span class=&na&&http-equiv&/span&&span class=&o&&=&/span&&span class=&s&&&X-UA-Compatible&&/span& &span class=&na&&content&/span&&span class=&o&&=&/span&&span class=&s&&&IE=edge,chrome=1&&/span& &span class=&p&&/&&/span&
&/code&&/pre&&/div&&p&IE=edge 告诉 IE 使用最新的引擎渲染网页,chrome=1 则可以激活 Chrome Frame[1]。&/p&&h2&0x0a 条件注释 or 条件编译&/h2&&p&最后说说 IE 的条件注释,用法如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&!
The NOT operator. This is placed immediately in front of the feature, operator, or subexpression to reverse the Boolean meaning of the expression.
[if lt IE 5.5]
The less-than operator. Returns true if the first argument is less than the second argument.
[if lte IE 6]
The less-than or equal operator. Returns true if the first argument is less than or equal to the second argument.
[if gt IE 5]
The greater-than operator. Returns true if the first argument is greater than the second argument.
[if gte IE 7]
The greater-than or equal operator. Returns true if the first argument is greater than or equal to the second argument.
[if !(IE 7)]
Subexpression operators. Used in conjunction with boolean operators to create more complex expressions.
[if (gt IE 5)&(lt IE 7)]
The AND operator. Returns true if all subexpressions evaluate to true
[if (IE 6)|(IE 7)]
The OR operator. Returns true if any of the subexpressions evaluates to true.
&/code&&/pre&&/div&&p&另外一个类似的东西是在 Javascript 中的条件编译(conditional compilation)。我们可以使用这段简单的代码来做浏览器嗅探:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&var isIE = /*@cc_on!@*/false
&/code&&/pre&&/div&&p&在其他浏览器中,false 前的被视为注释,而在 IE 中,/*@cc_on .... @*/ 之间的部分可以被 IE 识别并作为程序执行,同时启用 IE 的条件编译。&/p&&p&常用变量如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&* @_win32 如果在 Win32 系统上运行,则为 true。
* @_win16 如果在 Win16 系统上运行,则为 true。
* @_mac 如果在 Apple Macintosh 系统上运行,则为 true。
* @_alpha 如果在 DEC Alpha 处理器上运行,则为 true。
* @_x86 如果在 Intel 处理器上运行,则为 true。
* @_mc680x0 如果在 Motorola 680x0 处理器上运行,则为 true。
* @_PowerPC 如果在 Motorola PowerPC 处理器上运行,则为 true。
* @_jscript 始终为 true。
* @_jscript_build 包含 JavaScript 脚本引擎的生成号。
* @_jscript_version 包含 major.minor 格式的 JavaScript 版本号。
&/code&&/pre&&/div&&blockquote&&p&Internet Explorer 11 之前的所有版本的 Internet Explorer 都支持条件编译。 从 Internet Explorer 11 标准模式开始,Windows 8.x 应用商店应用不支持条件编译。&/p&&/blockquote&&h2&后:&/h2&&p&之前一直在做移动端的开发,没想到做 PC 端也会遇到这么多的兼容性问题。不同于移动端设备的繁杂和不确定性,PC 版的兼容更侧重于对特定浏览器的特性的了解,相比而言更为明确,而非因为某一款手机的诡异表现。&/p&&br&&h2&参考文档:&/h2&&a href=&https://link.zhihu.com/?target=https%3A//github.com/airbnb/javascript/issues/61& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Allow reserved words for properties&/a&&a href=&https://link.zhihu.com/?target=https%3A//github.com/es-shims/es5-shim/issues/5& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&IE8 defineProperty/getOwnPropertyDescriptor clash with shim&/a&&a href=&https://link.zhihu.com/?target=http%3A//babeljs.io/docs/plugins/transform-runtime/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Runtime transform&/a&&a href=&https://link.zhihu.com/?target=https%3A//github.com/babel/babel/blob/master/packages/babel-plugin-transform-runtime/src/definitions.js& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&babel-plugin-transform-runtime definitions&/a&&a href=&https://link.zhihu.com/?target=https%3A//github.com/babel/babelify/issues/133& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&super() not calling parent's constructor on IE9&/a&&a href=&https://link.zhihu.com/?target=http%3A//help.dottoro.com/ljgheukc.php& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&postMessage method (window) Javascript&/a&&a href=&https://link.zhihu.com/?target=https%3A//msdn.microsoft.com/library/ggv%3Dvs.85%29.aspx& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&使用 F12 工具控制台查看错误和状态&/a&&a href=&https://link.zhihu.com/?target=https%3A//msdn.microsoft.com/zh-cn/library/ccv%3Dvs.85%29.aspx& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&定义文档兼容性&/a&&a href=&https://link.zhihu.com/?target=https%3A//msdn.microsoft.com/zh-cn/library/121hztk3%28v%3Dvs.94%29.aspx& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&条件编译 (JavaScript)&/a&
以前对浏览器兼容性问题只是大概知道一些点,没想到这次真正着手去做的时候,还是碰到了很多问题。刚开始的时候一边解决问题,一边想着:用 IE8 的都是神经病,到后来,我发现完了,I LOVE IE。0x00 起源在这次做小蜜 PC 版的时候,由于早于 PC 版,无线版…
&p&谢邀。对前端工程化有了新的认识,对部分内容做了修改。
&/p&&br&&p&目前来说,Web业务日益复杂化和多元化,前端开发已经由以WebPage模式为主转变为以WebApp模式为主了。现在随便找个前端项目,都已经不是过去的拼个页面+搞几个jQuery插件就能完成的了。工程复杂了就会产生许多问题,比如:如何进行高效的多人协作?如何保证项目的可维护性?如何提高项目的开发质量?...&/p&&br&&p&前端工程化是前端架构中重要的一环,主要就是为了解决上述大部分问题的。而前端工程本质上是软件工程的一种,因此我们应该从软件工程的角度来研究前端工程。&/p&&br&&p&那么前端工程化需要考虑哪些因素?&/p&&p&本人总结经验,认为前端工程化主要应该从模块化、组件化、规范化、自动化四个方面来思考,下面一一展开。&/p&&br&&p&## 模块化&/p&&br&&p&简单来说,模块化就是&b&将一个大文件拆分成相互依赖的小文件,再进行统一的拼装和加载&/b&。只有这样,才有多人协作的可能。&/p&&br&&p&### JS的模块化&/p&&br&&p&在ES6之前,JavaScript一直没有模块系统,这对开发大型复杂的前端工程造成了巨大的障碍。对此社区制定了一些模块加载方案,如CommonJS、AMD和CMD等,某些框架也会有自己模块系统,比如Angular1.x。&/p&&br&&p&现在ES6已经在语言层面上规定了模块系统,完全可以取代现有的CommonJS和AMD规范,而且使用起来相当简洁,并且有静态加载的特性。&/p&&br&&p&规范确定了,然后就是模块的打包和加载问题:&/p&&p&1. 用&a href=&//link.zhihu.com/?target=http%3A//webpack.github.io/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Webpack&/a&+&a href=&//link.zhihu.com/?target=https%3A//babeljs.io/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Babel&/a&将所有模块打包成一个文件同步加载,也可以打成多个chunk异步加载;&/p&&p&2. 用&a href=&//link.zhihu.com/?target=https%3A//github.com/systemjs/systemjs& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&SystemJS&/a&+&a href=&//link.zhihu.com/?target=https%3A//babeljs.io/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Babel&/a&主要是分模块异步加载;&/p&&p&3. 用浏览器的&script type=&module&&加载&/p&&p&目前Webpack远比SystemJS流行。Safari已经支持用type=&module&加载了。&/p&&br&&p&### CSS的模块化&/p&&br&&p&虽然SASS、LESS、Stylus等预处理器实现了CSS的文件拆分,但没有解决CSS模块化的一个重要问题:选择器的全局污染问题。&/p&&br&&p&按道理,一个模块化的文件应该要隐藏内部作用域,只暴露少量接口给使用者。而按照目前预处理器的方式,导入一个CSS模块后,已存在的样式有被覆盖的风险。虽然重写样式是CSS的一个优势,但这并不利于多人协作。&/p&&br&&p&为了避免全局选择器的冲突,各厂都制定了自己的CSS命名风格:&/p&&ul&&li&&a href=&//link.zhihu.com/?target=http%3A//getbem.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&BEM风格&/a&;
&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//getbootstrap.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Bootstrap风格&/a&;&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//semantic-ui.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Semantic UI风格&/a&;&/li&&li&我们公司的&a href=&//link.zhihu.com/?target=http%3A//nec.netease.com/standard& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&NEC风格&/a&;&/li&&li&...&/li&&/ul&&br&&p&但这毕竟是弱约束。选择器随着项目的增长变得越多越复杂,然后项目组里再来个新人带入自己的风格,就更加混乱了。&/p&&br&&p&所以我很赞同这句话:&/p&&blockquote&与其费尽心思地告诉别人要遵守某种规则,以规避某种痛苦,倒不如从工具层面就消灭这种痛苦。&/blockquote&&p&从工具层面,社区又创造出Shadow DOM、CSS in JS和CSS Modules三种解决方案。&/p&&ul&&li&Shadow DOM是WebComponents的标准。它能解决全局污染问题,但目前很多浏览器不兼容,对我们来说还很久远;&/li&&li&CSS in JS是彻底抛弃CSS,使用JS或JSON来写样式。这种方法很激进,不能利用现有的CSS技术,而且处理伪类等问题比较困难;&/li&&li&CSS Modules仍然使用CSS,只是让JS来管理依赖。它能够最大化地结合CSS生态和JS模块化能力,目前来看是最好的解决方案。Vue的scoped style也算是一种。
&/li&&/ul&&p&### 资源的模块化&/p&&br&&p&Webpack的强大之处不仅仅在于它统一了JS的各种模块系统,取代了Browserify、RequireJS、SeaJS的工作。更重要的是它的万能模块加载理念,即所有的资源都可以且也应该模块化。&/p&&br&&p&资源模块化后,有三个好处:&/p&&ol&&li&依赖关系单一化。所有CSS和图片等资源的依赖关系统一走JS路线,无需额外处理CSS预处理器的依赖关系,也不需处理代码迁移时的图片合并、字体图片等路径问题;&/li&&li&资源处理集成化。现在可以用loader对各种资源做各种事情,比如复杂的vue-loader等等。&/li&&li&项目结构清晰化。使用Webpack后,你的项目结构总可以表示成这样的函数:
dest = webpack(src, config)&/li&&/ol&&br&&br&&p&## 组件化&/p&&br&&p&首先,组件化≠模块化。好多人对这两个概念有些混淆。&/p&&br&&p&模块化只是在文件层面上,对代码或资源的拆分;而组件化是在设计层面上,对UI(用户界面)的拆分。&/p&&br&&p&从UI拆分下来的&b&每个包含模板(HTML)+样式(CSS)+逻辑(JS)功能完备的结构单元,我们称之为组件。&/b&&/p&&br&&p&其实,组件化更重要的是一种&b&分治思想&/b&。&/p&&blockquote&Keep Simple. Everything can be a component.
&/blockquote&&p&这句话就是说页面上所有的东西都是组件。页面是个大型组件,可以拆成若干个中型组件,然后中型组件还可以再拆,拆成若干个小型组件,小型组件也可以再拆,直到拆成DOM元素为止。DOM元素可以看成是浏览器自身的组件,作为组件的基本单元。&/p&&p&传统前端框架/类库的思想是先组织DOM,然后把某些可复用的逻辑封装成组件来操作DOM,是DOM优先;而组件化框架/类库的思想是先来构思组件,然后用DOM这种基本单元结合相应逻辑来实现组件,是组件优先。这是两者本质的区别。&/p&&br&&p&其次,组件化实际上是一种按照模板(HTML)+样式(CSS)+逻辑(JS)三位一体的形式&b&对面向对象的进一步抽象&/b&。&/p&&br&&p&所以我们除了封装组件本身,还要合理处理组件之间的关系,比如&b&(逻辑)继承&/b&、&b&(样式)扩展&/b&、&b&(模板)嵌套&/b&和&b&包含&/b&等,这些关系都可以归为&b&依赖&/b&。&/p&&br&&p&其实组件化不是什么新鲜的东西,以前的客户端框架,像WinForm、WPF、Android等,它们从诞生的那天起就是组件化的。而前端领域发展曲折,是从展示页面为主的WebPage模式走过来的,近两年才从客户端框架经验中引入了组件化思想。其实我们很多前端工程化的问题都可以从客户端那里寻求解决方案。&/p&&br&&p&目前市面上的组件化框架很多,主要的有&a href=&//link.zhihu.com/?target=https%3A//cn.vuejs.org/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Vue&/a&、&a href=&//link.zhihu.com/?target=https%3A//facebook.github.io/react/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&React&/a&、&a href=&//link.zhihu.com/?target=http%3A//angular2.axuer.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Angular 2&/a&、我们公司 &a class=&member_mention& href=&//www.zhihu.com/people/f7f6a8faef3b8bedb53e7& data-hash=&f7f6a8faef3b8bedb53e7& data-hovercard=&p$b$f7f6a8faef3b8bedb53e7&&@郑海波&/a& 的&a href=&//link.zhihu.com/?target=http%3A//regularjs.github.io/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Regular&/a&、&a href=&//link.zhihu.com/?target=http%3A//avalonjs.coding.me/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Avalon&/a&等。你感兴趣可以都研究一下,选择一套中意的。其实Vue文档中的&a href=&//link.zhihu.com/?target=https%3A//cn.vuejs.org/v2/guide/comparison.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&对比其他框架&/a&一文已经讲得很详细了。&/p&&br&&p&## 规范化&/p&&br&&p&模块化和组件化确定了开发模型,而这些东西的实现就需要规范去落实。&/p&&p&规范化其实是工程化中很重要的一个部分,项目初期规范制定的好坏会直接影响到后期的开发质量。&/p&&br&&p&我能想到的有以下一些内容:&/p&&ul&&li&目录结构的制定&/li&&li&编码规范&/li&&li&前后端接口规范&/li&&li&文档规范&/li&&li&组件管理&/li&&li&Git分支管理&/li&&li&Commit描述规范&/li&&li&定期CodeReview&/li&&li&视觉图标规范&/li&&li&...&/li&&/ul&&br&&p&其中编码规范最好采取&a href=&//link.zhihu.com/?target=http%3A//eslint.cn/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&ESLint&/a&和StyleLint等强制措施,配置git hooks可以实现Lint不过不能提交代码等机制,因为人是靠不住的。&/p&&p&前后端接口管理可以了解一下我们公司出的&a href=&//link.zhihu.com/?target=https%3A//nei.netease.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&NEI - 接口管理平台&/a&。&/p&&br&&p&## 自动化&/p&&br&&p&作了这么多年程序猿的我,一直秉持的一个理念是:&/p&&blockquote&任何简单机械的重复劳动都应该让机器去完成。
&/blockquote&&p&所以我也认为,前端工程化的很多脏活累活都应该交给自动化工具来完成。&/p&&br&&p&### 图标合并&/p&&br&&ul&&li&不要再用PS拼雪碧图了,统一走Webpack吧;
&/li&&li&不要再用Icomoon了,统一走Webpack吧。&/li&&/ul&&br&&p&### 持续

我要回帖

更多关于 html+css+js 的文章

 

随机推荐