等不上wwWbj3a啦为什么应该,是js跳转到上一页bj3acOm别的地方啦

404 Not Found
404 Not Found
The requested URL was not found on this server.
您要找的内容已被删除前端学习指南前端学习指南jirengu.com / xiedaimala.com关注专栏更多前端开发{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\u002Fpay.zhihu.com\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&title&:&直播-JS工程师必备之数据结构与算法(已结束附录屏回顾)&,&author&:&amo-47-40&,&content&:&\u003Cp\u003E什么是数据结构?\u003Cbr\u003E什么是算法?\u003Cbr\u003E为什么我要学?\u003Cbr\u003E为啥学得好的码农可以升职加薪?\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-33f802acbb8.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&200\& data-rawheight=\&200\&\u003E\u003Cp\u003E本期公开课将在以上问题的基础上,为入门阶段的JS工程师提供数据结构和算法的概论。\u003Cbr\u003E通过演示的方式为同学提供一个直观、深刻的印象,实乃转行、入门阶段必修课哦~\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E主讲者:\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E杨恪,\u003C\u002Fb\u003E毕业于南京大学,全栈(溢出)工程师,三次创业均任技术负责人,其间受邀到复旦大学做过移动开发后端服务设计的分享,现任某百万用户APP后端负责人。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E主题:《JS工程师必备之数据结构与算法》\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E课程纲要\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E1.何为数据结构和算法\u003Cbr\u003E2.常见数据结构及其特点\u003Cbr\u003E3.正确使用数据结构带来的提升\u003Cbr\u003E4.常见算法简介和演示\u003Cbr\u003E5.如何选取合理的算法和数据结构解决问题\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E本期公开课直播已结束,录屏回顾地址:\u003Ca href=\&https:\u002F\u002Fjirengu.com\u002Fapp\u002Falbum\u002F97\&\u003E免费公开课:JS工程师必备之数据结构与算法\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Cp\u003E-------------------------------\u003C\u002Fp\u003E\u003Cp\u003E加饥人谷官方微信号: hungervalley ,暗号:来自知乎\u003C\u002Fp\u003E\u003Cp\u003E每日一题,每周资源推荐,精彩博客推荐,工作、笔试、面试经验交流解答,免费直播课,群友轻分享... 数不尽的福利免费送\u003C\u002Fp\u003E&,&updated&:new Date(&T10:12:05.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:6,&likeCount&:14,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T18:12:05+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:6,&likesCount&:14},&&:{&title&:&我们的线下分享会马上要开始啦!&,&author&:&zhihusucks&,&content&:&\u003Cp\u003E饥人谷教前端三年了,相信没有哪个机构像我们一样,把毕业一两年的学生请回来做分享。\u003C\u002Fp\u003E\u003Cp\u003E这几年我们的学生来自世界各地,毕业生也走向了世界各地。\u003C\u002Fp\u003E\u003Cp\u003E他们有的已经成为合格前端,有的已经成长为小组 leader,还有的在追求更好的职业生涯。\u003C\u002Fp\u003E\u003Cp\u003E如果你想看看他们学编程从 0 到 1 的过程,欢迎了参加我们的线下分享会(门票免费),和我们交流。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E我这次也会到杭州,分享一些在知乎上不方便说的事情 ?\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E时间:日(本周六)9:30~18:00\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E地点:滨江区秋溢路223号江虹国际创意园3B幢203室\u003C\u002Fp\u003E\u003Cp\u003E门票:免费\u003C\u002Fp\u003E\u003Cp\u003E分享内容:前端知识交流、从业经验分享。\u003C\u002Fp\u003E\u003Cp\u003E分享嘉宾主要有我们的讲师(均有 BAT 经历,从业多年)、我们的优秀毕业生已经关注饥人谷很久了的伙伴们。\u003C\u002Fp\u003E\u003Cp\u003E详情见 \u003Ca href=\&http:\u002F\u002Fe-conf.sxl.cn\u002F\&\u003E活动官网\u003C\u002Fa\u003E 和 \u003Ca href=\&http:\u002F\u002Furl.cn\u002F56CsI2k\&\u003E微信页面\u003C\u002Fa\u003E。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Chr\u003E\u003Cp\u003EEConf 是由饥人谷主办的WEB前端开发者非盈利性技术交流会,正值饥人谷成立三周年之际,第一届EConf将于日在互联网和科技企业云集的杭州市滨江区举办。\u003C\u002Fp\u003E\u003Cp\u003E饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。\u003C\u002Fp\u003E\u003Cp\u003E如今,我们江湖再相见,诚邀各界有识之士,共话代码之美,技术人之魂!\u003C\u002Fp\u003E\u003Cp\u003E\u003C\u002Fp\u003E\u003Chr\u003E\u003Cp\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003C\u002Fp\u003E&,&updated&:new Date(&T10:24:12.000Z&),&canComment&:false,&commentPermission&:&review&,&commentCount&:32,&likeCount&:32,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T18:24:12+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic2.zhimg.com\u002Fv2-e0c0ac31e04e2b558349_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:32,&likesCount&:32},&&:{&title&:&带你理解 JS 容易出错的坑和细节&,&author&:&amo-47-40&,&content&:&\u003Cp\u003E本文原载于\u003Ca href=\&https:\u002F\u002Fjuejin.im\u002Fpost\u002F59fdaf\&\u003E掘金\u003C\u002Fa\u003E,作者\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fjuejin.im\u002Fuser\u002F574f8d8d2e958a005fd4edac\&\u003E夕阳\u003C\u002Fa\u003E(饥人谷学员),转载已获作者授权。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E本文重在列出并解释说明 JS 中各种容易出错的坑和细节,供大家更加深入理解为什么 JS 会这样\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E执行环境(Execution context)\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Ch2\u003Evar 和 let 的正确解释\u003C\u002Fh2\u003E\u003Cp\u003E当执行 JS 代码时,会生成执行环境,只要代码不是写在函数中的,就是在全局执行环境中,函数中的代码会产生函数执行环境,只此两种执行环境。\u003C\u002Fp\u003E\u003Cp\u003E接下来让我们看一个老生常谈的例子,\u003Ccode class=\&inline\&\u003Evar\u003C\u002Fcode\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eb() \u002F\u002F call b \nconsole.log(a) \u002F\u002F undefined \nvar a = 'Hello world' \nfunction b() {\n
console.log('call b')\n} \u003C\u002Fcode\u003E\u003Cp\u003E想必以上的输出大家肯定都已经明白了,这是因为函数和变量提升的原因。通常提升的解释是说将声明的代码移动到了顶部,这其实没有什么错误,便于大家理解。但是更准确的解释应该是:在生成执行环境时,会有两个阶段。第一个阶段是创建的阶段,JS 解释器会找出需要提升的变量和函数,并且给他们提前在内存中开辟好空间,函数的话会将整个函数存入内存中,变量只声明并且赋值为 undefined,所以在第二个阶段,也就是代码执行阶段,我们可以直接提前使用。\u003C\u002Fp\u003E\u003Cp\u003E在提升的过程中,相同的函数会覆盖上一个函数,并且函数优先于变量提升\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eb() \u002F\u002F call b second\n function b() {\n
console.log('call b fist')\n}\nfunction b() {\n
console.log('call b second')\n}\nvar b = 'Hello world'\u003C\u002Fcode\u003E\u003Cp\u003E\u003Ccode class=\&inline\&\u003Evar\u003C\u002Fcode\u003E 会产生很多错误,所以在 ES6中引入了 \u003Ccode class=\&inline\&\u003Elet\u003C\u002Fcode\u003E。\u003Ccode class=\&inline\&\u003Elet\u003C\u002Fcode\u003E 不能在声明前使用,但是这并不是常说的 \u003Ccode class=\&inline\&\u003Elet\u003C\u002Fcode\u003E 不会提升,\u003Ccode class=\&inline\&\u003Elet\u003C\u002Fcode\u003E 提升了,在第一阶段内存也已经为他开辟好了空间,但是因为这个声明的特性导致了并不能在声明前使用。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E作用域\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Ccode lang=\&text\&\u003Efunction b() {\n
console.log(value)\n}\n\nfunction a() {\n
var value = 2\n
b()\n}\n\nvar value = 1\na() \u003C\u002Fcode\u003E\u003Cp\u003E可以考虑下 b 函数中输出什么。你是否会认为 b 函数是在 a 函数中调用的,相应的 b 函数中没有声明 \u003Ccode class=\&inline\&\u003Evalue\u003C\u002Fcode\u003E 那么应该去 a 函数中寻找。其实答案应该是 1。\u003C\u002Fp\u003E\u003Cp\u003E当在产生执行环境的第一阶段时,会生成 \u003Ccode class=\&inline\&\u003E[[Scope]]\u003C\u002Fcode\u003E 属性,这个属性是一个指针,对应的有一个作用域链表,JS 会通过这个链表来寻找变量直到全局环境。这个指针指向的上一个节点就是该函数声明的位置,因为 b 是在全局环境中声明的,所以 \u003Ccode class=\&inline\&\u003Evalue\u003C\u002Fcode\u003E 的声明会在全局环境下寻找。如果 b 是在 a 中声明的,那么 log 出来的值就是 2 了。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E异步\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003EJS 是门同步的语言,你是否疑惑过那么为什么 JS 还有异步的写法。其实 JS 的异步和其他语言的异步是不相同的,本质上还是同步。因为浏览器会有一个 Event Queue 存放异步通知,JS 在执行代码时会产生一个执行栈,同步的代码在执行栈中,异步的在 Event Queue 中。只有当执行栈为空时,JS 才会去 Event Queue 中查看是否有需要处理的通知,有的话拿到执行栈中去执行。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Efunction sleep() {\n
var ms = 2000 + new Date().getTime()\n
while( new Date() & ms) {}\n
console.log('sleep finish')\n}\n\ndocument.addEventListener('click', function() {\n
console.log('click')\n})\n\nsleep()\nconsole.log('finish')\u003C\u002Fcode\u003E\u003Cp\u003E以上代码如果你在 \u003Ccode class=\&inline\&\u003Esleep\u003C\u002Fcode\u003E 被调用期间点击,只有当 \u003Ccode class=\&inline\&\u003Esleep\u003C\u002Fcode\u003E 执行结束并且 log finish 后才会响应点击事件。所以要注意 \u003Ccode class=\&inline\&\u003EsetTimeout\u003C\u002Fcode\u003E 并不是你设定多久 JS 就会准时的响应,并且 \u003Ccode class=\&inline\&\u003EsetTimeout\u003C\u002Fcode\u003E 也有个小细节,第二个参数设置为 0 也许会有人认为这样就不是异步了,其实还是异步。这是因为 HTML5 标准规定这个函数第二个参数不得小于 4 毫秒,不足会自动增加。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E类型\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E\u003Cb\u003E原始值\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003EJS 共有 6 个原始值,分别为 \u003Ccode class=\&inline\&\u003EBoolean, Null, Undefined, Number, String, Symbol\u003C\u002Fcode\u003E,这些类型都是值不可变的。\u003C\u002Fp\u003E\u003Cp\u003E有一个易错的点是:虽然 \u003Ccode class=\&inline\&\u003Etypeof null\u003C\u002Fcode\u003E 是 object 类型,但是 Null 不是对象,这是 JS 语言的一个很久远的 Bug 了。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E深浅拷贝\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E对于对象来说,直接将一个对象赋值给另外一个对象就是浅拷贝,两个对象指向同一个地址,其中任何一个对象改变,另一个对象也会被改变\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Evar a = [1, 2]\nvar b = a\nb.push(3)\nconsole.log(a, b) \u002F\u002F -& 都是 [1, 2, 3]\u003C\u002Fcode\u003E\u003Cp\u003E有些情况下我们可能不希望有这种问题,那么深拷贝可以解决这个问题。深拷贝不仅将原对象的各个属性逐个复制出去,而且将原对象各个属性所包含的对象也依次采用深复制的方法递归复制到新对象上。深拷贝有多种写法,有兴趣的可以看\u003Ca href=\&http:\u002F\u002Fjerryzou.com\u002Fposts\u002Fdive-into-deep-clone-in-javascript\u002F\&\u003E这篇文章\u003C\u002Fa\u003E\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E函数和对象\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E\u003Cb\u003Ethis\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003Ethis 是很多人会混淆的概念,但是其实他一点都不难,你只需要记住几个规则就可以了。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Efunction foo() {\n
console.log(this.a)\n}\nvar a = 2\nfoo() \n\nvar obj = {\n
foo: foo\n}\nobj.foo() \n\n\u002F\u002F 以上两者情况 this 只依赖于调用函数前的对象,优先级是第二个情况大于第一个情况 \u002F\u002F 以下情况是优先级最高的,this 只会绑定在 c 上 \nvar c = new foo()\nc.a = 3 \nconsole.log(c.a)\n\n\u002F\u002F 还有种就是利用 call,apply,bind 改变 this,这个优先级仅次于 new\u003C\u002Fcode\u003E\u003Cp\u003E以上几种情况明白了,很多代码中的 this 应该就没什么问题了,下面让我们看看箭头函数中的 this\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Efunction a() {\n
return () =& {\n
return () =& {\n
console.log(this)\n
}\n}\nconsole.log(a()()()) \u003C\u002Fcode\u003E\u003Cp\u003E箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a 符合前面代码中的第一个情况,所以 this 是 window。并且 this 一旦绑定了上下文,就不会被任何代码改变。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E下面我们再来看一个例子,很多人认为他是一个 JS 的问题\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Evar a = {\n
name: 'js',\n
log: function() {\n
console.log(this)\n
function setName() {\n
this.name = 'javaScript' \n
console.log(this)\n
setName()\n
}\n}\na.log()\u003C\u002Fcode\u003E\u003Cp\u003EsetName 中的 this 指向了 window,很多人认为他应该是指向 a 的。这里其实我们不需要去管函数是写在什么地方的,我们只需要考虑函数是怎么调用的,这里符合上述第一个情况,所以应该是指向 window。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E闭包和立即执行函数\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E闭包被很多人认为是一个很难理解的概念。其实闭包很简单,就是一个能够访问父函数局部变量的函数,父函数在执行完后,内部的变量还存在内存上让闭包使用。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Efunction a(name) {\n
\u002F\u002F 这就是闭包,因为他使用了父函数的参数 \n
return function() {\n
console.log(name)\n
}\n}\nvar b = a('js')\nb() \u002F\u002F -& js\u003C\u002Fcode\u003E\u003Cp\u003E现在来看一个面试题\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Efunction a() {\n
var array = []\n\n
for(var i = 0; i & 3; i++) {\n
array.push(\n
function() {\n
console.log(i)\n
return array\n}\n\nvar b = a()\nb[0]()\nb[1]()\nb[2]()\u003C\u002Fcode\u003E\u003Cp\u003E这个题目因为 \u003Ccode class=\&inline\&\u003Ei\u003C\u002Fcode\u003E 被提升了,所以 \u003Ccode class=\&inline\&\u003Ei = 3\u003C\u002Fcode\u003E,当 a 函数执行完成后,内存中保留了 a 函数中的变量 i。数组中 push 进去的只是声明,并没有执行函数。所以在执行函数时,输出了 3 个 3。\u003C\u002Fp\u003E\u003Cp\u003E如果我们想输出 0 ,1,2 的话,有两种简单的办法。第一个是在 for 循环中,使用 \u003Ccode class=\&inline\&\u003Elet\u003C\u002Fcode\u003E 声明一个变量,保存每次的 i 值,这样在 a 函数执行完成后,内存中就保存了 3 个不同 let 声明的变量,这样就解决了问题。\u003C\u002Fp\u003E\u003Cp\u003E还有个办法就是使用立即执行函数,创建函数即执行,这样就可以保存下当前的 i 的值。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Efunction a() {\n
var array = []\n\n
for(var i = 0; i & 3; i++) {\n
array.push(\n
(function(j) {\n
return function() {\n
console.log(j)\n
return array\n} \u003C\u002Fcode\u003E\u003Cp\u003E立即执行函数其实就是直接调用匿名函数\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Efunction() {} ()\u003C\u002Fcode\u003E\u003Cp\u003E但是以上写法会报错,因为解释器认为这是一个函数声明,不能直接调用,所以我们加上了一个括号来让解释器认为这是一个函数表达式,这样就可以直接调用了。\u003C\u002Fp\u003E\u003Cp\u003E所以我们其实只需要让解释器认为我们写了个函数表达式就行了,其实还有很多种立即执行函数写法\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Etrue && function() {} ()\nnew && function() {} ()\u003C\u002Fcode\u003E\u003Cp\u003E立即执行函数最大的作用就是模块化,其次就是解决上述闭包的问题了。\u003C\u002Fp\u003E\u003Ch2\u003E\u003Cb\u003E原型,原型链和 instanceof 原理\u003C\u002Fb\u003E\u003C\u002Fh2\u003E\u003Cp\u003E原型可能很多人觉得很复杂,本章节也不打算重复复述很多文章都讲过的概念,你只需要看懂我画的图并且自己实验下即可\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Efunction P() {\n
console.log('object')\n}\n\nvar p = new P()\n\u003C\u002Fcode\u003E\u003Cimg src=\&v2-d25dc7f4a697de6a2e94f11bd878feb8.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&960\& data-rawheight=\&1280\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E原型链就是按照 \u003Ccode class=\&inline\&\u003E__proto__\u003C\u002Fcode\u003E 寻找,直到 Object。\u003Ccode class=\&inline\&\u003Einstanceof\u003C\u002Fcode\u003E 原理也是根据原型链判断的\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Ep instanceof P \u002F\u002F true\np instanceof Object \u002F\u002F true \u003C\u002Fcode\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E————————————————end——————————————————\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E加饥人谷官方微信号: hungervalley ,暗号:来自知乎\u003C\u002Fp\u003E\u003Cp\u003E每日一题,每周资源推荐,精彩博客推荐,工作、笔试、面试经验交流解答,免费直播课,群友轻分享... 数不尽的福利免费送\u003C\u002Fp\u003E&,&updated&:new Date(&T03:12:55.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:4,&likeCount&:60,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T11:12:55+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:4,&likesCount&:60},&&:{&title&:&webpack:从入门到真实项目配置&,&author&:&amo-47-40&,&content&:&\u003Cp\u003E本文原载于\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fjuejin.im\u002Fpost\u002F59bb37fa6fb9a00a554f89d2%3Futm_source%3Dgold_browser_extension\&\u003E掘金\u003C\u002Fa\u003E,作者\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fjuejin.im\u002Fuser\u002F574f8d8d2e958a005fd4edac\&\u003E夕阳\u003C\u002Fa\u003E(饥人谷学员),转载已获作者授权。\u003C\u002Fp\u003E\u003Cblockquote\u003E该文使用的 Webpack 版本为 3.6.0,本文分两部分。第一步是简单的使用 webpack,第二部分通过一个真实项目来配置 webpack,没有使用任何的 CLI,都是一步步配置直到完成生产代码的打包。\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Flink.juejin.im\u002F%3Ftarget%3Dhttps%253A%252F%252Fgithub.com%252FKieSun%252Fwebpack-demo\&\u003E这是本项目对应的仓库\u003C\u002Fa\u003E,每个小节基本都对应了一次 commit。\u003C\u002Fblockquote\u003E\u003Cp\u003E这是本文的大纲,如果觉得有兴趣你就可以往下看了\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-4f76fc22a812ccb2dbf699efd78e4977.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&1280\& data-rawheight=\&419\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003EWebpack 到底是什么\u003C\u002Fh2\u003E\u003Cp\u003E自从出现模块化以后,大家可以将原本一坨代码分离到个个模块中,但是由此引发了一个问题。每个 JS 文件都需要从服务器去拿,由此会导致加载速度变慢。Webpack 最主要的目的就是为了解决这个问题,将所有小文件打包成一个或多个大文件,官网的图片很好的诠释了这个事情,除此之外,Webpack 也是一个能让你使用各种前端新技术的工具。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-adddc12a98e30fe17afc2.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&1016\& data-rawheight=\&417\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E简单使用\u003C\u002Fh2\u003E\u003Ch2\u003E安装\u003C\u002Fh2\u003E\u003Cp\u003E在命令行中依次输入\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Emkdir
webpack-demo\ncd webpack-demo\n\u002F\u002F 创建 package.json,这里会问一些问题,直接回车跳过就行\nnpm init \n\u002F\u002F
推荐这个安装方式,当然你也安装在全局环境下\n\u002F\u002F 这种安装方式会将 webpack 放入 devDependencies 依赖中\nnpm install --save-dev webpack\n\u003C\u002Fcode\u003E\u003Cp\u003E然后按照下图创建文件\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-2e67bcbf0ec59ac028e9297.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&264\& data-rawheight=\&215\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E在以下文件写入代码\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F\u002F sum.js\n\u002F\u002F 这个模块化写法是 node 环境独有的,浏览器原生不支持使用\nmodule.exports = function(a, b) {\n
return a + b\n}\n\u002F\u002F index.js\nvar sum = require('.\u002Fsum')\nconsole.log(sum(1, 2))\n&!DOCTYPE html&\n&html lang=\&en\&&\n&head&\n
&title&Document&\u002Ftitle&\n&\u002Fhead&\n&body&\n
&div id=\&app\&&&\u002Fdiv&\n
&script src=\&.\u002Fbuild\u002Fbundle.js\&&&\u002Fscript&\n&\u002Fbody&\n&\u002Fhtml&\n\u003C\u002Fcode\u003E\u003Cp\u003E现在我们开始配置最简单的 webpack,首先创建 webpack.config.js 文件,然后写入如下代码\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F\u002F 自带的库\nconst path = require('path')\nmodule.exports = {\n
'.\u002Fapp\u002Findex.js', \u002F\u002F 入口文件\n
output: {\n
path: path.resolve(__dirname, 'build'), \u002F\u002F 必须使用绝对地址,输出文件夹\n
filename: \&bundle.js\& \u002F\u002F 打包后输出文件的文件名\n
}\n\u003C\u002Fcode\u003E\u003Cp\u003E现在我们可以开始使用 webpack 了,在命令行中输入\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Enode_modules\u002F.bin\u002Fwebpack\n\u003C\u002Fcode\u003E\u003Cp\u003E没问题的话你应该可以看到类似的样子\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-791d512c61dda9c5813a6ce.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&444\& data-rawheight=\&149\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E可以发现原本两个 JS 文件只有 100B,但是打包后却增长到 2.66KB,这之中 webpack 肯定做了什么事情,我们去 bundle.js 文件中看看。\u003C\u002Fp\u003E\u003Cp\u003E把代码简化以后,核心思路是这样的\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Evar array = [(function () {\n
var sum = array[1]\n
console.log(sum(1, 2))\n
(function (a,b) {\n
return a + b\n
})\n]\narray[0]() \u002F\u002F -& 3\n\u003C\u002Fcode\u003E\u003Cp\u003E因为 module.export 浏览器是不支持的,所以 webpack 将代码改成浏览器能识别的样子。现在将 index.html 文件在浏览器中打开,应该也可以看到正确的 log。\u003C\u002Fp\u003E\u003Cp\u003E我们之前是在文件夹中安装的 webpack,每次要输入 node_modules\u002F.bin\u002Fwebpack 过于繁琐,可以在 package.json 如下修改\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\&scripts\&: {\n
\&start\&: \&webpack\&\n
},\n\u003C\u002Fcode\u003E\u003Cp\u003E然后再次执行 npm run start,可以发现和之前的效果是相同的。简单的使用到此为止,接下来我们来探索 webpack 更多的功能。\u003C\u002Fp\u003E\u003Ch2\u003ELoader\u003C\u002Fh2\u003E\u003Cp\u003ELoader 是 webpack 一个很强大功能,这个功能可以让你使用很多新的技术。\u003C\u002Fp\u003E\u003Ch2\u003EBabel\u003C\u002Fh2\u003E\u003Cp\u003EBabel 可以让你使用 ES\u002F17 写代码而不用顾忌浏览器的问题,Babel 可以帮你转换代码。首先安装必要的几个 Babel 库\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Enpm i --save-dev babel-loader babel-core babel-preset-env\n\u003C\u002Fcode\u003E\u003Cp\u003E先介绍下我们安装的三个库\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003Ebabel-loader 用于让 webpack 知道如何运行 babel\u003C\u002Fli\u003E\u003Cli\u003Ebabel-core 可以看做编译器,这个库知道如何解析代码\u003C\u002Fli\u003E\u003Cli\u003Ebabel-preset-env 这个库可以根据环境的不同转换代码\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E接下来更改 webpack-config.js 中的代码\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Emodule.exports = {\n\u002F\u002F ......\n
module: {\n
rules: [\n
\u002F\u002F js 文件才使用 babel\n
test: \u002F\\.js$\u002F,\n
\u002F\u002F 使用哪个 loader\n
use: 'babel-loader',\n
\u002F\u002F 不包括路径\n
exclude: \u002Fnode_modules\u002F\n
}\n}\n\u003C\u002Fcode\u003E\u003Cp\u003E配置 Babel 有很多方式,这里推荐使用 .babelrc 文件管理。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F\u002F ..babelrc\n{\n
\&presets\&: [\&babel-preset-env\&]\n}\n\u003C\u002Fcode\u003E\u003Cp\u003E现在将之前 JS 的代码改成 ES6 的写法\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F\u002F sum.js\nexport default (a, b) =& {\n
return a + b\n}\n\u002F\u002F index.js\nimport sum from '.\u002Fsum'\nconsole.log(sum(1, 2))\n\u003C\u002Fcode\u003E\u003Cp\u003E执行 npm run start,再观察 bundle.js 中的代码,可以发现代码被转换过了,并且同样可以正常 输出3。\u003C\u002Fp\u003E\u003Cp\u003E当然 Babel 远不止这些功能,有兴趣的可以前往官网自己探索。\u003C\u002Fp\u003E\u003Ch2\u003E处理图片\u003C\u002Fh2\u003E\u003Cp\u003E这一小节我们将使用 url-loader 和 file-loader,这两个库不仅可以处理图片,还有其他的功能,有兴趣的可以自行学习。\u003C\u002Fp\u003E\u003Cp\u003E先安装库\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Enpm i --save-dev url-loader file-loader\n\u003C\u002Fcode\u003E\u003Cp\u003E创建一个 images 文件夹,放入两张图片,并且在 app 文件夹下创建一个 js 文件处理图片\u003Cbr\u003E,目前的文件夹结构如图\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-99c7c1ee17eb4a1a478fd400bc116d65.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&232\& data-rawheight=\&372\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F\u002F addImage.js\nlet smallImg = document.createElement('img')\n\u002F\u002F 必须 require 进来\nsmallImg.src = require('..\u002Fimages\u002Fsmall.jpeg')\ndocument.body.appendChild(smallImg)\n\nlet bigImg = document.createElement('img')\nbigImg.src = require('..\u002Fimages\u002Fbig.jpeg')\ndocument.body.appendChild(bigImg)\n\u003C\u002Fcode\u003E\u003Cp\u003E接下来修改 webpack.config.js 代码\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Emodule.exports = {\n\u002F\u002F ...\n
module: {\n
rules: [\n
\u002F\u002F ...\n
\u002F\u002F 图片格式正则\n
test: \u002F\\.(png|jpe?g|gif|svg)(\\?.*)?$\u002F,\n
loader: 'url-loader',\n
\u002F\u002F 配置 url-loader 的可选项\n
options: {\n
\u002F\u002F 限制 图片大小 10000B,小于限制会将图片转换为 base64格式\n
limit: 10000,\n
\u002F\u002F 超出限制,创建的文件格式\n
\u002F\u002F build\u002Fimages\u002F[图片名].[hash].[图片格式]\n
name: 'images\u002F[name].[hash].[ext]'\n
}\n\u003C\u002Fcode\u003E\u003Cp\u003E运行 npm run start,打包成功如下图\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-ea8cd578ea71c8b29f28eac.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&657\& data-rawheight=\&279\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E可以发现大的图片被单独提取了出来,小的图片打包进了 bundle.js 中。\u003C\u002Fp\u003E\u003Cp\u003E在浏览器中打开 HTML 文件,发现小图确实显示出来了,但是却没有看到大图,打开开发者工具栏,可以发现我们大图的图片路径是有问题的,所以我们又要修改 webpack.config.js 代码了。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Emodule.exports = {\n
'.\u002Fapp\u002Findex.js', \u002F\u002F 入口文件\n
output: {\n
path: path.resolve(__dirname, 'build'), \u002F\u002F 必须使用绝对地址,输出文件夹\n
filename: \&bundle.js\&, \u002F\u002F 打包后输出文件的文件名\n
publicPath: 'build\u002F' \u002F\u002F 知道如何寻找资源\n
\u002F\u002F ...\n
}\n\u003C\u002Fcode\u003E\u003Cp\u003E最后运行下 npm run start,编译成功了,再次刷新下页面,可以发现这次大图被正确的显示了。下一小节我们将介绍如何处理 CSS 文件。\u003C\u002Fp\u003E\u003Ch2\u003E处理 CSS 文件\u003C\u002Fh2\u003E\u003Cp\u003E添加 styles 文件夹,新增 addImage.css 文件,然后在该文件中新增代码\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eimg {\n
border: 5\n}\n.test {border: 5}\n\u003C\u002Fcode\u003E\u003Cp\u003E这一小节我们先使用 css-loader 和 style-loader 库。前者可以让 CSS 文件也支持 impost,并且会解析 CSS 文件,后者可以将解析出来的 CSS 通过标签的形式插入到 HTML 中,所以后面依赖前者。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Enpm i --save-dev css-loader style-loader\n\u003C\u002Fcode\u003E\u003Cp\u003E首先修改 addImage.js 文件\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Eimport '..\u002Fstyles\u002FaddImage.css'\n\nlet smallImg = document.createElement('img')\nsmallImg.src = require('..\u002Fimages\u002Fsmall.jpeg')\ndocument.body.appendChild(smallImg)\n\n\u002F\u002F let bigImg = document.createElement('img')\n\u002F\u002F bigImg.src = require('..\u002Fimages\u002Fbig.jpeg')\n\u002F\u002F document.body.appendChild(bigImg)\n\u003C\u002Fcode\u003E\u003Cp\u003E然后修改 webpack.config.js 代码\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Emodule.exports = {\n\u002F\u002F ...\n
module: {\n
rules: [\n
test: \u002F\\.css$\u002F,\n
use: ['style-loader',\n
loader: 'css-loader',\n
options: {\n
modules: true\n
}\n\u003C\u002Fcode\u003E\u003Cp\u003E运行下 npm run start,然后刷新页面,可以发现图片被正确的加上了边框,现在我们来看一下 HTML 的文件结构\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-77d411a75c82bf6ed17c.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&328\& data-rawheight=\&231\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E从上图可以看到,我们在 addImage.css 文件中写的代码被加入到了 style 标签中,并且因为我们开启了 CSS 模块化的选项,所以 .test 被转成了唯一的哈希值,这样就解决了 CSS 的变量名重复问题。\u003C\u002Fp\u003E\u003Cp\u003E但是将 CSS 代码整合进 JS 文件也是有弊端的,大量的 CSS 代码会造成 JS 文件的大小变大,操作 DOM 也会造成性能上的问题,所以接下来我们将使用 extract-text-webpack-plugin插件将 CSS 文件打包为一个单独文件\u003C\u002Fp\u003E\u003Cp\u003E首先安装 npm i --save-dev extract-text-webpack-plugin\u003C\u002Fp\u003E\u003Cp\u003E然后修改 webpack.config.js 代码\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Econst ExtractTextPlugin = require(\&extract-text-webpack-plugin\&)\n\nmodule.exports = {\n\u002F\u002F ....\n
module: {\n
rules: [\n
test: \u002F\\.css$\u002F,\n
\u002F\u002F 写法和之前基本一致\n
loader: ExtractTextPlugin.extract({\n
\u002F\u002F 必须这样写,否则会报错\n
fallback: 'style-loader',\n
loader: 'css-loader',\n
options: { \n
modules: true\n
\u002F\u002F 插件列表\n
plugins: [\n
\u002F\u002F 输出的文件路径\n
new ExtractTextPlugin(\&css\u002F[name].[hash].css\&)\n
}\n\u003C\u002Fcode\u003E\u003Cp\u003E运行下 npm run start,可以发现 CSS 文件被单独打包出来了\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-67b767fdd2e91eee93456f5.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&614\& data-rawheight=\&230\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E但是这时候刷新页面会发现图片的边框消失了,那是因为我们的 HTML 文件没有引用新的 CSS 文件,所以这里需要我们手动引入下,在下面的章节我们会通过插件的方式自动引入新的文件。\u003C\u002Fp\u003E\u003Cp\u003E接下来,会用一个项目来继续我们的 webpack 学习,在这之前,先 clone 一下项目。该项目原地址是 \u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Flink.juejin.im\u002F%3Ftarget%3Dhttps%253A%252F%252Fgithub.com%252FStephenGrider%252FWebpackProject\&\u003E这里\u003C\u002Fa\u003E,因为使用的 webpack 版本太低,并且依赖的库也有点问题,故我将项目拷贝了过来并修改了几个库的版本号。\u003C\u002Fp\u003E\u003Cp\u003E请依次按照以下代码操作\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Egit clone https:\u002F\u002Fgithub.com\u002FKieSun\u002Fwebpack-demo.git\ncd webpack-demo\n\u002F\u002F 切换到 0.1 标签上并创建一个新分支\ngit checkout -b demo 0.1\n\u002F\u002F 查看分支是否为 demo,没问题的话就可以进行下一步\ngst\n\u003C\u002Fcode\u003E\u003Ch2\u003E如何在项目中使用 webpack\u003C\u002Fh2\u003E\u003Cp\u003E项目中已经配置了很简单的 babel 和 webpack,直接运行 npm run start 即可\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-f5ecaa91b5e.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&506\& data-rawheight=\&114\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E这时候你会发现这个 bundle.js 居然有这么大,这肯定是不能接受的,所以接下来章节的主要目的就是将单个文件拆分为多个文件,优化项目。\u003C\u002Fp\u003E\u003Ch2\u003E分离代码\u003C\u002Fh2\u003E\u003Cp\u003E先让我们考虑下缓存机制。对于代码中依赖的库很少会去主动升级版本,但是我们自己的代码却每时每刻都在变更,所以我们可以考虑将依赖的库和自己的代码分割开来,这样用户在下一次使用应用时就可以尽量避免重复下载没有变更的代码,那么既然要将依赖代码提取出来,我们需要变更下入口和出口的部分代码。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F\u002F 这是 packet.json 中 dependencies 下的\nconst VENOR = [\&faker\&,\n
\&lodash\&,\n
\&react\&,\n
\&react-dom\&,\n
\&react-input-range\&,\n
\&react-redux\&,\n
\&redux\&,\n
\&redux-form\&,\n
\&redux-thunk\&\n]\n\nmodule.exports = {\n\u002F\u002F 之前我们都是使用了单文件入口\n\u002F\u002F entry 同时也支持多文件入口,现在我们有两个入口\n\u002F\u002F 一个是我们自己的代码,一个是依赖库的代码\n
entry: {\n
\u002F\u002F bundle 和 vendor 都是自己随便取名的,会映射到 [name] 中\n
bundle: '.\u002Fsrc\u002Findex.js',\n
vendor: VENOR\n
output: {\n
path: path.join(__dirname, 'dist'),\n
filename: '[name].js'\n
\u002F\u002F ...\n }\n\u003C\u002Fcode\u003E\u003Cp\u003E现在我们 build 一下,看看是否有惊喜出现\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-d2f1d9c82b.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&498\& data-rawheight=\&158\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-6bdd7fb1aa889ce7f552b57.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&309\& data-rawheight=\&300\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E真的有惊喜。。为什么 bundle 文件大小压根没变。这是因为 bundle 中也引入了依赖库的代码,刚才的步骤并没有抽取 bundle 中引入的代码,接下来让我们学习如何将共同的代码抽取出来。\u003C\u002Fp\u003E\u003Ch2\u003E抽取共同代码\u003C\u002Fh2\u003E\u003Cp\u003E在这小节我们使用 webpack 自带的插件 CommonsChunkPlugin。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Emodule.exports = {\n\u002F\u002F...\n
output: {\n
path: path.join(__dirname, 'dist'),\n
\u002F\u002F 既然我们希望缓存生效,就应该每次在更改代码以后修改文件名\n
\u002F\u002F [chunkhash]会自动根据文件是否更改而更换哈希\n
filename: '[name].[chunkhash].js'\n
plugins: [\n
new webpack.optimize.CommonsChunkPlugin({\n
\u002F\u002F vendor 的意义和之前相同\n
\u002F\u002F manifest文件是将每次打包都会更改的东西单独提取出来,保证没有更改的代码无需重新打包,这样可以加快打包速度\n
names: ['vendor', 'manifest'],\n
\u002F\u002F 配合 manifest 文件使用\n
minChunks: Infinity\n
]\n};\n\u003C\u002Fcode\u003E\u003Cp\u003E当我们重新 build 以后,会发现 bundle 文件很明显的减小了体积\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-3cbf0a88a373ae71b8fd581.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&621\& data-rawheight=\&157\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E但是我们使用哈希来保证缓存的同时会发现每次 build 都会生成不一样的文件,这时候我们引入另一个插件来帮助我们删除不需要的文件。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Enpm install --save-dev clean-webpack-plugin\n\u003C\u002Fcode\u003E\u003Cp\u003E然后修改配置文件\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Emodule.exports = {\n\u002F\u002F...\n
plugins: [\n
\u002F\u002F 只删除 dist 文件夹下的 bundle 和 manifest 文件\n
new CleanWebpackPlugin(['dist\u002Fbundle.*.js','dist\u002Fmanifest.*.js'], {\n
\u002F\u002F 打印 log\n
verbose: true,\n
\u002F\u002F 删除文件\n
dry: false\n
]\n};\n\u003C\u002Fcode\u003E\u003Cp\u003E然后 build 的时候会发现以上文件被删除了。\u003C\u002Fp\u003E\u003Cp\u003E因为我们现在将文件已经打包成三个 JS 了,以后也许会更多,每次新增 JS 文件我们都需要手动在 HTML 中新增标签,现在我们可以通过一个插件来自动完成这个功能。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Enpm install html-webpack-plugin --save-dev\n\u003C\u002Fcode\u003E\u003Cp\u003E然后修改配置文件\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Emodule.exports = {\n\u002F\u002F...\n
plugins: [\n
\u002F\u002F 我们这里将之前的 HTML 文件当做模板\n
\u002F\u002F 注意在之前 HTML 文件中请务必删除之前引入的 JS 文件\n
new HtmlWebpackPlugin({\n
template: 'index.html'\n
]\n};\n\u003C\u002Fcode\u003E\u003Cp\u003E执行 build 操作会发现同时生成了 HTML 文件,并且已经自动引入了 JS 文件\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-3bd46d319d6cd19439dcfc31ae2d165b.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&546\& data-rawheight=\&159\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E按需加载代码\u003C\u002Fh2\u003E\u003Cp\u003E在这一小节我们将学习如何按需加载代码,在这之前的 vendor 入口我发现忘记加入 router 这个库了,大家可以加入这个库并且重新 build 下,会发现 bundle 只有不到 300KB 了。\u003C\u002Fp\u003E\u003Cp\u003E现在我们的 bundle 文件包含了我们全部的自己代码。但是当用户访问我们的首页时,其实我们根本无需让用户加载除了首页以外的代码,这个优化我们可以通过路由的异步加载来完成。\u003C\u002Fp\u003E\u003Cp\u003E现在修改 src\u002Frouter.js\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\u002F\u002F 注意在最新版的 V4路由版本中,更改了按需加载的方式,如果安装了 V4版,可以自行前往官网学习\nimport React from 'react';\nimport { Router, Route, IndexRoute, hashHistory } from 'react-router';\n\nimport Home from '.\u002Fcomponents\u002FHome';\nimport ArtistMain from '.\u002Fcomponents\u002Fartists\u002FArtistMain';\n\nconst rootRoute = {\n
component: Home,\n
path: '\u002F',\n
indexRoute: { component: ArtistMain },\n
childRoutes: [\n
path: 'artists\u002Fnew',\n
getComponent(location, cb) {\n
System.import('.\u002Fcomponents\u002Fartists\u002FArtistCreate')\n
.then(module =& cb(null, module.default))\n
path: 'artists\u002F:id\u002Fedit',\n
getComponent(location, cb) {\n
System.import('.\u002Fcomponents\u002Fartists\u002FArtistEdit')\n
.then(module =& cb(null, module.default))\n
path: 'artists\u002F:id',\n
getComponent(location, cb) {\n
System.import('.\u002Fcomponents\u002Fartists\u002FArtistDetail')\n
.then(module =& cb(null, module.default))\n
]\n}\n\nconst Routes = () =& {\n
return (\n
&Router history={hashHistory} routes={rootRoute} \u002F&\n
);\n};\n\nexport default R\n\u003C\u002Fcode\u003E\u003Cp\u003E然后执行 build 命令,可以发现我们的 bundle 文件又瘦身了,并且新增了几个文件\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-fa3abeac823.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&638\& data-rawheight=\&196\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E将 HTML 文件在浏览器中打开,当点击路由跳转时,可以在开发者工具中的 Network 一栏中看到加载了一个 JS 文件。\u003C\u002Fp\u003E\u003Cp\u003E首页\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-8e63dc8a303dc600e8f5.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&512\& data-rawheight=\&291\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E点击右上角 Random Artist 以后\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-b1e8ab9832f92aab35ca9dea119e4f17.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&504\& data-rawheight=\&298\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E自动刷新\u003C\u002Fh2\u003E\u003Cp\u003E每次更新代码都需要执行依次 build,并且还要等上一会很麻烦,这一小节介绍如何使用自动刷新的功能。\u003C\u002Fp\u003E\u003Cp\u003E首先安装插件\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Enpm i --save-dev webpack-dev-server\n\u003C\u002Fcode\u003E\u003Cp\u003E然后修改 packet.json 文件\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\&scripts\&: {\n
\&build\&: \&webpack\&,\n
\&dev\&: \&webpack-dev-server --open\&\n
},\n\u003C\u002Fcode\u003E\u003Cp\u003E现在直接执行 npm run dev 可以发现浏览器自动打开了一个空的页面,并且在命令行中也多了新的输出\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-97e556d2f5c668f9f1ff46ecabf09138.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&643\& data-rawheight=\&135\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E等待编译完成以后,修改 JS 或者 CSS 文件,可以发现 webpack 自动帮我们完成了编译,并且只更新了需要更新的代码\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-80c583ecf928d605cc2d6e0ea5e521b3.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&634\& data-rawheight=\&241\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E但是每次重新刷新页面对于 debug 来说很不友好,这时候就需要用到模块热替换了。但是因为项目中使用了 React,并且 Vue 或者其他框架都有自己的一套 hot-loader,所以这里就略过了,有兴趣的可以自己学习下。\u003C\u002Fp\u003E\u003Ch2\u003E生成生产环境代码\u003C\u002Fh2\u003E\u003Cp\u003E现在我们可以将之前所学和一些新加的插件整合在一起,build 生产环境代码。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Enpm i --save-dev url-loader optimize-css-assets-webpack-plugin file-loader extract-text-webpack-plugin\n\u003C\u002Fcode\u003E\u003Cp\u003E修改 webpack 配置\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Evar webpack = require('webpack');\nvar path = require('path');\nvar HtmlWebpackPlugin = require('html-webpack-plugin')\nvar CleanWebpackPlugin = require('clean-webpack-plugin')\nvar ExtractTextPlugin = require('extract-text-webpack-plugin')\nvar OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')\n\nconst VENOR = [\&faker\&,\n
\&lodash\&,\n
\&react\&,\n
\&react-dom\&,\n
\&react-input-range\&,\n
\&react-redux\&,\n
\&redux\&,\n
\&redux-form\&,\n
\&redux-thunk\&,\n
\&react-router\&\n]\n\nmodule.exports = {\n
entry: {\n
bundle: '.\u002Fsrc\u002Findex.js',\n
vendor: VENOR\n
\u002F\u002F 如果想修改 webpack-dev-server 配置,在这个对象里面修改\n
devServer: {\n
port: 8081\n
output: {\n
path: path.join(__dirname, 'dist'),\n
filename: '[name].[chunkhash].js'\n
module: {\n
rules: [{\n
test: \u002F\\.js$\u002F,\n
use: 'babel-loader'\n
test: \u002F\\.(png|jpe?g|gif|svg)(\\?.*)?$\u002F,\n
loader: 'url-loader',\n
options: {\n
limit: 10000,\n
name: 'images\u002F[name].[hash:7].[ext]'\n
test: \u002F\\.css$\u002F,\n
loader: ExtractTextPlugin.extract({\n
fallback: 'style-loader',\n
\u002F\u002F 这边其实还可以使用 postcss 先处理下 CSS 代码\n
loader: 'css-loader'\n
plugins: [\n
new webpack.optimize.CommonsChunkPlugin({\n
name: ['vendor', 'manifest'],\n
minChunks: Infinity\n
new CleanWebpackPlugin(['dist\u002F*.js'], {\n
verbose: true,\n
dry: false\n
new HtmlWebpackPlugin({\n
template: 'index.html'\n
\u002F\u002F 生成全局变量\n
new webpack.DefinePlugin({\n
\&process.env.NODE_ENV\&: JSON.stringify(\&process.env.NODE_ENV\&)\n
\u002F\u002F 分离 CSS 代码\n
new ExtractTextPlugin(\&css\u002F[name].[contenthash].css\&),\n
\u002F\u002F 压缩提取出的 CSS,并解决ExtractTextPlugin分离出的 JS 重复问题\n
new OptimizeCSSPlugin({\n
cssProcessorOptions: {\n
safe: true\n
\u002F\u002F 压缩 JS 代码\n
new webpack.optimize.UglifyJsPlugin({\n
compress: {\n
warnings: false\n
]\n};\n\u003C\u002Fcode\u003E\u003Cp\u003E修改 packet.json 文件\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E\&scripts\&: {\n
\&build\&: \&NODE_ENV=production webpack -p\&,\n
\&dev\&: \&webpack-dev-server --open\&\n
}\n\u003C\u002Fcode\u003E\u003Cp\u003E执行 npm run build\u003C\u002Fp\u003E\u003Cimg src=\&v2-b60b0d4caa6a2fc4ed28b8.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&797\& data-rawheight=\&206\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E可以看到我们在经历了这么多步以后,将 bundle 缩小到了只有 27.1KB,像 vendor 这种常用的库我们一般可以使用 CDN 的方式外链进来。\u003C\u002Fp\u003E\u003Ch2\u003E补充\u003C\u002Fh2\u003E\u003Cp\u003Ewebpack 配置上有些实用的小点在上文没有提到,统一在这里提一下。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003Emodule.exports = {\n
resolve: {\n
\u002F\u002F 文件扩展名,写明以后就不需要每个文件写后缀\n
extensions: ['.js', '.css', '.json'],\n \u002F\u002F 路径别名,比如这里可以使用 css 指向 static\u002Fcss 路径\n
alias: {\n
'@': resolve('src'),\n
'css': resolve('static\u002Fcss')\n
\u002F\u002F 生成 source-map,用于打断点,这里有好几个选项\n
devtool: '#cheap-module-eval-source-map',\n}\n\u003C\u002Fcode\u003E\u003Ch2\u003E后记\u003C\u002Fh2\u003E\u003Cp\u003E如果你是跟着本文一个个步骤敲下来的,那么大部分的 webpack 配置你应该都是可以看懂了,并且自己应该也知道如何去配置。谢谢大家看到这里,\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Flink.juejin.im\u002F%3Ftarget%3Dhttps%253A%252F%252Fgithub.com%252FKieSun%252Fwebpack-demo\&\u003E这是本项目对应的仓库\u003C\u002Fa\u003E,每个小节基本都对应了一次 commit。\u003C\u002Fp\u003E\u003Cp\u003E文章较长,有错误也难免,如果你发现了任何问题或者我有任何表述的不明白的地方,都可以留言给我。\u003C\u002Fp\u003E\u003Cp\u003E—————————end———————————\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E加饥人谷官方微信号: hungervalley ,暗号:来自知乎\u003C\u002Fp\u003E\u003Cp\u003E每日一题,每周资源推荐,精彩博客推荐,工作、笔试、面试经验交流解答,免费直播课,群友轻分享... 数不尽的福利免费送\u003C\u002Fp\u003E&,&updated&:new Date(&T03:26:27.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:12,&likeCount&:103,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T11:26:27+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:12,&likesCount&:103},&&:{&title&:&我们的线下分享会开始啦!现场直播中!&,&author&:&ji-ren-gu&,&content&:&\u003Cul\u003E\u003Cli\u003E\u003Ca href=\&http:\u002F\u002Fwww.itdks.com\u002Feventlist\u002Fdetail\u002F1572\&\u003E现场直播地址\u003C\u002Fa\u003E\u003C\u002Fli\u003E\u003Cli\u003E手机微信扫码观看\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cimg src=\&v2-bc53efcd21530cfdb948.jpg\& data-caption=\&\& data-rawwidth=\&540\& data-rawheight=\&970\&\u003E\u003Cp\u003E\u003Cb\u003E主题:\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E张磊磊: 《前端面试那些事儿》--饿了么、途牛、七牛面试经验分享\u003C\u002Fli\u003E\u003Cli\u003E洪凯敏:《前端异步发展》--谈一谈前端的异步处理发展历史\u003C\u002Fli\u003E\u003Cli\u003Eslashhuang:《响应式编程之rxjs》--复杂场景下的数据流及异步处理框架rxjs介绍\u003C\u002Fli\u003E\u003Cli\u003E叶世清:《前端入行两年思悟》--无网工作环境下的奋斗史:压力应对、项目维护、带新人等问题\u003C\u002Fli\u003E\u003Cli\u003E张新望: 《懂\&你\&的Rails框架》--半年Rails的开发体验\u003C\u002Fli\u003E\u003Cli\u003E杨恪:《微服务架构》--时下火热的微服务架构到底是怎么一回事,以及应用的场景\u003C\u002Fli\u003E\u003Cli\u003E邵志远:《我是如何从园林设计转行到前端的》--转行学习经验和学习方法分享\u003C\u002Fli\u003E\u003Cli\u003E方方:《这些年,中国前端社区在做什么(吐槽向)》-- 前端学什么,前端怎么晋升,以及前端怎么造轮子\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Chr\u003E\u003Cp\u003E饥人谷教前端三年了,相信没有哪个机构像我们一样,把毕业一两年的学生请回来做分享。\u003C\u002Fp\u003E\u003Cp\u003E这几年我们的学生来自世界各地,毕业生也走向了世界各地。\u003C\u002Fp\u003E\u003Cp\u003E他们有的已经成为合格前端,有的已经成长为小组 leader,还有的在追求更好的职业生涯。\u003C\u002Fp\u003E\u003Cp\u003E如果你想看看他们学编程从 0 到 1 的过程,欢迎了参加我们的线下分享会(门票免费),和我们交流。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E我这次也会到杭州,分享一些在知乎上不方便说的事情 ?\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E时间:日(本周六)9:30~18:00\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E地点:滨江区秋溢路223号江虹国际创意园3B幢203室\u003C\u002Fp\u003E\u003Cp\u003E门票:免费\u003C\u002Fp\u003E\u003Cp\u003E分享内容:前端知识交流、从业经验分享。\u003C\u002Fp\u003E\u003Cp\u003E分享嘉宾主要有我们的讲师(均有 BAT 经历,从业多年)、我们的优秀毕业生已经关注饥人谷很久了的伙伴们。\u003C\u002Fp\u003E\u003Cp\u003E详情见 \u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Fe-conf.sxl.cn\u002F\&\u003E活动官网\u003C\u002Fa\u003E 和 \u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=http%3A\u002F\u002Furl.cn\u002F56CsI2k\&\u003E微信页面\u003C\u002Fa\u003E。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Chr\u003E\u003Cp\u003EEConf 是由饥人谷主办的WEB前端开发者非盈利性技术交流会,正值饥人谷成立三周年之际,第一届EConf将于日在互联网和科技企业云集的杭州市滨江区举办。\u003C\u002Fp\u003E\u003Cp\u003E饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。\u003C\u002Fp\u003E\u003Cp\u003E如今,我们江湖再相见,诚邀各界有识之士,共话代码之美,技术人之魂!\u003C\u002Fp\u003E\u003Cp\u003E\u003C\u002Fp\u003E&,&updated&:new Date(&T03:39:47.000Z&),&canComment&:false,&commentPermission&:&review&,&commentCount&:3,&likeCount&:2,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&sourceUrl&:&&,&publishedTime&:&T11:39:47+08:00&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&url&:&\u002Fp\u002F&,&titleImage&:&https:\u002F\u002Fpic1.zhimg.com\u002Fv2-5c2738e3deecfe06a8d2450f_r.jpg&,&summary&:&&,&href&:&\u002Fapi\u002Fposts\u002F&,&meta&:{&previous&:null,&next&:null},&snapshotUrl&:&&,&commentsCount&:3,&likesCount&:2},&&:{&title&:&Web静态资源缓存及优化&,&author&:&reseted4&,&content&:&\u003Ch2\u003E前言\u003C\u002Fh2\u003E\u003Cp\u003E对于页面中静态资源(html\u002Fjs\u002Fcss\u002Fimg\u002Fwebfont),理想中的效果:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E页面以最快的速度获取到所有必须静态资源,渲染飞快;\u003C\u002Fli\u003E\u003Cli\u003E服务器上静态资源未更新时再次访问不请求服务器;\u003C\u002Fli\u003E\u003Cli\u003E服务器上静态资源更新时请求服务器最新资源,加载又飞快。\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E总结下来也就是2个指标:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E静态资源加载速度\u003C\u002Fli\u003E\u003Cli\u003E页面渲染速度\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cb\u003E静态资源加载速度\u003C\u002Fb\u003E引出了我们今天的主题,因为最直接的方式就是将静态资源进行缓存。\u003Cb\u003E页面渲染速度\u003C\u002Fb\u003E建立在资源加载速度之上,但不同资源类型的加载顺序和时机也会对其产生影响,所以也留给了我们更多的优化空间。\u003C\u002Fp\u003E\u003Cp\u003E当然除了速度,缓存还有另外2大功效,\u003Cb\u003E减少用户请求的带宽\u003C\u002Fb\u003E和\u003Cb\u003E减少服务器压力\u003C\u002Fb\u003E。\u003C\u002Fp\u003E\u003Cp\u003E先用一张图来概括下本文中将会涉及到的内容。\u003C\u002Fp\u003E\u003Cimg src=\&v2-d7ae9c9c2961fff7bdadbfc.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&1111\& data-rawheight=\&461\&\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Ch2\u003E常见缓存类型\u003C\u002Fh2\u003E\u003Cp\u003E\u003Cb\u003E1、浏览器缓存\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E对于前端而言,这可能是我们最容易忽略的缓存类型,原因在于大部分设置都在服务器运维层面上进行,不属于前端开发的维护范围。但静态资源的内容更新时机其实前端是最清楚的,如果能在理解浏览器缓存策略的基础上合理配置效果最佳。\u003C\u002Fp\u003E\u003Cp\u003E浏览器缓存策略一般通过资源的\u003Cb\u003EResponse Header\u003C\u002Fb\u003E来定义,html文件在\u003Ci\u003E很早之前的规范里\u003C\u002Fi\u003E也可以通过\u003Cb\u003EMeta标签的\u003C\u002Fb\u003Ehttp-equiv来定义。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E一个Response Header示例:\u003C\u002Fp\u003E\u003Cimg src=\&v2-79a7cbb2b75bdeddecef75a.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&1144\& data-rawheight=\&854\&\u003E\u003Cp\u003E可在w3c的\u003Ca href=\&https:\u002F\u002Fwww.w3.org\u002FProtocols\u002FrfcFrfc2616-sec14.html\&\u003E官方文档\u003C\u002Fa\u003E中查看所有HTTP Response Header字段的定义,跟缓存相关的主要有上图中被圈出来的几个\u003Cb\u003E:\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Ca href=\&https:\u002F\u002Fwww.w3.org\u002FProtocols\u002FrfcFrfc2616-sec14.html#sec14.9\&\u003ECache-Control\u003C\u002Fa\u003E:\u003C\u002Fli\u003E\u003Cul\u003E\u003Cli\u003Epublic:响应被缓存,并且在多用户间共享。\u003C\u002Fli\u003E\u003Cli\u003Eprivate:默认值,响应只能够作为私有的缓存(e.g., 在一个浏览器中),不能再用户间共享;\u003C\u002Fli\u003E\u003Cli\u003Eno-cache:响应不会被缓存,而是实时向服务器端请求资源。\u003C\u002Fli\u003E\u003Cli\u003Emax-age:数值,单位是秒,从请求时间开始到过期时间之间的秒数。基于请求时间(Date字段)的相对时间间隔,而不是绝对过期时间;\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Ci\u003E注:HTTP\u002F1.0 没有实现 Cache-Control,所以为了兼容HTTP\u002F1.0出现了Pragma字段。\u003C\u002Fi\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EPragma: 只有一个用法Pragma: no-cache,它和Cache-Control:no-cache作用一模一样。(Cache-Control: no-cache是http 1.1才提供的, 因此Pragma: no-cache可以使no-cache应用到http 1.0 和http 1.1。)\u003C\u002Fli\u003E\u003Cli\u003EExpires:指定了在浏览器上缓冲存储的页距过期还有多少时间,\u003Cb\u003E等同Cache-control中的max-age的效果,如果同时存在,则被Cache-Control的max-age覆盖\u003C\u002Fb\u003E。若把其值设置为0,则表示页面立即过期。并且若此属性在页面当中被设置了多次,则取其最小值。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Ci\u003E注:这个规则允许源服务器,对于一个给定响应,向 HTTP\u002F1.1(或之后)缓存比 HTTP\u002F1.0 提供一个更长的过期时间。\u003C\u002Fi\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EDate:生成消息的具体时间和日期;\u003C\u002Fli\u003E\u003Cli\u003ELast-Modified\u002FIf-Modified-Since:本地文件在服务器上的最后一次修改时间。缓存过期时把浏览器端缓存页面的最后修改时间发送到服务器去,服务器会把这个时间与服务器上实际文件的最后修改时间进行对比,如果时间一致,那么返回304,客户端就直接使用本地缓存文件。\u003C\u002Fli\u003E\u003Cli\u003EEtag\u002FIf-None-Match:(EntityTags)是URL的tag,用来标示URL对象是否改变,一般为资源实体的哈希值。和Last-Modified类似,如果服务器验证资源的ETag没有改变(该资源没有更新),将返回一个\u003Cb\u003E304\u003C\u002Fb\u003E状态告诉客户端使用本地缓存文件。Etag的\u003Cb\u003E优先级高于Last-Modified\u003C\u002Fb\u003E,Etag主要为了解决 \u003Cb\u003ELast-Modified\u003C\u002Fb\u003E 无法解决的一些问题。\u003C\u002Fli\u003E\u003Cul\u003E\u003Cli\u003E文件也许会周期性的更改,但是他的内容并不改变,不希望客户端重新get;\u003C\u002Fli\u003E\u003Cli\u003EIf-Modified-Since能检查到的粒度是s级;\u003C\u002Fli\u003E\u003Cli\u003E某些服务器不能精确的得到文件的最后修改时间。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E缓存策略执行过程\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cimg src=\&v2-dc56b24bd4a.jpg\& data-caption=\&\& data-size=\&normal\& data-rawwidth=\&1219\& data-rawheight=\&738\&\u003E\u003Cp\u003E本地缓存过期后,浏览器会像服务器发送请求,request中会携带以下两个字段:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003EIf-Modified-Since:值为之前response中Last-Modified;\u003C\u002Fli\u003E\u003Cli\u003EIf-None-Match:值为之前response中Etag(如果存在的话);\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E其中在图右侧的“file modified?”判断中,服务器会读取请求头这两个值,判断出客户端缓存的资源是否最新,如果是的话服务器就会返回HTTP\u002F304 Not Modified响应头,但没有响应体。客户端收到304响应后,就会从缓存中读取对应的资源;否则返回HTTP\u002F200和响应体。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E\u003Ca href=\&https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FHTML\u002FElement\u002Fmeta\&\u003EHtml Meta\u003C\u002Fa\u003E\u003C\u002Fb\u003E \u003C\u002Fp\u003E\u003Cp\u003Emeta是html语言head区的一个辅助性标签,其中的http-equiv字段定义了服务器和用户代理的一些行为。在之前的规范中\u003Cb\u003E,\u003C\u002Fb\u003Emeta的http-equiv字段中有以下值与http
header缓存相关的字段功能类似。\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003ECache-Control\u003C\u002Fli\u003E\u003Cli\u003EPragma\u003C\u002Fli\u003E\u003Cli\u003EExpires\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E使用方法:\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E&meta http-equiv=\&Cache-Control\& content=\&no-cache\& \u002F& &!-- HTTP 1.1 --&\n&meta http-equiv=\&Pragma\& content=\&no-cache\& \u002F& &!-- 兼容HTTP1.0 --&\n&meta http-equiv=\&Expires\& content=\&0\& \u002F& &!-- 资源到期时间设为0 --&\u003C\u002Fcode\u003E\u003Cp\u003E但现在w3c的规范字段中这些值已经被移除,一个很好的理由是:\u003C\u002Fp\u003E\u003Cblockquote\u003EPutting caching instructions into meta tags is not a good idea, because although browsers may read them, proxies won't. For that reason, they are invalid and you should send caching instructions as real HTTP headers.\u003C\u002Fblockquote\u003E\u003Cp\u003E其实也很好理解,写在meta标签中代表必须解析读取html的内容,但代理服务器是不会去读取的。大多浏览器已经不再支持,会忽略这样的写法,所以缓存还是通过HTTP headers去设置。\u003C\u002Fp\u003E\u003Cp\u003E\u003Ci\u003E注:HTTP Headers中的缓存设置优先级比meta中http-equiv更高一些。\u003C\u002Fi\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E2、HTML5 Application Cache\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003EApplication Cache是html5引入的本地存储方案之一,可以构建离线缓存。目前除IE10-外其他浏览器均支持。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E使用方法\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003Ea、增加manifest文件\u003C\u002Fp\u003E\u003Cp\u003Eapplication cache是通过mannifest文件来管理的,manifest文件是简单的文本文件,内容是需要被缓存供离线使用的文件列表,及不需要被缓存或读取缓存失败的文件控制。\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E文件的第一行必须是 CACHE MANIFEST\u003C\u002Fli\u003E\u003Cli\u003E#开头的行作为注释语句\u003C\u002Fli\u003E\u003Cli\u003E网站的缓存不能超过5M\u003C\u002Fli\u003E\u003Cli\u003E文件资源路径可以使用绝对路径也可以使用相对路径\u003C\u002Fli\u003E\u003Cli\u003E文件列表中任意一个缓存失败都会导致整个缓存失效\u003C\u002Fli\u003E\u003Cli\u003E既可以站点使用同一个minifest文件,也可以每个页面使用一个\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E文件包含3个指令\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003ECACHE:需要缓存的资源文件,浏览器会自动缓存带有manifest属性的html页面;\u003C\u002Fli\u003E\u003Cli\u003ENETWORK:不需要缓存的文件,可以使用通配符;\u003C\u002Fli\u003E\u003Cli\u003EFALLBACK:无法访问缓存文件的备选文件,可以使用通配符。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003Eb、服务器配置\u003C\u002Fp\u003E\u003Cp\u003Emannifest文件可以使用任意拓展名,但需要在服务器中添加MIME类型匹配,使用apache比较简单,如果使用.manifest作为拓展名在apache配置文件中添加。\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003EAddType text\u002Fcache-manifest .appcache\u003C\u002Fcode\u003E\u003Cp\u003Ec、html中引用\u003C\u002Fp\u003E\u003Ccode lang=\&text\&\u003E&html lang=\&zh\& manifest=\&main.manifest\&&\u003C\u002Fcode\u003E\u003Cp\u003E\u003Ci\u003E注:千万不要把manifest文件本身放在缓存文件列表中,不然浏览器无法更新manifest文件文件,最好在manifest文件的http headers中设置其立即过期。\u003C\u002Fi\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E缓存加载及更新过程\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E1、事件\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003Ecached\u002Fchecking\u002Fdownloading\u002Ferror\u002Fnoupdate\u002Fobsolete\u002Fprogress\u002Fupdateready\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E2、执行过程\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E第一次加载\u003C\u002Fb\u003E:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cb\u003ECreating Application Cache with manifest\u003C\u002Fb\u003E(访问到带manifest属性的html文件,将manifest文件存储,加载html文件及其他资源文件);\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache Checking event\u003C\u002Fb\u003E(检查要缓存的文件列表)\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache Downloading event\u003C\u002Fb\u003E(开始下载缓存文件)\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache Progress event (0 of 4)\u003C\u002Fb\u003E(依次下载缓存文件)\u003C\u002Fli\u003E\u003Cli\u003E……\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache Progress event (4 of 4)\u003C\u002Fb\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache Cached event\u003C\u002Fb\u003E(文件缓存完毕)\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cb\u003E第二次加载\u003C\u002Fb\u003E:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cb\u003EDocument was loaded from Application Cache with manifest\u003C\u002Fb\u003E(从缓存中读取html文件和其他静态资源文件,供页面展示)\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache Checking event\u003C\u002Fb\u003E(获取新的manifest文件,检查是否更新)\u003C\u002Fli\u003E\u003Cul\u003E\u003Cli\u003E是:重新下载缓存文件,供下次访问使用(不会影响当前浏览器展示内容)\u003C\u002Fli\u003E\u003Cul\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache Downloading event\u003C\u002Fb\u003E(开始下载缓存文件)\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache Progress event (0 of 4)\u003C\u002Fb\u003E(依次下载缓存文件)\u003C\u002Fli\u003E\u003Cli\u003E……\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache Progress event (4 of 4)\u003C\u002Fb\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache UpdateReady event\u003C\u002Fb\u003E(缓存文件更新完毕)\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003C\u002Ful\u003E\u003Cli\u003E否\u003C\u002Fli\u003E\u003Cul\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache NoUpdate event\u003C\u002Fb\u003E(啥也不做)\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cb\u003E删除html中manifest文件引用\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cb\u003EDocument was loaded from Application Cache with manifest\u003C\u002Fb\u003E(从缓存中读取html文件和其他静态资源文件,供页面展示)\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache Checking event\u003C\u002Fb\u003E(获取新的manifest文件,检查是否更新)\u003C\u002Fli\u003E\u003Cli\u003E\u003Cb\u003EApplication Cache Obsolete event\u003C\u002Fb\u003E(删除本地缓存中的所有文件,不再使用缓存)\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E一些问题\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003EApplication Cache会默认缓存引用manifest文件的HTML文档,对于动态更新的html页面来说是个坑(可以使用tricky的iframe嵌入方式来避免);\u003C\u002Fli\u003E\u003Cli\u003E只要缓存列表中的一个资源加载失败,所有文件都将缓存失败;\u003C\u002Fli\u003E\u003Cli\u003E如果资源没有被缓存,而又没有设置NETWORK的情况下,将会无法加载,所以Network中必须使用通配符配置;\u003C\u002Fli\u003E\u003Cli\u003E缓存更新后第一次只能加载manifest文件,其他静态资源需要第二次加载才能看到最新效果;\u003C\u002Fli\u003E\u003Cli\u003E缓存文件清单中的文件本身更新浏览器是不会重新缓存,那怎么告诉浏览器缓存需要更新了呢?\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cul\u003E\u003Cli\u003E更新manifest文件:修改注释的版本号或者日期。\u003C\u002Fli\u003E\u003Cli\u003E通过Application Cache提供的接口(window.applicationCache.swapCache)来检查更新。\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E还有最后一个问题,该标准已经从 Web 标准中删除……\u003C\u002Fp\u003E\u003Cblockquote\u003E该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。在此刻使用这里描述的应用程序缓存功能高度不鼓励; 它正在处于从Web平台中被删除的过程。请改用\u003Ca href=\&https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FService_Worker_API\u002FUsing_Service_Workers\&\u003EService Workers\u003C\u002Fa\u003E 代替。\u003C\u002Fblockquote\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E3、PWA(Service Worker)\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003EPWA全称为“Progressive Web Apps”,渐进式网页应用,Service Worker是其几大核心技术之一。\u003C\u002Fp\u003E\u003Cblockquote\u003EService worker is a programmable network proxy, allowing you to control how network requests from your page are handled.\u003C\u002Fblockquote\u003E\u003Cp\u003E没错,这就是官方建议替代Application Cache的方案。早在2014年,W3C就公布了Service Worker的草案。它作为一个独立的线程,是一段在后台运行的脚本。它的出现使得web app也可以具有类似native app的离线使用、消息推送、后台自动更新等能力。\u003C\u002Fp\u003E\u003Cp\u003E不过它有以下限制:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E不能访问 DOM\u003C\u002Fli\u003E\u003Cli\u003E不能使用同步 API\u003C\u002Fli\u003E\u003Cli\u003E需要HTTPS协议(http:\u002F\u002Flocalhost 或 http:\u002F\u002F127.0.0.1也可)\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Cp\u003E虽然现在其\u003Ca href=\&http:\u002F\u002Fcaniuse.com\u002F#search=service%20worker\&\u003E浏览器支持情况\u003C\u002Fa\u003E并不是很广泛,但以后应该会大面积支持。本文做简单介绍,具体使用方法可以参考官方文档《\u003Ca href=\&http:\u002F\u002Flink.zhihu.com\u002F?target=https%3A\u002F\u002Fdevelopers.google.com\u002Fweb\u002Ffundamentals\u002Finstant-and-offline\u002Foffline-cookbook\u002F\&\u003EThe Offline Cookbook\u003C\u002Fa\u003E》。\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003E简单使用\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cp\u003E1、首先}

我要回帖

更多关于 自动跳转上一页代码 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信