求<你是我的遥不可及无损百度云>百度云

&&&&&&&&&&&&
121613人浏览
45398人浏览
155160人浏览
186422人浏览
403393人浏览
127942人浏览
······
汤川的日记
前不久,《吐槽大会》第二季落幕,49 岁的陶晶莹作为副咖,在椅子上笑吟吟地坐着,...
&&&&&&&&&&&&
&&&&&&&&&&&&
&&&&&&&&&&&&
&&&&&&&&&&&&
134992 个成员
123473 个成员
305247 个成员
16717 个成员
310378 个成员
265805 个成员
64536 个成员
33837 个成员
3802 个成员
121340 个成员
95744 个成员
······
〔日〕柚木麻子...
〔日〕谷崎润一...
〔英〕毛姆
〔日〕村上春树...
原创数字作品
······
流派: 摇滚 Rock
流派: 原声 Soundtrack
27795人关注
流派: 电子 Electronica
流派: 摇滚 Rock
5436人关注
流派: 说唱 Rap
豆瓣新碟榜
&&&&&&&&&&&&
&&&&&&&&&&&&
【每周精推】是你的PostRock掉了
我感知这个世界后心中的那些歌~
不要丢弃白日梦
&&&&&&&&&&&&
&&&&&&&&&&&&
无锡 · 本周热门活动
&&&&&&&&&&&&
无锡大剧院 小剧场 江苏省...
无锡大剧院 小剧场 江苏省...
无锡大剧院-歌剧厅 江苏省...
无锡大剧院-歌剧厅 江苏省...
douban.com, all rights reserved 北京豆网科技有限公司
京ICP备号 网络视听许可证号
京网文[8号
&&新出网证(京)字129号
违法和不良信息举报电话:&
电话:12377完整版「你是我的遥不可及」全文阅读完整版「你是我的遥不可及」全文阅读向往生活的逆风百家号小说《你是我的遥不可及》已上线。在【醉书吧】这个微丨信公众号回复”428“即可阅读全文。以下是精彩章节内容推荐。《你是我的遥不可及》未完待续~完整版已经在微丨信公众号(醉书吧)更新完结。关注后回复本书书号: 428 即可阅读全文。你是我的遥不可及岱月容不欲待太久,立刻从后花园疾走而奔,一个飞身,翻进了窗户。岱月容翻身进了屋,从桌上倒了杯茶,嘬了一口,是江南塘洲一事,巫蛊族从中作乱,远在江南的同伴,鸟语传话,听说是有了魈逐玉的下落,今日在皇宫,太子询问西娅是否属于江南塘洲之人,她猜想,太子是知道江南塘洲的事情,只是不方便让西娅知道,毕竟,她还没有正名,如果是西娅听说此事,必然不会管什么正不正名,肯定会回江南,既然如此,那么她就配合一下太子也无妨,既然大家的目标都是为了魈逐玉,那么,不如‘螳螂捕蝉,黄雀在后’!那么她这只黄雀就等着坐收渔翁之利了,况且,这本就是她逐月教的东西,岂容落在外人手里。阴历七月初五,宜祈福,&祭&祀,婚嫁,求子,立约是正名的好日子,西娅一大早的被许藤琉带着众丫鬟从床上拖起来,头脑昏昏沉沉的她,半梦半醒的被一群丫鬟摆弄,穿上一声大红色的外袍,金丝线镶边,里衣腰带是上吊着无数串长长的金叶金花吊坠,头戴金花大发冠,长发及腰,脸上化了精致的妆容,才遮住脸上的疲倦。老夫人被方婷若和汤茹左右扶持着走进铭霄苑,见西娅依然紧闭着双眼,忍不住笑道,“这丫头,这样都还能睡得着,今天可是她的大日子啊。”方婷若和汤茹各自抿嘴笑,汤茹道,“西娅这是还没睡醒呢,不过倒也确实能睡,一大早的被人拖起来梳妆打扮,这么大一番动静,她竟然竟然连点反应都不给,若是平常人,怎么都被弄醒了。”老夫人看了看西娅,问许藤琉,“差不多了吧,什么时候能好?”“回老夫人,就快好了,最后再为西娅小姐插上发簪就好了。”汤茹笑了笑,像是看一件宝贝般,“老夫人,您还别说,这西娅一打扮起来,可真是比平时漂亮多了。”“你的意思是西娅平时不漂亮?”老夫人嗔怪她一眼。汤茹唯恐得罪老夫人,却依然心平气和,没有半点慌乱,笑道,“老夫人,您误会了,西娅平时里当然漂亮,要说皇宫里的那些美人,恐怕也没有几个比得上西娅那么有灵气,那么可爱,逗人喜欢的。”老夫人这才满意的点头。“回老夫人,收拾好了。”许藤琉半躬身行李道。“好,把西娅叫醒。”“是。”转身轻轻的将西娅摇醒,“西娅小姐,该醒了,西娅小姐……”西娅这才模模糊糊的睁开眼睛,伸出手去抚弄眼睛,突然觉得脑袋一沉,差点摘倒在地,这一下,睡意彻底没了,站起身,差点走不动路,眼前站着的正好是老夫人和父亲的两位妃子,“奶奶,大姨娘,二姨娘,早安。”根据这几天珑绾耐心教她的礼法,虽然没有学个透彻,但还是记住了个大概。“西娅啊,现在醒了没?”老夫人调笑道。“醒了醒了,只是…”摸了摸脖子,“奶奶,这头冠好重啊。”汤茹笑了道,“那是自然,纯金打造的嘛,不重才怪,瞧你,多讨老夫人的喜啊,老夫人为了你,可是下了血本!”老夫人笑了笑,“为了西娅,我愿意。”方婷若走至西娅身边,拉住她手臂,“西娅,这身衣服确实很重,现在暂时就先委屈你一下了,等过了那些繁复的礼节后,你想取再取吧,我会叫珑绾还有你的月容姐姐陪着你的。”西娅一听,心下放松些了,“谢谢大姨娘。”好在不是戴一整天,但是心中一想到那个繁复的礼节,头上和身上的某些金灿灿的东西,仿佛又重了千金,腿一软,差点摔倒,好在方婷若眼疾手快的将她扶住,“小心点。”“好了,西娅,时候到了,该出去了,别让客人久等。”老夫人说道。“哦!”抬腿间,脑袋一晃,差点再次摘倒,许藤琉赶紧从身后冲上来扶住她的手臂,扶着她走出门外,岱月容正手拿把剑,双臂交叉环至胸前,依然是一身蓝色的薄裙装扮,见西娅出来,很自然而然从西娅的另一边扶住她,走了几步,珑绾适时的出现,方婷若赶紧说道,“珑绾,你去哪儿呢?这么久才来,今天你的任务就是一直陪着西娅,照顾好她。”“知道了,大姨娘,您吩咐的任务,我确定一定以及肯定,保证完成任务!”珑绾的信誓旦旦引得方婷若发笑,嗔道,“你这孩子!”珑绾笑笑,冲到西娅面前,许藤琉朝珑绾行了个礼,珑绾摇手道,“你先下去吧,你来扶。”珑绾和岱月容左右相扶着西娅,珑绾忍不住调笑西娅,“西娅,你穿这么多,热不热?”西娅瞪了她一眼,“你觉得呢,你要不要试试!这些首饰重的我都抬不起腿了,你还一边说风凉话。”“哈哈,没事,忍着点吧,现在你就受不了了,一会儿可有得你受。”“啊,我都不想去了,月容姐姐,我不想去了,你不是武功高强吗?干脆你带我逃走吧!”扭头一脸希冀的看着岱月容。岱月容是何许人也?岂会被她的表情所迷惑,知道她所受的苦,想要开口安慰她一下,珑绾适时的开口,“别理她,月容姐姐,让这家伙遭点罪,谁叫她之前不好好学礼法的,要是好好学的话,恐怕现在这痛苦怎么都要减轻十倍。”岱月容笑笑,见西娅叫苦不迭,安慰道,“西娅,忍耐点吧!”“除了忍耐就没有别的办法了吗?”“没有!”珑绾睁大无辜的双眼,得意忘形的说着,西娅狠狠地瞪她一眼,“没良心的家伙。”一路上,西娅和珑绾吵吵闹闹,终于到了礼堂外,和锦王府一个偌大的场地,全部站满了人,人潮拥挤,众人见门口走进一个头戴金发冠,身穿一身大红色镂空纱裙宽袖袍服,里衣的腰带上,满是一片金灿灿,俏生生的脸蛋,尤其是那一双水汪汪的大眼睛,颇有灵气,像极了集天地之精华,吸日月之光辉的灵石一般;身边的两个女子,其中一位众人都认得,正是锦王爷的三女儿游竺珑绾,一张不输给任何人的秀丽的脸庞,虽穿着的没有西娅那般雍容华贵,一身橙黄的衣裙穿在身上颇有一番贵气;另外一名身穿蓝色连裙,腰上紧束一根白色绸丝带,勒出一根细细的腰肢,不盈一握,秀丽清冷的脸,十足的冰山美人。三人的目标便是前面的礼堂,众人纷纷让出道来,西娅低声朝身边两人道,“哇,好多人啊,看见没有,好热闹。”见两人似乎不再理会她,岱月容本就清冷,喜平静她知道,珑绾向来同她一样也不是个安分的人,但此刻确实一脸的正经和严肃,见没人理会她,识相的闭上了嘴,直到跨入了礼堂的大门。老夫人和方婷若、汤茹一同随后进门,方婷若和汤茹将老夫人扶上礼堂最上方的座位,皇上和游竺乙耽分别坐在老夫人的左右两侧,各自下方又是皇后,德妃,太子和方婷若,汤茹和帛逸,其他的位置,自然是留给宫中的正一品的重要官员,其中包括过家。过齐鸣,过娴的父亲,荆国宰相。一脸正气盎然的坐在太子子砚的位置之下,仿佛高傲的不可一世。西娅眼神毫无顾忌的四处飘荡,对朝她笑的大哥帛逸和太子子砚咧嘴笑,突然想起珑绾教她的礼法,笑不露齿,忙将嘴巴闭上,依旧望着他们笑,然后一阵爽朗的声音从礼堂外传来,“今天是我西娅妹妹正名啊,我这个做哥哥的,没来晚吧!”西娅一听这熟悉的声音,顿时兴奋的回头看,“是佟哥哥!”可是抬头在人群中张望了好几眼,也没看见踪影,众人也好奇的朝外张望,佟铭宣从人群中走出来,手中摇了一把紫色的纸扇,身着一件淡蓝色和白色腰带的薄衣袍,如此看上去,倒是和岱月容遥相呼应,西娅一见是佟铭宣,顿时忘了身上所持的重量,向佟铭宣狂奔而去,毫不忌讳的抱住他的腰身,“佟哥哥,我想死你了,你终于来了。”佟铭宣将扇子一收,捧着西娅的脸,“来,让我看看我的西娅妹妹有没有长胖,哟,确实长胖了点嘛,这脸上的肉确实比在佟府时多了一圈了。”“讨厌,佟哥哥,你怎么能说我长胖了呢。”“呐,还变的更漂亮了。”佟铭宣咂砸嘴,继而松开西娅的怀抱,向堂上的老夫人、皇上和游竺乙耽走上去,向众人一鞠礼,“臣佟铭宣拜见皇上,太后娘娘,锦王爷。”老夫人笑了道,“佟铭宣,你怎么这时才来,今天是我孙儿的大喜之日,你也忒不给面子了吧!”“回太后娘娘,臣也不想啊,西娅是臣最最疼爱的妹妹,怎么可能会不给面子呢,只是路上遇到点意外之事,所以来晚了,臣在这里向太后道个不是。”你是我的遥不可及本文仅代表作者观点,不代表百度立场。系作者授权百家号发表,未经许可不得转载。向往生活的逆风百家号最近更新:简介:最深和最重的爱,必须和时日一起成长。作者最新文章相关文章刚在&a href=&https://www.zhihu.com/question/& class=&internal&&关于 Android 平台开发相关的有哪些推荐书籍? - 书籍推荐&/a&回答了一下,还可再答一遍吗?&br&个人推荐如下:&br&&ul&&li&《深入理解计算机系统》——程序员是要跟计算机打交道的,那么最好首先明白它的工作原理,才能让你的代码游刃有余。&/li&&li&《图解HTTP》——如今单机版的App还有吗?绝大多数App的网络通信是基于HTTP协议的,所以你需要深入了解它。&/li&&li&《Effective Java 中文版(第2版)》——会Java只能让你写出普通的Android App,而这本书将告诉你如何用Java写出高效的App。如果你说你用H5、JS、Kotlin或者以后用Swift开发Android,那就当我没推荐。&/li&&li&《Efficient Android Threading》——Android中极为重要的概念之一,想一想每个界面的主线程、工作线程及线程通信吧。&/li&&li&《Pro Android Apps Performance Optimization》——这本书教你从哪些方面并且怎样进行Android性能优化。&/li&&li&《Android开发艺术探索》——为数不多的国内作者写的不错的书,对Android知识点结合源码进行较为深入的分析。&/li&&li&《App研发录》——也是国内作者写的,我也看到某人在博文里批此书内容空洞,但我认为是一本好书,喜欢作者提纲挈领,概括总结的风格,而不是拘泥于细节和代码。&/li&&li&《构建安全的Android App》——你的App被反编译过吗或者你反编译过别人的App吗?在这个年代,App安全越来越得到重视,所以它也是你需要的。PS:此书刚刚到手,简单翻阅了目录,如果看完觉得不好,再把它撤下来。&/li&&li&Android官方开发文档及Android源码——原谅我不得不把它列进来,甚至它比上述所有书籍都重要,它是一座宝库,希望大家千万要重视并好好利用,同时还可以练习英语哦,一举两得。&/li&&/ul&具体文章地址:&a href=&//link.zhihu.com/?target=http%3A//blog.csdn.net/ahence/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android开发书籍推荐&/a&
刚在回答了一下,还可再答一遍吗? 个人推荐如下: 《深入理解计算机系统》——程序员是要跟计算机打交道的,那么最好首先明白它的工作原理,才能让你的代码游刃有余。《图解HTTP》——如今单机版的…
我录制了一套完整的C#视频,非常适合于入门,不是我吹牛,看过的人都说好。全免费全开放。&br&链接在我的个人网站上可以找到:&a href=&//link.zhihu.com/?target=http%3A//jinxuliang.com& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&jinxuliang.com&/span&&span class=&invisible&&&/span&&/a&
我录制了一套完整的C#视频,非常适合于入门,不是我吹牛,看过的人都说好。全免费全开放。 链接在我的个人网站上可以找到:
既然你喜欢这个行业,就一定要坚持下去。 我的建议是接下来应该要学得“深”。 &br&推荐你一个blog,
他也是本科,估计也是自学的,后来跟google deepmind, bengio等人合作, 估计对你的规划有所帮助。(他写得每篇博文都是经典)&br&&a href=&//link.zhihu.com/?target=http%3A//colah.github.io/archive.html& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&colah.github.io/archive&/span&&span class=&invisible&&.html&/span&&span class=&ellipsis&&&/span&&/a&
既然你喜欢这个行业,就一定要坚持下去。 我的建议是接下来应该要学得“深”。 推荐你一个blog, 他也是本科,估计也是自学的,后来跟google deepmind, bengio等人合作, 估计对你的规划有所帮助。(他写得每篇博文都是经典)
&ul&&li&&a href=&//link.zhihu.com/?target=http%3A//www.echojs.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&echojs,英文&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//div.io/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&div.io&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.w3cplus.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&w3cplus&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//web.jobbole.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&伯乐在线:WEB前端 - 伯乐在线&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.html-js.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&前端乱炖&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//stackoverflow.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&stackoverflow&/a&&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//segmentfault.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&SegmentFault&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.csdn.net/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&CSDN&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.alloyteam.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&腾讯AlloyTeam Blog,腾讯全端 AlloyTeam 团队 Blog&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//fex.baidu.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&百度:首页 - FEX&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//www.75team.com/about& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&360团队,奇舞团博客&/a&&/li&&li&&a href=&//link.zhihu.com/?target=https%3A//75team.com/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&奇舞团:奇舞团博客&/a&&/li&&li&&a href=&//link.zhihu.com/?target=http%3A//f2e.souche.com/blog/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&大搜车:大搜车前端团队博客&/a&&/li&&/ul&
&figure&&img src=&https://pic1.zhimg.com/v2-059eedade0d207ebebd9032d_b.jpg& data-rawwidth=&1280& data-rawheight=&800& class=&origin_image zh-lightbox-thumb& width=&1280& data-original=&https://pic1.zhimg.com/v2-059eedade0d207ebebd9032d_r.jpg&&&/figure&&p&前两天我就能预料到,我的第 10k 个关注者会在这两天出现了,按理说我应该在昨天关注者超过 10000 人的时候就写这篇文章,但有点事拖了一下,于是想着还是 10240 人的时候再写吧,结果今晚就达到了。&/p&&br&今天在上班的地铁上,听了一场 &a href=&https://www.zhihu.com/people/fe9dfbda2ae4eceeceafc5& data-hash=&fe9dfbda2ae4eceeceafc5& class=&member_mention& data-editable=&true& data-title=&@刀姐& data-hovercard=&p$b$fe9dfbda2ae4eceeceafc5&&@刀姐&/a& 的 Live,讲的是关于职业规划的一些想法。在 Live 中她提到关于不可被替代性的话题,大意就是说要把自己打造成一个不可被替代的人。她举了个自己的例子,说的是自己是读计算机出身的,会编程,在 HR 界非常罕有,于是自己有一个不可被替代的能力。&br&&br&其实我一直都有想过这个问题:&b&作为一个前端工程师,我的核心竞争力是什么?我有哪些能力是不可(或很难)被替代的?我的壁垒在哪?&/b&当然,技术能力是我作为一名前端工程师的根基,我自然是要不断去深入研究和挖掘的。&br&&br&但除了技术能力之外呢?我首先想到的是&b&影响力&/b&。&br&&br&记得当时去美团面试的时候,我们的大 BOSS 问我:“你觉得自己能为公司带来什么价值?” 除了技术能力和管理经验之外,我说:“我在前端或者互联网圈子里有一定的影响力,有一定的人脉。当团队需要招聘时,当公司需要处理互联网上的公关问题时,当需要接触业界的朋友时,我可以提供帮助。”(我在入职的第一天开始兑现我的话,发出了第一份内推简历。入职两周收到了数十封简历,筛掉一大批后,推荐了几个人进入面试流程,用两周完成了我两个月的任务,今天还发了一个 offer)我不确定我当时这个回答是否能让他满意,但我至少知道当 HR 看到我的定级时是非常惊讶的。&br&&br&有了影响力之后,其实&b&人脉&/b&也会非常自然地在不断累积。网上有句话说得好:&u&当你想和牛逼的人成为朋友的时候,你就要成为和他一样牛逼的人&/u&。在影响力不断上升的过程中,我认识的曾经认为遥不可及的大牛,慢慢地变得没那么的遥远,好些我们还成为了好朋友,无论是工作上还是生活上,彼此都能互相帮助。&br&&br&当然了,要「成为和他一样牛逼」是一件很困难的事,我们可能要花相当长的时间。最近我在知乎某个问题的回答下看到一句话:&u&Spend money on things money can buy, spend time on things money can't buy&i&(花钱在用钱能买到的事情上,花时间在用钱不能买到的事情上)&/i&&/u&。于是我想,我能不能用钱去缩短这个时间?然后我突然想起最近几天突然火起来的「小密圈」。&br&&br&这是一个做付费内容社交的产品,当用户付费加入圈子后,就可以直接看到圈主发的动态,并可以直接提问(现在来说都比较容易获得回答)。在逛过 &a href=&https://www.zhihu.com/people/8366575cfcfb919baedbd19& data-hash=&8366575cfcfb919baedbd19& class=&member_mention& data-editable=&true& data-title=&@小爝& data-hovercard=&p$b$8366575cfcfb919baedbd19&&@小爝&/a& 圈子之后,我感觉花这点钱真心值了。对于普通用户来说,花每天几毛钱的钱,就可以直接和有影响力的人交流;对于圈主来说,在知乎迟迟没有做好付费咨询的情况下,能够通过这种方式把知识变现;这是双赢。&br&&br&于是,在关注者超过 10K 的日子,我也开了一个圈子,名字叫《&a href=&https://link.zhihu.com/?target=https%3A//wx.xiaomiquan.com/mweb/views/joingroup/join_group.html%3Fgroup_id%3D%26secret%3D3xmb3xhdo8iddwzl26ewvxdkunc4cu1f%26extra%3D212b4fbdb54ef718ac354d373b208bf236cea8e36e26& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&前端工程师的下班路上&/a&》,因为我会在下班的路上回答大家的问题,以及写一些工作上或者生活上的思考。定价我也直接写出来,是 99 元/年,平均下来一天是 0.27 元。&br&&br&如果你还想知道除了这两天我在想什么之外,我每天都在想什么,可以加入我的小密圈:&a href=&https://link.zhihu.com/?target=https%3A//wx.xiaomiquan.com/mweb/views/joingroup/join_group.html%3Fgroup_id%3D%26secret%3D3xmb3xhdo8iddwzl26ewvxdkunc4cu1f%26extra%3D212b4fbdb54ef718ac354d373b208bf236cea8e36e26& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《前端工程师的下班路上》&/a&
前两天我就能预料到,我的第 10k 个关注者会在这两天出现了,按理说我应该在昨天关注者超过 10000 人的时候就写这篇文章,但有点事拖了一下,于是想着还是 10240 人的时候再写吧,结果今晚就达到了。 今天在上班的地铁上,听了一场
的 Live,讲的是关…
&div&新开了一个公众号大数据日报(bigdatadaily),届时 Live 相关资料也会一并放到上面供各位同学参阅。&/div&&a href=&http://mp.weixin.qq.com/s?__biz=MzA5NDUzMTY5Mg==&mid=&idx=1&sn=62b46bbf7be60e6294aed826&chksm=904ce838ebaed7cec9aaee3efcafrd& class=&link-box& target=&_blank&&
&span class=&content&&
&span class=&title&&「如何快速攻克算法和数据结构」Live 资料&/span&
&span class=&url&&mp.weixin.qq.com&/span&
新开了一个公众号大数据日报(bigdatadaily),届时 Live 相关资料也会一并放到上面供各位同学参阅。
&figure&&img src=&https://pic4.zhimg.com/v2-865df610517fbebc9ce3a4_b.jpg& data-rawwidth=&760& data-rawheight=&378& class=&origin_image zh-lightbox-thumb& width=&760& data-original=&https://pic4.zhimg.com/v2-865df610517fbebc9ce3a4_r.jpg&&&/figure&旅游了一天,睡觉前刚好有点时间,就上来写点东西。后面还有将近一个星期的路程。&p&在Office工作也已经超过两年了。尽管Office是微软C++新标准最有力的推手之一,我在这里面学到的东西其实跟语言倒没什么关系,主要还是跟老代码(legacy code)相关的事情。&a href=&https://www.zhihu.com/people/42c7d84ae7d50b31d2a14& data-hash=&42c7d84ae7d50b31d2a14& class=&member_mention& data-hovercard=&p$b$42c7d84ae7d50b31d2a14&&@Hush&/a& 曾经安利过我一本《Working Efficiency with Legacy Code》,不过我还没看,你们有兴趣可以去读一读。&/p&&p&面对legacy code的情况有很多,不仅仅在工作中会遇到一些198x年写的代码跟2016年写的代码混在一起的情况,哪怕是开发自己的GacUI也会有。尽管GacUI公开立项是在C++11发布之前不久,但是实际上整份代码是在我读大学的时候造各种轮子的时候,慢慢组合起来的。现在在注释里面还能看到类似Vczh Library ++ 3.0的字样,那1.0是什么呢?&/p&&p&Borland是在我上大一的时候把Delphi卖掉的,我也是差不多在那个时候第一次感觉到了自己学到的东西好像突然就没有价值了,于是趁这个机会全方面转向C++。第一个任务自然就是要把我以前写Delphi的时候积累下来的轮子用C++重写一遍,那就是Vczh Library++ 1.0。++的意思就是这是C++写的,怀念Delphi。后来慢慢的一边修改一遍重构一边删除各种东西,因此我自己所有的C++个人项目都是围绕着这个库来开发的。从这个角度来看,GacUI的一部分代码算起来也有十年之久了,这个年龄其实已经超过了大家工作的时候能遇到的大部分项目的年龄了。所以在这里介绍的经验,对大部分的人都应该是合适的。&/p&&p&&b&Legacy code造成的最大的问题是什么?其实就是最新的best practice和标准,与过去的开发经验的矛盾&/b&。这是面对legacy code开发的时候,遇到的的主要矛盾。这个问题在Office尤为明显。GacUI嘛,也只有十几万行。只要我哪天中了彩票,我可以辞职在家里从头优化,花个一年还是能够把所有的东西都改成最好的。至于Office,哪怕你让全球的办公软件开发商停下来等你,好让你把所有的代码都翻新一遍改成最好的,也是一件不可能的事情。&/p&&p&Office客户端的一个版本的代码(不包括分支也不包括历史),拉下来所有的文件就有300多G。这里面有差不多20-30G其实是所有平台的编译器和SDK,还有一些全球语言的字符串和配置,还有一些图标和测试,剩下的占了大部分内容的都是代码。Office现在有很多千人在做,30多年通过不断的收购以及打字,最终创造了这么多代码。平均每个人要负责的代码就有超过30M那么多(是GacUI的十几倍)。要全部翻新一遍,量子计算机应该也普及了。&b&所以首先我们要明白的事情就是,用最新的标准来要求程序员产出的代码是不可能的。哪怕是新的代码,只要这些东西跟古老的部分有一点关系,你做起来就会更加困难&/b&。那落实到具体应该怎么做呢?实际上最合适的办法就是,当你在修改哪一个年代的代码的时候,就按照那个时候的要求,也就是整份代码的风格来写。&/p&&p&其次就是重构。前辈们的经验告诉我们,&b&重构最大的好处就是,通过现在多花一点时间,来节省未来无穷多的时间。那节省的时间到底是什么呢?其实就是新的需求跟就的架构的矛盾带来的开发效率的降低&/b&。你为了现在的需求做了一个设计,很好的满足了需要,架构弄好了之后业务逻辑写出来特别的快。但是需求总是会变更的,总有一天你的架构就会成为落后的架构,在上面实现新的需求就会变得很困难,开发效率就降低了。在我们总是希望软件的生命无限延续的前提下,我们需要适当地做一些重构,来满足现在或者短期的快速开发业务逻辑的需要。&/p&&p&举个很简单的例子,如果我们在命令行里面打印一个菜单,按下数字键就可以做一些不同的事情,那当软件刚刚诞生,里面的东西还不多的时候,我们会直接的使用if(input == 1) { ... } else ... 的方法来写。后来你加进去的东西越来越多,你会发现if的那些代码就总是重复,所以有一天你改成了switch(input) { case 1: ... ...}。再后来,你发现由于业务逻辑的变化,这个菜单开始有增删改的要求,那你总不能每次拿掉一个东西就把所有的case重新修正一遍吧?这个时候就会开始使用函数指针数组,在main函数里面初始化之后,input就可以直接当下标。后来这个软件中遇到了更加复杂的需求,菜单开始不是线性的了,你可能也就即将开始感觉到一个UI库的重要性,慢慢的就引进了各种设计模式。软件的迭代从宏观上来看,道理也是一样的。&/p&&p&但是面对legacy code的重构有其独特的难点。一个持续进化软件的legacy code很legacy,通常也就意味着这个软件也不小,那你重构的时候需要处理的地方就非常多。你这项工作可能要持续半年,在这半年里面你又不能push,因为重构了一半的代码多半是跑不起来的。别人也不可能停下来等你重构,所以会在旧的架构的基础上不断地添加新东西,那么你需要处理的事情就会越来越多,直到爆炸。这也是很多古老的软件无法进行任何重构的重要原因之一。&/p&&p&但是这个问题并不是无法解决的。在Office里面有三种风格的重构。&/p&&p&第一种就是靠一个牛逼的人,就是可以迅速结束战斗,同时一个change上去感染了几千个C++代码文件,上去还能跑。遇到这样的人只能每天路过办公室门口的时候进行膜拜。我就有幸目睹了一个principal的毛子干了这样的事情,因为之前一直都有合作,觉得真是太伟大了。&/p&&p&第二种就是让大家一起来。你开一个branch,把基础的东西弄好,然后让每一个人都在自己的工作之余加入到你的重构工作来,其实也就是把他们自己的组件的代码改成兼容你的新架构的。等到所有的组件都翻新过后,最后让大家一起再解决一遍pull request里面的conflict。&/p&&p&第三种就是,在旧的库的旁边写一个新的库,然后只要你库的对象不是在整个系统里到处传播的,那么你总是可以一个一个文件慢慢地把#include换掉,把代码改成兼容新库的形式。这样在后面修改这个文件的人自然也就被迫使用你的新库了。一直到所有对老库的#include都消失了,把老的删掉,重构工作就完成了。这样做的好处是你的新代码是不断地push给大家的,不会有merge的噩梦出现。&/p&&p&&b&但是重构也不是万能的。因为毕竟一个架构如果没有影响到你的开发效率的话,为什么要去重构他呢?做这个很容易就变成过度设计了&/b&。这在GacUI的身上就很明显。大家可能会发现,我在知乎上说C++新标准下面应该如何如何做的时候,GacUI出现的却总是那些过时的方法。其实一个很重要的原因就是,事情还没发展到我非翻新旧代码不可的时候。&/p&&p&举个例子,C++11说你们可以用shared_ptr、weak_ptr和unique_ptr来表达不同对象的生命周期,从而最大程度的避免对裸指针的使用(避免粗心用错)。但是GacUI仍然大量使用Ptr&T&和裸指针。其实原因有三个。&/p&&p&第一个就是,代码是旧的,在这套东西还没反映在标准里面的时候,Ptr&T&已经被广泛使用了。那现在我要不要再添加新功能的时候,使用shared_ptr,让系统里面同时出现两套智能指针呢?这当然是不行的,因为智能指针有自己管理引用计数的方法,不同的智能指针几乎是不可能混用的。&/p&&p&第二个就是,既然如此,为什么我不把Ptr&T&删掉直接全部换成shared_ptr?这就反映了上面粗体的内容。Ptr&T&换成shared_ptr真的就能提高开发效率嘛?如果GacUI是由很多个人写的,那这个很难说,毕竟不是所有人都知道Ptr&T&的各方面细节。但是我作为GacUI的“几乎”唯一的作者,我对代码的所有方面都有无限的了解,我用裸指针也很少犯错误,所以shared_ptr的好处仍然不值得我做一次全文替换。&/p&&p&第三个就是,其实Ptr&T&还有shared_ptr所没有的功能。GacUI的脚本引擎支持脚本创建新的类,这个新类可以继承自若干个C++里面的类,新类还能被反射出来使用。这就在实际上造成了,在内存布局上面,新类其实就是N个对象组成的,N-1个C++里面的类,还有一个脚本创建的模拟的类。那么这几个类的实例其实应该共享同一个引用计数的指针。不然父类实例用了一半,子类实例被早早析构了,这就尴尬了。&/p&&p&大家可以发现,用新的智能指针实现这个东西的方法就是,脚本创建的类去unique_ptr所有的父类,然后dynamic_cast其实就是去获得一个内部用shared_ptr装着子类的、父类接口的shared_ptr或unique_ptr,实现就是把虚函数redirect到父类那里去。当然这样你的父类就要求全部都写成接口(COM就是这么干的)。看起来很别扭是不是?因为C++这一套东西对这个场景其实不能很好的表达。&/p&&p&但是反过来,我可以很轻易地通过修改Ptr&T&、可以被脚本继承的类的基类DescriptableObject,加上通过SFINAE来让Ptr&T&面对普通类型的时候使用普通的实现,来轻松的做到这一点。要换成shared_ptr就要变成另一套做法了,重构难度还是挺大,超过了对以后开发效率的改善。COM的Aggregation是对相同的问题的另一套做法,我不小心重新发明了一次。&/p&&p&类似的内容还有很多,C++11出了for(auto x : xs){ ... },然而我所有的地方还是用FOREACH(X, x, xs){ ... }宏。C++17即将就要有range了,而我却有老早就为了弥补range不存在,#include &algorithm&里面的东西组合起来又太难而山寨的Linq。C++11有了T&&之后,容器就变得非常好用。但是我自己需要的容器不多,加上这个T&&的支持也不难,所以我最终也没有把自己的类换成STL。给定新标准下一个支持X&&够早的X类,一个返回X类型的属性的setter最好的写法是SetX(X x),不是X&&x,也不是const X& x,也不是两个都有。但是其实很多地方我也没换。因为这些东西的替换实际上都无法带来什么显著的好处,所以干脆就留着了。&/p&&p&剩下的还有很多琐碎的地方我也就不一一提到了,10年前的一些次要组件现在看起来可能会发现代码里有各种问题,但是反正已经写好了,改得更好用起来也不会更方便,干脆就放着不管了。&/p&&p&&b&GacUI就几乎只有我一个人在写,添加新feature赶紧做完,好腾出手来造新的轮子,才是第一目标。代码是否符合最新的C++规范,那是其次&/b&。这放在很多商业软件上也是成立的。我的理由是由于我想造新轮子所以要赶紧把GacUI做完,商业软件为了生存下去给自己员工涨工资要迅速发布和迭代产品,这两个理由其实是等价的。&/p&&p&回到Office里面,其实也会经常遇到类似的问题。三个不同的古老的组件,使用了收购回来的时候内部就已经有的三个字符串类。有一天我要把他们整合到一起,怎么办?其实最经济的方法就是,把他们都严严实实地封装进自己的接口里面,别人不要去碰他们,只有我来碰。那我内部最多也就是多写几个恶心的字符串转换的代码而已。&b&隔绝的好处就是,落后的组件的实现,通过我改头换面之后,对别人的伤害降到了最低&/b&。因此我也没有必要去重构他们了。这样就使用最短的时间,在保证质量的前提下,写出了不会降低别人开发效率的代码。&/p&&p&&b&重构是要看成本的。当然反过来,哪怕是一个重构很难,规模很大,但是他创造的效益更大的话,就值得你去说服大老板让你去做。&/b&&/p&&p&在讲完了重构之后,剩下的一个大问题就是,那添加新的代码怎么办?其实这也是要按照相同的标准去做的。你添加新的代码,是否会因为旧的架构的影响,让使用你的新代码进行二次开发的人的开发效率被降低?如果你认为答案是“会”,这可能就是一个重构的信号,你要先重构,然后再加新代码。否则,你就按照跟原有的部分兼容的方法去写就好了。当然了,如果情况允许的话,你也可以通过上面讲到的第三种重构的方法,先从你的新代码开始,推广新的架构。等整个系统的每一个角落都被你翻新了之后,旧的架构就删掉了,你就在完成了一次重构的同时,要加的新feature早就上线了,不会影响到release的日期。&/p&&p&总的来说,为什么会有这些准则?其实根本的原因是,修改代码会造成regression,如果你测试的覆盖不够,重构也会引发大量的问题(这就是为什么重构跟测试是相辅相成的,少了一个都不行)。老的代码没有重构的必要就不要重构,你的整体工作量也就大大降低了,同时保证了软件可以被release。&/p&&p&一个刚刚加入工作的程序员,或者是一个学生,可能在遇到类似的问题的时候都比较激进,然后就会被事实教做人。其实这都是因为年轻人眼界不够高,没办法在全局观上看到很多事情背后的cost。&b&所以如果你恰好加入了一个古老的软件的项目组,不要对旧的东西产生抵触,就是一个良好的开始&/b&。&/p&
旅游了一天,睡觉前刚好有点时间,就上来写点东西。后面还有将近一个星期的路程。在Office工作也已经超过两年了。尽管Office是微软C++新标准最有力的推手之一,我在这里面学到的东西其实跟语言倒没什么关系,主要还是跟老代码(legacy code)相关的事情。…
&p&我是大自然的搬运工。&/p&&p&这是我在博客园正好写的一篇Blog,希望对你有用:&a href=&//link.zhihu.com/?target=http%3A//www.cnblogs.com/indream/p/3602348.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&从Script到Code Blocks、Code Behind到MVC、MVP、MVVM&/a&&/p&&br&&p&=====================(Hi,我是分割线)=====================&/p&&p&刚过去的周五(3-14)例行地主持了技术会议,主题正好是《UI层的设计模式——从Script、Code Behind到MVC、MVP、MVVM》,是前一天晚上才定的,中午花了半小时准备了下就开讲了。&/p&&p&今天看到了&a href=&//link.zhihu.com/?target=http%3A//www.cnblogs.com/xueduanyang/p/3601471.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&大家在为MVVM knockout.js友(ji)好(lie)地交流&/a&,所以就整理下然后更扩展地分享。&/p&&p&主要目的也不是为了争论,毕竟只是正巧主题相近,原本的打算也就是一次技术分享并且记录下来。&/p&&p&那么我们就按照大致的历史进程将这些概念进行划分:&/p&&ul&&li&Script&/li&&li&Code Blocks、Code Behind&/li&&li&MVC、MVP、MVVM&/li&&/ul&&p&我们知道的是现实的历史发生顺序并不如上,因为思想都是相似的,比如MVC很早很早就出现了,解释型语言至今基本上也有很多分支而且在互联网时代大行其道。&/p&&p&但我要说的是:&b&不要在意这些细节!&/b&&/p&&p&当然了,这是玩笑,我的意思是,这些内容我懒得应该在另外独立的主题探讨,篇幅也有限。&/p&&br&&br&&ul&&li&&b&&u&Script&/u&&/b&&/li&&/ul&&p&这里脚本的意思不是指当时是用脚本开发,而是像写脚本一样开发。它们都有一个特点:&b&功能单一、管理单一、入口单一&/b&。&/p&&p&我们最早的程序是汇编,当时的码农的工作是兼职,工作内容是编写一套寿命不长的机器控制指令,有些甚至是命令,比如至今依然保留的Command(亮点自寻):&/p&&br&&figure&&img src=&https://pic4.zhimg.com/a06b7aa8ec3f_b.jpg& data-rawwidth=&677& data-rawheight=&522& class=&origin_image zh-lightbox-thumb& width=&677& data-original=&https://pic4.zhimg.com/a06b7aa8ec3f_r.jpg&&&/figure&&br&&p&到后来计算机被用于科学计算等,需求推动了需要更高的开发效率,所以我们有了高级语言。&/p&&p&那个时候码农其实多是数学家,程序的作用很简单,就是执行一些数学计算,类似今天ICPC的一些算法问题,比如Hello World:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&main()
printf(&hello,world\n&);
&/code&&/pre&&/div&&br&&p&这时候,程序还可以被归结为输入到输出的过程,我们还能去讲讲冯诺依曼模型。&/p&&p&在这个时代,开发是指编写机器指令,甚至都不配用“开发”这个词来描述这项工作。&/p&软件、UI和Markup Language&p&在那个时代讲UI等于放屁,根本不存在这种概念。但全赖我们有神器——&b&摩尔定律&/b&。&/p&&p&但我个人认为摩尔定律是不足以让一个敲命令行的时代在几十年间转变成这种各类框架技术架构实践模式的时代,真正推动计算机形成自有的工程学体系的是还有两样东西就是:&/p&&ul&&li&&b&人的能力并没有变强&/b&,至少没有在同级数下变强。&/li&&li&&b&人类一定会物尽其用&/b&&/li&&/ul&&p&因为人的能力并没有“跟上”机器,所以才会出现各种模式、方法、工具等等来补足人的不足,以最大地透支机器性能。就像我前几天在闪存无聊时突然想到的一句: &b&架构是对客观不足的妥协,规范是对主观不足的妥协&/b&。&/p&&p&当我们需要机器做的事情多了起来,我们就没办法在一个芯片上解决所有事情,所以才会有冯诺依曼模型、计算机架构,没办法用一台机器解决,所以才要互联网、分布式、云计算。&/p&&p&同样,随着计算机的发展,&b&要做的事情多了,就出现了软件的概念&/b&。当“开发”正式化,我们需求的软件就变得:&b&功能繁杂、管理统一、多入口&/b&。&/p&&p&&b&真正变化的不是客观本质,而是需求&/b&。就像这里说的“软件入口”在客观上我们还是只有一个,原理上始终都只有一个启动程序、一个启动代码片段。但&b&“软件的入口”,已经从指代Main函数变成了指代起始UI,用户已经从指代专业人士变成了指代一般消费者,先有软件的需求,才有软件的定义,而需求是在变化的&/b&。&/p&&p&一个软件需要比当时多几个数量级的代码:&/p&&ul&&li&客观上我们没办法做一个能显示所有代码的显示器&/li&&li&主观上我们没办法在超快速滚动中看清所有代码&/li&&/ul&&p&&b&所以我们需要添加索引、用多个文件组织代码&/b&。&/p&&p&机器的发展和软件的需求扩大和细化,我们开始出现了用户界面(User Interface)的概念和最适合用于界面的语言——标记语言(Markup Language)。当然,ML不是为UI而生的,它只是十分适合UI,所以才和UI坠入爱河。&/p&&p&&b&因为有了更高UI的需求,所以代码才正式被分化为描述做什么(业务逻辑)和有什么(UI)的两部分,因为我们开发时没办法在两种思维方式下同时工作,开发时的人脑是单线程的&/b&。我们所看到的同时进行UI和逻辑开发只不过是我们学会了在两种模式下快速切换,看起来像同时进行,而不是真正的同时进行。同样的情况也发生在不同的代码片段的开发中。&/p&&p&分化的情况除了UI,还发生在方方面面,比如数据操作、UI的对象和样式分离,我们还是继续从UI讲下去吧。&/p&&br&&br&&ul&&li&&b&&u&Code Block和Code Behind(其实还有Code Inside,比如onclick=&javascript:alert('哎呀我*')&)&/u&&/b&&/li&&/ul&&p&&b&UI和逻辑分开了两种语言来写,但是它们也要放在同一个项目中,因为它们原本就是要一起工作的。&/b&即使是分开,也需要相连,因为这是它们本来要解决的问题。&/p&&p&这时候我们出现的(通常的)解决方案就是Code Block和Code Behind。虽然从时间上似乎Code Block比Code Behind要早,有种感觉就是越新越好,但实质上它们正交替地发展着,因为&b&谁也没办法解决UI和逻辑代码分化后的一个哲学问题——UI和逻辑是一起的,但是它们却不是一起的&/b&。&/p&&p&Code Block能很好地处理UI和逻辑间在一起的关系,你在同一个地方可以同时维护UI和逻辑:&/p&&br&&br&&div class=&highlight&&&pre&&code class=&language-text&& 1 @model GM.OA.Finance2.Web.Models.FinancialBase.CurrencyModel
ViewBag.Title = &EditCurrencyDrawer&;
Layout = &~/Views/Shared/_DrawerLayout.cshtml&;
7 @section styles {
&link href=&/Content/base/table-form.css& rel=&stylesheet& /&
&link href=&/Content/base/drawer.bigtitle.css& rel=&stylesheet& /&
&/code&&/pre&&/div&&br&&br&&br&&br&&div class=&highlight&&&pre&&code class=&language-text&&&a href=&#& class=&addcurrency oa-btn& oa-style=&green&&添加新币别&/a&
&script type=&text/javascript&&
$(document).ready(function () {
$('.addcurrency').click(function () {
$.oa.drawer.openUrl('AddCurrencyDrawer/', 'addcurrency', {
width: 300
&/code&&/pre&&/div&&br&&br&&p&Code Behind能很好地处理UI和逻辑各自分开的关系,你可以让UI和逻辑各自做好各自的事情:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&asp:Button ID=&RemoveSelectedCurrency_Button& runat=&server& Text=&删除选中币别& OnClick=&RemoveSelectedCurrency_Button_Click& /&
&asp:Button ID=&RemoveAllCurrencies_Button& runat=&server& Text=&删除所有币别& OnClick=&RemoveAllCurrencies_Button_Click& /&
&/code&&/pre&&/div&&br&&br&&div class=&highlight&&&pre&&code class=&language-text&& 1 protected void RemoveSelectedCurrency_Button_Click(object sender, EventArgs e)
var currencyId = Currencies_ListBox.SelectedItem.V
currencyManager.Remove(currencyId);
protected void RemoveAllCurrencies_Button_Click(object sender, EventArgs e)
currencyManager.RemoveAll();
&/code&&/pre&&/div&&br&&br&&p&&b&因为存在两种实现方式,所以就存在了对比,因为存在了对比,所以就存在了争论&/b&。就像是Java和.NET、PHP和.NET、WebForm和MVC、Mac OS和Windows、iOS和Android、腾讯和所有其他互联网公司,等等。&/p&&p&问题不在哪个更好,而是我们要解决什么问题。当然,这听(ben)着(lai)像(jiu)是客气话了。&/p&&p&真正在UI和逻辑分化后带来的实质问题是:&/p&&ul&&li&&b&是按逻辑和UI划分地管理,还是按单界面的事务进行划分地管理&/b&&/li&&/ul&&p&至少UI和逻辑刚分化的时代,在软件上,我们还认为同一项事务是基于同一个UI的一系列操作完成的。&/p&&p&在摩尔定律还持续发挥作用的时候,计算机领域依旧高速发展,所以&b&通常我们还在为一样事物争论、思考、辩证的时候,它已经发生了质变了,不变的只是我们要解决的问题&/b&。&/p&&br&&br&&ul&&li&&b&&u&事务,以及界面、数据、事件、业务&/u&&/b&&/li&&/ul&&p&在之前说到过了,&b&当需求变得庞大,解决方案也会变得庞大;当解决方案变得庞大,就会出现细分;当出现细分,就会出现按哪种方式管理的问题&/b&。&/p&&p&&b&软件从处理一件事务发展到了要处理许多事务&/b&,各事务间有包含、顺序、主次等等的关系,变得越来越复杂。因为数据与逻辑庞大了,所以数据与逻辑就分离了,然后事件和业务分离了。&/p&&p&&b&它们的关系已经在我们还理得清之前持续发展而变得更加难理得清&/b&,但在一个时间点上,它们UI的领域大致分化成这些原子:&/p&&ul&&li&界面&/li&&li&数据&/li&&li&事件&/li&&li&业务&/li&&/ul&&p&你要细化的话会有更多繁杂的细节,但相信这么写的话争议性比较小。&/p&&p&&b&当一个问题出现一次的时候它是一个问题,当一个问题出现了无数次的时候它会成为历史,当一个问题将会出现无数次的时候,它将需要一个明确的定义和解决方案。&/b&&/p&&p&其中,&b&数据的更新和界面的更新这一特殊事件的问题被放大了无数倍,因为它出现了无数次&/b&。&/p&&br&&br&&br&&ul&&li&&b&&u&Model-View-Controller&/u&&/b&&/li&&/ul&&p&在ASP还在奋斗的时候WebForm突然到来,正如WebForm还在奋斗的时候MVC突然到来。当然,&b&我这里讲的MVC还是最原始的MVC,因为MVC在我们还在争论的时候已经发展了许多不同分支了&/b&。&/p&&p&有一点相信大家同意的就是,&b&我们今天讨论争论的MVC、MVP、MVVM、Code Behind等等都源自于职能分化和规划的思想与目的,MVC不是它们的开始,但是一个很好的开始&/b&。&/p&&p&相信MVC的模型大家很熟悉,也很容易找到,我们这里用一下某百科的图:&/p&&figure&&img src=&https://pic4.zhimg.com/a1c71efe626f7affc1bec2be6600b67f_b.jpg& data-rawwidth=&512& data-rawheight=&353& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&https://pic4.zhimg.com/a1c71efe626f7affc1bec2be6600b67f_r.jpg&&&/figure&&p&我们可以看到的是,界面被分到了View,数据分到了载体Model上由Model“携带”,业务集中在Controller中,而推动业务的事件由用户与View交互,通过View向Controller发动。&/p&&p&当然,实现由很多种,每种细节上都有不同,所以我才只讲也只能讲大致的MVC。&b&MVC的其中一个缺点便是没有明确的定义,所以不同的实现(比如Struts和&a href=&//link.zhihu.com/?target=http%3A//ASP.NET& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&ASP.NET&/span&&span class=&invisible&&&/span&&/a& MVC)细节上都是不一样的&/b&。&/p&&p&我们需要知道的是,&b&MVC并不是像上面所说的一些事情那样是一种“必然的”结果,它是一系列必然结果问题中的一种解决方案,而且是不完美的解决方案&/b&。我们顺着推理去到一个地方很容易犯的一个错误就是认为路只有这一条而忽视其他可能性(估计这也是导致很多争斗的原因)。另外,我们在讨论一件事物不完美的时候是有一个情境的,所以请不要像“我说它色彩单一,然后你把它涂成彩色后证明我是错的”。&/p&&p&MVC的一般流程是这样的:&b&View(界面)触发事件--》Controller(业务)处理了业务,然后触发了数据更新--》不知道谁更新了Model的数据--》Model(带着数据)回到了View--》View更新数据&/b&&/p&&p&这里也不多再陈述MVC的原理、实践等等,因为这就太长篇大论了。&/p&&br&&br&&ul&&li&&b&&u&Model-View-Presenter和一些衍生&/u&&/b&&/li&&/ul&&p&像我们之前推理的,&b&分化是一种需求的必然结果,但却没有个一个确定的结果&/b&,比如Code Behind和Code Block的问题等等。&/p&&p&MVC顺着需求把UI相关的工作分化成了三份,这点经过实践证明无可厚非。&b&但是它们的三角关系却被一些人认为带来了一些问题,或者应该说他们有“更好的”解决方案&/b&。&/p&&p&在只有Code Behind和Code Block的那个时候维护是很直接的,不是在同一段代码内解决就是在同一个关联的事件上解决。三角关系的问题就是维护问题。&b&在MVC,当你有变化的时候你需要同时维护三个对象和三个交互,这显然让事情复杂化了&/b&。&/p&&p&我们之前说到,随着摩尔定律,软件的需求不断地变化和变得庞大。&b&随着需求变得庞大的时候,需求变化也变得频繁,这是一个出现了无数次以后也将会出现无数的无数次的一个问题,所以它需要一个解决方案,哪怕它不一定能被解决&/b&。&/p&&p&为了解决需求变化,从《人月神话》到敏捷到DDD,&b&它不是我们已经解决了的问题,而是我们正在解决的问题&/b&。放在UI的模式和MVC上来讲,&b&就是优化或者替代MVC模式&/b&,其中之一就是Model-View-Presenter(MVP)模式。&/p&&p&我们先看看两个MVP模式的图:&/p&&figure&&img src=&https://pic2.zhimg.com/1ad9bffd8a988d3be012551_b.jpg& data-rawwidth=&600& data-rawheight=&315& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&https://pic2.zhimg.com/1ad9bffd8a988d3be012551_r.jpg&&&/figure&&p&(图一)&/p&&figure&&img src=&https://pic1.zhimg.com/ffa885b9adc7f4dca8bfec_b.jpg& data-rawwidth=&612& data-rawheight=&393& class=&origin_image zh-lightbox-thumb& width=&612& data-original=&https://pic1.zhimg.com/ffa885b9adc7f4dca8bfec_r.jpg&&&/figure&&br&&p&(图二)&/p&&p&两幅图是不同的,但是对MVC的改进的思想却是一样的:&b&切断的View和Model的联系,让View只和Presenter(原Controller)交互,减少在需求变化中需要维护的对象的数量&/b&。&/p&&p&这种方式很符合我们的期待,因为我们倾向于:&/p&&ul&&li&&b&用更低的成本解决问题&/b&&/li&&li&&b&用更容易理解的方式解决问题&/b&&/li&&/ul&&p&许多时候并不是一种模式不好,而是因为人没办法执行,比如不容易理解,我们就会选择容易理解的方式。&b&计算机依赖摩尔定律用数量的增长来解决问题,而人是用方式的改变来解决问题的&/b&。同样因为客观原因我们不善于维护多个对象和多个对象之间的关系,所以我们改变了,或者说简化了这种方式。&/p&&p&&b&MVP定义了Presenter和View之间的接口,让一些可以根据已有的接口协议去各自分别独立开发,以此去解决界面需求变化频繁的问题&/b&。上面两图都有接口,不过接口的实现和使用细节不一样,不过思想上是一致的。&/p&&p&在这里要提到的是,事实上,&b&需求变化最频繁的并不一定是最接近用户的界面,但基本可以确定的是,最接近用户的界面是因为需求变化而需要最频繁更改的&/b&。当然,如果View如果是API而不是UI,那就另说了。&/p&&p&还有一些用来“解决”MVC这项缺点的比如有:&a href=&//link.zhihu.com/?target=http%3A//ASP.NET& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&ASP.NET&/span&&span class=&invisible&&&/span&&/a& MVC的ViewBag,Cocoa的delegate。它们都为了简化数据更新的问题而存在,包括MVVM。&/p&&br&&br&&ul&&li&&b&&u&Model-View-ViewModel&/u&&/b&&/li&&/ul&&p&先直接看看Model-View-ViewModel(MVVM)的图:&/p&&figure&&img src=&https://pic4.zhimg.com/1d9336019baa5cabdf0e5e30b3effffb_b.jpg& data-rawwidth=&715& data-rawheight=&151& class=&origin_image zh-lightbox-thumb& width=&715& data-original=&https://pic4.zhimg.com/1d9336019baa5cabdf0e5e30b3effffb_r.jpg&&&/figure&&p&从图上看是比MVP简单了,更不用说MVC了。个人不认为MVVM是从MVP进化而来,我只觉得这是在MVP之后出现的一种“更好的”UI模式解决方案,但是用MVP来与之对比比较容易说明问题。&/p&&p&&b&ViewModel大致上就是MVP的Presenter和MVC的Controller了,而View和ViewModel间没有了MVP的界面接口,而是直接交互,用数据“绑定”的形式让数据更新的事件不需要开发人员手动去编写特殊用例,而是自动地双向同步&/b&。数据绑定你可以认为是Observer模式或者是Publish/Subscribe模式,原理都是为了&b&用一种统一的集中的方式实现频繁需要被实现的数据更新问题&/b&。&/p&&p&&b&比起MVP,MVVM不仅简化了业务与界面的依赖关系,还优化了数据频繁更新的解决方案,甚至可以说提供了一种有效的解决模式&/b&。&/p&&p&至此,我们能理解为什么许多人认为MVVM是最好的一种模式,没有之一。但事实上,&b&MVVM也是依赖于我们至今所讲的“特有的情境”&/b&。&/p&&p&当然,最优雅的也是第一个能作代表的实践就是Windows Presentation Foundation(WPF)了。&/p&&br&&br&&ul&&li&&u&&b&Web&/b&&/u&&/li&&/ul&&p&之上,我们在模式演变的推论基本上都还是基于桌面软件的,但是&b&过去十年却是互联网的时代&/b&。实际上大部分&b&让大家争议的并不是在桌面领域最合适的是那个,而是在Web领域的模式问题,也就是在B/S场景下的问题&/b&。&/p&&p&当软件离开单机,去到网络的时候,因为场景变了,所以原有的解决方案也变了,不过需求依然是不变的。&b&我们依然要解决的问题是用户交互与数据更新的问题,还有维护等等的问题&/b&。&/p&&p&当场景变到Web的时候,我们发现&b&MVVM用来做服务端是极其不适用的,至少现在是不适用的&/b&。而MVP你提都不用提。为什么呢?因为:&/p&&ul&&li&&b&网络资源成本过高&/b&&/li&&li&&b&开发成本过高&/b&&/li&&/ul&&p&问大家一个问题,当一个网页的数据更新后,你希望更新用户看到的数据,你会怎么做?&/p&&p&一般情况下,你会:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&window.location.reload();
&/code&&/pre&&/div&&br&&p&就算你不这么做,用户也会:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&F5
&/code&&/pre&&/div&&br&&p&就像之前说的,我们会选择更直接的方式解决问题。直接刷新页面的原因是因为这样更直接,更容易解决数据更新的问题。&/p&&p&很多时候&b&你不会愿意为了一个数据更新写一个AJAX,更别说这个AJAX要带来Loading、事件顺序处理、网络问题、异常处理等等,这就是开发成本过高&/b&。&/p&&p&另一个网络成本过高就更容易解释了,虽然宽带是基本包月的,但也不带这么用的,何况还有移动用户。更主要的原因是,&b&在本地软件,更新数据是一个引用问题,而在网络应用上,这是一个传输问题&/b&。传输成本远高于引用成本,引用之上顶多是在本地内存中再进行一次内存拷贝。&/p&&p&这个时候,&b&我们会更倾向于用MVC模式,因为在Web层面,我们更倾向于一次性更新数据&/b&。&/p&&br&&br&&ul&&li&&b&&u&Web的MVVM&/u&&/b&&/li&&/ul&&p&所有问题都不是问题,就算有问题也要解决问题。&/p&&p&为什么这个标题下突然冒出这么一句话?我想说的是,&b&需求依旧是不变的,是推动进步的原动力&/b&。&/p&&p&还有我之前说过,当我们讨论或者争论一个问题的时候,问题的对象已经发生改变了,而且这次是在我们讨论这个问题之前已经发生改变了。&/p&&p&&b&网络资源成本不断下降&/b&,相信已经不需要多提及。摩尔定律和相近的一些原理正在发挥着它应用的作用,网络带宽越来越高、相应速度越来越快。&/p&&p&&b&如果传输因为相对成本下降而导致数据传输的成本低于开发人员拒绝客户的成本,那么它就会被实现而不是被拒绝&/b&。&/p&&p&另外还有一点就是&b&因为技术的进步,技术的资源不断被更大规模地压榨,需求也不断地增长,那么需求始终会增长超过相对不变的开发成本的&/b&。比如jQuery的出现解决了很多问题,我们现在更多地去使用AJAX,哪怕很大一部分依然是为了解决网络资源不足的问题;我们会用更多的样式代码而用了相对更少的图片;我们不再那么依赖Flash一类的矢量图解决方案而直接录制视频。&/p&&p&至少上一节我们说到的两个导致大家选用MVC的问题都正在被解决,所以我们有理由相信未来Web不仅仅需要MVC,可能会需要MVVM或其他解决方案。至少我们能理解容易理解为什么前端会出现一些MVVM的框架,比如先驱knockout.js和AngularJs。这些框架本身的好坏就不作讨论了,因为我们讨论的是模式。&/p&&p&&b&在Web上,MVVM的对比对象就不是MVC,而是Code Block&/b&。&/p&&p&数据即时更新的需求在扩大,但未必有达到一定要用MVVM这一等级的高大上的模式,实际上如果你要更新一个数据,你还是会采取:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&$('.notice').html('发送成功!');
&/code&&/pre&&/div&&br&&p&因为......我们依然会采取更直接的方式解决问题......&/p&&p&&b&实际上,现在Web MVVM主要并不是用在了Web或者Wap上,而是移动App上&/b&。按照前面的说法,只可能是:&/p&&ul&&li&HTML+JS比原生在一些场景上更适合Native&/li&&li&在移动App上比Web上更适合使用MVVM&/li&&/ul&&p&哪怕是Native开发,实际上iOS的开发上也是用类似的数据绑定的方式的。这里也不深究了,毕竟我也不算懂iOS。&/p&&p&要说的是,在Web MVVM或者Web的模式上,也就是&b&Web的富应用上,现在还不过是个初期由膨胀的需求推动的阶段&/b&。&b&重要的不是技术会怎么走,而是需求和客观条件会怎么走&/b&。&/p&&p&可能Webform会因为高速开发而焕发第二春,它的AJAX的模式也十分满足于简单开发,但似乎大家需要的不是GUI式的网页。&/p&&br&&br&&ul&&li&&b&&u&结尾语&/u&&/b&&/li&&/ul&&p&我们不一定需要MVVM,但我们一定需要更强大的方式去解决不断膨胀的Web需求。&/p&&p&我们可以预见的是:&/p&&ul&&li&会有更强大的浏览器&/li&&li&会有更强大的JavaScript或者框架&/li&&li&会有更加适合的模式&/li&&/ul&&p&除去客气话的部分,我还是想说,在不同的需求下其实有最适合的解决方案,&b&通常让我们纠结的不是因为哪个解决方案更好,而是我们看到的条件不够多&/b&。&/p&&p&编译语言当然比解释语言效率高,但考虑到开发和维护成本,JavaScript等始终会大行其道,比如Node.JS、Python;.NET和微软当然很强大,移植.NET到其他平台也很容易,但微软是家有自己商业模式和要赚钱的公司;当然有些实践和技术更好,但其他开发人员会避开甚至否定自己不擅长的东西,大家都喜欢确定的东西;有些技术更强大,但是只是基于特殊的客观条件和需求,如果你想做大,要么创造客观条件,要么把它结合需求......&/p&
我是大自然的搬运工。这是我在博客园正好写的一篇Blog,希望对你有用: =====================(Hi,我是分割线)=====================刚过去的周五(3-14)例行地主持了技术会议,主题正好是《UI层的…
&figure&&img src=&https://pic2.zhimg.com/v2-0e892f7f9a0ad46130f9e_b.jpg& data-rawwidth=&1358& data-rawheight=&826& class=&origin_image zh-lightbox-thumb& width=&1358& data-original=&https://pic2.zhimg.com/v2-0e892f7f9a0ad46130f9e_r.jpg&&&/figure&&blockquote&&p&原文链接:&a href=&https://link.zhihu.com/?target=https%3A//medium.freecodecamp.com/javascript-modules-part-2-module-bundling-6& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&JavaScript Modules Part 2: Module Bundling&/a&&/p&&p&作者:&a href=&https://link.zhihu.com/?target=https%3A//medium.freecodecamp.com/%40preethikasireddy& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Preethi Kasireddy&/a&&/p&&/blockquote&&p&在&a href=&http://zhuanlan.zhihu.com/p/& class=&internal&&上一篇教程&/a&里我们已经讨论过什么是模块,为什么要使用模块以及多种实现模块化的方式。&/p&&p&这次,我们会聊一聊什么是模块打包,为什么要打包模块,模块打包的方式工具,还有它当前在Web开发中的运用。&/p&&h2&什么是模块打包?&/h2&&p&粗俗一点来讲,模块打包就是把一小坨一小坨的代码粘成一大坨。&/p&&p&实际操作起来的时候当然还需要关注一些细节。&/p&&h2&为什么要打包模块?&/h2&&p&一般来讲,我们用模块化组织代码的时候,都会把模块划分在不同的文件和文件夹里,也可能会包含一些诸如React和Underscore一类的第三方库。&/p&&p&而后,所有的这些模块都需要通过&script&标签引入到你的HTML文件中,然后用户在访问你网页的时候它才能正常显示和工作。每个独立的&script&标签都意味着,它们要被浏览器分别一个个地加载。&/p&&p&这就有可能导致页面载入时间过长。&/p&&p&为了解决这个问题,我们就需要进行模块打包,把所有的模块合并到一个或几个文件中,以此来减少HTTP请求数。这也可以被称作是从开发到上线前的构建环节。&/p&&p&还有一种提升加载速度的做法叫做代码压缩(混淆)。其实就是去除代码中不必要的空格、注释、换行符一类的字符,来保证在不影响代码正常工作的情况下压缩其体积。&/p&&p&更小的文件体积也就意味着更短的加载时间。要是你仔细对比过带有 .min后缀的例如 jquery.min.js和jquery.js的话,应该会发现压缩版的文件相较之下要小很多。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-60a0bef5aebbbcf32c0d80e_b.jpg& data-rawwidth=&340& data-rawheight=&77& class=&content_image& width=&340&&&/figure&&br&&p&Gulp和Grunt一类的构建工具可以很方便地解决上述的需求,在开发的时候通过模块来组织代码,上线时再合并压缩提供给浏览器。&/p&&h2&打包模块的方法有哪些?&/h2&&p&如果你的代码是通过之前介绍过的模块模式来组织的,合并和压缩它们其实就只是把一些原生的JS代码合在一起而已。&/p&&p&但如果你使用的是一些浏览器原生不支持的模块系统(例如CommonJS 或 AMD,以及ES6 模块的支持现在也不完整),你就需要使用一些专门的构建工具来把它们转换成浏览器支持的代码。这类工具就是我们最近经常听说的Browserify, RequireJS, Webpack等等模块化构建、模块化加载工具了。&/p&&p&为了实现模块化构建或载入的功能,这类工具提供许多诸如在你改动源代码后自动重新构建(文件监听)等一系列的功能。&/p&&p&下面我们就一起来看一些实际的例子吧:&/p&&h2&打包 CommonJS&/h2&&p&在上一篇教程中我们了解到, CommonJS是同步载入模块的,这对浏览器来说不是很理想。其实下面介绍的模块化构建工具Browserify在上一篇也提到过。它是一个专门用来打包CommonJS模块以便在浏览器里运行的构建工具。&/p&&p&举个例子,假如你在 main.js 文件中引入了一个用来计算平均数的功能模块:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&var&/span& &span class=&nx&&myDependency&/span& &span class=&o&&=&/span& &span class=&nx&&require&/span&&span class=&p&&(&/span&&span class=&s1&&'myDependency'&/span&&span class=&p&&);&/span&
&span class=&kd&&var&/span& &span class=&nx&&myGrades&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&mi&&93&/span&&span class=&p&&,&/span& &span class=&mi&&95&/span&&span class=&p&&,&/span& &span class=&mi&&88&/span&&span class=&p&&,&/span& &span class=&mi&&0&/span&&span class=&p&&,&/span& &span class=&mi&&91&/span&&span class=&p&&];&/span&
&span class=&kd&&var&/span& &span class=&nx&&myAverageGrade&/span& &span class=&o&&=&/span& &span class=&nx&&myDependency&/span&&span class=&p&&.&/span&&span class=&nx&&average&/span&&span class=&p&&(&/span&&span class=&nx&&myGrades&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&在这个示例中,我们只有一个名为 myDependency 的模块依赖。通过下面的命令,Browserify会依次把main.js里引入的所有模块一同打包到一个名为 bundle.js 的文件里:&/p&&div class=&highlight&&&pre&&code class=&language-bash&&&span&&/span&browserify main.js -o bundle.js
&/code&&/pre&&/div&&p&Browserify 首先会通过抽象语法树(&a href=&https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Abstract_syntax_tree& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AST&/a&)来解析代码中的每一个 require 语句,在分析完所有模块的依赖和结构之后,就会把所有的代码合并到一个文件中。然后你在HTML文件里引入一个bundle.js就够啦。&/p&&p&多个文件和多个依赖也只需要再稍微配置一下就能正常工作了。&/p&&p&之后你也可以使用一些例如Minify-JS的工具来压缩代码。&/p&&h2&打包 AMD&/h2&&p&假若你使用的是AMD,你会需要一些例如RequireJS 或 Curl的AMD加载器。模块化加载工具可以在你的应用中按需加载模块代码。&/p&&p&需要再次提醒一下,AMD 和 CommonJS 的最主要区别是AMD是异步加载模块的。这也就意味着你不是必须把所有的代码打包到一个文件里,模块加载不影响后续语句执行,逐步加载的的模块也不会导致页面阻塞无法响应。&/p&&p&不过在实际应用中,为了避免用户过多的请求对服务器造成压力。大多数的开发者还是选择用RequireJS optimizer, &a href=&https://link.zhihu.com/?target=http%3A//requirejs.org/docs/optimization.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&r.js&/a&一类的构建工具来合并和压缩AMD的模块。&/p&&p&总的来说,AMD 和 CommonJS 在构建中最大的区别是,在开发过程中,采用AMD的应用直到正式上线发布之前都不需要构建。&/p&&p&要是你对CommonJS vs. AMD的讨论感兴趣,可以看这一篇&a href=&https://link.zhihu.com/?target=http%3A//tomdale.net/2012/01/amd-is-not-the-answer/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AMD is Not the Answer&/a&&/p&&h2&Webpack&/h2&&p&Webpack 是新推出的构建工具里最受欢迎的。它兼容CommonJS, AMD, ES6各类规范。&/p&&p&也许你会质疑,我们已经有这么多诸如Browserify 或 RequireJS 的工具了,为什么还需要 Webpack 呢?究其原因之一,Webpack 提供许多例如 code splitting(代码分割) 的有用功能,它可以把你的代码分割成一个个的 chunk 然后按需加载优化性能。&/p&&p&举个例子,要是你的Web应用中的一些代码只在很少的情况下才会被用到,把它们全都打包到一个文件里是很低效的做法。所以我们就需要 code splitting 这样的功能来实现按需加载。而不是把那些很少人才会用到的代码一股脑儿全都下载到客户端去。&/p&&p&code splitting 只是 Webpack 提供的众多强大功能之一。当然,网上也为这些模块化构建工具吵得不可开交。你要是感兴趣的话也可以在下面这些地方观摩一下:&/p&&ul&&li&&a href=&https://link.zhihu.com/?target=https%3A//gist.github.com/substack/68f8d502be42d5cd4942& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&gist.github.com/substac&/span&&span class=&invisible&&k/68f8d502be42d5cd4942&/span&&span class=&ellipsis&&&/span&&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=http%3A//mattdesl.svbtle.com/browserify-vs-webpack& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Browserify vs. Webpack&/a&&/li&&li&&a href=&https://link.zhihu.com/?target=http%3A//blog.namangoel.com/browserify-vs-webpack-js-drama& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Browserify VS Webpack&/a&&/li&&/ul&&h2&ES6 模块&/h2&&p&看他们吵够了的话,接下来我就要介绍一下ES6模块了。假如你采用ES6模块,在不远的将来对那些构建工具的需求可能会小一些。首先我们还是看看ES6模块是怎么加载的吧。&/p&&p&ES6模块和CommonJS, AMD一类规范最主要的区别是,当你载入一个模块时,载入的操作实际实在编译时执行的——也就是在代码执行之前。所以去掉那些不必要的exports导出语句可以优化我们应用的性能。&/p&&p&有一个经常会被问到的问题:去除exports和冗余代码消除(UglifyJS一类工具执行后的效果)之间有什么区别?&/p&&p&答案是这个要具体情况具体分析,感兴趣的话可以上Github看这个Repo:&a href=&https://link.zhihu.com/?target=https%3A//github.com/rollup/rollup& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Rollup’s wiki&/a&&/p&&p&让ES6模块与冗余代码消除(Dead code elimination)不同的是一种叫做tree shaking的技术。Tree shaking其实恰好是冗余代码消除的反向操作。它只加载你需要调用的代码,而不是删掉不会被执行的代码。我们还是用一个具体的例子说明吧:&/p&&p&假设我们有如下一个使用ES6语法,名为 utils.js 的函数:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kr&&export&/span& &span class=&kd&&function&/span& &span class=&nx&&each&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&iterator&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nb&&Array&/span&&span class=&p&&.&/span&&span class=&nx&&isArray&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&))&/span& &span class=&p&&{&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&i&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span& &span class=&o&&&&/span& &span class=&nx&&collection&/span&&span class=&p&&.&/span&&span class=&nx&&length&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span&&span class=&o&&++&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&[&/span&&span class=&nx&&i&/span&&span class=&p&&],&/span& &span class=&nx&&i&/span&&span class=&p&&,&/span& &span class=&nx&&collection&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&key&/span& &span class=&k&&in&/span& &span class=&nx&&collection&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&[&/span&&span class=&nx&&key&/span&&span class=&p&&],&/span& &span class=&nx&&key&/span&&span class=&p&&,&/span& &span class=&nx&&collection&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&kr&&export&/span& &span class=&kd&&function&/span& &span class=&nx&&filter&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&test&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&filtered&/span& &span class=&o&&=&/span& &span class=&p&&[];&/span&
&span class=&nx&&each&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&item&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&test&/span&&span class=&p&&(&/span&&span class=&nx&&item&/span&&span class=&p&&))&/span& &span class=&p&&{&/span&
&span class=&nx&&filtered&/span&&span class=&p&&.&/span&&span class=&nx&&push&/span&&span class=&p&&(&/span&&span class=&nx&&item&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&});&/span&
&span class=&k&&return&/span& &span class=&nx&&filtered&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&kr&&export&/span& &span class=&kd&&function&/span& &span class=&nx&&map&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&iterator&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&mapped&/span& &span class=&o&&=&/span& &span class=&p&&[];&/span&
&span class=&nx&&each&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&value&/span&&span class=&p&&,&/span& &span class=&nx&&key&/span&&span class=&p&&,&/span& &span class=&nx&&collection&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&mapped&/span&&span class=&p&&.&/span&&span class=&nx&&push&/span&&span class=&p&&(&/span&&span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&value&/span&&span class=&p&&));&/span&
&span class=&p&&});&/span&
&span class=&k&&return&/span& &span class=&nx&&mapped&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&kr&&export&/span& &span class=&kd&&function&/span& &span class=&nx&&reduce&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&iterator&/span&&span class=&p&&,&/span& &span class=&nx&&accumulator&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&startingValueMissing&/span& &span class=&o&&=&/span& &span class=&nx&&accumulator&/span& &span class=&o&&===&/span& &span class=&kc&&undefined&/span&&span class=&p&&;&/span&
&span class=&nx&&each&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&item&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span&&span class=&p&&(&/span&&span class=&nx&&startingValueMissing&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&accumulator&/span& &span class=&o&&=&/span& &span class=&nx&&item&/span&&span class=&p&&;&/span&
&span class=&nx&&startingValueMissing&/span& &span class=&o&&=&/span& &span class=&kc&&false&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&nx&&accumulator&/span& &span class=&o&&=&/span& &span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&accumulator&/span&&span class=&p&&,&/span& &span class=&nx&&item&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&});&/span&
&span class=&k&&return&/span& &span class=&nx&&accumulator&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&现在我们也不清楚到底需要这个函数的哪些功能,所以先全部引入到 main.js 中:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&//main.js&/span&
&span class=&kr&&import&/span& &span class=&o&&*&/span& &span class=&nx&&as&/span& &span class=&nx&&Utils&/span& &span class=&nx&&from&/span& &span class=&s1&&'./utils.js'&/span&&span class=&p&&;&/span&
&/code&&/pre&&/div&&p&之后我们再调用一下 each 函数:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&//main.js&/span&
&span class=&kr&&import&/span& &span class=&o&&*&/span& &span class=&nx&&as&/span& &span class=&nx&&Utils&/span& &span class=&nx&&from&/span& &span class=&s1&&'./utils.js'&/span&&span class=&p&&;&/span&
&span class=&nx&&Utils&/span&&span class=&p&&.&/span&&span class=&nx&&each&/span&&span class=&p&&([&/span&&span class=&mi&&1&/span&&span class=&p&&,&/span& &span class=&mi&&2&/span&&span class=&p&&,&/span& &span class=&mi&&3&/span&&span class=&p&&],&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&x&/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&&x&/span&&span class=&p&&)&/span& &span class=&p&&});&/span&
&/code&&/pre&&/div&&p&通过 &tree shaken& 之后的 main.js 看起来就像下面这样:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&//treeshake.js
function each(collection, iterator) {
if (Array.isArray(collection)) {
for (var i = 0; i & collection. i++) {
iterator(collection[i], i, collection);
for (var key in collection) {
iterator(collection[key], key, collection);
each([1, 2, 3], function(x) { console.log(x) });
&/code&&/pre&&/div&&p&注意到这里只导出了我们调用过的 each 方法。&/p&&p&再如果我们只调用 filter 方法的话:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&//main.js&/span&
&span class=&kr&&import&/span& &span class=&o&&*&/span& &span class=&nx&&as&/span& &span class=&nx&&Utils&/span& &span class=&nx&&from&/span& &span class=&s1&&'./utils.js'&/span&&span class=&p&&;&/span&
&span class=&nx&&Utils&/span&&span class=&p&&.&/span&&span class=&nx&&filter&/span&&span class=&p&&([&/span&&span class=&mi&&1&/span&&span class=&p&&,&/span& &span class=&mi&&2&/span&&span class=&p&&,&/span& &span class=&mi&&3&/span&&span class=&p&&],&/span& &span class=&kd&&function&/span&&span class=&p&&(&/span&&span class=&nx&&x&/span&&span class=&p&&)&/span& &span class=&p&&{&/span& &span class=&k&&return&/span& &span class=&nx&&x&/span& &span class=&o&&===&/span& &span class=&mi&&2&/span& &span class=&p&&});&/span&
&/code&&/pre&&/div&&p&&Tree shaken& 之后就会变成这样:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&function&/span& &span class=&nx&&each&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&iterator&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nb&&Array&/span&&span class=&p&&.&/span&&span class=&nx&&isArray&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&))&/span& &span class=&p&&{&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&i&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span& &span class=&o&&&&/span& &span class=&nx&&collection&/span&&span class=&p&&.&/span&&span class=&nx&&length&/span&&span class=&p&&;&/span& &span class=&nx&&i&/span&&span class=&o&&++&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&[&/span&&span class=&nx&&i&/span&&span class=&p&&],&/span& &span class=&nx&&i&/span&&span class=&p&&,&/span& &span class=&nx&&collection&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&k&&for&/span& &span class=&p&&(&/span&&span class=&kd&&var&/span& &span class=&nx&&key&/span& &span class=&k&&in&/span& &span class=&nx&&collection&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&iterator&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&[&/span&&span class=&nx&&key&/span&&span class=&p&&],&/span& &span class=&nx&&key&/span&&span class=&p&&,&/span& &span class=&nx&&collection&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&p&&};&/span&
&span class=&kd&&function&/span& &span class=&nx&&filter&/span&&span class=&p&&(&/span&&span class=&nx&&collection&/span&&span class=&p&&,&/span& &span class=&nx&&test&/span&}

我要回帖

更多关于 你是我遥不可及的祝音 的文章

更多推荐

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

点击添加站长微信