xixijiang的主页


  • Home

  • Archives

  • Tags

js模块系统详解(一)

Posted on 2019-03-25

前端涉及到的模块规范有:CommonJS、AMD、ES6的模块系统、NodeJS的模块系统。

CommonJS

nodeJS的诞生标志着”JavaScript模块化编程“的正式诞生。在浏览器环境下,没有模块化编程,倒也不是很大的问题,毕竟网页程序的复杂性有限。但是在服务端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程。

nodejs的模块系统,就是参考commonjs规范实现的。

AMD

AMD的全称是Asynchronous Module Define,异步模块定义。

为什么AMD会诞生呢?

在有了服务端模块后,我们自然也想要客户端模块,而且最好这两个能兼容,一个模块都不用修改,在服务端和客户端可以同时运行。

但是,CommonJS有一个重大的局限,导致它不适合在浏览器环境运行。

1
2
var math = require('math);
math.add(2, 3);

看上面的代码,乍一看似乎没有什么问题。但实际上,第二行代码需要在第一行代码执行后,才能执行。因此必须要等到math.js加载完成后。如果math.js加载时间很长,整个应用就卡在这里了。

这对于服务端编程并不是一个问题,因为服务端所有的模块都放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时取决于网速,可能要等很长时间。

因此,浏览器的模块,不能采用”同步加载”,而是要异步加载,因此AMD就诞生了。

AMD

AMD也是采用require语句,但是语法与CommonJS不同。

1
require([module], callback);

其中,第一个参数[module]是所有依赖的模块,是一个数组;第二个参数callback,则是加载成功之后的回调。

上面的那个代码,如果用AMD实现,则是:

1
2
3
require(['math'], function() {
math.add(2, 3);
});

math.add()与math模块加载不是同步的,浏览器不会发生假死。所以很显然,AMD比较适合浏览器环境。

参考:

http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_definition.html

ES6的模块系统

ES6实现了模块规范,完全可以取代CommonJS和AMD,成为服务器和浏览器通用的模块解决方案。

ES6的设计思想

设计思想是:尽量的静态化,也就是编译时加载(静态加载)。

这样在编译时就能确定模块之间的依赖关系、输入和输出的变量。

而CommonJS和AMD只能在运行时才能确定这些东西(运行时加载)(此处可以详看阮一峰的es6模块系统)。

NodeJS的模块系统

获取隐藏元素的高度

Posted on 2019-03-22

最近组内开发组件库,这是初次参与开发组件库,学到了很多知识。特此记录。

对于collapse折叠面板来说,难点是展开/折叠的动画效果。首先想到的自然是利用transition来做过渡效果,但是transition对于元素动画起止高度不知道的情况下,无法做出过渡效果。所以,我们要做的就是拿到隐藏的过渡元素的实际高度,并赋值给el.style.height。

那么如何拿到过渡元素的实际高度呢?

首先来看,一般隐藏元素的方式有如下几种:

  1. display: none;
  2. height: 0;overflow: hidden;
  3. visibility: hidden;
  4. opacity: 0;
  5. transform: scale(0);

以上五种,除了第一种display: none;是真实隐藏,其他的都是视觉隐藏,即仍然在文档流中占有位置。

视觉隐藏的元素仍然可以获取到元素的高度。所以重点就是display: none;元素的高度如何获取。而我们会发现,第二种方式虽然仍然在文档流中占有位置,但是高度为0,具有display: none;的视觉效果,此时该元素的clientHeight,offsetHeight为0,但是scrollHeight是存在的。因此可以利用scrollHeight来得到display: none;元素的高度。

后记:

由于scrollHeight、clientHeight、offsetHeight这几个容易让人混淆的概念,每次用到都是百度一下,所以这里再记录一次,决心搞定它!

scrollHeight

元素内容的总高度,包含滚动区域。该值等于height + padding + 伪元素高度

clientHeight

元素的可见区域的高度,包括padding,但不包括border,margin,水平滚动条。该值等于heihgt + padding - 水平滚动条高度

offsetHeight

元素的高度,包括padding、border、水平滚动条高度,不包括伪元素。

如果该元素被隐藏了,比如设置了display: none,则offsetHeight为0

srollHeight
clientHeight

在查看MDN文档时,发现,有的属性是挂载在HTMLElement下的,有的是挂载在Element下的,比如Element.scrollHeight,Element.clientHeight,HTMLElement.offsetHeight。这是为什么呢?

继续查看MDN文档,发现了他们之间的关系:

element HTMLElement

好了,到此为止我们已经了解高度相关的几个属性了。所以在beforeEnter中我们可以先将过渡元素设置为height: 0; overflow: hidden;,在enter钩子中就可以通过el.scrollHeight获取到元素总高度,并将过渡元素的height设置为el.scrollHeight,这样就得到了过渡元素的高度。从而就可以利用transition了。

由vue引发的模板引擎学习(一)字符串模板

Posted on 2019-02-13

1. 前言

又看了一遍vue官方文档,迄今为止是第三遍看了,看第一遍时无比懵逼,看第二遍时加深了一些常用api的理解,而第三遍则是工作近一年后的补盲。确实发现了不少盲点,特此记录。这一篇是讲模板引擎。为什么要写这个?因为看到vue文档说Vue.js使用了基于html的模板语法,一开始不理解什么是基于html的语法,所以进行了学习,从而有了这篇及后续几篇文章。

2. 模板引擎的概念

模板引擎是为了使用户界面与业务数据分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档。

3. web前端模板引擎

这里将介绍三种模板引擎技术,分别是:

  • 基于字符串的模板
  • 基于DOM操作的模板
  • 基于虚拟DOM的模板

4. 为什么需要模板引擎?

页面中经常有些内容依赖于后端数据。比如,我们有这样一段代码:

1
2
3
4
5
6
7
8
9
<div id="container">
<ul>
<li>喜洋洋</li>
<li>懒洋洋</li>
<li>美羊羊</li>
<li>灰太狼</li>
<li>红太狼</li>
</ul>
</div>

实际上面的li里的内容通常是从后端获取的,并不能写死,所以在没有模板引擎的情况下,我们需要这么写:

1
2
3
4
5
6
7
8
9
// 这里data应该是请求后端接口得到的,这里只是为了模拟一下
var data = ['喜洋洋', '懒洋洋', '美羊羊', '灰太狼', '红太狼'];
var container = document.getElementById('container');
var str = '<ul>';
for (var i = data.length - 1; i >= 0; i--) {
str += '<li>' + data[i] + '</li>';
}
str += '</ul>';
container.innerHTML = str;

写过的同学应该都有这样的体验:

  • 开发效率低,字符串拼接写起来麻烦
  • 容易出错,尤其是结构复杂的情况
  • 代码看起来不清晰明了
  • 可维护性低,后期改起来不方便
  • 可扩展性低,想要增加新需求时更该不方便

所以这就带来了一些思考,有没有一种方式能将前端结构与数据分开,从而老司机们就创造出了各种基于字符串的模板引擎。

5. 基于字符串的模板引擎

这些模板引擎又分为两类:一种是不包含逻辑处理,只做数据绑定用的,如mustache.js;另一种是既有逻辑处理,也有数据绑定的,如ejs。

下面以EJS的语法为例,实现一个简单的字符串模板引擎、模板引擎的编译流程如下:

基于字符串的模板引擎编译原理

还是上面的例子,如果用EJS的语法来写,应该是这样的:

1
2
3
4
5
6
7
<div id="container">
<ul>
<% for (var i = 0; i < data.length; i++) {%>
<li><%= data[i] %></li>
<% } %>
</ul>
</div>

其中,<%= 和 %>之间的是JS表达式,而在<% 和 %> 之间是普通的JS语句,可以进行逻辑判断和条件循环等操作。


那么模板引擎需要做的事情是什么呢?就是将上述模板编译成第4小结里的js。

那我们现在比较一下第4小结里的js与所写模板的区别,就可以知道如何将模板编译成想要的js:
我们将例子中的模板字符串用template表示:

1
2
3
4
5
6
7
var template = `
<ul>
<% for(var i=0; i<data.supplies.length; i++) {%>
<li><%= data.supplies[i] %></li>
<% } %>
</ul>
`;

1.我们首先需要将普通html与模板语法区分开
这一步可以用正则表达式判断。

1
2
3
4
// 匹配JS语句
var senReg = /<%([\s\S]+?)%>/g;
// 匹配JS表达式
var expReg = /<%=(.+?)%>/g;

2.需要定义一个变量str,保存编译后的模板。普通的html无需任何处理,需当做字符串拼接到str上;JS表达式不是当做字符串而是直接拼接到str上;JS语句则不需要拼接,直接执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var str = '';
// 需要定义一个拼接函数
function echo(html) {
str += html;
}
template = template
//转换JS表达式
.replace(expReg, '`); \n echo( $1 ); \n echo(`')
// 转换JS语句
.replace(senReg, '`); \n $1 \n echo(`');
template = 'echo(`' + template + '`)';

// 内容为空的部分
var empty = /echo\(\"\"\);/g;
template = template.replace(empty, '');

到这一步,得到的template为:

1
2
3
4
5
6
7
template = 'echo(`<ul>`); 
for (var i = 0; i < data.length; i++) {
echo(`<li>`);
echo( data[i] );
echo(`</li>`);
}
echo(`</ul>`)';

3.此时,我们需要将上面写的代码封装成一个函数,并将模板字符串作为参数传递进去,该函数返回编译后的模板,该函数称之为编译函数compile。这里返回的模板应该是一个函数(实际是个字符串),并且参数是所需要的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function compile(template) {
// 匹配JS语句
var senReg = /<%([\s\S]+?)%>/g;
// 匹配JS表达式
var expReg = /<%=(.+?)%>/g;

template = template
//转换JS表达式
.replace(expReg, '`); \n echo( $1 ); \n echo(`')
// 转换JS语句
.replace(senReg, '`); \n $1 \n echo(`');
template = 'echo(`' + template + '`)';

// 内容为空的部分
var empty = /echo\(\"\"\);/g;
template = template.replace(empty, '');

// 这里借助了es6的语法${}
var script = `(function parse(data) {
var str = '';
// 需要定义一个拼接函数
function echo(html) {
str += html;
}

${template}

return str;
})`;
return script;
}

4.由于compile函数返回的是个字符串,要想该字符串转为真正的函数执行,需要借助eval,最后再将数据传给compile的子函数parse函数,这样就得到了想到的html结构,最后只需要插入dom即可。

1
2
3
var parse = eval(compile(template));
var output = parse(['喜洋洋', '懒洋洋', '美羊羊', '灰太狼', '红太狼']);
document.getElementById('container').innerHTML = output;

完整的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var parse = eval(compile(template));
var output = parse(['喜洋洋', '懒洋洋', '美羊羊', '灰太狼', '红太狼']);
document.getElementById('container').innerHTML = output;

function compile(template) {
// 匹配JS语句
var senReg = /<%([\s\S]+?)%>/g;
// 匹配JS表达式
var expReg = /<%=(.+?)%>/g;

template = template
//转换JS表达式
.replace(expReg, '`); \n echo( $1 ); \n echo(`')
// 转换JS语句
.replace(senReg, '`); \n $1 \n echo(`');
template = 'echo(`' + template + '`)';

// 内容为空的部分
var empty = /echo\(\"\"\);/g;
template = template.replace(empty, '');

// 这里借助了es6的语法${}
var script = `(function parse(data) {
var str = '';
// 需要定义一个拼接函数
function echo(html) {
str += html;
}

${template}

return str;
})`;
return script;
}

参考文章:Web前端模板引擎の字符串模板

《月亮和六便士》

Posted on 2018-12-31

《月亮和六便士》是英国作家毛姆所写,描述了一个平凡的伦敦证券经纪人思特里克兰德,突然对画画着魔,抛妻弃子,放弃了旁人看来优越美满的生活,奔赴南太平洋的塔希提岛,把生命的价值全部注入绚烂的画布的故事。

主要人物

  • 主人公思特里克兰德是伦敦的一个证券经纪人。大家对他印象是:不太爱说话,对文学艺术一点儿也不感兴趣,是一个忠厚老实、索然无味的普通人。他长得魁梧壮实,大手大脚,晚礼服穿在身上有些笨拙,给人的印象多少同一个装扮起来的参加宴会的马车夫差不多。他在夜校学习了一年的画画之后,抛妻弃子,到了巴黎去画画。当然,夜校老师包括其他人都觉得他并没有什么绘画天赋。在巴黎,他生活的穷困潦倒,画的画很少有人买,当然,他也很不情愿卖他的画。他似乎被某种力量控制了,像变了一个人,冷漠无情,说话尖酸刻薄,冷嘲热讽,从不给人留情面,幸灾乐祸,厚颜无耻,总让人觉得自己似乎很愚蠢。他一直说“我必须画画儿,由不得自己”。后来,由于一个船长,他到了塔希提岛,并与当地土著女子结婚,专心画画,一直到病死。也就是在塔希提岛,他创作出了伟大的作品。
  • 思特里克兰德太太,妻子面容和蔼可亲,端庄典雅,是一位能干的主妇和贤妻良母。虽然不美,但长得很讨人喜欢。可能为了满足内心的某种虚荣,喜欢结交各类文人名士。她对这些文人名士道德伦理上的奇行怪癖感到非常有趣,但是却一分钟也不想按照他们的方式调整自己的生活。小说对思特里克兰德太太的描写很多:
    • “除了她的声音很悦耳外,我没有发现她有什么特别的地方”
    • “说话不多,但是她也有一种可爱的本领,能够引导大家的谈话总是围绕着一个共同的话题;一出现冷场,她总能说一句合适的话使谈话继续下去”
    • “这一年37岁,身材略高,体态丰腴,但又不显得太胖。她生的并不美,但面庞很讨人喜欢,这可能主要归功于她那双棕色的、非常和蔼的眼睛。她的皮肤血色不太好,一头黑发梳理得非常精巧”。
    • 为什么思特里克兰德太太喜欢结交文人名士呢?小说是这样描述的:“她为自己创造了一个幻想的天地,生活于其中,感到日常生活所无无从享受到的自由。当她同作家结识以后,她有一种感觉,仿佛过去只能隔着脚灯瞭望的舞台,这回却亲身登上去了。她看着这些人粉墨登场,好像自己的生活也扩大了”
  • 施特略夫,是小说另一个有意思的人物,是一个滑稽角色。他身材又矮又胖,一张滚圆的脸,面色红润,皮肤很白,两颊同嘴唇却总是红通通的,一双蓝眼睛也生得滚圆,戴着一副金边大眼镜,眉毛淡到看不出来。他是一个画家,但他的画很一般,色彩总是很艳丽,俗不可耐。虽然他不是一个高明的画家,但对艺术却有敏锐的鉴赏力,他对绘画、音乐和文学的鉴赏力非常有见地。他总是受人嘲弄挖苦,同行毫不掩饰对他作品的笔试。他很大方,非常重感情,非常善良,虽然被人的嘲弄让他觉得痛苦不堪,但却从不停止给人制造嘲弄的机会,也从来不肯怀恨人。他认为思特里克兰德是个天才,了不起的画家。
  • 勃朗什,是施特略夫的太太,有一个美丽的身段,她的这种体型对雕塑家比对服装商更有吸引力。她的一头棕色的浓发样式很简单,面色白净,五官秀丽,但并不美艳。她曾是一个罗马贵族的家庭教师,被男主人欺骗了爱情,身临绝境准备自尽时,遇到了好心的施特略夫,并结了婚。由于施特略夫的热心,使她认识了思特里克兰德,一开始她非常讨厌思特里克兰德,认为他没有礼貌,而且很害怕他,后来才认识到自己的内心想法, 她喜欢思特里克兰德,并决定离开她的丈夫,跟思特里克兰德走。但后来发现思特里克兰德并不能给她想象中的生活,非常绝望,喝酸自尽。
  • 爱塔,是塔希提岛的土著居民,她在鲜花旅店打工,并置办了一小块地产。她喜欢思特里克兰德,并在店长的帮助下,与思特里克兰德结婚,过着简单幸福的生活。

分析

主题

六便士是当时英国货币的最小单位。月亮代表高高在上的理想,六便士则是现实的代表。作者探讨了生活与艺术的矛盾和相互作用。小说所揭示的逃避现实的主题,与西方许多人的追求相吻合。

艺术特色

  • 观念性:小说表面上是讲描写了主人公的命运和遭遇,实际上表达了自己对艺术与生活关系的思考。艺术的本质究竟是什么?如何处理艺术和经验的关系?传统表达手段是否可靠?驾驭现代思维的崭新形式?在经历了挫折后,主人公终于明白,艺术是具有极大的自主性独立性的东西,变换不同的叙述角度,就会得到不同的结局。现实生活是真实丑陋残酷无情的,因此表面上优美高雅的艺术只是对现实的粉饰,艺术的本质是虚假的。在小说中,主人公还表现出对传统艺术手段的不信任感,这种感觉使他在画画时困难重重,不得不寻找新的适合自己表达的新途径。
  • 人物的虚化:它的人物既不注重鲜明的个性化特征,也不是某种类型人物的代表,而往往是一种情欲一种精神的象征,人物形象模糊不清,像水墨画上远山背景一样做了虚化处理,给人一种捉摸不定感,读者需要根据作者布置的悬念、暗示、细节、启发或者某种总体氛围去猜测推断,才能逐渐发现隐含在人物背后的象征意义。这在主人公身上突出体现出来。他的行为总是具有不循常规的神秘性,不胜预防的突兀性。他在言语上沉默寡言,说起话来总是半吞半吐欲言又止,或者短小零碎;或者干脆躲开同读者的正面接触,或者通过别的见证人旁敲侧击的侧面暗示。这种表现一方面展示了他对语言的不信任感,语言已经无法表现自己对事物真实意义的探索,所以他在表达时不得不寻找能准确达意的词语,从而犹豫不决。另一方面,他的行为也显示了对暴露真实自我的恐惧和忧虑。最后干脆逃到了思特里克兰德,想以此掩饰真实的自我。主人公的这两种情况,无论是对言语的极度不信任感,还是对袒露自我的恐惧都显示出了很强的现代意味。

我的感受

读完这本书感觉作者深谙人性。比如在思特里克兰德太太说起思特里克兰德的时候:

“你知道,他一点儿也没有文学修养”,她说, “他是个十足的小市民”。她用这个词一点儿也没有贬抑的意思,相反地,倒是怀着一股深情,好像由她自己说出他最大的缺点就可以保护他不受她的朋友们的挖苦似的。

这就写出了我们平时自嘲的心理状态,哈哈哈。

再比如,有一段描述思特里克兰德的话:

他只不过是一个忠厚老实、索然无味的普通人。一个人可以钦佩他的为人,却不愿意同他待在一起。他是一个毫不引起人注意的人。他可能是一个令人起敬的社会成员,一个诚实的经纪人,一个恪尽职责的丈夫和父亲,但是在他身上你没有必要浪费时间。

我对这段话会感到共鸣,因为我觉得这似乎也是对我自己的一个描述(可能很多人都有这样的想法吧)。我觉得自己就是一个没有存在感的人,让人不会想花时间在自己身上。我一直想改变这种状态,我尽量充实自己,尽量去接触各种东西,以为懂得多一些,就会与别人有更多的共同语言,就会让人觉得我懂得很多,让人越来越觉得我很重要。哈哈哈~当然这几句与小说无关,只是把自己的一些心里想法写出来而已。

语录

  • 人们满不在乎地谈论美,由于他们说话并不经过深思熟虑,所以美这个词被用的太过泛滥,已经失去了原有的力量。许许多多微不足道的东西都冠以它的名义,于是它所代表的东西变得不再崇高。人们用美来形容裙子、小狗和布道,当遇到真正的美时,他们却又认不出来。人们试图用这种本末倒置来装饰他们毫无价值的思想,结果反而钝化了他们对美的感受力。

  • 卑鄙与伟大、恶毒与善良、仇恨与热爱是可以互不排斥地并存在同一颗心里的

  • 魔鬼要干坏事总可以印证《圣经》
  • 这世上有的是怪人怪事,也许大溪地居民更能理解,大多数人所成为的,并非是他们想成为的人,而是不得不成为的人。
  • 同情体贴是一种很难得的本领,但是却常常被那些知道自己有这种本领的人滥用了
  • 我觉得有些人就是生错了地方。造化弄人,他们被抛到某处,却惦念着一个隐约朦胧的故乡。出生地则是异乡,从小熟悉的绿荫小巷、曾经玩耍的拥挤闹市,都只是沿途风景。他们在亲友中也许一辈子都落落寡合,对自己唯一熟悉的环境淡然疏离。也许正是这种陌生感促使他跑遍千山万水寻觅自己永恒的归宿。也许有某种根深蒂固的返祖欲,促使迷途者返回祖先在鸿蒙初辟时离开的故土。有时一个人偶然来到某地,会有莫名其妙的归属感。这就是他寻找的家园,他将融入自己从未见过的环境,与从未谋面的人相伴,似乎生来就和这一切相熟,在这里他终得安歇。
  • 这正是小说的虚幻所在。对男人来说,爱情往往不过是日常生活纷纷事务中的一个片段,小说却把爱情推崇到根本不现实的重要地位。少数男人会拿爱情当人生头等大事,但这种人没什么意思,就连爱情至上的女人也会轻视他们,被他们追求的女人,固然受了奉承兴奋激动,心里却不怎么舒服,觉得他们没出息。
  • 也许,要品味生活的浪漫,你得有点儿演员精神,必须像个旁观者,对自己的所作所为既超然事外又忘我投入
  • 钟爱神话是人类天性。大家贪婪地抓住杰出人物生活中令人诧异或迷惑的事,捏造出自己深信不疑的传说。这是浪漫情调对平庸现实的抗议,传奇故事是人物步入永恒殿堂的最佳武器。

当我们在谈论前端架构时,我们到底在谈论什么(转)

Posted on 2018-12-13

架构到底是什么?前端架构又是什么?

我们先看维基百科对软件架构的定义。

软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件。
各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。
在面向对象领域中,组件之间的连接通常用接口来实现。

传统架构的离家近和前端架构的理解略有不同,这个稍后讨论,我们先看看传统意义上对于软件架构的定义。

这两句话里可以总结出几个核心名词出来,抽象、解耦、组合,而架构的实际工作,其实就是对这些架构方法和实际场景的梳理把握。

传统意义上的架构师,在实际项目层面,高级些的负责整个系统的整体分解服务分层设计等,而低级的架构师,则负责其中某些模块的系统分析。在项目产出上看,分别是架构图和系统分析图。

架构图体现的是整个大型服务包含的模块,及其运行关系。而系统分析,则是每个服务内部的具体逻辑以及与外部服务接驳的方式。

软件工程师在拿到这些分析之后,就可以用框架将其按照架构师的逻辑来实现,这种工作方式,可以保证大型软件的系统合理解耦,并且可以合理实现。

而对于软件工程师这一环来说,他们无需关心整个系统是如何运行的,只需要按照系统分析来实现自己部分的逻辑,将大型软件工程化。

这里就引申出了前端架构的重点。

其实,前端在整个软件工程中扮演的只是其中的一部分,它的定位较为特殊,不是独立的子系统,却又跨域于整个系统之间,而且最重要的特点是它的内部极为分散。

这就造就了我们无法用传统的软件架构来定义“前端架构”这个词汇。实际上,通常所说的前端架构在整体软件工程中扮演的是架构之下,代码之上的一个层面,它关注的不是整个系统的解耦和组合,而是横向的面的开展效率和一致性。(这里排除了非常复杂的SPA应用的场景)

很多前端同学其实对于“架构”这个词非常的困惑,我觉得没有必要去斟字酌句,非要将它做成一个固定的职位或者职责。

在项目层面,甚至某个页面层面,遇到复杂的交互和数据逻辑,你做一个抽象,你抽离组件,你设计组件的参数和内部状态,这些是不是架构?当然是!它是一个微小系统的内部架构。

前端是一个和服务端工程完全悖论的领域,服务端可能从整体来看就是一整个系统,然后抽象,然后分层,最后组合,到了细化的软件层面的时候,就是一些固定的组合逻辑。

而对于前端来说,没有整体的概念,一个公司的前端,必然是分散于各个业务各个系统之间的,虽然这些业务系统最后可能也是一个整体。

但是对于前端来说,他们就是分散的,反而在每个前端系统内部,又有一套软件工程的思想,像我刚才说的,针对一个页面进行解耦抽象组合。

所以,我们总结前端架构的第一层含义:某个系统内部的逻辑抽象和组合。

我们继续观察前端系统的特点,前面也提到了,前端的系统是分散的,这个分散不光是最终实现上的分散,甚至连刚才提到的抽象组合也是分散的,甚至在团队上也是分散的,这样分散的局面,如何变得可控而让整个前端开发变成一个工程式的工作?这就引申出了前端届最重要的一个词汇:工程化。

实际上,这就是我们要的总结的前端架构第二层含义:中立于各个系统又植根于各个系统中的前端工程化。

工程化的核心关注点是什么?可控,效率!

事实上,接下来讲的一切都是围绕这两点,由【可控】,我们分化出开发框架、开发规范,甚至是脚手架的一些开发约束,还有诸多开发流程和开发工具的保障,诸如Review机制和Eslint检查、线上错误报警等。

由【效率】,我们分化出组件库复用跨业务的组件,脚手架将整个流程封装进几个固定的命令,mock系统快速模拟数据等。

架构师的工作方式和职责

针对上述对架构的定义,我们来看看具体在日常中如何开展架构工作。通常,对于一个成熟的前端团队来说,建立一个专门的架构师小组来说是一个比较合理的结构。

这个小组可以是虚拟的,也可以是专门的脱离于项目之外的,特别是业务开发较多时其实工程化需要做的事情还是很多的。

再聊聊架构师的工作方式,首先要抛弃传统的工作思想,之前在业务团队的时候,很多事情都是有专门的人来安排(诸如PM、PD),然后一些技术上的事情则往往草草产出,在成立架构组织后这两个方面都会发生很大的变化。

  1. 脱离于业务之外后,对自我规划、自我任务和时间管理的要求变高了,要有非常强的自我管理意识。
  2. 技术的产出要有严格的流程,因为你产出的是通用方案,要保证技术方案的质量,这时候需要有一套流程,从发现问题到调研到初步方案到评审确认可行性到详细架构系统分析到开始编码到推广到文档。
  3. 之前一整个项目组一起做的流程,现在可能都落到了你头上,看似繁琐但是却是毕业的,因为你是一个专业的架构师。

架构流程

这里我特别想强调下这种思维的转换,程序员通常比较厌恶这种流程上的事情,喜欢自己捣鼓研究敲代码,殊不知其实对于程序员来说最简单的事情其实就是敲代码,如果你一直想敲代码不想做设计/规划/推广,那绝对是在精神换上偷懒。

而有挑战的事情是什么呢?是设计,设计数据结构,设计组件,设计解决方案。更有挑战的则是将这些设计做完美,做通用,并落地。

所以,做架构绝非只是一直在做有意思的事情,底层的调研,代码的实现只是其中一部分,一个很重要的自我衡量的标准就是工作时间中最多只有20%是在写代码,而且越少越好,正如上面所说,你的工作是设计落地完善的通用方案,解决特定的问题,而不是玩新技术给团队挖坑。

转换了工作角色之后,我们再来聊聊架构的职责。

  1. 宏观上,把控整个团队的技术选型和技术栈,技术发展方向。这看似是一次性的工作,但是却是需要持续优化的。例如推动整个公司向Vue或者React转换,推进ReactNative的实施,需要架构师对这些技术栈有深入的了解,能够正确的权衡选型,评估风险,并且给出切实可落地的方案。
  2. 各个技术栈的技术体系。业务开发的技术体系,包含脚手架,开发框架,开发规范,组件库,配套工具等。

    • 细说起来其实是个挺庞大的事情,每一部分都可以展开成一个话题。例如脚手架,其实是在管理规范或者简化一个业务项目从创建到开发到调试到测试到发布的整个过程(通常会做诸多定制)

    • 例如开发框架,抛开底层的mvvm框架,上层需要做封装,将一些难以理解的概念或者写法繁琐的东西封装起来,同时糅合一些强制的开发规范,还有就是通过框架层规避开发中可能会出问题的风险点(例如数据类型转换之类)

    • 这里提到的每个点可能都是个大工程,而且可能会有好几套体系,例如Vue体系,React native体系。

    • 其中有些体系甚至要做到让不是前端的开发去写前端,稍后我会介绍。其封装、规范、工具需要做到简单强大的程度可想而知。

  3. 统一环境管理,开发发布流程制定等。制定公司统一的前端开发方式/流程,中间可能会需要一些工具来提高开发效率,推进这些工具如何融入流程被大家接受等。

    • 还有前端开发需要的测试、预发、线上环境资源管理的方式、权限等。

    • 但是业务开发需要做的事情可能就一两步而且非常简单,这种维护方式转化成最终集成结果,把复杂的方案包装成极简的使用方式,这也是架构的重要职责之一。

  4. 提供特殊解决方案,例如服务端渲染,可能原理不难,但是需要有专人将其构建成低入侵、高性能、高可靠、统一的服务集群,业务可以非常低成本的接入,并且不需要关心起运维、可用性、性能问题。

    • 其他例如 数据统计大点、跨端调用等,都需要做一些统一的封装处理,让业务方方便使用。
  5. 提供一些特殊的工具和系统,例如性能收集,错误收集,mock系统,在线调试,可视化编辑,短链管理等。

  6. 提供业务中除了UI组件之外公用的部分业务,独立维护,我们称之为业务SDK,例如跟金融相关的钱包业务,数据业务,聊天业务,全景业务,都会作为独立的业务系统服务于其他业务。

转自https://juejin.im/entry/59800fe651882537d00e0179

前端简史(转)

Posted on 2018-12-11

前端从无到有,经历了什么?

1995年5月,布兰登·艾奇(Brendan Eich,javascript之父)花了10天时间写了第一版javascript。从这时候开始,web开发诞生,为后来前后端的职位细分,奠定了基础。

正像人类发展史一样,前端开发的历史也经历了几个阶段,回顾整个阶段,我们可以看到前端开发的历史是随着浏览器及相关的javascript api的完善逐渐变化的,10年前,我们关注切图,布局,10年后我们关注移动端体验,多端打通。正式介绍这几个阶段之前,我们先来简单回顾一下第一版js诞生到现在,那些重要的历史时刻:

1
2
3
4
5
6
7
1994年12月,Netscape公司发布了Navigator 1.0版,市场份额一举超过90%。
1995年12月4日,Netscape公司与Sun公司联合发布了Javascript语言
1996年3月,Navigator 2.0浏览器正式内置了Javascript脚本语言
1996年11月,Netscape公司决定将Javascript提交给国际标准化组织ECMA
1997年7月,ECMA组织发布263号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为ECMAScript。这个版本就是ECMAScript 1.0版
2011年6月,ECMAScript 5.1版发布,并且成为ISO国际标准
2015年6月,ECMAScript 6正式发布,并且更名为ECMAScript 2015

周边生态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1996年,样式表标准CSS第一版发布
2001年,微软公司时隔5年之后,发布了IE浏览器的下一个版本IE 6,这是当时最先进的浏览器
2002年,Mozilla项目发布了它的浏览器的第一版,后来起名为Firefox
2003年,苹果公司发布了Safari浏览器的第一版
2004年,Google公司发布了Gmail,促成了互联网应用程序(Web Application)这个概念的诞生。
2004年,WHATWG组织成立,致力于加速HTML语言的标准化进程
2005年,Ajax方法正式诞生,Jess James Garrett发明了这个词汇。它开始流行的标志是,2月份发布的Google Maps项目大量采用该方法。它几乎成了新一代网站的标准做法,促成了web 2.0时代的来临。
2006年,jQuery函数库诞生,作者为John Resig。
2008年,V8编译器诞生
2009年,Node.js项目诞生,创始人为Ryan Dahl,它标志着Javascript可以用于服务器端编程,从此网站的前端和后端可以使用同一种语言开发。
2013年5月,Facebook发布UI框架库React,引入了新的JSX语法,使得UI层可以用组件开发
2015年3月,Facebook发布了React Native项目,将React框架移植到了手机端
2015年vuejs发布1.0版本
2016年vuejs2.x版本发布

有些历史时刻,是对前端开发产生了深远影响的,比如ie6的发布,jQuery的发布,ajax的流行,知历史,我们可以预判到前端的未来发展方向。,知历史,我们可以打通知识体系,尤其是资历尚浅的前端同学。

下面正式介绍前端开发的几个阶段:

天地初开

中国历史,传说中是始于盘古开天辟地,前端的历史始于javascript的诞生,网景公司在发布0.9版本的Navigator浏览器后,立马发现一个问题:Navigator只能浏览页面,浏览器无法与用户互动。比如你登录一个网站输入完用户名点击提交的时候,浏览器并不知道你是否输入了,也无法判断。只能传给服务器去判断。网景公司急于解决浏览器与用户交互这个问题,当时解决这个问题有两个办法,一个是采用现有语言,比如Perl、Python、Tcl、Scheme等,允许他们直接嵌入网页。另一个是发明一种全新的语言。此时,刚入职的34岁的系统程序员Brendan Eich登场了,Brendan Eich的主要方向和兴趣是函数式编程,网景公司招聘他的目的,是研究将Scheme语言作为网页脚本语言的可能性。然鹅,1995年5月,也就是Brendan Eich入职一个后,网景公司做出决策,未来的网页脚本语言必须“看上去与java足够相似”,但是比java简单,使得非专业的网页作者也能很快上手。这个决策实际上将Perl、Python、Tcl、Scheme等非面向对象的语言都排除在外了。Brendan Eich被指定为这种“简化版Java语言”的设计师。但是,他对java一点儿兴趣也没有,为了应付公司安排的任务,他只用了10天时间就把javascript设计出来了…

总的来说,他的设计思路是这样的:

(1)借鉴C语言的基本语法
(2)借鉴java语言的数据类型和内存管理
(3)借鉴Scheme语言,将函数提升到“第一等公民”(first class)的地位
(4)借鉴Self语言,使用基于原型(prototype)的继承机制

所以,javascript实际上是两种语言风格的混合产物—-(简化的)函数式编程+(简化的)面向对象编程。这是由Brendan Eich(函数式编程)和网景公司(面向对象编程)共同决定的。

由于设计时间太短,语言的一些细节考虑得不够严谨,导致后来很长一段时间,javascript写出来的程序混乱不堪。

the part that is good is not original, and the part that is original is not good. (它的优秀之 处并非原创,它的原创之处并不优秀。)[十八世纪英国文学家约翰逊博士]

不管怎样,javascript诞生了,它的设计之初很简单,就是为了解决浏览器上表单提交的人机交互,而作为一种脚本语言,它天生的设计缺陷为后来的大型应用程序开发,留下了隐患。

石器时代

大约2007年,前端处在基于table(表格)布局的时代。
哪个时候没有前后端之分,web工程师真的是“全栈”,写的了后端,套的了表格,查的了数据库,写的了js,js主要承载的作用是网页特效(对,如果有前端的话,其实也是写点特效)。

js写的代码,一般以内联方式放在页面的任何地方,就像一块一块的补丁,看了让人难受,不过还好,这个阶段很快就结束了。(也许很多很烂的政府网站还在用table布局也说不定)

铜器时代

2001年发布的ie6当时是世界上最先进的浏览器
2004年2月9日,Mozilla Firebird改称为“Mozilla Firefox”,简称“Firefox”
在2006年年尾,微软发布了rebranded代号的IE7
在2008年3月6日,微软发布了IE8的第一个公开测试版本
2008年9月2日,Chrome beta测试版本发布

铜器时代,是令人悲伤的一个阶段,因为这个阶段终于有前端开发工程师这个职位了,但是主要工作居然是处理浏览器兼容性问题,上面是那个时代的浏览器情况,虽然ie6在2001年就发布了,但是它持续的时间太长,2012年貌似淘宝才不兼容ie6,大部分的时间,前端同学要兼容ie6、ie7、ie8、ie9、FF、chrome、safari…

大部分的css代码里会有这样熟悉的hack存在:

1
2
3
4
5
6
7
8
9
10
11
.hack{  
background-color:red; /* All browsers */
background-color:blue !important;/* All browsers but IE6 */
*background-color:black; /* IE6, IE7 */
+background-color:yellow;/* IE6, IE7*/
background-color:gray\9; /* IE6, IE7, IE8, IE9, IE10 */
background-color:purple\0; /* IE8, IE9, IE10 */
background-color:orange\9\0;/*IE9, IE10*/
_background-color:green; /* Only works in IE6 */
*+background-color:pink; /* WARNING: Only works in IE7 ? Is it right? */
}

那个时候的js代码已经不再内联在页面代码里,转而以文件的方式引入,类似这样:

1
2
3
4
<script src=“../your/code/a.js”></script>
<script src=“../your/code/b.js”></script>
<script src=“../your/code/c.js”></script>
<script src=“../your/code/d.js”></script>

但是,模块化,依旧毫无头绪,就像上边的代码,如果功能复杂的模块,维护起来难度较大,打包工具基本是借助别人家的ant或者YUI Compressor,前端工程师这个阶段的职责已经开始开发复杂的应用,另外值得注意的是后端MVC的架构开发模式逐渐成熟。

前端的架构模式呢?不知道在哪里。

铁器时代

2006年,Jquery诞生了,时至今日,jquery之所以这么成功,就是处理了大量的浏览器兼容性问题!

1
$('J_Hook').html('haha');

这样的代码让人重拾对web开发的乐趣,jquery诞生后,各种基于jquery的组件铺天盖地而来,随着html5+css3的支持程度的加强,好日子终于来了。2010年,随着模块加载器(LABjs、RequireJS、SeaJS)的涌现,前端开发的生产效率大幅提高,前端真正可以去关注业务本身,而不用投入太多精力去处理兼容,处理模块关系了,前端的代码可能类似这样了:

1
2
<script src=“js/jquery.min.js”></script>
<script src="js/require.js" data-main="js/main"></script>

随着2004年ajax技术的出现,异步加载/按需加载盛行起来,我亲身经历了2010~2014年淘宝瀑布流布局的兴起,那时候的前端的代码可能是这样的:

1
2
3
4
5
6
7
8
<!—— your dom element ——>
<div class=‘J_RenderCont’></div>

<!—— your javascript source ——>
<script src=“js/jquery.min.js”></script>
<script s

rc="js/require.js" data-main="js/main"></script>

vm中很少的html代码作为钩子,大部分内容使用js异步渲染的方式在今天的大部分网站非常常见,并将持续很久。

蒸汽时代

终于来到了蒸汽时代,生产效率成数量级似的快速增长,这个时代有两件事,特别有历史意义:

2009年5月,Ryan Dahl在Github上发布了最初版本的部分Node.js包
2010年6月8日凌晨1点,史蒂夫·乔布斯在美国Moscone West会展中心举行的苹果全球开发者大会(WWDC 2010)上发布了苹果第四代手机iPhone4

第一件事,对于前端的意义是前端同学可以一个人搞定全栈开发了;
第二件事,对于前端的意义是前端同学可以开发在手机上访问的应用了;
前端迎来了一个逆天的时代,一个最美好的时代,一个基于Nodejs开发的时代

Nodejs是一个能够在服务器端运行js的开放源代码,跨平台js运行环境
与js语法相同,只是少了浏览器相关的环境(DOM、BOM之类)
核心模块包括文件系统I/O、网络(HTTP、TCP、UDP、DNS、TLS/SSL等)、二进制数据流、加密算法、数据流等等

那么nodejs都能做什么呢?

  1. web框架:express koa
  2. im及时聊天:socket.io
  3. api包装:移动端、pc、h5
  4. http proxy(淘宝首页)/ http proxy延伸,组装rpc服务,作为微服务的一部分
  5. 前端构建工具:grunt/gulp/bower/webpack/fis3
  6. OS: nodeOS
  7. 跨平台打包工具:nw.js、electron、cordova/phonegap
  8. 编辑器:atom、vscode

哦,为了给其他端工程师留点面子,就不再列举下去了,你只需要知道阿特伍德定律就可以了:

any application that can be written in JavaScript, will eventually be written in JavaScript

nodejs给前端带来了空前强大的利好,智能手机却给前端带来了前端未有的跳转:

  1. 面向多终端的开发(pc端、移动端)
  2. 很多新概念产生:响应式设计、多端适配
  3. 移动端js框架(库): zepto、jquery-mobile、kimi、vue、react
  4. 性能调优:首屏渲染、懒加载、webp、300ms延迟、css3、canvas动画

相比于pc端,手机端的硬件网络都受限,前端只能继续挖掘自身的经验并等待着手机硬件的加强(好消息是到17年,智能手机的硬件和依赖的网络情况越来越好)。
是选择native还是hybrid?no,我们有weex和react-native,磨平三端的差异已经是必然趋势,说不定未来的某一天,js已经可以直接搞定三端开发,调用系统硬件,处理流畅的动画和人机交互。
说道这里,其实前端开发简史2017年之前的部分已经结束了,但是作为前端的一份子,我们可以大胆的憧憬一下下一个时代——基于js的生态时代。

基于js的生态时代

这个时代的典型特点是:js已经不再单纯的承担页面脚本的职责,他可以构建复杂的企业应用。
各种细分领域,层出不穷,原来想也不敢想的事情,今天都可以用js去做,前端的未来10年将会是多彩纷呈的繁荣景象,现在已经有的领域:

web框架:
vue.js
react
angularjs

数据可视化:
D3.js
Echarts
hichcharts

移动端打通:
weex
react native

桌面软件:
electron
nw.js

游戏:
cocos2d-x

VR/AR:
aframe
three.js
react-vr

企业应用:
express
koa
egg.js

硬件/互联网:
Ruff

操作系统:
os-js

感谢作者https://zhuanlan.zhihu.com/p/29924966

Object.assign

Posted on 2018-10-15

语法:
Object.assign(target, …source)

1、Object.assign是将一个或多个源对象的自身的且可枚举的属性复制到目标对象。即,继承属性和不可枚举属性是不能拷贝的。

1
2
3
4
5
6
7
8
9
10
11
12
var obj1 = Object.create({foo: 1}, { // foo是个继承属性
bar: {
value: 2, // bar是个不可枚举属性。
},
baz: {
value: 3,
enumerable: true, // baz是个自身可枚举属性。
}
});

var copy = Object.assign({}, obj);
console.log(copy); // {baz: 3}

2、 该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关getter和setter。
3、 String类型和Symbol类型的属性都会被拷贝

1
2
3
4
5
var obj1 = {a: 1};
var obj2 = {[Symbol('foo)]: 2};
var obj = Object.assign({}, obj1, obj2);
console.log(obj); // {a: 1, [Symbol('foo')]: 2}
Object.getOwnPropertySymbols(obj); // [Symbol(foo)]

4、 在出现错误的情况下,会打断后续拷贝任务,但已拷贝的会保留在目标对象

1
2
3
4
5
6
7
8
9
var target = Object.defineProperty({}, 'foo', {
value: 1,
writable: false,
}); // target的foo属性是个只读属性

Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4});
// TypeError: 'foo' is read-only
// 注意这个异常是在拷贝第二个源对象的第二个属性时发生的
console.log(target); // {bar: 2, foo2: 3, foo: 1}

5、 不会跳过值为null或undefined的对象

6、 针对深拷贝,需要使用其他方法。因为Object.assign()拷贝的是属性值。如果源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj1 = {a: 0, b: {c: 0}};
var obj2 = Object.assign({}, obj1);
console.log(JSON.stringify(obj2)); // {a: 0, b: {c: 0}}

obj1.a = 1;
console.log(JSON.stringify(obj1)); // {a: 1, b: {c: 0}}
console.log(JSON.stringify(obj2)); // {a: 0, b: {c: 0}} obj2.a不会受影响

obj2.b.c = 3;
console.log(JSON.stringify(obj1)); // {a: 1, b: {c: 3}} obj1.b.c也会变化
console.log(JSON.stringify(obj2)); // {a: 0, b: {c: 3}}

// 深拷贝
obj1 = {a: 0, b: {c: 0}};
var obj3 = JSON.parse(JSON.stringify(obj1));
obj1.a = 4;
obj1.b.c = 4;
console.log(JSON.stringify(obj3)); // {a: 0, b: {c: 0}}

7、原始类型会被包装成对象

1
2
3
4
5
6
7
8
9
10
var v1 = "abc";
var v2 = true;
var v3 = 10;
var v4 = Symbol('foo');

var obj = Object.assign({}, v1, null, v2, undefined, v3, v4);

// 原始类型会被包装,null和undefined会被忽略
// 注意,只有字符串的包装对象才可能有自身可枚举属性
console.log(obj); // {0: 'a', 1: 'b', 2: 'c'}

按位非~妙用

Posted on 2018-09-26

Object.values、Object.keys、OBject.entries

Posted on 2018-09-26

HTTP代理

Posted on 2018-09-16

HTTP代理

代理的实际作用就是一个带有控制的转发服务器,在面对客户端的时候,起着服务器的作用;在面对服务器的时候,起着客户端的作用。

不同代理有不同的作用,常见的作用有:缓存代理、访问控制、反向代理。

缓存代理

当客户端请求一个资源的时候,在客户端和服务器之间可能存在一个代理服务器,该代理服务器会检查自己的缓存中是否有当前客户端请求的资源,并且是否过期。

如果资源过期或者资源不存在,代理服务器会向服务器请求资源并将该资源缓存下来。

采用缓存代理可以提高请求速度、降低原始服务器的压力。

访问控制

在内网与外网的某个交汇点增加一个代理服务器,当请求通过该代理时,代理会对请求进行处理,比如分类、身份鉴定等。

正向代理

正向代理,也叫做前向代理。

正向代理建立在客户端一侧,由代理来伪装客户端,使得多个客户端在服务端眼里是同一个客户端。

VPN就是一种正向代理。

反向代理

反向代理,建立在服务端一侧,在客户端眼中,代理服务器就是服务器,而真正的服务器对客户端是透明的。

反向代理一般用于负载均衡。

当一个请求到达时,反向代理会判断将该请求发到哪个服务端,客户端收到的相应报文是来自全局最优的服务端提供的服务。

1234…8
xixijiang

xixijiang

切莫停下前进的脚步

74 posts
1 categories
12 tags
© 2019 xixijiang
Powered by Hexo
Theme - NexT.Muse