235 | ......
236 | ```
237 | 自 8 月 18 日起到 1 月 13 日今日我在公司项目的提交次数达到 988 次。感觉有点多了。提交 commit 的质量可能需要控制一下。
238 |
239 | - 统计自己的代码提交量,包括增加,删除:
240 |
241 | ``` bash
242 | $ git log --author="$(git config --get user.name)" --pretty=tformat: --numstat | gawk '{ add += $1 ; subs += $2 ; loc += $1 - $2 } END { printf "added lines: %s removed lines : %s total lines: %s\n",add,subs,loc }' -
243 | 得到结果如下
244 | added lines: 15490 removed lines : 57870 total lines: -42380
245 | ```
246 | 上面这个情况确实只能说明我重(shan)构(chu)了不少冗余东西=. =。不过马上这个项目又要基于 ES6 + Vue 重写一下。只能说可能发展快速、人员更迭迅速的创业公司的项目可能或多或少都存在这种代码质量难以保证的情况吧?
247 |
248 | - 统计仓库提交者排名前 5
249 |
250 | ``` bash
251 | $ git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r | head -n 5
252 | 与shortlog -sne显示的内容差不多
253 | ```
254 |
255 | - 贡献者数量统计
256 |
257 | ``` bash
258 | $ git log --pretty='%aN' | sort -u | wc -l
259 |
260 | 39
261 | ```
262 | 这个项目前后有 39 个人曾参与过,创业公司..大家应该都懂..
263 |
264 |
--------------------------------------------------------------------------------
/由一次浏览器控件重绘问题详谈浏览器重排、重绘机制.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 由一次浏览器控件重绘问题概述浏览器重排、重绘、渲染机制
3 | date: 2015-08-25 13:44:41
4 | categories: 浏览器
5 | tags: [浏览器,前端知识,页面]
6 | ---
7 |
8 | ## 问题描述
9 | 今天遇到了传说中的页面重排 BUG,导致页面本来应该显示出来的内容在页面上显示不出来,具体效果及代码大概如下。
10 |
11 | 
12 |
13 | 从图中可以看到浏览器 `Elements` 面板中已经包含了本该显示的元素,但是页面中却没有显示,只是保留了元素占用的空间。而只要再点击一次触发一次页面重排,或是修改 `background-color` 属性,触发一次页面重绘,就可以恢复正常。
14 |
15 | 在正常的重绘过程中,浏览器会执行重绘如下图,而在上述问题中,则没有此重绘操作。
16 | 
17 |
18 | 这里的实现代码大致如下:
19 |
20 | 
21 |
22 | 可见这里 append 的方式由于浏览器重排并未渲染页面造成了不显示的 BUG。通过重排机制,我们添加了类似这样的触发重排的代码。
23 |
24 | 
25 |
26 | 问题得到修复
27 | 
28 |
29 | ## 相关知识
30 | Web 开发者 Alexander Skutin 写过一篇文章,讲述的比较详细,这里主要引用自其文章及其译文:
31 | [原文地址](http://frontendbabel.info/articles/webpage-rendering-101/)
32 | [[译]有关网页渲染,每个前端开发者都该知道的那点事](http://segmentfault.com/a/1190000002907021)
33 |
34 | ### 浏览器是如何完成网页渲染?
35 |
36 | 首先,我们回顾一下网页渲染时,浏览器的动作:
37 |
38 | 1. 根据来自服务器端的 HTML 代码形成文档对象模型(DOM)
39 |
40 | 2. 加载并解析样式,形成 CSS 对象模型。
41 |
42 | 3. 在文档对象模型和 CSS 对象模型之上,创建一棵由一组待生成渲染的对象组成的渲染树(在 Webkit 中这些对象被称为渲染器或渲染对象,而在 Gecko 中称之为 `frame`)渲染树反映了文档对象模型的结构,但是不包含诸如 `` 标签或含有 `display:none` 属性的不可见元素。在渲染树中,每一段文本字符串都表现为独立的渲染器。每一个渲染对象都包含与之对应的 DOM 对象,或者文本块,还加上计算过的样式。换言之,渲染树是一个文档对象模型的直观展示。
43 |
44 | 4. 对渲染树上的每个元素,计算它的坐标,称之为布局。浏览器采用一种流方法,布局一个元素只需通过一次,但是表格元素需要通过多次。
45 |
46 | 5. 最后,渲染树上的元素最终展示在浏览器里,这一过程称为 `painting`。
47 |
48 | 当用户与网页交互,或者脚本程序改动修改网页时,前文提到的一些操作将会重复执行,因为网页的内在结构已经发生了改变。
49 |
50 | ### 重绘
51 |
52 | 当改变那些不会影响元素在网页中的位置的元素样式时,譬如 `background-color`,`border-color`,`visibility`,浏览器只会用新的样式将元素重绘一次(这就是重绘,或者说重新构造样式)。
53 |
54 | ### 重排
55 |
56 | 当改变影响到文本内容或结构,或者元素位置时,重排或者说重新布局就会发生。这些改变通常由以下事件触发:
57 |
58 | - DOM 操作(元素添加,删除,修改,或者元素顺序的改变);
59 | - 内容变化,包括表单域内的文本改变;
60 | - CSS 属性的计算或改变;
61 | - 添加或删除样式表;
62 | - 更改“类”的属性;
63 | - 浏览器窗口的操作(缩放,滚动);
64 | - 伪类激活(:hover 悬停);
65 |
66 | ### 浏览器如何优化渲染?
67 |
68 | 浏览器尽可能将重绘/重构 限制在被改变元素的区域内。比如,对于位置固定或绝对的元素,其大小改变只影响元素本身及其子元素,然而,静态定位元素的大小改变会触发后续所有元素的重流。
69 |
70 | 另一种优化技巧是,在运行几段 JavaScript 代码时,浏览器会缓存这些改变,在代码运行完毕后再将这些改变经一次通过加以应用。举个例子,下面这段代码只会触发一个重构和重绘:
71 |
72 | ``` js
73 | var $body = $('body');
74 | $body.css('padding', '1px'); // reflow, repaint
75 | $body.css('color', 'red'); // repaint
76 | $body.css('margin', '2px'); // reflow, repaint
77 | // only 1 reflow and repaint will actually happen
78 | ```
79 |
80 | 然而,如前所述,改变元素的属性会触发强制性的重排。如果我们在上面的代码块中加入一行代码,用来访问元素的属性,就会发生这种现象。
81 |
82 | ``` js
83 | var $body = $('body');
84 | $body.css('padding', '1px');
85 | $body.css('padding'); // reading a property, a forced reflow
86 | $body.css('color', 'red');
87 | $body.css('margin', '2px');
88 | ```
89 |
90 | 其结果就是,重排发生了两次。因此,你应该把访问元素属性的操作都组织在一起,从而优化网页性能。
91 |
92 | 有时,你必须触发一个强制性重排。比如,我们必须将同样的属性(比如左边距)两次赋值给同一个元素。起初,它应该设置为 100px,且不带动效。接着,它必须通过过渡 (`transition`) 动效改变为 50px。在这儿我们来更详细地介绍它。
93 |
94 | 首先,我们创建一个带过渡效果的 CSS 类:
95 |
96 | ``` css
97 | .has-transition {
98 | -webkit-transition: margin-left 1s ease-out;
99 | -moz-transition: margin-left 1s ease-out;
100 | -o-transition: margin-left 1s ease-out;
101 | transition: margin-left 1s ease-out;
102 | }
103 | ```
104 |
105 | 然后继续执行:
106 |
107 | ``` js
108 | // our element that has a "has-transition" class by default
109 | var $targetElem = $('#targetElemId');
110 |
111 | // remove the transition class
112 | $targetElem.removeClass('has-transition');
113 |
114 | // change the property expecting the transition to be off, as the class is not there
115 | // anymore
116 | $targetElem.css('margin-left', 100);
117 |
118 | // put the transition class back
119 | $targetElem.addClass('has-transition');
120 |
121 | // change the property
122 | $targetElem.css('margin-left', 50);
123 | ```
124 |
125 | 然而,这个执行无法奏效。所有改变都被缓存,只在代码块末尾加以执行。我们需要的是强制性的重排,我们可以通过以下更改加以实现:
126 |
127 | ``` js
128 | // remove the transition class
129 | $(this).removeClass('has-transition');
130 |
131 | // change the property
132 | $(this).css('margin-left', 100);
133 |
134 | // trigger a forced reflow, so that changes in a class/property get applied immediately
135 | $(this)[0].offsetHeight; // an example, other properties would work, too
136 |
137 | // put the transition class back
138 | $(this).addClass('has-transition');
139 |
140 | // change the property
141 | $(this).css('margin-left', 50);
142 | ```
143 |
144 | 现在代码如预期那样执行了。
145 |
146 | ### 有关性能优化的实际建议
147 | 总结现有的资料,提出以下建议:
148 |
149 | - 创建有效的 HTML 和 CSS 文件,不要忘记指明文档的编码方式。样式应该包含在
150 | ``标签内,脚本代码则应该加在 `` 标签末端。
151 | - 尽量简化和优化 CSS 选择器(这种优化方式几乎被使用 CSS 预处理器的开发者统一忽视了)将嵌套程度保持在最低水平。以下是 CSS 选择器的性能排名(从最快者开始)
152 |
153 | 1. 识别器: #id
154 | 2. 类: .class
155 | 3. 标签:div
156 | 4. 相邻兄弟选择器:a + i
157 | 5. 父类选择器:ul> li
158 | 6. 通用选择器:*
159 | 7. 属性选择:input[type="text"]
160 | 8. 伪类和伪元素:a:hover
161 |
162 | 你应该记住,浏览器在处理选择器时依照从右到左的原则,因此最右端的选择器应该是最快的:#id 或则 .class:
163 |
164 | ``` css
165 | div * {...} // bad
166 | .list li {...} // bad
167 | .list-item {...} // good
168 | #list .list-item {...} // good
169 | ```
170 |
171 | 1. 在你的脚本代码中,尽可能减少 DOM 操作。缓存所有东西,包括元素属性以及对象(如果它们被重用的话)。当进行复杂的操作时,使用“孤立”元素会更好,之后可以将其加到 DOM 中(所谓“孤立”元素是与 DOM 脱离,仅保存在内存中的元素)。
172 | 2. 如果你使用 jQuery 来选择元素,请遵从 jQuery 选择器最佳实践方案。
173 | 3. 为了改变元素的样式,修改“类”的属性是奏效的方法之一。执行这一改变时,处在 DOM 渲染树的位置越深越好(这还有助于将逻辑与表象脱离)。
174 | 4. 尽量只给位置绝对或者固定的元素添加动画效果。
175 | 5. 在使用滚动时禁用复杂的悬停动效(比如,在``中添加一个额外的不悬停类)。
176 |
177 | 想了解更多的细节问题,大家也可以看看这两篇文章:
178 |
179 | 1. [How browsers work?](http://taligarsiel.com/Projects/howbrowserswork1.htm)
180 | 2. [Rendering: repaint, reflow/relayout, restyle](http://www.phpied.com/rendering-repaint-reflowrelayout-restyle/)
181 |
182 | ## 问题总结
183 | 在探究重绘重排问题时,发现了以下的一些相似问题和内容,在这里也做一些分享
184 |
185 | 下面是利用 Firefox 对维基百科页面渲染的可视化视频。供大家熟悉参考
186 |
187 |
188 |
189 | ### 最小化重排和重绘方法
190 | 重排和重绘在实际开发中不可避免,我们只能尽量减少重排和重绘的次数,降低浏览器渲染网页的开销,以此带来的性能提升在移动平台上效果显著。结合上述内容,总结如下:
191 |
192 | 1. 不要一条一条的修改 CSS 属性,最好是整体替换 CSS 类或重写 DOM 的 `cssText` 属性。
193 | 2. 将多次 DOM 修改合并成一次。可以使用 `documentFragment` 对象缓存更改,或是复制你需要修改的 node 节点,修改完成后再替换掉原来的。也可以隐藏元素后再对其进行操作,最后把它显示出来。
194 | 3. 考虑要修改的元素的层级以及改动它引起的重排面积,选择其中开销最小的方式。
195 | 4. 不要频繁获取元素的位置属性,如果需要经常使用就用变量把它缓存下来。
196 | 5. 为需要有动画效果的元素设置 `position:absolute`。同时动画越平滑开销越大,需要在速度和平滑度上取得平滑。
197 | 6. 保持 DOM 树正确/简洁,减少不必要的 CSS 规则和复杂的选择器(尤其是后代选择器)。
198 | 为页面中的图片显式的声明宽度和高度。
199 | 7. 不要使用 table 布局。尽量不要动态更新 table 元素。
200 | 8. jQuery 中如果为 `append()` 方法传入多个元素组成的数组时,jQuery 可能会用到 `documentFragment`,但是使用 `$.each()` 方法就不会用到 `documentFragment`。
201 |
202 | 例:
203 |
204 | ``` js
205 | //修改CSS类名而不是逐条修改属性
206 | function changeStyle(element,className) {
207 | element.className = className;
208 | }
209 | //借助DocumentFragment
210 | function CreateFragments(){
211 | var fragment = document.createDocumentFragment();
212 | for(var i = 0;i < 10000;i++){
213 | var tmpNode = document.createElement("div");
214 | tmpNode.innerHTML = "test" + i;
215 | fragment.appendChild(tmpNode);
216 | }
217 | document.body.appendChild(fragment);
218 | }
219 | ```
220 |
221 | ### 浏览器渲染机制的另一个例子
222 | 在之后也看到一个例子也很好的演示了浏览器渲染的机制
223 |
224 | 核心代码如下:
225 | 问题核心代码:
226 |
227 | ``` js
228 | for (var i = 0; i < testTimes; i++) {
229 | for (var j = 0; j < allFunc.length; j++) {
230 | var currentResult=$('.result').eq(j);
231 | var gapTime=test(times,allFunc[j]);
232 | var testTime=currentResult.find('span').length+1;
233 | if(!resultMatrix[testTime-1]){
234 | resultMatrix[testTime-1]=[];
235 | }
236 | console.log('1');
237 | var result="第"+testTime+'次实验结果:耗时'+gapTime+'ms';
238 | currentResult.append(result);//append不是一条一条加,而是全部结果出来后才加上去
239 | /* 这是后来改成原生appendChild的代码,但是依旧不起作用
240 | var result=document.createElement('span');
241 | result.innerHTML='第'+testTime+'次实验结果:耗时'+gapTime+'ms';
242 | currentResult.get(0).appendChild(result);
243 | */
244 | resultMatrix[testTime-1][j]=gapTime;
245 | };
246 | };
247 | ```
248 |
249 | [点击查看 DEMO](http://codepen.io/yangzj1992/full/epMrvJ/)
250 |
251 | 这里 Chrome 有个很有趣的现象就是开发者工具的 `Elements` 面板里显示 span 已经加上去了,但是页面中没有任何反应。而 IE 和 FF 中则没有这种情况。
252 | 
253 |
254 | 这里正是因为:浏览器里 DOM 树的管理和渲染页面是分开的,浏览器会有一个用来控制渲染的渲染树的数据结构,除了隐藏的节点,DOM 树上所有节点都在渲染树上有一个对应节点,浏览器会将渲染树上的节点按照他的逻辑渲染到视口中,就形成了用户所见的页面。
255 |
256 | 然而,渲染是一种性能消耗不小的事情,所以大部分浏览器都有他们自己对渲染的优化,其中就包括了批量渲染(代表浏览器: Chrome),就是对于 DOM 树的修改并不是立刻产生渲染逻辑,而是一定时间间隔内将所有的 DOM 操作对应的所需要改变的渲染逻辑批量完成渲染。所以就看到了 span 已经加上去了,但是页面上没有任何反应。
257 |
258 | 想让浏览器立刻执行渲染逻辑,就需要访问诸如 offsetWidth 等一系列需要即时获得的信息,这列操作会使浏览器刷新渲染树并执行相应的渲染操作,因为 offset 里面存储的总是最新的。
259 |
--------------------------------------------------------------------------------
/360 & 美团前端社招面经及部分面试题.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 360 & 美团前端社招面经及部分面试题
3 | date: 2017-04-09 22:37:26
4 | categories: 面试
5 | tags: [职业生涯,前端知识,题目]
6 | ---
7 | ## 基本流程及要点
8 |
9 | 一般是至少三轮的技术面 + 一轮 HR 面,三轮技术一定程度上可能是你未来的同事 + 架构 + 上级。
10 |
11 | ## 面试题目
12 |
13 | ### 闭包原理及应用
14 |
15 | ### jquery 源码,如 delegate 原理
16 |
17 | 原理利用了JS 事件的事件冒泡机制,在 document(或事件源的父层)进行监听,冒泡到监听点后,判断事件源是否自己设定的元素
18 |
19 | ``` js
20 | function delegate(parent, eventType, selector, fn){
21 | //参数处理
22 | if(typeof parent === 'string'){
23 | var parent = document.getElementById(parent);
24 | !parent && alert('parent not found');
25 | }
26 |
27 | if(typeof selector !== 'string'){
28 | alert('selector is not string');
29 | }
30 |
31 | if(typeof fn !== 'function'){
32 | alert('fn is not function');
33 | }
34 |
35 | function handle(e){
36 | // 获取 event 对象
37 | // 标准 DOM 方法事件处理函数第一个参数是 event 对象
38 | // IE可以使用全局变量 window.event
39 | var evt = window.event ? window.event : e;
40 |
41 | // 获取触发事件的原始事件源
42 | // 标准 DOM 方法是用 target 获取当前事件源
43 | // IE 使用 evt.srcElement 获取事件源
44 | var target = evt.target || evt.srcElement;
45 |
46 | // 获取当前正在处理的事件源
47 | // 标准 DOM 方法是用 currentTarget 获取当前事件源
48 | // IE 中的 this 指向当前处理的事件源
49 | var currentTarget= e ? e.currentTarget : this;
50 |
51 | if(target.id === selector || target.className.indexOf(selector) != -1){
52 | fn.call(target);
53 | }
54 | }
55 |
56 | parent[eventType]=handle;
57 | }
58 |
59 | delegate('container', 'onclick', 'listener', function(){
60 | alert(this);
61 | });
62 | ```
63 |
64 | ### 简述 JS drag 事件以及模拟 drag 事件的方案
65 |
66 | 一个完整的 drag and drop 流程通常包含以下几个步骤:
67 |
68 | 1. 设置可拖拽目标. 设置属性 draggable="true" 实现元素的可拖拽.
69 |
70 | 2. 监听 dragstart 设置拖拽数据
71 |
72 | 3. 为拖拽操作设置反馈图标 (可选)
73 |
74 | 4. 设置拖放效果, 如 copy, move, link
75 |
76 | 5. 设置拖放目标, 默认情况下浏览器阻止所有的拖放操作, 所以需要监听 dragenter 或者 dragover 取消浏览器默认行为使元素可拖放.
77 |
78 | 6. 监听 drop 事件执行所需操作
79 |
80 | 实现 drag
81 |
82 | 1. onmousedown + onmousemove → startDrag()
83 |
84 | 2. onmouseup → stopDrag()
85 |
86 | 3. 偏移值,两次事件的鼠标位置记录:
87 | e.pageX || e.clientX + scrollX;
88 | e.pageY || e.clientY + scrollY;
89 |
90 |
91 | ### get 参数通过正则获取 window.location.search
92 |
93 | ``` js
94 | window.location.search.match(/(([^?&=]+)(=([^?&=]*))*)/g)
95 | ```
96 |
97 |
98 | ### 简单介绍 CSS3 动画属性及用法,如何优化动画性能
99 |
100 | 1. transition
101 |
102 | 2. animation
103 |
104 | transition 主要设置四个过渡属性:
105 |
106 | - transition-property // 规定设置过渡效果的 CSS 属性的名称。
107 |
108 | - transition-duration // 规定完成过渡效果需要多少秒或毫秒。
109 |
110 | - transition-timing-function // 规定速度效果的速度曲线。
111 |
112 | - transition-delay // 定义过渡效果何时开始。
113 |
114 | ``` css
115 | div{
116 | width:100px;
117 | background:blue;
118 | transition:width 2s;
119 | }
120 |
121 | div:hover{
122 | width:300px;
123 | }
124 | ```
125 |
126 | 注释:请始终设置 transition-duration 属性,否则时长为 0,就不会产生过渡效果。
127 |
128 |
129 | 2. animation 属性是一个简写属性,用于设置六个动画属性:
130 |
131 | - animation-name // 规定需要绑定到选择器的 keyframe 名称。
132 | - animation-duration // 规定完成动画所花费的时间,以秒或毫秒计。
133 | - animation-timing-function // 规定动画的速度曲线。
134 | - animation-delay // 规定在动画开始之前的延迟。
135 | - animation-iteration-count // 规定动画应该播放的次数。
136 | - animation-direction // 规定是否应该轮流反向播放动画。
137 |
138 | 优化具体参考即为如下几点:
139 |
140 | 由于渲染三阶段分为:Layout—>Paint—>Composite,可针对此三者分别进行优化。
141 | 优化目标:15FPS 不流畅 ,30FPS+感觉流畅,60FPS舒适完美
142 |
143 | 触发Layout的方式有:
144 |
145 | - 改变 width, height, margin 等和大小、位置相关的属性
146 | - 读取 size, position 相关得属性
147 |
148 | 要点:
149 |
150 | - 使用 transform 代替 top, left 的动画
151 | - 分离读写,减少 Layout
152 | - 面对解耦代码,使用 rAF 推迟的方法分离读写。
153 |
154 | 触发 Paint 的方式:
155 |
156 | 当修改 border-radius, box-shadow, color 等展示相关属性时,会触发 paint
157 | 要点:
158 |
159 | - 简化绘制的复杂度
160 | - 避免不必要的绘制
161 | - 减少绘制区域
162 |
163 | Composite 小结:
164 |
165 | GPU 是有限度的,不要滥用 GPU 资源生成不必要的 Layer
166 | 留意意外生成的 Layer
167 |
168 |
169 | 可结合贝塞尔曲线(cubic-bezier),开启硬件加速。will-change 属性
170 |
171 | ``` css
172 | div{
173 | width:100px;
174 | height:100px;
175 | background:red;
176 | position:relative;
177 | animation:mymove 5s infinite;
178 | -webkit-animation:mymove 5s infinite; /*Safari and Chrome*/
179 | }
180 |
181 | @keyframes mymove{
182 | from {left:0px;}
183 | to {left:200px;}
184 | }
185 |
186 | @-webkit-keyframes mymove{
187 | from {left:0px;}
188 | to {left:200px;}
189 | }
190 | ```
191 |
192 | ### 柯里化连加实现
193 |
194 | ``` js
195 | function add() {
196 | var sum = 0, i, len;
197 | for (i = 0, len = arguments.length; i < len; i++) {
198 | sum += arguments[i];
199 | }
200 | return sum;
201 | }
202 |
203 | var currying = function(fn) {
204 | console.log(arguments)
205 | var _args = [];
206 |
207 | return function cb() {
208 |
209 | if (arguments.length === 0) {
210 | return fn.apply(this, _args);
211 | }
212 |
213 | Array.prototype.push.apply(_args, arguments);
214 |
215 | return cb;
216 | }
217 | }
218 |
219 | var curryingAdd = currying(add);
220 |
221 | curryingAdd(1)(2)(3)(4)(); // 10
222 |
223 | var add321 = curryingAdd(3)(2, 1);
224 |
225 | add321(4)(); // 10
226 |
227 | ```
228 |
229 | ### position 关键字值有哪几种,以及相应的表现形式
230 |
231 | static / relative / absolute / fixed / sticky(新特性)
232 |
233 | 粘性定位是相对定位和固定定位的混合。元素在跨越特定阈值前为相对定位,之后为固定定位。
234 |
235 | 其盒位置根据正常流计算,然后相对于该元素在流中的 flow root(BFC)和 containing block(最近的块级祖先元素)定位。在所有情况下,该元素定位均不对后续元素造成影响。当元素 B 被粘性定位时,后续元素的位置仍按照 B 未定位时的位置来确定。position: sticky 对 table 元素的效果与 position: relative 相同。
236 |
237 | ```
238 | #one { position: sticky; top: 10px; }
239 | ```
240 |
241 | ### 中间件和插件的区别
242 |
243 | 中间件是一种独立的系统软件或服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源。中间件位于客户机 / 服务器的操作系统之上,管理计算机资源和网络通讯。是连接两个独立应用程序或独立系统的软件。相连接的系统即使它们具有不同的接口,但通过中间件相互之间仍能交换信息。执行中间件的一个关键途径是信息传递。通过中间件,应用程序可以工作于多平台或 OS 环境。
244 |
245 | 插件是一种遵循一定规范的应用程序接口编写出来的程序。
246 |
247 |
248 | ### Bootstrap grid 实现原理。
249 |
250 | 浮动,宽度瓜分,媒体查询,响应式布局。
251 |
252 | ### 继承的方式。
253 |
254 | 原型链继承(对象间的继承)
255 | 类式继承(构造函数间的继承)
256 | ES6 Class
257 |
258 | ### Canvas SVG 区别。
259 |
260 | Canvas 适用场景:
261 |
262 | Canvas 提供的功能更原始,适合像素处理,动态渲染和大数据量绘制;
263 | Canvas 是使用 JavaScript 程序绘图,提供画布标签和绘制 API(动态生成);
264 | Canvas 是基于位图的图像,它不能够改变大小,只能缩放显示;
265 |
266 | SVG 适用场景:
267 |
268 | SVG 功能更完善,适合静态图片展示,高保真文档查看和打印的应用场景
269 | SVG 是使用 XML 文档描述来绘图,是一整套独立的矢量图形语言;
270 | SVG 更适合用来做动态交互,而且 SVG 绘图很容易编辑,只需要增加或移除相应的元素就可以了。
271 | SVG 是基于矢量的,所有它能够很好的处理图形大小的改变。
272 |
273 |
274 | 二者是有不同用途的,作为一个开发者,你应该做的是理解应用程序的具体需求并选择正确的技术来实现它。
275 |
276 |
277 | ### 日期换算,求天数差等
278 |
279 | ``` js
280 | var a = '2014-04-23';
281 | var date1 = new Date(a);
282 | var b = '2014-04-24';
283 | var date2 = new Date(b);
284 | var days = Math.floor((date2 - date1) / 1000 / 60 / 60 / 24)
285 | ```
286 |
287 | ### let 具体为什么会有临时死域。
288 |
289 | 详见[JavaScript 变量的生命周期:为什么 let 不存在变量提升
290 | ](http://www.qcyoung.com/2016/08/03/%E3%80%90%E8%AF%91%E3%80%91JavaScript%20%E5%8F%98%E9%87%8F%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%EF%BC%9A%E4%B8%BA%E4%BB%80%E4%B9%88%20let%20%E4%B8%8D%E5%AD%98%E5%9C%A8%E5%8F%98%E9%87%8F%E6%8F%90%E5%8D%87/)
291 |
292 | ### 监控一个 DOM 替换过程
293 |
294 | 原生方法监听 DOM 结构改变事件
295 | [https://developer.mozilla.org/en-US/docs/XUL/Events#Mutation_DOM_events]()
296 |
297 | ``` js
298 | document.addEventListener('DOMNodeInserted',function(){alert(1)},false);
299 | document.addEventListener('DOMAttrModified',function(){alert(1)},false);
300 | document.addEventListener('DOMNodeRemoved',function(){alert(1)},false);
301 | ```
302 |
303 | 变动事件包括以下不同事件类型:
304 |
305 | - DOMSubtreeModified: 在 DOM 结构中发生任何变化时触发
306 |
307 | - DOMNodeInserted: 在一个节点作为子节点被插入到另一个节点中时触发
308 |
309 | - DOMNodeRemoved: 在节点从其父节点中被移除时触发
310 |
311 | - DOMNodeRemovedFromDocument: 在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发
312 |
313 | - DOMNodeInsertedIntoDocument: 在一个节点被直接插入文档或通过子树间接插入文档之后触发
314 |
315 | - DOMAttrModified: 在属性被修改之后触发
316 |
317 | - DOMCharacterDataModified: 在文本节点的值发生变化时触发
318 |
319 | [构造函数 MutationObserver](https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver)
320 |
321 | ``` js
322 | // Firefox和Chrome早期版本中带有前缀
323 | var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver
324 |
325 | // 选择目标节点
326 | var target = document.querySelector('#some-id');
327 |
328 | // 创建观察者对象
329 | var observer = new MutationObserver(function(mutations) {
330 | mutations.forEach(function(mutation) {
331 | console.log(mutation.type);
332 | });
333 | });
334 |
335 | // 配置观察选项:
336 | var config = { attributes: true, childList: true, characterData: true }
337 |
338 | // 传入目标节点和观察选项
339 | observer.observe(target, config);
340 |
341 | // 随后,你还可以停止观察
342 | observer.disconnect();
343 | ```
344 |
345 | ### WebSocket 简述原理
346 |
347 | WebSocket 协议基于 TCP,解决过去为了实现即时通讯而采用的轮询方案,解决了大量交换 HTTP header,信息交换效率低的问题。在浏览器和服务器之间建立双向连接。
348 |
349 | ### 冒泡 + 快排 + 插入排序原理等
350 |
351 | 详见:[JavaScript 排序算法汇总](http://www.qcyoung.com/2016/12/18/JavaScript%20%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%E6%B1%87%E6%80%BB/)
352 |
353 | ### 网络协议 HTTP HTTPS HTTP2 的基本概念等
354 |
355 | ### 自适应布局,移动端自适应等问题
356 |
357 | ### 设计模式
358 |
359 | 这方面被问到的比较多的:观察者模式,工厂模式,职责链模式等等
360 |
361 | 主要是经常涉及应用于 js 开发组件。比如如何去设计一个前端UI组件,应该公开出哪些方法,应该提供哪些接口,应该提供哪些事件。哪部分逻辑流程应该开放出去让用户自行编写,如何实现组件与组件之间的通信,如何实现高内聚低耦合,如何实现组件的高复用等等。
362 |
363 | ### 你怎么看 XXX
364 |
365 | 结合框架原理,社区环境,技术选型(技术、业务、人)等
366 |
367 |
368 |
--------------------------------------------------------------------------------
/【译】JSON Web Tokens (JWT) 与 Sessions.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【译】JSON Web Tokens (JWT) 与 Sessions
3 | date: 2016-07-04 23:21:10
4 | categories: 身份认证
5 | tags: [JWT,RESTful,session,web安全,token,JSON]
6 | ---
7 | > 原文链接 : [JSON Web Tokens (JWT) vs Sessions](https://float-middle.com/json-web-tokens-jwt-vs-sessions/)
8 | > 原文作者 : [Jacek Ciolek](https://float-middle.com/author/jacek-ciolek/)
9 | > 译文出自 : [众成翻译](http://zcfy.cc/)
10 | > 译者 : [yangzj1992](http://qcyoung.com)
11 | > 校对者: [lisa](https://www.zhihu.com/people/ha-ha-qiu-52)
12 | > 首发于: [众成翻译](http://zcfy.cc/article/json-web-tokens-jwt-vs-sessions-685.html)
13 |
14 | ## 什么是 JWT?
15 |
16 | > 本质上它是一段签名的 JSON 格式的数据。由于它是带有签名的,因此接收者便可以验证它的真实性。同时由于它是 JSON 格式的因此它的体积也很小。如果你想了解有关它的正式定义,可以在 [RFC 7519](https://tools.ietf.org/html/rfc7519) 中找到。
17 |
18 | _这篇文章发布于[黑客新闻](https://news.ycombinator.com/item?id=11929267)上。在这里也可以看一下关于这篇文章的[案例分析](https://float-middle.com/i-got-featured-on-hacker-news-case-study/),它主要包含了文章内容的公开分析、SEO 影响、性能影响以及更多其他的内容。_
19 |
20 | 数据签名已经不是什么新事物了 - 令人值得兴奋的是如何在不依靠 sessions 的情况下使用 JWT 创建真正的 RESTful 服务,目前这个想法已经被事实证明有一段时间了。下面是介绍它在现实中具体实现的工作原理 - 首先在这里我来做一个类比:
21 |
22 | 想象一下你刚从国外度完假回来,你在边境上说 - 你可以让我通过,我是这里的公民。这样的回答很好也没有问题,但是你要如何去支持你的说法呢?最有可能的方案是你携带了护照来证明你的身份。这里我们假设边境工作人员也都被要求去核实护照是真正由你的国家的护照办签发的。那么护照就会被核实,这样他们也才会放你回国。
23 |
24 | 现在,让我们从 JWT 的角度看一下这个故事,它们各自又都扮演着什么样的角色:
25 |
26 | * **护照办** - 发布 JWT 的身份验证服务。
27 |
28 | * **护照** - 你通过"护照办"获得的 JWT 签名。你的身份对于任何人都是可读的,但是只有它是真实的时候相关方才会对其核实。
29 |
30 | * **公民资格** - 在 JWT 中包含的你的声明(你的护照)。
31 |
32 | * **边境** - 你的应用程序的安全层,在被允许访问受保护的资源之前由它来核实你的 JWT 令牌身份,在这种情况下指的是 - 国家。
33 |
34 | * **国家** - 你想要获取的资源(例如 API)。
35 |
36 | ## 看啊!没有 session!
37 |
38 | 简单来说,JWT 非常的酷,因为你不用再为了鉴别用户而在你的服务器上去保留你的 session 数据。这个工作流将会变得像下面这样:
39 |
40 | * 用户调用身份验证服务,通常是发送了用户名及密码。
41 |
42 | * 身份验证服务响应并返回了签名的 JWT,上面包含了用户是谁的内容。
43 |
44 | * 用户向安全服务发送请求收到安全服务返回的令牌。
45 |
46 | * 安全层检验令牌上的签名并且在签名为真实的时候授权予以通过。
47 |
48 | 让我们考虑一下这样做的结果。
49 |
50 | ### 没有会话存储
51 |
52 | 没有 sessions 意味着你没有会话存储。但除非您的应用程序需要横向扩展,否则这也不太重要,如果你的应用程序是运行在多个服务器上的,那么共享 session 数据将会成为一个负担。你需要一个专门的服务器来只存储会话数据或是共享磁盘空间或是在负载均衡上粘滞会话。当你不使用 sessions 时上面的这些也就自然不再需要了。
53 |
54 | ### 没有对 sessions 的垃圾收集
55 |
56 | 通常来讲 sessions 需要留意过期和垃圾收集的情况。JWT 可以在用户数据中包含自己的过期日期。因此安全层在检验 JWT 的授权时可以同时核对它的过期时间来拒绝访问。
57 |
58 | ### 真正的 RESTful 服务
59 |
60 | 只有在无 sessions 的情况下你可以创建真正的 RESTful 服务,因为它被认为是[无状态的](https://en.wikipedia.org/wiki/Representational_state_transfer#Stateless)。 JWT 很小所以它可以在每一个请求中被一起发出去,就像一个 session cookie一样。然而与 session cookie 不同的是,它并不指向服务器上的任何存储数据, JWT 本身包含了这些数据。
61 |
62 | ## 真实的 JWT 到底是什么样的?
63 |
64 | 在我们更深入讨论之前,有一件事需要了解。JWT 自身并不是一个东西。它是 [JSON 网络签名(JWS)](https://tools.ietf.org/html/rfc7515)或 [JSON 网络加密 (JWE)](https://tools.ietf.org/html/rfc7516)中的一种类型。它的定义如下:
65 |
66 | > 一个 JWT 的声明内容会被编码为一个 JSON 对象,它被作为 JSON 网络签名结构的有效载荷或是作为 JSON 网络加密结构的明文信息。
67 |
68 | 前者给我们的只是一个签名并且它包含的数据(或是平时所称呼的 `claims` 的命名)是对任何人都可读的。后者则提供了加密的内容,所以只有拥有密钥的人可以解密它。JWS 在实现上更加容易并且基本用法上是不需要加密的 - 毕竟如果你在客户端上有密钥的话,你还不如把所有的东西不加密的好。因此 JWS 在大多数情况下都是适用的,也因此在之后我将主要关注 JWS。
69 |
70 | ### 那么 JWT/JWS 是由什么构成的?
71 |
72 | * **头部** - 关于签名算法的信息,以 JSON 格式的负载类型(JWT)等等。
73 |
74 | * **负载** - JSON 格式的实际的数据(或是声明)。
75 |
76 | * **签名** - 额... 就是签名。
77 |
78 | 我将在之后具体解释这些细节。现在让我们先来分析下基础要素。
79 |
80 | 上述所提到的每一部分(头部,负载和签名)是基于 base64url 编码的,然后他们用 `.` 作为分隔符粘连起来组成 JWT。 下面是这个实现方式可能看上去的样子:
81 |
82 | ``` js
83 | var header = {
84 | // The signing algorithm.
85 | "alg": "HS256",
86 | // The type (typ) property says it's "JWT",
87 | // because with JWS you can sign any type of data.
88 | "typ": "JWT"
89 | },
90 | // Base64 representation of the header object.
91 | headerB64 = btoa(JSON.stringify(header)),
92 | // The payload here is our JWT claims.
93 | payload = {
94 | "name": "John Doe",
95 | "admin": true
96 | },
97 | // Base64 representation of the payload object.
98 | payloadB64 = btoa(JSON.stringify(payload)),
99 | // The signature is calculated on the base64 representation
100 | // of the header and the payload.
101 | signature = signatureCreatingFunction(headerB64 + '.' + payloadB64),
102 | // Base64 representation of the signature.
103 | signatureB64 = btoa(signature),
104 | // Finally, the whole JWS - all base64 parts glued together with a '.'
105 | jwt = headerB64 + '.' + payloadB64 + '.' + signatureB64;
106 | ```
107 |
108 | 由此得到的 JWS 结果看上去整洁而优雅,有点像这样:
109 |
110 | ``` bash
111 | `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZX0.OLvs36KmqB9cmsUrMpUutfhV52_iSz4bQMYJjkI_TLQ`
112 | ```
113 |
114 | 你也可以试着在 [jwt.io](https://jwt.io/#debugger) 这个网站上来创建令牌试试。
115 |
116 | 有一点相当重要,那就是签名是依据头部和负载计算出来的。因此头部和负载的授权也很容易同样被检验:
117 |
118 | ``` js
119 | [headerB64, payloadB64, signatureB64] = jwt.split('.');
120 |
121 | if (atob(signatureB64) === signatureCreatingFunction(headerB64 + '.' + payloadB64) {
122 | // good
123 | } else
124 | // no good
125 | })
126 | ```
127 |
128 | ### JWT 头部中可以存放什么?
129 |
130 | 事实上,JWT 头部被称为 JOSE 头部。JOSE 表示的是 JSON 对象的签名和加密。也正如你期望的那样,JWS 和 JWE 都是这样的一个头部,然而它们各自之间存在着一套稍微不同的注册参数。下面是在 JWS 中使用的头部注册参数列表。所有的参数除了第一个参数(alg)以外,其他参数都是可选的:
131 |
132 | * **alg** 算法 (必选项)
133 |
134 | * **typ** 类型 (如果是 JWT 那么就带有一个值 `JWT`,如果存在的话)
135 |
136 | * **kid** 密钥 ID
137 |
138 | * **cty** 内容类型
139 |
140 | * **jku** JWK 指定 URL
141 |
142 | * **jwk** JSON 网络值
143 |
144 | * **x5u** X.509 URL
145 |
146 | * **x5c** X.509 证书链
147 |
148 | * **x5t** X.509 证书 SHA-1 指纹
149 |
150 | * **x5t#S256** X.509 证书 SHA-256 指纹
151 |
152 | * **crit** 临界值
153 |
154 | 前两个参数是最常用的,所以典型的头部看起来有点类似下面这样:
155 |
156 | ``` json
157 | {
158 | "alg": "HS256",
159 | "typ": "JWT"
160 | }
161 | ```
162 |
163 | 上面列出的第三个参数 `kid` 是基于安全原因使用的。`cty` 参数在另一方面应该只被用于处理嵌套的 JWT。剩下的参数你可以在[规范文档](https://tools.ietf.org/html/rfc7515#section-4.1)中阅读了解,我认为它们不适合在这篇文章中被提及。
164 |
165 | #### `alg` (算法)
166 |
167 | `alg` 参数的值可以是 [JSON 网络算法(JWA)](https://tools.ietf.org/html/rfc7518#section-3.1)中的任意指定值 - 这是我所知道的另一个规范。下面是 JWS 的注册列表:
168 |
169 | * **HS256** - HMAC 使用 SHA-256 算法
170 |
171 | * **HS384** - HMAC 使用 SHA-384 算法
172 |
173 | * **HS512** - HMAC 使用 SHA-512 算法
174 |
175 | * **RS256** - RSASSA-PKCS1-v1_5 使用 SHA-256 算法
176 |
177 | * **RS384** - RSASSA-PKCS1-v1_5 使用 SHA-384 算法
178 |
179 | * **RS512** - RSASSA-PKCS1-v1_5 使用 SHA-512 算法
180 |
181 | * **ES256** - ECDSA 使用 P-256 和 SHA-256 算法
182 |
183 | * **ES384** - ECDSA 使用 P-384 和 SHA-384 算法
184 |
185 | * **ES512** - ECDSA 使用 P-521 和 SHA-512 算法
186 |
187 | * **PS256** - RSASSA-PSS 使用 SHA-256 和基于 SHA-256 算法的 MGF1
188 |
189 | * **PS384** - RSASSA-PSS 使用 SHA-384 和基于 SHA-384 算法的 MGF1
190 |
191 | * **PS512** - RSASSA-PSS 使用 SHA-512 和基于 SHA-512 算法的 MGF1
192 |
193 | * **none** - 没有数字签名或 MAC 执行
194 |
195 | 请注意最后一个值 `none`,从安全性的角度来看这是最有趣的。[这是已知的被用来进行降级防御攻击的方法](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/)。它是如何工作的呢?想象一个客户端生成的带有一些声明的 JWT 。它在头部指定 `none` 值的签名算法并进行发送验证。如果攻击者比较单纯,那么它会使 `alg` 参数为真来确保被授权通过,然而实际上则是不会被允许的。
196 |
197 | 底线是,你的应用的安全层应该总是对头部的 `alg` 参数进行校验。那里就是 `kid` 参数用的上的地方。
198 |
199 | #### `typ` (类型)
200 |
201 | 这一个参数非常简单。如果它是已知的,那么它就是 JWT,因为应用不会去索取其他的值,如果这个参数没有值就会被忽视掉。因此它是可选的。如果需要被指定值,它应该按大写字母拼写 - `JWT` 。
202 |
203 | 在某些情况下,当应用程序接受到没有 JWT 类型的请求却又包含了 JWT 时,去重新指定它是很重要的,因为这样应用程序才不会崩溃。
204 |
205 | #### `kid` (密钥 id)
206 |
207 | 如果你的应用程序中的安全层只使用了一个算法来签名 JWTs,你不用太担心 `alg` 参数,因为你会总是使用相同的密钥和算法来校验令牌的完整性。但是,如果你的应用程序使用了一堆不同的算法和密钥,你就需要能够分辨出是由谁签署的令牌。
208 |
209 | 正如我们之前看到的,单独依靠 `alg` 参数可能会导致一些...不便。然而,如果你的应用维护了一个密钥/算法的列表,并且每一对都有一个名称(id),你可以添加这个密钥 id 到头部,这样在之后验证 JWT 时你会有更多的信心去选择算法。这就是头部参数 `kid` - 你的应用中用来签名令牌所使用的密钥 id 。这个 id 是由你来任意指定的。最重要的是 - 这是你给的 id ,所以你可以验证。
210 |
211 | #### `cty` (内容类型)
212 |
213 | 这里把[规范](https://tools.ietf.org/html/rfc7519#section-5.2)介绍的很清楚,所以这里我就只是引用了:
214 |
215 | > 在通常情况下,在不使用嵌套签名或是加密操作时,是不推荐使用这个头部参数的。而在使用嵌套签名或加密时,这个头部参数必须存在;在这种情况下,它的值必须是 "JWT",来表明这是一个在 JWT 中嵌套的 JWT。虽然媒体类型名字对大小写并不敏感,但这里为了与现有遗留实现兼容还是推荐始终用 `JWT` 大写字母来拼写。
216 |
217 | ### 在 JWT 声明中可以有什么?
218 |
219 | `claims` 这个名称是否让你感到困惑?在最初它也确实让我很困惑。我相信你需要重复读几次来尝试适应它。简而言之,`claims` 是 JWT 的主要内容 - 是我们十分关心的签名的数据。它被叫做 `claims` 是因为通常它就是声明这个意思 - 客户端声明了用户名,用户角色或者其他什么的来让它可以获得对资源的访问。
220 |
221 | 还记得我在最开始提到的那个可爱的故事吗?你的公民资格就是你的声明而你的护照则就是 - JWT
222 |
223 | 你可以在声明中放置任何你想要的参数,这儿有一个[注册表](https://tools.ietf.org/html/rfc7519#section-4.1)应当被视为公认的参考实现方法。请注意这里的每一个参数都是可选的并且大多数是应用程序特定的,下面就是这个列表:
224 |
225 | * **exp** - 过期时间
226 |
227 | * **nbf** - 有效起始日期
228 |
229 | * **iat** - 发行时间
230 |
231 | * **sub** - 主题
232 |
233 | * **iss** - 发行者
234 |
235 | * **aud** - 受众
236 |
237 | * **jti** - JWT ID
238 |
239 | 值得注意的是,除了最后三个(issuer ,audience 和 JWT ID)参数通常是在更复杂的情况下(例如包含多个发行者时)才被使用。下面让我们来讨论一下它们吧。
240 |
241 | #### `exp` (过期时间)
242 |
243 | `exp` 是时间戳值表示着在什么时候令牌会失效。规范上要求"当前日期/时间"必须在指定的 `exp` 值之前,从而保证令牌可以得到处理。这里也表明了存在一些余地(几分钟)来应对时间差。
244 |
245 | #### `nbf` (有效起始时间)
246 |
247 | `nbf` 是时间戳值表示着在什么时候令牌开始生效。规范上要求"当前日期/时间"必须与指定的 `nbf` 值相等或在其之后,从而保证令牌可以得到处理。这里也表明了存在一些余地(几分钟)来应对时间差。
248 |
249 | #### `iat` (发行时间)
250 |
251 | `iat` 是时间戳值表示什么时候令牌被发行。
252 |
253 | #### `sub` (主题)
254 |
255 | `sub` 在规范上被要求"是JWT 中的声明中通常用于陈述主题的值"。这里主题必须是内容中唯一的发行者或全局上的唯一值。`sub` 声明可以用来鉴别用户,例如 [JIRA](https://developer.atlassian.com/static/connect/docs/latest/concepts/understanding-jwt.html#token-structure-claims) 文档上那样。
256 |
257 | #### `iss` (发行者)
258 |
259 | `iss` 是被用来确认令牌的发行者的字符串值。如果值中包含 `:` 那么它就是一个 URI。如果有很多的发行者而在一个安全层中应用程序需要去识别发行人时,它将会是有用的。例如 [Salesforce](https://help.salesforce.com/HTViewHelpDoc?id=remoteaccess_oauth_jwt_flow.htm) 要求了去使用 OAuth client_id 来作为 `iss` 的值。
260 |
261 | #### `aud` (受众)
262 |
263 | `aud` 是被用来确认令牌的可能接受者的字符串值或数组。如果值中包含 `:` 那么它就是一个 URI。
264 | 通常使用 URI 资源的声明是有效的。例如,在 [OAuth](https://tools.ietf.org/html/rfc7523#section-3) 中,接受者是授权服务器。应用程序处理令牌时,在针对不同的接受者的情况下,必须验证接受者是否是正确的或者拒绝令牌。
265 |
266 | #### `jti` (JWT id)
267 |
268 | 令牌的唯一标识符。每个发布的令牌的 `jti` 必须是唯一的,即使有很多发行人也是一样。`jti` 声明可以用于一次性的不能重放的令牌。
269 |
270 | ## 如何在我的应用中使用 JWT ?
271 |
272 | 在最常见的场景中,客户端的浏览器将在认证服务中认证并接受返回的 JWT。然后客户端用某种方式(如内存,localStorage)存储这个令牌并与受保护的资源一起发送返回。通常令牌发送时是作为 cookie 或是 HTTP 请求中 `Authorization` 头部。
273 |
274 | ``` bash
275 | GET /api/secured-resource HTTP/1.1
276 | Host: example.com
277 | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZX0.OLvs36KmqB9cmsUrMpUutfhV52_iSz4bQMYJjkI_TLQ
278 | ```
279 |
280 | 首选头部方法是出于安全的原因 - cookies 会很容易受 [CSRF](https://www.owasp.org/index.php/Cross-Site_Request_Forgery)(跨站请求伪造)的影响,除非 CSRF 令牌是使用过的。
281 |
282 | 其次,cookies 只能发送返回到被发出的相同的域下(或者最多二级域下)。如果身份验证服务驻留在不同的域下,那么 cookies 得需要更强烈的创造性才行。
283 |
284 | ### 如何通过 JWT 登出?
285 |
286 | 因为没有 session 数据存储在服务端了,所以不能再通过破坏 session 来注销了。因此登出成为了客户端的职责 - 一旦客户丢失了令牌不能再被授权,就可以被认为是登出了。
287 |
288 | ## 总结
289 |
290 | 我认为 JWTs 是一个在脱离 sessions 的情况下非常聪明的授权方式。它允许创建真正的服务端无状态的基于 RESTful 的服务,这也意味着不需要 session 存储。
291 |
292 | 与浏览器自动发送 session cookie 到任意匹配域/路径组合(老实说,在大多数情况下这里只有域的情况)的 URL 不一样的是,JWTs 可以选择性的只向需要身份授权的资源来发送。
293 |
294 | 对于客户端和服务端来说,它的实现非常简单,特别是已经有[专门的库](https://jwt.io/#libraries-io)来制造签名和验证令牌了。
295 |
296 | 感谢阅读!
297 |
298 | 如果你喜欢这篇文章的话,欢迎分享它。同样也十分欢迎你对它进行评论!
--------------------------------------------------------------------------------
/【译】对一行混淆 JS 代码的逆向分析过程.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【译】对一行混淆 JS 代码的逆向分析过程
3 | date: 2017-08-11 00:43:02
4 | categories: JavaScript
5 | tags: [JavaScript,源码解读]
6 | ---
7 | > 原文链接 : [Reverse Engineering One Line of JavaScript](https://www.alexkras.com/reverse-engineering-one-line-of-javascript/)
8 | > 原文作者 : [Alex](https://www.alexkras.com/about/)
9 | > 译文出自 : [众成翻译](http://zcfy.cc/)
10 | > 译者 : [yangzj1992](http://www.qcyoung.com)
11 | > 首发于: [众成翻译](http://zcfy.cc/article/reverse-engineering-one-line-of-javascript-3615.html?t=new)
12 |
13 |
14 | 不久前,我看到有人提了个问题,能否有人将下面这行 JS 代码进行逆向分析。
15 |
16 | ```
17 |
18 | ```
19 |
20 | 这行代码的效果如下所示,它是一个大小仅为 128b 的光线追踪挡板 Demo 。你也可以访问[这里](https://codepen.io/yangzj1992/pen/owOyjr)查看效果。这行代码的作者是[p01](http://www.p01.org/128b_raytraced_checkboard/), 发布于[Pouet.net](http://www.pouet.net/prod.php?which=59204)。你可以访问[他的网站](http://www.p01.org/)看到更多有趣的 Demo。
21 |
22 |
23 | 
24 |
25 | 下面我们试着对这行代码进行一下逆向分析。
26 |
27 | ## 第一部分,使代码可读。
28 |
29 | 首先,我们将 HTML 和 JS 代码分离。这里我们保留相关的 id 指向。
30 |
31 | ``` html
32 |
33 |
34 | ```
35 |
36 | 这里我们注意到有个变量 `k`。我们将它置顶申明,并重命名为 `delay`。
37 |
38 | ``` js
39 | var delay = 64;
40 | var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
41 | var n = setInterval(draw, delay);
42 | ```
43 |
44 | `var draw` 由于是一个字符串,它会被 setInterval 用 eval() 方法执行(setInterval 可以接受一个 function 或是 string 来执行)。所以这里我们将它重写成一个真实的 function。
45 |
46 | 另外这里还对元素 p 进行了直接的 DOM 操作,这里我们用 JS 获取这个 id 来重新书写,让它更加易懂。
47 |
48 | ``` js
49 | var delay = 64;
50 | var p = document.getElementById("p"); // < ---------------
51 | // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
52 | var draw = function() {
53 | for (n += 7, i = delay, P = 'p.\n'; i -= 1 / delay; P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2]) {
54 | j = delay / i; p.innerHTML = P;
55 | }
56 | };
57 | var n = setInterval(draw, delay);
58 | ```
59 |
60 | 接下来,我们将变量 `i`,`p`, `j` 也作提前声明。
61 |
62 | ``` js
63 | var delay = 64;
64 | var p = document.getElementById("p");
65 | // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
66 | var draw = function() {
67 | var i = delay; // < ---------------
68 | var P ='p.\n';
69 | var j;
70 | for (n += 7; i > 0 ;P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2]) {
71 | j = delay / i; p.innerHTML = P;
72 | i -= 1 / delay;
73 | }
74 | };
75 | var n = setInterval(draw, delay);
76 | ```
77 |
78 | 然后,我们将 for 循环转为 while 循环,将 for 循环中间的条件语句作为条件,其他的语句放到 while 循环的内外部。
79 |
80 | ``` js
81 | var delay = 64;
82 | var p = document.getElementById("p");
83 | // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
84 | var draw = function() {
85 | var i = delay;
86 | var P ='p.\n';
87 | var j;
88 | n += 7;
89 | while (i > 0) { // <----------------------
90 | //Update HTML
91 | p.innerHTML = P;
92 |
93 | j = delay / i;
94 | i -= 1 / delay;
95 | P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2];
96 | }
97 | };
98 | var n = setInterval(draw, delay);
99 | ```
100 |
101 | 接下来我们展开三元表达式。
102 |
103 | 这里 `i % 2` 的作用是判断 i 是否为偶数,如果是偶数,那么将会返回 2,否则返回 `(i % 2 * j - j + n / delay ^ j) & 1`。
104 |
105 | 而这一堆式子也将会作为 `P` 的 index 进行处理。 即 `P += P[index];`
106 |
107 | ``` js
108 | var delay = 64;
109 | var p = document.getElementById("p");
110 | // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
111 | var draw = function() {
112 | var i = delay;
113 | var P ='p.\n';
114 | var j;
115 | n += 7;
116 | while (i > 0) {
117 | //Update HTML
118 | p.innerHTML = P;
119 |
120 | j = delay / i;
121 | i -= 1 / delay;
122 |
123 | let index;
124 | let iIsOdd = (i % 2 != 0); // <---------------
125 |
126 | if (iIsOdd) { // <---------------
127 | index = (i % 2 * j - j + n / delay ^ j) & 1;
128 | } else {
129 | index = 2;
130 | }
131 |
132 | P += P[index];
133 | }
134 | };
135 | var n = setInterval(draw, delay);
136 | ```
137 |
138 | 这里我们接下来注意到与运算符 `& 1`。
139 |
140 | `(i % 2 * j - j + n / delay ^ j) & 1`,这段代码可以巧妙地去比较一个数是否为奇偶,它的原理其实是数值转换为二进制进行与运算返回的十进制结果。
141 |
142 | ``` js
143 | 0 & 1 // 0
144 | 1 & 1 // 1
145 | 2 & 1 // 0
146 | 3 & 1 // 1
147 | 3 & 2 // 2
148 | 8 & 8 // 8
149 | ```
150 |
151 | 然后我们再次对变量进行重命名整合。
152 |
153 | ``` js
154 | var delay = 64;
155 | var p = document.getElementById("p");
156 | // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
157 | var draw = function() {
158 | var i = delay;
159 | var P ='p.\n';
160 | var j;
161 | n += 7;
162 | while (i > 0) {
163 | //Update HTML
164 | p.innerHTML = P;
165 |
166 | j = delay / i;
167 | i -= 1 / delay;
168 |
169 | let index;
170 | let iIsOdd = (i % 2 != 0);
171 |
172 | if (iIsOdd) {
173 | let magic = (i % 2 * j - j + n / delay ^ j);
174 | let magicIsOdd = (magic % 2 != 0); // &1 < --------------------------
175 | if (magicIsOdd) { // &1 <--------------------------
176 | index = 1;
177 | } else {
178 | index = 0;
179 | }
180 | } else {
181 | index = 2;
182 | }
183 |
184 | P += P[index];
185 | }
186 | };
187 | var n = setInterval(draw, delay);
188 | ```
189 |
190 | 由于这里 P ='p.\n'; 而我们的 index 的值为:0, 1, 2。对应即有:
191 |
192 | ``` js
193 | P[0] = 'p'
194 | P[1] = '.'
195 | P[2] = '\n'
196 | ```
197 |
198 | 所以这里我们还可以用 switch 重写代码:
199 |
200 | ``` js
201 | var delay = 64;
202 | var p = document.getElementById("p");
203 | // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
204 | var draw = function() {
205 | var i = delay;
206 | var P ='p.\n';
207 | var j;
208 | n += 7;
209 | while (i > 0) {
210 | //Update HTML
211 | p.innerHTML = P;
212 |
213 | j = delay / i;
214 | i -= 1 / delay;
215 |
216 | let index;
217 | let iIsOdd = (i % 2 != 0);
218 |
219 | if (iIsOdd) {
220 | let magic = (i % 2 * j - j + n / delay ^ j);
221 | let magicIsOdd = (magic % 2 != 0); // &1
222 | if (magicIsOdd) { // &1
223 | index = 1;
224 | } else {
225 | index = 0;
226 | }
227 | } else {
228 | index = 2;
229 | }
230 |
231 | switch (index) { // P += P[index]; <-----------------------
232 | case 0:
233 | P += "p"; // aka P[0]
234 | break;
235 | case 1:
236 | P += "."; // aka P[1]
237 | break;
238 | case 2:
239 | P += "\n"; // aka P[2]
240 | }
241 | }
242 | };
243 |
244 | var n = setInterval(draw, delay);
245 | ```
246 |
247 | 现在我们来梳理 `var n = setInterval(draw, delay);`。因为 setInterval 返回一个从 1 开始的整数 ID 。并在每次 setInterval 方法被调用时依次递增。(这个 ID 可以被用于 clearInterval 等方法。)在我们的例子中,setInterval 只被调用了一次,所以 n 被设置为 1。
248 |
249 | 此外,我们把 `delay` 重命名为 `DELAY` 以作为常量。
250 |
251 | 最后,我们对 `i % 2 * j - j + n / DELAY ^ j` 进行排序,由于 `^` 位异或运算符的优先级较低。所以加上括号后整理如下:
252 |
253 | ``` js
254 | const DELAY = 64; // approximately 15 frames per second
255 | var n = 1;
256 | var p = document.getElementById("p");
257 | // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
258 |
259 | /**
260 | * Draws a picture
261 | * 128 chars by 32 chars = total 4096 chars
262 | */
263 | var draw = function() {
264 | var i = DELAY; // 64
265 | var P ='p.\n'; // First line, reference for chars to use
266 | var j;
267 |
268 | n += 7;
269 |
270 | while (i > 0) {
271 |
272 | j = DELAY / i;
273 | i -= 1 / DELAY;
274 |
275 | let index;
276 | let iIsOdd = (i % 2 != 0);
277 |
278 | if (iIsOdd) {
279 | let magic = ((i % 2 * j - j + n / DELAY) ^ j); // < ------------------
280 | let magicIsOdd = (magic % 2 != 0); // &1
281 | if (magicIsOdd) { // &1
282 | index = 1;
283 | } else {
284 | index = 0;
285 | }
286 | } else {
287 | index = 2;
288 | }
289 |
290 | switch (index) { // P += P[index];
291 | case 0:
292 | P += "p";
293 | break;
294 | case 1:
295 | P += ".";
296 | break;
297 | case 2:
298 | P += "\n";
299 | }
300 | }
301 | //Update HTML
302 | p.innerHTML = P;
303 | };
304 |
305 | setInterval(draw, 64);
306 | ```
307 |
308 | 你可以在[这里](https://codepen.io/yangzj1992/pen/jLqNLY)看到最后的代码。
309 |
310 | ## 第二部分,理解代码。
311 |
312 | 在 `draw()` 函数第一次执行时 i 被 `var i = DELAY;` 初始化为 64,并在每次循环中被 `i -= 1 / DELAY;` 进行逐次 1/64 的递减。直到 `i > 0` 时结束(循环 64 * 64 次)
313 |
314 | 而我们的图像则是由 32 行组成,每行包含了 128 个字符。这里我们可以注意到我们会对 i 进行奇偶判断:`let iIsOdd = (i % 2 != 0)` 当 i 为偶数时,总计会发生 32 次。(即 64、62、60...等)。此时 index 将被赋值为 2。此时通过 `P += "\n";` 来添加新的一行。剩下的 127 次循环产生的字符即为 `p` 或 `.`。
315 |
316 | 由代码可知,当 `((i % 2 * j - j + n / DELAY) ^ j);` 为奇数时。我们会添加 `.`,反之会添加 `p`。
317 |
318 | 这里问题的关键来了,这串式子什么时候分别为奇偶呢?在我们研究前,我们可以做一个实验,让我们从 `let magic = ((i % 2 * j - j + n / DELAY) ^ j);` 中移除 `+ n/DELAY`。刷新页面后,我们获得了一份静止的图像输出。
319 |
320 | 
321 |
322 | 那么,我们就先在移除 `+ n/DELAY` 的情况下进行探讨。对于 `(i % 2 * j - j) ^ j` 因为在每次循环中有: `j = DELAY / i;` 所以我们可以将式子简化为一元式:`(i % 2 * 64/i - 64/i) ^ 64/i`。
323 |
324 | 这里我们借助一个[在线的图表生成工具](https://www.desmos.com/calculator)来帮忙绘制函数。
325 |
326 | 例如,首先我们要绘制的 `i % 2`,它的展示为下图所示的重复一次函数片段,y 值的范围在 0 到 2 之间。
327 |
328 | 
329 |
330 | 如果我们绘制 `64/i` ,所对应的图表展示如下:
331 |
332 | 
333 |
334 | 现在我们将式子结合起来,绘制图如下:
335 |
336 | 
337 |
338 | 将两个函数绘制在一张图上,绘制图如下:
339 |
340 | 
341 |
342 | ### 这些图表是什么意思
343 |
344 | 现在让我们着力观察图表的前16行,即 `i` 值的范围是从 64 到 32。
345 |
346 | 
347 |
348 | 通过 JS 的 XOR (位异或)运算符的计算规则,当你的位运算两端都为 0 或 1 时,将返回 0 ,两端不同时为 1。同时如果你的数是小数的话,将会抛弃小数部分进行计算。
349 |
350 | ```
351 | First Second Result
352 | 1 1 1
353 | 1 0 1
354 | 0 1 1
355 | 0 0 0
356 | ```
357 |
358 | > 异或运算的窍门:对两个数进行三次异或运算,可以互换他们的值,不需要引入临时变量。
359 | > ```
360 | var a=12;
361 | var b=23;
362 | a^=b,b^=a,a^=b;//a=23,b=12
363 | ```
364 |
365 | > 异或运算也可以用来取整 `1.23^0 //1`,`3.5^0 //3`
366 | > 当负数与整数进行异或运算时。负数先进行补码,取反再加一。正数的原码和补码一致。
367 | >
368 | > 如,对于 -2:
369 | > ```
370 | 源码:1000 0000 0000 0010 (负数,最高为是 1)
371 | 反码:1111 1111 1111 1101 (按位取反)
372 | 补码:1111 1111 1111 1110 (加一)
373 | ```
374 | > ```
375 | -2 ^ 3 =
376 | 1111 1111 1111 1110
377 | ^ 0000 0000 0000 0011
378 | = 1111 1111 1111 1101
379 | ```
380 | > 再转回原码(负数最高位不变,其他位取反,+1,正数不变) = - 3
381 |
382 | 在 `i` 值的这个范围内 `j` 将从 1 开始慢慢的走向 2(即基本为 1.XXX)。这样在另一端也为 1 时,我们将会得到异或计算结果为 0(偶数),最后获得 `p` 字符输出。
383 |
384 | 换句话说,每条蓝色的对角线代表着我们 Demo 图表中的一行。因为 `j` 在这 16 行里总是大于 1 而小于 2。我们得到奇数的唯一办法就是使式子 `(i % 2 * j - j)^ j` 即 `i % 2 * i/64 - i/64` 即蓝色的对角线应该处于大于 1 或小于 -1的范围。
385 |
386 | 通过图我们可以看到,在最右侧的对角线上很少有到大于 1 和小于 -1 的地方。随着对角线往左的描绘,对角线逐渐开始变长。到第 16 行位置对角线到达 -2 到 2 的位置。在 16 行以后,我们从静态 Demo 图上也可以看到图的展示规律变成了另一种模式。
387 |
388 | 
389 |
390 | 在第 16 行后,`j` 的值开始大于 2 。这时我们的式子期望也发生了反转,在蓝色对角线大于 2 和小于 -2 时或是 -1 到 1 的范围时式子才能为偶数。这就是为什么在 17 行以后我们能看到更多组 `p` 的展示。此外如果你仔细观察 Demo 的底部几行,你会注意到它们也并没有遵循同样的展示规则,因为在后面图的波动也越来越大了。
391 |
392 | 让我们回到 `+ n/DELAY`,通过代码我们可以知道 n 是从 8 开始(从 1 开始并在每次执行 setInterval 时加 7)。
393 |
394 | 当 n 变成 64 时,此时绘图如下:
395 |
396 | 
397 |
398 | 现在 `j` 的值趋近于 1,x 轴在 62 - 63 上的值为 0.x , 63 - 64 的值为 1.x。可推得在 63 - 64 时对角线的值是 (1 ^ 1 = 0 // even) 添加一串 `p`,62 - 63 时对角线的值是(1^0 = 1 // odd) 添加一串 `.`。
399 |
400 | 此时呈现的 Demo 静态图像如下所示(在 codepen 的 demo 里你可以自行修改 `n` 值进行测试)。它的第一行正如我们所推测的那样。
401 |
402 | 
403 |
404 | 当 n 在下一次执行 setInterval,图表产生了如下的轻微变化。
405 |
406 | 
407 |
408 | 注意,第一行对角线此时增长了 7/64 ≈ 0.1 ,由于 Demo 中 1 行有 128 个字符(对应图表值范围为 2)。相应影响的字符数应该为 0.1 * (128 / 2) = 6.4。我们看一下对应静态图修改后的展示,在第一行中实际移动了 7 个字符,这与我们的猜想也吻合。
409 |
410 | 
411 |
412 | 再来最后一个例子,这是当 setInterval 被调用 7 次时,n = 64 + 9 * 7。
413 |
414 | 
415 |
416 | 此时第一行 `j` 依然等于 1。而 63 -- 64 值为 2.x,62 -- 63 值为 1.x。由于 `1^2 = 3 // odd - .`,`1 ^ 1 = 0 //even - p`。所以展示效果为一串 `.` 后面跟随了一串 `p`。如图所示:
417 |
418 | 
419 |
420 | 之后 Demo 将会按类似的规则反复进行渲染。
421 |
422 | 代码的原理大致如此,尽管亲手进行正向压缩简化代码到如此程度确实很难,但是去尝试着理解它也是很有趣的。希望这篇文章能对你有所帮助。
--------------------------------------------------------------------------------
/githug游戏推介及参考答案『55关版』.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: githug游戏推介及参考答案『55关版』
3 | date: 2016-01-04 00:21:21
4 | categories: Git
5 | tags: [Git]
6 | ---
7 |
8 | # 游戏简介
9 | 这几天看到一个 git 的游戏—— [githug](https://github.com/Gazler/githug),试了下发现可以帮助大家熟悉 git 的一些常用操作,试着做了一下感觉还不错,通关过程基本没有什么太大的难点,这里做一个推荐并把自己通关的过程记录一下方便后人查看。
10 |
11 | 当然这些游戏练习只是帮助熟悉,难度基本属于日常操作,如果是git新手最好的学习方法还是先看一遍 [git 文档](https://git-scm.com/book/zh/v2)
12 |
13 | githug 题目 list(更新日期为2016-01-03)
14 |
15 | ``` bash
16 | #1: init
17 | #2: config
18 | #3: add
19 | #4: commit
20 | #5: clone
21 | #6: clone_to_folder
22 | #7: ignore
23 | #8: include
24 | #9: status
25 | #10: number_of_files_committed
26 | #11: rm
27 | #12: rm_cached
28 | #13: stash
29 | #14: rename
30 | #15: restructure
31 | #16: log
32 | #17: tag
33 | #18: push_tags
34 | #19: commit_amend
35 | #20: commit_in_future
36 | #21: reset
37 | #22: reset_soft
38 | #23: checkout_file
39 | #24: remote
40 | #25: remote_url
41 | #26: pull
42 | #27: remote_add
43 | #28: push
44 | #29: diff
45 | #30: blame
46 | #31: branch
47 | #32: checkout
48 | #33: checkout_tag
49 | #34: checkout_tag_over_branch
50 | #35: branch_at
51 | #36: delete_branch
52 | #37: push_branch
53 | #38: merge
54 | #39: fetch
55 | #40: rebase
56 | #41: repack
57 | #42: cherry-pick
58 | #43: grep
59 | #44: rename_commit
60 | #45: squash
61 | #46: merge_squash
62 | #47: reorder
63 | #48: bisect
64 | #49: stage_lines
65 | #50: find_old_branch
66 | #51: revert
67 | #52: restore
68 | #53: conflict
69 | #54: submodule
70 | #55: contribute
71 | ```
72 |
73 | 个人git alias如下
74 |
75 | ```
76 | alias.co=checkout
77 | alias.ci=commit
78 | alias.st=status
79 | alias.br=branch
80 | ```
81 | 整个过程中如果暂时想不起来也可以查看提示
82 |
83 | ```
84 | githug hint
85 | ```
86 |
87 | # 攻略
88 |
89 | ---
90 | ## Name: init
91 |
92 | Level: 1
93 |
94 | Difficulty: *
95 |
96 | ```
97 | A new directory, `git_hug`, has been created; initialize an empty repository in it.
98 | ```
99 |
100 | Answer:
101 |
102 | ```
103 | $ git init
104 |
105 | ```
106 | ---
107 | ## Name: config
108 |
109 | Level: 2
110 |
111 | Difficulty: *
112 |
113 | ```
114 | Set up your git name and email, this is important so that your commits can be identified.
115 | ```
116 |
117 | Answer:
118 |
119 | ```
120 | $ git config --global user.name "yangzj1992"
121 |
122 | $ git config --global user.email yangzj1992@qq.com
123 | ```
124 | ---
125 | ## Name: add
126 |
127 | Level: 3
128 |
129 | Difficulty: *
130 |
131 | ```
132 | There is a file in your folder called `README`, you should add it to your staging area
133 |
134 | Note: You start each level with a new repo. Don't look for files from the previous one.
135 | ```
136 |
137 |
138 | Answer:
139 |
140 | ```
141 | $ git add README
142 | ```
143 | ---
144 | ## Name: commit
145 |
146 | Level: 4
147 |
148 | Difficulty: *
149 |
150 | ```
151 | The `README` file has been added to your staging area, now commit it.
152 | ```
153 |
154 | Answer:
155 |
156 | ```
157 | $ git ci -m "add"
158 | ```
159 | ---
160 | ## Name: clone
161 |
162 | Level: 5
163 |
164 | Difficulty: *
165 |
166 | ```
167 | Clone the repository at https://github.com/Gazler/cloneme.
168 | ```
169 | Answer:
170 | ```
171 | $ git clone https://github.com/Gazler/cloneme
172 | ```
173 | ---
174 | ## Name: clone_to_folder
175 |
176 | Level: 6`
177 |
178 | Difficulty: *
179 |
180 | ```
181 | Clone the repository at https://github.com/Gazler/cloneme to `my_cloned_repo`.
182 | ```
183 |
184 | Answer:
185 | ```
186 | $ git clone https://github.com/Gazler/cloneme my_cloned_repo
187 | ```
188 | ---
189 | ## Name: ignore
190 |
191 | Level: 7
192 |
193 | Difficulty: **
194 |
195 | ```
196 | The text editor 'vim' creates files ending in `.swp` (swap files) for all files that are currently open. We don't want them creeping into the repository. Make this repository ignore `.swp` files.
197 | ```
198 |
199 | Answer:
200 | 在.gitignore文件里添加
201 |
202 | ```
203 | *.swp
204 | ```
205 | ---
206 | ## Name: include
207 |
208 | Level: 8
209 |
210 | Difficulty: **
211 |
212 | Notice a few files with the '.a' extension. We want git to ignore all but the 'lib.a' file.
213 |
214 | Answer:
215 | 在.gitignore文件里添加
216 |
217 | ```
218 | *.a
219 | !lib.a
220 | ```
221 | ---
222 | ## Name: status
223 |
224 | Level: 9
225 |
226 | Difficulty: *
227 |
228 | There are some files in this repository, one of the files is untracked, which file is it?
229 |
230 | Answer:
231 |
232 | ```
233 | $ git st
234 | 输入Untracked files 的名字(database.yml)
235 | ```
236 |
237 | ---
238 | ## Name: number_of_files_committed
239 |
240 | Level: 10
241 |
242 | Difficulty: *
243 |
244 | There are some files in this repository, how many of the files will be committed?
245 |
246 | Answer:
247 |
248 | ```
249 | $ git st
250 | 输入Changes to be committed的文件数量(2)
251 | ```
252 |
253 | ---
254 | ## Name: rm
255 |
256 | Level: 11
257 |
258 | Difficulty: **
259 |
260 | A file has been removed from the working tree, however the file was not removed from the repository. Find out what this file was and remove it.
261 |
262 | Answer:
263 |
264 | ```
265 | $ git st
266 | $ git rm deleteme.rb
267 | ```
268 | ---
269 | ## Name: rm_cached
270 |
271 | Level: 12
272 |
273 | Difficulty: **
274 |
275 | A file has accidentally been added to your staging area, find out which file and remove it from the staging area. *NOTE* Do not remove the file from the file system, only from git.
276 |
277 | Answer:
278 |
279 | ```
280 | $ git st
281 | $ git rm --cached deleteme.rb
282 | ```
283 | ---
284 | ## Name: stash
285 |
286 | Level: 13
287 |
288 | Difficulty: **
289 |
290 | You've made some changes and want to work on them later. You should save them, but don't commit them.
291 |
292 | Answer:
293 |
294 | ```
295 | $ git stash
296 | ```
297 | ---
298 | ## Name: rename
299 |
300 | Level: 14
301 |
302 | Difficulty: ***
303 |
304 | We have a file called `oldfile.txt`. We want to rename it to `newfile.txt` and stage this change.
305 |
306 | Answer:
307 |
308 | ```
309 | $ git mv oldfile.txt newfile.txt
310 | ```
311 | ---
312 | ## Name: restructure
313 |
314 | Level: 15
315 |
316 | Difficulty: ***
317 |
318 | You added some files to your repository, but now realize that your project needs to be restructured. Make a new folder named `src` and using Git move all of the .html files into this folder.
319 |
320 | Answer:
321 |
322 | ```
323 | $ mkdir src
324 | $ git mv *.html src
325 | ```
326 | ---
327 | ## Name: log
328 |
329 | Level: 16
330 |
331 | Difficulty: **
332 |
333 | You will be asked for the hash of most recent commit. You will need to investigate the logs of the repository for this.
334 |
335 | Answer:
336 |
337 | ```
338 | $ git log
339 | 输入recent commit 的hash(afc31d4ce6322353cc6bd32e9e661dd8d974e419)
340 | ```
341 |
342 | ---
343 | ## Name: tag
344 |
345 | Level: 17
346 |
347 | Difficulty: **
348 |
349 | We have a git repo and we want to tag the current commit with `new_tag`.
350 |
351 | Answer:
352 |
353 | ```
354 | $ git tag new_tag
355 | ```
356 | ---
357 | ## Name: push_tags
358 |
359 | Level: 18
360 |
361 | Difficulty: **
362 |
363 | There are tags in the repository that aren't pushed into remote repository. Push them now.
364 |
365 | Answer:
366 |
367 | ```
368 | $ git push --tags
369 | ```
370 | ---
371 | ## Name: commit_amend
372 |
373 | Level: 19
374 |
375 | Difficulty: **
376 |
377 | The `README` file has been committed, but it looks like the file `forgotten_file.rb` was missing from the commit. Add the file and amend your previous commit to include it.
378 |
379 | Answer:
380 |
381 | ```
382 | $ git st
383 | $ git ci --amend
384 | ```
385 | ---
386 | ## Name: commit_in_future
387 |
388 | Level: 20
389 |
390 | Difficulty: **
391 |
392 | Commit your changes with the future date (e.g. tomorrow).
393 |
394 | Answer:
395 |
396 | ```
397 | $ git ci --date=2016-01-04T00:00:00
398 | ```
399 | ---
400 | ## Name: reset
401 |
402 | Level: 21
403 |
404 | Difficulty: **
405 |
406 | There are two files to be committed. The goal was to add each file as a separate commit, however both were added by accident. Unstage the file `to_commit_second.rb` using the reset command (don't commit anything).
407 |
408 | Answer:
409 |
410 | ```
411 | $ git reset head to_commit_second.rb
412 | ```
413 | ---
414 | ## Name: reset_soft
415 |
416 | Level: 22
417 |
418 | Difficulty: **
419 |
420 | You committed too soon. Now you want to undo the last commit, while keeping the index.
421 |
422 | Answer:
423 |
424 | ```
425 | $ git reset --soft HEAD^
426 | ```
427 | ---
428 | ## Name: checkout_file
429 |
430 | Level: 23
431 |
432 | Difficulty: ***
433 |
434 | A file has been modified, but you don't want to keep the modification. Checkout the `config.rb` file from the last commit.
435 |
436 | Answer:
437 |
438 | ```
439 | $ git co -- config.rb
440 | ```
441 | ---
442 | ## Name: remote
443 |
444 | Level: 24
445 |
446 | Difficulty: **
447 |
448 | This project has a remote repository. Identify it.
449 |
450 | Answer:
451 |
452 | ```
453 | $ git remote -v
454 | 输入remote_repo name(my_remote_repo)
455 | ```
456 |
457 | ---
458 | ## Name: remote_url
459 |
460 | Level: 25
461 |
462 | Difficulty: **
463 |
464 | The remote repositories have a url associated to them. Please enter the url of remote_location.
465 |
466 | Answer:
467 |
468 | ```
469 | $ git remote -v
470 | 输入remote_repo url(https://github.com/githug/not_a_repo)
471 | ```
472 |
473 | ---
474 | ## Name: pull
475 |
476 | Level: 26
477 |
478 | Difficulty: **
479 |
480 | You need to pull changes from your origin repository.
481 |
482 | Answer:
483 |
484 | ```
485 | $ git pull origin master
486 | ```
487 | ---
488 | ## Name: remote_add
489 |
490 | Level: 27
491 |
492 | Difficulty: **
493 |
494 | Add a remote repository called `origin` with the url https://github.com/githug/githug
495 |
496 | Answer:
497 |
498 | ```
499 | $ git remote add origin https://github.com/githug/githug
500 | ```
501 | ---
502 | ## Name: push
503 |
504 | Level: 28
505 |
506 | Difficulty: ***
507 |
508 | Your local master branch has diverged from the remote origin/master branch. Rebase your commit onto origin/master and push it to remote.
509 |
510 | Answer:
511 |
512 | ```
513 | $ git rebase origin/master
514 | $ git push origin master
515 | ```
516 | ---
517 | ## Name: diff
518 |
519 | Level: 29
520 |
521 | Difficulty: **
522 |
523 | There have been modifications to the `app.rb` file since your last commit. Find out which line has changed.
524 |
525 | Answer:
526 |
527 | ```
528 | $ git diff
529 | 输入changed line number(26)
530 | ```
531 |
532 | ---
533 | ## Name: blame
534 |
535 | Level: 30
536 |
537 | Difficulty: **
538 |
539 | Someone has put a password inside the file `config.rb` find out who it was.
540 |
541 | Answer:
542 |
543 | ```
544 | $ git blame config.rb
545 | 输入password行的最近提交人(Spider Man)
546 | ```
547 |
548 | ---
549 | ## Name: branch
550 |
551 | Level: 31
552 |
553 | Difficulty: *
554 |
555 | You want to work on a piece of code that has the potential to break things, create the branch test_code.
556 |
557 | Answer:
558 |
559 | ```
560 | $ git co -b test_code
561 | ```
562 | ---
563 | ## Name: checkout
564 |
565 | Level: 32
566 |
567 | Difficulty: **
568 |
569 | Create and switch to a new branch called my_branch. You will need to create a branch like you did in the previous level.
570 |
571 | Answer:
572 |
573 | ```
574 | $ git co -b my_branch
575 | ```
576 | ---
577 | ## Name: checkout_tag
578 |
579 | Level: 33
580 |
581 | Difficulty: **
582 |
583 | You need to fix a bug in the version 1.2 of your app. Checkout the tag `v1.2`.
584 |
585 | Answer:
586 |
587 | ```
588 | $ git co v1.2
589 | ```
590 | ---
591 | ## Name: checkout_tag_over_branch
592 |
593 | Level: 34
594 |
595 | Difficulty: **
596 |
597 | You need to fix a bug in the version 1.2 of your app. Checkout the tag `v1.2` (Note: There is also a branch named `v1.2`).
598 |
599 | Answer:
600 |
601 | ```
602 | $ git co tags/v1.2
603 | ```
604 | ---
605 | ## Name: branch_at
606 |
607 | Level: 35
608 |
609 | Difficulty: ***
610 |
611 | You forgot to branch at the previous commit and made a commit on top of it. Create branch test_branch at the commit before the last.
612 |
613 | Answer:
614 |
615 | ```
616 | $ git br test_branch -v a2ba76984a08d706d40a4d206462140e2ec4c53b
617 | ```
618 |
619 | ---
620 | ## Name: delete_branch
621 |
622 | Level: 36
623 |
624 | Difficulty: **
625 |
626 | You have created too many branches for your project. There is an old branch in your repo called 'delete_me', you should delete it.
627 |
628 | Answer:
629 |
630 | ```
631 | $ git br -d delete_me
632 | ```
633 | ---
634 | ## Name: push_branch
635 |
636 | Level: 37
637 |
638 | Difficulty: **
639 |
640 | You've made some changes to a local branch and want to share it, but aren't yet ready to merge it with the 'master' branch. Push only 'test_branch' to the remote repository
641 |
642 | Answer:
643 |
644 | ```
645 | $ git push origin test_branch
646 | ```
647 | ---
648 | ## Name: merge
649 |
650 | Level: 38
651 |
652 | Difficulty: **
653 |
654 | We have a file in the branch 'feature'; Let's merge it to the master branch.
655 |
656 | Answer:
657 |
658 | ```
659 | $ git merge feature
660 | ```
661 | ---
662 | ## Name: fetch
663 |
664 | Level: 39
665 |
666 | Difficulty: **
667 |
668 | Looks like a new branch was pushed into our remote repository. Get the changes without merging them with the local repository
669 |
670 | Answer:
671 |
672 | ```
673 | $ git fetch origin
674 | ```
675 | ---
676 | ## Name: rebase
677 |
678 | Level: 40
679 |
680 | Difficulty: **
681 |
682 | We are using a git rebase workflow and the feature branch is ready to go into master. Let's rebase the feature branch onto our master branch.
683 |
684 | Answer:
685 |
686 | ```
687 | $ git co feature
688 | $ git rebase master
689 | ```
690 | ---
691 | ## Name: repack
692 |
693 | Level: 41
694 |
695 | Difficulty: **
696 |
697 | Optimise how your repository is packaged ensuring that redundant packs are removed.
698 |
699 | Answer:
700 |
701 | ```
702 | $ git repack -d
703 | ```
704 | ---
705 | ## Name: cherry-pick
706 |
707 | Level: 42
708 |
709 | Difficulty: ***
710 |
711 | Your new feature isn't worth the time and you're going to delete it. But it has one commit that fills in `README` file, and you want this commit to be on the master as well.
712 |
713 | Answer:
714 |
715 | ```
716 | $ git co new-master
717 | $ git blame README.md
718 | $ git cherry-pick ca32a6da
719 | ```
720 | ---
721 | ## Name: grep
722 |
723 | Level: 43
724 |
725 | Difficulty: **
726 |
727 | Your project's deadline approaches, you should evaluate how many TODOs are left in your code
728 |
729 | Answer:
730 |
731 | ```
732 | $ git grep TODO
733 | ```
734 | ---
735 | ## Name: rename_commit
736 |
737 | Level: 44
738 |
739 | Difficulty: ***
740 |
741 | Correct the typo in the message of your first (non-root) commit.
742 |
743 | Answer:
744 |
745 | ```
746 | $ git rebase -i HEAD~2
747 | ```
748 | 将错误的commit的pick修改为edit,然后
749 | ```
750 | $ git ci --amend 修改commit信息
751 | $ git rebase --continue
752 | ```
753 | ---
754 | ## Name: squash
755 |
756 | Level: 45
757 |
758 | Difficulty: ****
759 |
760 | You have committed several times but would like all those changes to be one commit.
761 |
762 | Answer:
763 |
764 | ```
765 | $ git rebase 331299b796c6e873dbffd08f2ac111454fa75b8a -i(最先的commit hash)
766 | 修改pick 为squash
767 | ```
768 | ---
769 | ## Name: merge_squash
770 |
771 | Level: 46
772 |
773 | Difficulty: ***
774 |
775 | Merge all commits from the long-feature-branch as a single commit.
776 |
777 | Answer:
778 |
779 | ```
780 | $ git merge --squash long-feature-branch
781 | $ git ci -a -m "merge branch"
782 | ```
783 | ---
784 | ## Name: reorder
785 |
786 | Level: 47
787 |
788 | Difficulty: ****
789 |
790 | You have committed several times but in the wrong order. Please reorder your commits.
791 |
792 | Answer:
793 |
794 | ```
795 | $ git rebase -i 89b082ce1e8c445b5ad336d51fe4491756949053
796 | 修改pick的顺序
797 | ```
798 | ---
799 | ## Name: bisect
800 |
801 | Level: 48
802 |
803 | Difficulty: ***
804 |
805 | A bug was introduced somewhere along the way. You know that running `ruby prog.rb 5` should output 15. You can also run `make test`. What are the first 7 chars of the hash of the commit that introduced the bug.
806 |
807 | Answer:
808 | 二分查找,可能不少人看不懂这关。。其实就是让你用 git 来找 BUG。
809 |
810 | ```
811 | $ git log --reverse 找到第一次提交hash
812 | $ git bisect start
813 | $ git bisect good f608824888b83bbedc1f658be7496ffea467a8fb
814 | $ git bisect bad
815 | $ git bisect run make test
816 | 日志里的18ed2ac1522a014412d4303ce7c8db39becab076 is the first bad commit回答此hash
817 | ```
818 |
819 | ---
820 | ## Name: stage_lines
821 |
822 | Level: 49
823 |
824 | Difficulty: ****
825 |
826 | You've made changes within a single file that belong to two different features, but neither of the changes are yet staged. Stage only the changes belonging to the first feature.
827 |
828 | Answer:
829 |
830 | ```
831 | $ git add -p
832 | stage this hunk 选择e编辑,删除second feature stage
833 | ```
834 | ---
835 | ## Name: find_old_branch
836 |
837 | Level: 50
838 |
839 | Difficulty: ****
840 |
841 | You have been working on a branch but got distracted by a major issue and forgot the name of it. Switch back to that branch.
842 |
843 | Answer:
844 |
845 | ```
846 | $ git reflog
847 | 可见操作历史中倒数第二条:6876e5b HEAD@{1}: checkout: moving from solve_world_hunger to kill_the_batman
848 | 说明原来是在 solve_world_hunger分支
849 | $ git co solve_world_hunger
850 | ```
851 | ---
852 | ## Name: revert
853 |
854 | Level: 51
855 |
856 | Difficulty: ****
857 |
858 | You have committed several times but want to undo the middle commit.
859 | All commits have been pushed, so you can't change existing history.
860 |
861 | Answer:
862 |
863 | ```
864 | $ git revert 8be480b0e0332dedf8dd9a2b4ee3c4d061a2c79d
865 | ```
866 | ---
867 | ## Name: restore
868 |
869 | Level: 52
870 |
871 | Difficulty: ****
872 |
873 | You decided to delete your latest commit by running `git reset --hard HEAD^`. (Not a smart thing to do.) You then change your mind, and want that commit back. Restore the deleted commit.
874 |
875 | Answer:
876 |
877 | ```
878 | $ git reflog
879 | $ git co 5fefc92
880 | ```
881 | ---
882 | ## Name: conflict
883 |
884 | Level: 53
885 |
886 | Difficulty: ****
887 |
888 | You need to merge mybranch into the current branch (master). But there may be some incorrect changes in mybranch which may cause conflicts. Solve any merge-conflicts you come across and finish the merge.
889 |
890 | Answer:
891 |
892 | ```
893 | $ git merge mybranch
894 | $ vim poem.txt
895 | $ git add .
896 | $ git ci -m "merge"
897 | ```
898 | ---
899 | ## Name: submodule
900 |
901 | Level: 54
902 |
903 | Difficulty: **
904 |
905 | You want to include the files from the following repo: `https://github.com/jackmaney/githug-include-me` into a the folder `./githug-include-me`. Do this without cloning the repo or copying the files from the repo into this repo.
906 |
907 | Answer:
908 |
909 | ```
910 | $ git submodule add https://github.com/jackmaney/githug-include-me githug-include-me
911 | ```
912 | ---
913 | ## Name: contribute
914 |
915 | Level: 55
916 |
917 | Difficulty: ***
918 |
919 | This is the final level, the goal is to contribute to this repository by making a pull request on Github. Please note that this level is designed to encourage you to add a valid contribution to Githug, not testing your ability to create a pull request. Contributions that are likely to be accepted are levels, bug fixes and improved documentation.
920 |
921 |
922 |
923 |
924 |
--------------------------------------------------------------------------------
/使用npm scripts构建项目.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 使用 npm scripts 构建项目
3 | date: 2016-02-28 11:49:21
4 | categories: 构建工具
5 | tags: [npm,构建工具,Gulp,Grunt]
6 | ---
7 | ## 背景
8 |
9 | 最近看到了几篇文章,讲述了运用 npm 来代替 Gulp,Grunt 进行构建的工作,文中一些说法和场景让我感同身受、深表赞同,故在此整理一下相关内容与方法。
10 | 原帖地址:
11 | [我为何放弃Gulp与Grunt,转投npm scripts](http://www.infoq.com/cn/news/2016/02/gulp-grunt-npm-scripts-part1)
12 | [Why npm Scripts?](https://css-tricks.com/why-npm-scripts/)
13 | [Why npm Scripts?【译】](http://www.cnblogs.com/zldream1106/p/why-npm-scripts.html)
14 |
15 | 众所周知,Gulp 与 Grunt 是很多项目所使用的构建工具,他们拥有非常丰富的插件。不过,我却认为 Gulp 与 Grunt 是完全不必要的抽象,npm scripts 更加强大,并且更易于使用。
16 |
17 | 我本人是 Gulp 的粉丝。不过在上一个项目中,gulpfile 竟然有 100 多行,而且还使用了不少 Gulp 插件。我尝试通过 Gulp 集成 Webpack、Browsersync、热加载、Mocha 等工具,为什么要这么做呢?这是因为有些插件的文档实在是太不充分了;还有些插件只公开了我所需的部分 API。其中有个插件存在一个奇怪的 Bug,它只能看到文件的部分内容。另一个插件则在输出到命令行时丢失了颜色。
18 |
19 | 当然了,这些问题都是可以解决的;不过,这势必会消耗我们相当的时间成本,最近,我注意到有很多开源项目只是使用了 npm scripts。因此,我决定重新审视一下自己的做法。我真的需要 Gulp 么?答案是并不需要。现在,相对于 Gulp 来说,我更倾向于使用 npm scripts,下面就来谈谈原因。
20 |
21 | ## Gulp 与 Grunt 怎么了?
22 |
23 | 随着时间的流逝,我们会逐渐发现诸如 Gulp 与 Grunt 等任务运行器都存在以下 3 个核心问题:
24 |
25 | - 对插件作者的依赖
26 | - 令人沮丧的调试
27 | - 脱节的文档
28 | 下面就来详细分析上述 3 个问题。
29 |
30 | ### 问题 1:对插件作者的依赖
31 |
32 | 在使用比较新或是不那么流行的技术时,可能根本就没有插件。当有插件可用时,插件可能已经过时了。比如说,Babel 6 前一阵发布了。其 API 变化非常大,这样很多 Gulp 插件都无法兼容于最新的版本。在使用 Gulp 时,我就感到深深的受伤,因为我所需要的 Gulp 插件还没有更新。在使用 Gulp 或是 Grunt 时,你不得不等待插件维护者提供更新,或是自己修复。这会阻碍你使用最新版现代化工具的机会。与之相反,在使用 npm scripts 时,我会直接使用工具,不必再添加一个额外的抽象层。这意味着当新版本的 Mocha、Istanbul、Babel、Webpack、Browserify 等发布时,我可以立刻就使用上新的版本。对于选择来说,没有什么能够打败 npm:
33 |
34 | 
35 |
36 | 从上图可以看到,Gulp 有将近 2,100 个插件;Grunt 有将近 5,400 个;而 npm 则提供了 227,000 多个包,同时还以每天 400 多个的速度在持续增加。
37 |
38 | 在使用npm scripts时,你无需再搜索Grunt或是Gulp插件;只需从227,000多个npm包中选择就行了。公平地说,如果所需要的 Grunt 或是 Gulp 插件不存在,你当然可以直接使用 npm packages。不过,这样就无法再针对这个特定的任务使用 Gulp 或是 Grunt 了。
39 |
40 | ### 问题 2:令人沮丧的调试
41 |
42 | 如果集成失败了,那么在 Grunt 和 Gulp 中调试是一件令人沮丧的事情。因为你面对的是一个额外的抽象层,对于任何 Bug 来说都有可能存在更多潜在的原因:
43 |
44 | - 基础工具出问题了么?
45 | - Grunt/Gulp 插件出问题了么?
46 | - 配置出问题了么?
47 | - 使用的版本是不是不兼容?
48 | 使用 npm scripts 可以消除上面的第 2 点,我发现第 3 点也很少会出现,因为我通常都是直接调用工具的命令行接口。最后,第 4 点也很少出现,因为我通过直接使用 npm 而不是任务运行器的抽象减少了项目中包的数量。
49 |
50 | ### 问题 3:脱节的文档
51 |
52 | 一般来说,我所需要的核心工具的文档质量总是要比与之相关的 Grunt 和 Gulp 插件的好。比如说,如果使用了 gulp-eslint ,那么我就要在 [gulp-eslint](https://github.com/adametry/gulp-eslint) 文档与 ESLint 网站之间来回切换;不得不在插件与插件所抽象的工具之间来回切换上下文。Gulp 与 Grunt 的问题在于:光理解所用的工具是远远不够的。Gulp 与 Grunt 要求你还得理解插件的抽象。
53 |
54 | 大多数构建相关的工具都提供了清晰、强大,且具有高质量文档的命令行接口。[ESLint 的 CLI 文档](http://eslint.org/docs/user-guide/command-line-interface)就是个很好的例子。我发现在 npm scripts 中阅读并实现一个简短的命令行调用会更加轻松,阻碍更少,也更易于调试(因为并没有抽象层存在)。既然已经知道了痛点,接下来的问题就在于,为何我们觉得自己还需要诸如 Gulp 与 Grunt 之类的任务运行器呢?
55 |
56 | 我相信个中原因应该是因人而异的。毫无疑问,Gulp 与 Grunt 等任务运行器已经出现很长一段时间了,而且围绕着这些任务运行器的插件生态圈也呈现出欣欣向荣的繁荣景象。依赖于这些插件,很多日常工作都可以实现自动化,并且运行良好。这样,人们就会认为只有通过这些任务运行器才能实现任务的构建、文件的打包、工作流的良好运行等等。另外一个原因就是人们对于 npm scripts 的认识还远远不够;对于 npm scripts 所能完成的事情与任务也流于表面。这也进一步造成了很多人并没有发现 npm scripts 可以实现很多日常开发时的构建任务的结果。相信随着开发者对于 npm scripts 认识的进一步深入,大家会逐步发现原来使用 npm scripts 也可以完成 Gulp 与 Grunt 等任务运行器所能完成的任务,而且配置更加简单,也更加直接,因为它会直接使用目标工具而不必再使用对目标工具的包装器了。
57 |
58 | 接下来我们谈谈 npm scripts 的强大功能以及人们为何会忽略 npm scripts。
59 |
60 | ## 在构建时我们为何会忽略掉 npm?
61 |
62 | 我认为有如下 4 点原因造成 Gulp 与 Grunt 等任务运行器变得如此流行:
63 |
64 | - 人们认为 npm scripts 需要强大的命令行技巧
65 | - 人们认为 npm scripts 不够强大
66 | - 人们认为 Gulp 的流对于快速构建来说是不可或缺的
67 | - 人们认为 npm scripts 无法实现跨平台运行
68 | 下面我们将按顺序依次解释一下这些误解。
69 |
70 | ### 误解 1:使用 npm scripts 需要强大的命令行技巧
71 |
72 | 体验 npm scripts 的强大功能其实并不需要对操作系统的命令行了解太多。当然了,[grep、sed、awk 与管道](http://www.tutorialspoint.com/unix/unix-useful-commands.htm)等是值得你去学习的,令你众生受用的技能;不过,为了使用 npm scripts,你不必非得成为 Unix 或是 Windows 命令行专家才行。你可以通过 npm 中 1000 多个拥有良好文档的脚本来完成工作。
73 |
74 | 比如说,你可能不知道在 Unix 中,命令 `rm -rf` 会强制删除一个目录,这没问题。你可以使用 [rimraf](https://www.npmjs.com/package/rimraf) 完成同样的事情(它也是跨平台的)。大多数 npm 包都提供了一些接口,这些接口假设你对所用操作系统的命令行了解不多。只需在 npm 中搜索想要使用的包即可,边做边学就行了。过去,我常常会搜索 Gulp 插件,不过现在则是搜索 npm 包了。[libraries.io](https://libraries.io/)是个非常棒的资源。
75 |
76 | ### 误解 2:npm scripts 不够强大
77 |
78 | npm scripts 本身其实是非常强大的。它提供了基于约定的 [pre 与 post 钩子](https://docs.npmjs.com/misc/scripts#description):
79 |
80 | ``` json
81 | {
82 | name: "npm-scripts-example",
83 | version: "1.0.0",
84 | description: "npm scripts example",
85 | scripts: {
86 | prebuild: "echo I run before the build script",
87 | build: "cross-env NODE_ENV=production webpack",
88 | postbuild: "echo I run after the build script"
89 | }
90 | }
91 | ```
92 | 你所要做的就是遵循约定。上述脚本会根据其前缀按照顺序运行。prebuild 脚本会在 build 脚本之前运行,因为他们的名字相同,但 prebuild 脚本有 `pre` 前缀。postbuild 脚本会在 build 脚本之后运行,因为它有 `post` 前缀。因此,如果创建了名为 prebuild、build 与 postbuild 的脚本,那么在我输入 `npm run build` 时,他们就会自动按照这个顺序运行。
93 |
94 | 此外,还可以通过在一个脚本中调用另一个脚本来对大的问题进行分解:
95 |
96 | ``` json
97 | {
98 | "name": "npm-scripts-example",
99 | "version": "1.0.0",
100 | "description": "npm scripts example",
101 | "scripts": {
102 | "clean": "rimraf ./dist && mkdir dist",
103 | "prebuild": "npm run clean",
104 | "build": "cross-env NODE_ENV=production webpack"
105 | }
106 | }
107 | ```
108 | 在上述示例中,prebuild 任务调用了 clean 任务。这样就可以将脚本分解为更小、命名良好、单职责,单行的脚本。
109 |
110 | 可以通过 && 在一行连续调用多个脚本。上述示例中,clean 步骤中的脚本会一个接着一个运行。如果你需要在 Gulp 中按照顺序一个接着一个地运行任务列表中的任务,那么这种简洁性肯定会吸引到你。
111 |
112 | 如果一个命令很复杂,那还可以调用一个单独的文件:
113 |
114 | ``` json
115 | {
116 | "name": "npm-scripts-example",
117 | "version": "1.0.0",
118 | "description": "npm scripts example",
119 | "scripts": {
120 | "build": "node build.js"
121 | }
122 | }
123 | ```
124 | 我在上述的 build 任务中调用了一个单独的脚本。该脚本会被 Node 所运行,这样就可以使用我所需的任何 npm 包了,同时还可以利用上 JavaScript 的能力。我还能列出很多,不过感兴趣的读者可以参考这份[核心特性文档](https://docs.npmjs.com/misc/scripts)。
125 |
126 | ### 误解 3:Gulp 的流对于快速构建来说是不可或缺的
127 |
128 | Gulp 出来后,人们之所以很快就被它吸引过去并放弃 Grunt 的原因在于 Gulp 的内存流要比 Grunt 基于文件的方式快很多。不过,要想享受到流的强大功能,实际上并不需要 Gulp。事实上,流早就已经被内建到 Unix 与 Windows 命令行中了。管道(|)运算符会将一个命令的输出以流的方式作为另一个命令的输入。重定向(>)运算符则会将输出重定向到文件。比如说在 Unix 中,我可以 `grep` 一个文件的内容,并将输出重定向到一个新的文件:
129 |
130 | ``` bash
131 | grep ‘Cory House’ bigFile.txt > linesThatHaveMyName.txt
132 | ```
133 |
134 | 上述过程是流式的,并不会被写入到中间文件中(想知道如何以跨平台的方式实现上面的命令么?请继续往下读)。
135 |
136 | 在 Unix 中,还可以通过 “&” 运算符同时运行两个命令:
137 |
138 | ``` bash
139 | npm run script1.js & npm run script2.js
140 | ```
141 |
142 | 上述两个脚本会同时运行。要想以跨平台的方式同时运行脚本,请使用 [npm-run-all](https://www.npmjs.com/package/npm-run-all)。这就造成了下面这个误解。
143 |
144 | ### 误解 4:npm scripts 无法实现跨平台运行
145 |
146 | 很多项目都会绑定到特定的操作系统上,因此跨平台是一件并不那么重要的事情。不过,如果需要以跨平台的方式运行,那么 npm scripts 依然可以工作得很好。无数的开源项目就是佐证。下面来介绍一下实现方式。
147 |
148 | 操作系统的命令行会运行你的 `npm scripts`。因此,在 Linux 与 OS X 上,`npm scripts` 会在 Unix 命令行中运行。在 Windows 上,`npm scripts` 则运行在 Windows 命令行中。这样,如果希望构建脚本能够运行在所有平台上,你需要适配 Unix 与 Windows。下面介绍 3 种实现方式:
149 |
150 | #### 方式 1:使用跨平台的命令
151 |
152 | 有很多跨平台的命令可供我们使用。下面列举一些:
153 |
154 | ``` bash
155 | && 链式任务(一个任务接着一个任务运行)
156 | < 将文件内容输入到一个命令
157 | > 将命令输出重定向到文件
158 | | 将一个命令的输出重定向到另一个命令
159 | ```
160 | #### 方式 2:使用 node 包
161 |
162 | 可以使用 node 包来代替 shell 命令。比如说,使用 [rimraf](https://www.npmjs.com/package/rimraf)来代替“rm -rf`”。使用 [cross-env](https://www.npmjs.com/package/cross-env) 以跨平台的方式设置环境变量。搜索 Google、npm 或是 [libraries.io](https://libraries.io/),寻找你所需要的,几乎都会有相应的 node 包以跨平台的方式实现你的目标。如果命令行调用过长,你可以在单独的脚本中调用 Node 包,就像下面这样:
163 |
164 | ``` bash
165 | node scriptName.js
166 | ```
167 | 上述脚本就是普通的 JavaScript,由 Node 运行。既然是在命令行调用了脚本,那么你就不会受限于 .js 文件。你可以运行操作系统所能执行的任何脚本,比如说 Bash、Python、Ruby 或是 Powershell 等等。
168 |
169 | #### 方式 3:使用 [ShellJS](https://www.npmjs.com/package/shelljs)
170 |
171 | ShellJS 是个通过 Node 来运行 Unix 命令的 npm 包。这样就可以通过它在所有平台上运行 Unix 命令了,也包括 Windows。
172 |
173 | 本文主要介绍了人们对于 npm scripts 存在的误解,以及 npm scripts 自身所提供的强大功能。借助于操作系统提供的各种基础设施、npm scripts 以及各种命令,我们完全可以通过 npm scripts 以更加轻量级的方式实现 Gulp 与 Grunt 等任务运行器所提供的功能。
174 |
175 | 接下来将会介绍 npm scripts 中存在的一些痛点以及解决之道。
176 |
177 | ## 痛点
178 |
179 | 显然,使用 npm scripts 也存在着一些问题:JSON 规范并不支持注释,因此无法在 package.json 中添加注释。不过有一些办法可以突破这个限制:
180 |
181 | - 编写小巧、命名良好、单一目的的脚本
182 | - 分离文档与脚本(比如说放在 README.md 中)
183 | - 调用单独的 .js 文件
184 |
185 | 我更偏爱第一种解决方案。如果将每个脚本都进行分解,使其保持单一职责,那么注释就变得不那么重要了。脚本的名字应该能完全描述其意图,就像任何短小、命名良好的函数一样。就像我在 “Clean Code: Writing Code for Humans” 中所说的那样,短小、单一职责的函数几乎是不需要注释的。如果觉得有必要添加注释,那么我会使用第 3 种方案,即将脚本移到单独的文件中。这样就可以利用 JavaScript 组合的强大力量了。
186 |
187 | Package.json 也不支持变量。这看起来貌似是个大问题,但实际上并非如此,原因有二。首先,很多时候我们所需的变量都涉及到环境,这可以通过命令行进行设置。其次,如果出于其他原因而需要变量,那么你可以调用单独的 .js 文件。
188 |
189 | 最后,还存在一种风险,那就是使用长长的、复杂的命令行参数,这些参数令人难以理解。代码审查与重构是确保 npm 脚本保持小巧、命名良好、单一职责,且每个人都能容易理解的好方式。如果脚本复杂到需要注释,那么你应该将单个脚本重构为多个命名良好的脚本,或是将其抽取为单独的文件。
190 |
191 | ### 我们需要证明抽象是有意义的
192 |
193 | Gulp 与 Grunt 是对我所使用的工具的抽象。抽象是很有用的,不过抽象是有代价的。它让我们过分依赖于插件维护者与文档,同时随着插件数量的不断攀升,他们也不断引入复杂性。此外不少开发者与此观点不谋而合,比如说下面这些:
194 |
195 | - [Task automation with npm run](http://substack.net/task_automation_with_npm_run)—James Holliday
196 | - [Advanced front-end automation with npm scripts](https://www.youtube.com/watch?v=0RYETb9YVrk)—Kate Hudson
197 | - [How to use npm as a build tool](http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/)—Kieth Cirkel
198 | - [Introduction to npm as a Build Tool](http://app.pluralsight.com/courses/npm-build-tool-introduction)—Marcus Hammarberg
199 | - [Gulp is awesome, but do we really need it?](http://gon.to/2015/02/26/gulp-is-awesome-but-do-we-really-need-it/)—Gonto
200 | - [NPM Scripts for Build Tooling](http://code.tutsplus.com/courses/npm-scripts-for-build-tooling)—Andrew Burgess
201 |
202 |
203 | ## npm 替换方案参考
204 | 近来直接使用 node package 提供的命令行界面的情绪愈发高涨,反之,人们对通过运行任务从而屏蔽抽象功能的热情逐渐降温。在一定程度是,你无论如何都要使用 npm,而同时 npm 也提供了脚本功能,为什么不用呢?
205 |
206 | Grunt, Gulp, Broccoli, Brunch 和类似的工具都需要将自己的任务配置的适合他们的范型和结构,这些工具每一个都需要学习他们不同的语法、奇怪的用法和特有的方法。这增加了编码复杂度、构建复杂度,使得你关注修复工具问题更甚于写代码。
207 |
208 | 由于这些构建工具依赖于包装了核心命令行工具的插件,并基于这个核心工具做了进一步的抽象,这使得出错的情况变得更加复杂。
209 |
210 | 但是,作者在此建议:
211 |
212 | **如果你对当前的构建系统很满意,并且它能够很好的完成你的需求的话,就请继续使用吧!不要因为 npm scripts 越来越流行就盲目的使用它,应该把精力集中在写代码而不是学习更多的工具。如果你开始觉得自己正在和使用的工具战斗,那么这个时候我建议你考虑使用一下 npm scripts。**
213 |
214 | 如果你现在做好决定想要调研或使用 npm scripts,那么请继续阅读本文!本文将会提供大量的案例任务,同时基于这些任务我也创建了 npm-build-boilerplate 以方便你学习。那么下面让我们开始吧!
215 |
216 | ### 写 npm scripts
217 |
218 | 我们会花费大量的时间在 `package.json` 文件上。这个文件描述了我们需要的所有的依赖和脚本。以下是我的 boilerplate 项目中的一部分内容:
219 |
220 | ``` json
221 | 1 {
222 | 2 "name": "npm-build-boilerplate",
223 | 3 "version": "1.0.0",
224 | 4 "scripts": {
225 | 5 ...
226 | 6 },
227 | 7 "devDependencies": {
228 | 8 ...
229 | 9 }
230 | 10 }
231 | ```
232 |
233 |
234 | 接下来我们将逐步创建 `package.json` 文件。我们的脚本会写入 scripts 对象中,所有我们要使用的工具都会被安装并且写入 devDependencies 对象中。在开始之前,以下是本文中的项目结构:
235 | 
236 |
237 | ### 编译 scss 为 css
238 |
239 | 为了将 SCSS 编译成 CSS,我使用了 node-sass。首先,我们需要安装 node-sass,在命令行下运行以下代码:
240 |
241 | ``` bash
242 | npm install --save-dev node-sass
243 | ```
244 | 这个命令会在当前目录下安装 node-sass,并添加到 `package.json` 的 devDependencies 对象中。当其他人使用你的项目时会非常方便,因为他们已经有了运行项目所需的所有内容。只要安装过一次,使用时在命令行运行以下代码即可:
245 |
246 | ``` bash
247 | node-sass --output-style compressed -o dist/css src/scss
248 | ```
249 | 让我们看一下这个命令做了什么:从后向前看,查找 `src/scss` 目录的 SCSS 文件,输出(-o 标识)编译的 CSS 到`dist/css`目录,压缩输出文件(使用 --output-style 标识,设置选项值为"compressed")。
250 | 现在我们知道了在命令行中如何工作,那么让我们把它放到 npm scirpt 中。在`package.json`的 scripts 对象中添加如下内容:
251 |
252 | ``` json
253 | "scripts": {
254 | "scss": "node-sass --output-style compressed -o dist/css src/scss"
255 | }
256 | ```
257 | 现在回到命令行并运行:
258 |
259 | ``` bash
260 | npm run scss
261 | ```
262 | 可以看到这样运行的输出结果和直接在命令行使用 node-sass 命令得到的结果一致。
263 | 本文剩余部分创建的任何一个 npm script,都可以像上例一样使用命令行运行。
264 |
265 | 只要把你想要运行的任务名从 scss 替换成你想要的名字即可。
266 |
267 | 你将看到,我们使用的很多命令行工具都有很多的配置项,你可以使用配置项来精确完成你想要做的工作。比如,这是node-sass的选项列表,以下展示了传多个配置项的配置方法:
268 |
269 | ``` json
270 | "scripts": {
271 | "scss": "node-sass --output-style nested --indent-type tab --indent-width 4 -o dist/css src/scss"
272 | }
273 | ```
274 | ### 使用 PostCSS 自动给 CSS 加前缀
275 |
276 | 我们已经能够把 SCSS 编译成 CSS,现在我们希望通过 Autoprefixer 和 PostCSS 自动给 CSS 代码添加厂商前缀,我们可以通过空格分隔的方式从而同时安装多个模块:
277 |
278 | ``` bash
279 | npm install --save-dev postcss-cli autoprefixer
280 | ```
281 | 因为 PostCSS 默认不做任何事情,所以我们安装了两个模块。PostCSS 依赖其他的插件来处理你提供的 CSS,比如 Autoprefixer。
282 | 安装并保存必要工具到 devDependencies 后,在你的 scripts 对象中添加一个新任务:
283 |
284 | ``` json
285 | "scripts": {
286 | ...
287 | "autoprefixer": "postcss -u autoprefixer -r dist/css/*"
288 | }
289 | ```
290 | 这个任务的意思是:嗨 postcss,使用(-u 标识符)Autoprefixer 替换(-r标识符)`dist/css`目录下的所有`.css`文件,给他们添加厂商前缀代码。就是这样简单!想要修改默认浏览器前缀?只要给脚本添加如下代码即可:
291 |
292 | ``` json
293 | "autoprefixer": "postcss -u autoprefixer --autoprefixer.browsers '> 5%, ie 9' -r dist/css/*"
294 | ```
295 | 再次申明,配置你自己的构建代码有很多选项可以使用:postcss-cli 和 autoprefixer。
296 | ### JavaScript 代码检查
297 |
298 | 对于写代码来说,保持标准格式和样式是非常重要的,它能够确保错误最小化并提高开发效率。"代码检查"帮助我们自动化的完成了这个工作,所以我们通过使用 eslint 来进行代码检查。
299 |
300 | 再次如上文所述,安装 eslint 的包,这次让我们使用快捷方式:
301 |
302 | ``` bash
303 | npm i -D eslint
304 | ```
305 | 这和如下代码是一样的效果:
306 |
307 | ``` bash
308 | npm install --save-dev eslint
309 | ```
310 | 安装完成后,我们给 eslint 配置一些运行代码的基本规则。使用如下代码开始一个向导:
311 |
312 | ``` bash
313 | eslint --init // 译者注:这里直接使用会抛错 eslint 找不到,因为这种使用方法必须安装在全局。
314 | 即通过 npm install i -g eslint 方式安装
315 | ```
316 |
317 | 我建议选择"回答代码风格问题"并回答提问的相关问题。这个过程中 eslint 会在你的项目根目录下生成一个新文件,并检测你的相关代码。
318 | 现在,让我们把代码风格检测任务添加到 `package.json` 的 scripts 对象中:
319 |
320 | ``` json
321 | "scripts": {
322 | ...
323 | "lint": "eslint src/js"
324 | }
325 | ```
326 | 我们的任务仅有 13 字符!它会查找 `src/js` 目录下的所有 JavaScript 文件,并根据刚才生成的规则进行代码检测。当然,如果感兴趣的话你可以详细配置各种规则:[get crazy with the options](http://eslint.org/docs/user-guide/command-line-interface#options)
327 |
328 | ### 混淆压缩 JavaScript 文件
329 |
330 | 让我们继续,下面我们需要使用 uglify-js 来混淆压缩 JavaScript 文件,首先需要安装 uglify-js:
331 |
332 | ``` bash
333 | npm i -D uglify-js
334 | ```
335 | 然后我们可以在 `package.json` 里创建压缩混合任务:
336 |
337 | ``` json
338 | "scripts": {
339 | ...
340 | "uglify": "mkdir -p dist/js && uglifyjs src/js/*.js -m -o dist/js/app.js"
341 | }
342 | ```
343 | npm scripts 的任务的本质是:可以重复执行的、命令行任务的快捷方式(别名),这也是 npm scripts 的优点之一。这就意味着你可以直接在脚本里使用标准命令行代码!这个任务使用了两个标准命令行特性:mkdir 和 &&。
344 |
345 | 这个任务的第一部分“ mkdir -p dist/js ”:如果不存在目录(-p 标识)就创建一个目录结构(mkdir),创建成功后执行 uglifyjs 命令。&& 帮助你连接多个命令,如果前一个命令成功执行的话,就分别顺序执行剩余的命令。
346 |
347 | 这个任务的第二部分告诉 uglifyjs 针对 `src/js/` 目录下的所有 JS 文件(`*.js`),使用 `mangle`命令(-m 标识),输出结果到 `dist/js/app.js` 文件中。这里是 uglifyjs 工具的全部配置选项 list of options。
348 |
349 | 让我们来更新一下 uglify 任务,创建一个 `dist/js/app.js` 的压缩版本,链接另外一个 uglifyjs 的命令并传参给 `compress`(-c标识)。
350 |
351 | ``` json
352 | "scripts": {
353 | ...
354 | "uglify": "mkdir -p dist/js && uglifyjs src/js/*.js -m -o dist/js/app.js && uglifyjs src/js/*.js -m -c -o dist/js/app.min.js"
355 | }
356 | ```
357 | ### 压缩图片
358 |
359 | 下面我们将进行图片压缩的工作。根据 httparchive.org 的数据统计,网络上前 1000 名的网站平均页面大小为 1.9 M ,其中图片占了 1.1 M(with images accounting for 1.1mb of that total)。所以提高网页加载速度的其中一个好办法就是减小图片大小。
360 |
361 | 安装 imagemin-cli:
362 |
363 | ``` bash
364 | npm i -D imagemin-cli
365 | ```
366 | Imagemin 非常棒,它可以压缩大多数图片类型,包括 GIF、JPG、PNG 和 SVG。 使用如下代码可以将一整个文件夹的图片全部压缩:
367 |
368 | ``` json
369 | "scripts": {
370 | ...
371 | "imagemin": "imagemin src/images dist/images -p",
372 | }
373 | ```
374 | 这个任务告诉 imagemin 找到并压缩 `src/images` 中的所有图片并输出到 `dist/images`中。-p 标志在允许的情况下将图片处理成渐进图片。更多配置可查看文档 all available options
375 |
376 | ### SVG 精灵(Sprites)
377 |
378 | 关于 SVG 的讨论近年来逐渐火热,SVG 有众多优点:在所有的设备上保持松散结构、可通过 CSS 编辑、对读屏软件友好。然而,SVG 编辑软件经常会产生大量的冗余代码。幸运的是,svgo 可以帮助你自动删除这些冗余信息(我们马上就会安装 svgo)。
379 |
380 | 接下来我们来安装 svg-sprite-generator,用于自动处理并整合多个 SVG 文件为一个 SVG 文件(更多处理方案:more on that technique here)。
381 |
382 | ``` bash
383 | npm i -D svgo svg-sprite-generator
384 | ```
385 | 你现在应该已经熟悉了这个过程——添加一个任务在你的 `package.json` scripts 对象中:
386 |
387 | ``` json
388 | "scripts": {
389 | ...
390 | "icons": "svgo -f src/images/icons && mkdir -p dist/images && svg-sprite-generate -d src/images/icons -o dist/images/icons.svg"
391 | }
392 | ```
393 | 注意 icons 任务通过两个 && 引导符做了三件事情:
394 | 1. 使用 svgo 传参一个 SVG 目录(-f标识),这个操作压缩了目录内的全部 SVG 文件;
395 | 2. 如果不存在 'dist/images' 目录则创建该目录(使用 mkdir -p命令);
396 | 3. 使用 svg-sprite-generator,传参一个 SVG 目录(-d 标识)以及输出处理后的 SVG 文件的目录路径名(-o 标识)。
397 |
398 | ### 通过 BrowserSync 提供服务、自动监测并注入变更
399 |
400 | 最后一个插件是 BrowserSync,它可以做如下事情:启动一个本地服务,向连接的浏览器自动注入更新的文件,并同步浏览器的点击和滚动。安装并添加任务的代码如下:
401 |
402 | ``` bash
403 | npm i -D browser-sync
404 | "scripts": {
405 | ...
406 | "serve": "browser-sync start --server --files 'dist/css/*.css, dist/js/*.js'"
407 | }
408 | ```
409 | BrowserSync 任务默认使用当前根目录下的路径启动一个服务器(`--server` 标识),`--files` 标识告诉 BrowserSync 去监测 `dist` 目录的 CSS 或 JS 文件,一旦有任何变化,则自动向页面注入变化的文件。
410 |
411 | 你可以同时打开多个浏览器(甚至在不同的设备上),他们都会实时更新文件变化的!
412 |
413 | ### 分组任务
414 |
415 | 使用以上任务我们可以做到如下功能:
416 |
417 | 编译 SCSS 到 CSS 并自动添加厂商前缀
418 | 对 Javascript 进行代码检查及混淆压缩
419 | 压缩图片
420 | 整合整个文件夹内的 SVG 文件为一个 SVG 文件
421 | 启动一个本地服务并向连接至该服务的浏览器自动注入更新。
422 | 这还不是全部内容!
423 |
424 | ### 合并 CSS 任务
425 |
426 | 我们会添加一个新的任务,用于合并两个 CSS 相关的任务(处理 SASS 和执行加前缀的 Autoprefixer),有了这个任务我们就不用分别执行两个相关任务了:
427 |
428 | ``` json
429 | "scripts": {
430 | ...
431 | "build:css": "npm run scss && npm run autoprefixer"
432 | }
433 | ```
434 | 当你运行 `npm run build:css` 时,这个任务会告诉命令行去执行 `npm run scss`,当这个任务成功完成后,会接着(&&)执行 `npm run autoprefixer`。
435 |
436 | 就像这个 build:css 任务一样,我们可以把 JavaScript 任务也链接到一起以方便执行:
437 |
438 | ### 合并 JavaScript 任务
439 |
440 | ``` json
441 | "scripts": {
442 | ...
443 | "build:js": "npm run lint && npm run uglify"
444 | }
445 | ```
446 | 现在,我们可以通过 `npm run build:js` 一步调用,来进行代码检测、混淆压缩 JavaScript 代码了!
447 |
448 | 合并剩余任务
449 |
450 | 对于图片任务、其他剩余构建任务,我们可以用相同的方法把他们变成一个任务:
451 |
452 | ``` json
453 | "scripts": {
454 | ...
455 | "build:images": "npm run imagemin && npm run icons",
456 | "build:all": "npm run build:css && npm run build:js && npm run build:images",
457 | }
458 | ```
459 | ### 变更监控
460 |
461 | 至此,我们的任务不断的需要对文件做一些变更,我们不断的需要切回命令行去运行相应的任务。针对这个问题,我们可以添加一个任务来监听文件变更,让文件在变更的时候自动执行这些任务。这里我推荐使用 onchange 插件,安装方法如下:
462 |
463 | ``` bash
464 | npm i -D onchange
465 | ```
466 | 让我们来给CSS和JavaScript设置监控任务:
467 |
468 | ``` json
469 | "scripts": {
470 | ...
471 | "watch:css": "onchange 'src/scss/*.scss' -- npm run build:css",
472 | "watch:js": "onchange 'src/js/*.js' -- npm run build:js",
473 | }
474 | ```
475 | 这些任务可以分解如下:onchange 需要你传参想要监控的文件路径(字符串),这里我们传的是 SCSS 和 JS 源文件,我们想要运行的命令跟在--之后,这个命令当路径内的文件发生增删改的时候就会被立即执行。
476 |
477 | 让我们再添加一个监控命令来完成我们的 npm scripts 构建过程。
478 |
479 | 再添加一个包,[parallelshell](https://github.com/keithamus/parallelshell ):
480 |
481 | ``` bash
482 | npm i -D parallelshell
483 | ```
484 | 再次给 scripts 对象添加一个新任务:
485 |
486 | ``` json
487 | "scripts": {
488 | ...
489 | "watch:all": "parallelshell 'npm run serve' 'npm run watch:css' 'npm run watch:js'"
490 | }
491 | ```
492 | parallelshell 支持多个参数字符串,它会给 npm run 传多个任务用于执行。
493 |
494 | 为什么时候 parallelshell 去合并多个任务,而不是像之前的任务一样使用 && 呢?最开始我也尝试这么做了,但是问题是:&& 链接多个命令到一块,需要等待每一个命令成功完成后才会执行下一个任务。然而当我们运行 watch 命令时,这些命令一直都不会结束!这样我们就会卡在一个无限循环里。
495 |
496 | 因此,使用 parallelshell 使得我们可以同时执行多个 watch 命令。(译者注:后来在评论里有人推荐使用 npm-run-all 插件来代替 parallelshell,它支持这种用法可以一次性检测全部 watch 任务更加方便:"watch": "npm-run-all --parallel serve watch:*")
497 |
498 | 这个任务使用了 BrowserSync 的 npm run serve 任务启动了一个服务,然后对 CSS 和 JavaScript 文件执行了监控命令,一旦 CSS 或 JavaScript 文件有变更,监控任务就会执行相应的构建(build)任务。由于 BrowserSync 被设置成监控 `dist` 目录下的变更,所以它会自动的向相关联的 URL 内注入新的文件,真是太棒了!
499 |
500 | ### 其他实用任务
501 |
502 | npm 有大量可以实用的插件([lots of baked in tasks](https://docs.npmjs.com/misc/scripts#description) ),让我们再添加一个新的任务来看看这些插件对构建脚本的影响:
503 |
504 | ``` json
505 | "scripts": {
506 | ...
507 | "postinstall": "npm run watch:all"
508 | }
509 | ```
510 | 当你在命令行中执行 `npm install` 的时候 postinstall 会立即执行,当团队合作时这个功能非常有用。当别人复制了你的项目并运行了 npm install 的时候,我们的 watch:all 任务就会马上执行,别人马上就会准备好一切开发环境:启动一个服务、打开一个浏览器窗口、监控文件变更。
511 |
512 | ### 打包
513 | 万一你忘记了什么知识点,我用以上所有提到的任务创建了一个 [npm-build-boilerplate](https://github.com/damonbauer/npm-build-boilerplate) 项目以方便你学习。
514 |
--------------------------------------------------------------------------------
/【译】基于 Meteor1.3 和 React 创建简单 App.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【译】基于 Meteor1.3 和 React 创建简单 App
3 | date: 2016-05-11 18:13:05
4 | categories: Meteor
5 | tags: [Meteor,React,原创翻译]
6 | ---
7 | > 原文链接 : [Build A Journaling App with Meteor 1.3 (Beta), React, React-Bootstrap, and Mantra](https://medium.com/@kenrogers/build-a-journaling-app-with-meteor-1-3-beta-react-react-bootstrap-and-mantra-7965d9e9fc23#.bjcr4yhbf)
8 | > 原文作者 : [Ken Rogers](https://medium.com/@kenrogers)
9 | > 译文出自 : [掘金翻译计划](https://github.com/xitu/gold-miner)
10 | > 译者 : [yangzj1992](http://qcyoung.com)
11 | > 校对者: [Zhongyi Tong](https://github.com/geeeeeeeeek), [刘鑫](https://github.com/lx7575000)
12 | > 首发于: [掘金](http://gold.xitu.io/#/entry/5732a5af79df540060df2e53)
13 |
14 | 由于目前 Meteor 1.3 正式版仍在开发中,在这份 Meteor 指南里我们采用了目前可以获取到的 Meteor 1.3 beta 版本进行开发。尽管 Meteor 1.3 版本很棒并有着许多精彩的改进,但部分人对于到底应该如何使用它来进行开发仍有一些困惑。 MDG(Meteor Development Group) 目前正在编写 Meteor 1.3 版指南,随着 1.3 正式版的发布,我们将会获得 Meteor 1.3 最佳开发实践的确切信息。
15 |
16 | **旁注:我写了一本关于使用 Meteor 1.3 ,React ,React-Bootstrap 遵循 Mantra 框架规范进行应用开发的书,点击[这里](http://kenrogers.co/meteor-react)可以了解更多并免费获取前三章的内容**。
17 |
18 | 我写这份指南的目的是让开发者现在就能用上 Meteor 1.3。当你阅读本指南时,需要留意 1.3 版本目前仍处于 beta 阶段,因此内容可能发生任何变化。我会尽我所能的更新这份指南来适应最新版本。如果你发现了什么过期的内容,希望能指出来让我知道。
19 |
20 | 在这份指南中,我们将要构建一个简单的任务清单,开个玩笑,不会再是任务清单了。我们将用 Meteor 1.3 ,React 和 React-Bootstrap 构建一个基本的日志应用。
21 |
22 | 我们将采用 Arunoda 的 Mantra 规范。如果你对 Mantra 不够熟悉,你可以访问[这里](https://github.com/kadirahq/mantra)了解更多。 基本来说, Mantra 应用程序架构规范向我们提供了一个宜于维护的方式去构建 Meteor 应用。
23 |
24 | 在我们开始前,你需要安装好 Meteor 并需要对 Meteor 的原理及使用方法具备一定的理解。如果你并不熟悉,可以看看[官方 Meteor 向导](https://www.meteor.com/tutorials/react/creating-an-app)。
25 |
26 | 首先我们将通过一些资源来熟悉 Meteor 1.3 和 Mantra ,然后运用它们创建一个简单的日志应用。
27 |
28 | #### 了解 Meteor 1.3
29 |
30 | 首先我们要介绍 Meteor 1.3 并且了解它的主要改动包含什么。在 1.3 版本中它最大的改动是完全支持 ES2015 并提供了模块功能。
31 |
32 | 一开始你会发现这和我们以往开发 Meteor 应用很不一样,但一旦你习惯了你会发现体验是相当不错的,尤其是你要使用 Mantra 的架构的话。
33 |
34 | 这里有一篇关于 Meteor 1.3 的模块机制是怎样工作的精彩介绍:[https://github.com/meteor/meteor/blob/release-1.3/packages/modules/README.md](https://github.com/meteor/meteor/blob/release-1.3/packages/modules/README.md)
35 |
36 | 使用模块可以让我们更容易的去写更多的代码,更加模块化。这样我们可以更好地组织我们的应用,由于 Meteor 1.3 也添加了对 npm 包的支持,我们不必再像过去那样只有 Meteor 包支持的情况下进行开发了。
37 |
38 | 接下来,你可以看看这三篇文章来了解如何在 Meteor 1.3 中配置 React ,并用它来处理数据。第二篇会向你介绍容器组件,这是使用 Mantra 开发的一个重要部分。
39 |
40 | 1. [https://voice.kadira.io/getting-started-with-meteor-1-3-and-react-15e071e41cd1#.qn4zj3420](https://voice.kadira.io/getting-started-with-meteor-1-3-and-react-15e071e41cd1#.qn4zj3420)
41 | 2. [https://voice.kadira.io/let-s-compose-some-react-containers-3b91b6d9b7c8#.pd37xdmpn](https://voice.kadira.io/let-s-compose-some-react-containers-3b91b6d9b7c8#.pd37xdmpn)
42 | 3. [https://voice.kadira.io/using-meteor-data-and-react-with-meteor-1-3-13cb0935dedb#.3oe66g4ye](https://voice.kadira.io/using-meteor-data-and-react-with-meteor-1-3-13cb0935dedb#.3oe66g4ye)
43 |
44 | #### 第一步 — 项目设置
45 |
46 | 通常来说,我们需要做的第一件事就是通过 Meteor 1.3 来创建我们的 Meteor 项目,像下面这样。
47 |
48 | ``` js
49 | meteor create journal --release 1.3-modules-beta.8
50 | ```
51 |
52 | **但是稍等一下**,构建一个 Mantra 应用需要非常多的项目设置
53 | ,为了加快开发速度,我已经使用 Meteor 1.3,React,Mantra 创建创建了一个样板项目。我们就用它来代替初始方案直接开始。
54 |
55 | 如果你想知道这些具体做了什么,查看 [Mantra 规范](https://kadirahq.github.io/mantra/)和 [Mantra 博客应用实例](https://github.com/mantrajs/mantra-sample-blog-app)。
56 |
57 | 现在我们安装完样板项目,它完全包含了遵循 Mantra 规范的 Meteor 项目中所有你需要的核心文件和目录。
58 |
59 | 你可以通过以下命令 clone 项目:
60 |
61 | ``` bash
62 | git clone git@github.com:kenrogers/mantraplate.git
63 | ```
64 |
65 | 然后切换到刚创建的目录中运行
66 |
67 | ``` bash
68 | npm install
69 | ```
70 |
71 | 这样会安装本应用依赖的所有的包。你可以查看示例项目来熟悉整个目录结构。
72 |
73 | 它包含完整的布局,路由系统以及具有注册,登录登出功能的用户系统。
74 |
75 | 在这份指南中,我们将要讨论这些内容是如何组合在一起的,以及如何使用户在应用中添加日志记录的功能。
76 |
77 | 在我们添加内容前我们来看看样例项目的目录结构,你可以发现,在客户端文件夹中我们将整个应用分成一个个模块,这些模块是你的应用的主要组成部分。
78 |
79 | 我们总是需要一个核心模块,如果你的 APP 比较简单,这个核心模块就是你所唯一需要的。在我们的 APP 中包含了核心模块和用户模块,这里还要加入一个条目模块来添加我们的日志记录。
80 |
81 | 这样的模块结构让我们可以轻松地组织我们的代码。
82 |
83 | 在用户模块中,看看 containers 和 components 文件夹中的 NewUser 文件,。container 文件夹如下所示。
84 |
85 | ``` js
86 | import NewUser from '../components/NewUser.jsx';
87 | import {useDeps, composeWithTracker, composeAll} from 'mantra-core';
88 | export const composer = ({context, clearErrors}, onData) => {
89 | const {LocalState} = context();
90 | const error = LocalState.get('CREATE_USER_ERROR');
91 | onData(null, {error});
92 | return clearErrors;
93 | };
94 | export const depsMapper = (context, actions) => ({
95 | create: actions.users.create,
96 | clearErrors: actions.users.clearErrors,
97 | context: () => context
98 | });
99 | export default composeAll(
100 | composeWithTracker(composer),
101 | useDeps(depsMapper)
102 | )(NewUser);
103 | ```
104 |
105 | 你可以看到我们在这里实际上并没有进行任何渲染,我们只是做一些设置和清理的工作,然后在 NewUser 组件中我们才实际上渲染了视图。
106 |
107 | 如果你运行应用并访问 /register 路由,打开 React 开发者工具,你可以看到 react-komposer 正在后台执行。它会创建一个容器组件负责处理底层子组件的数据或是 UI 组件。
108 |
109 | 当我们获取数据时容器组件的用途将会得到具体的展现,但是这里我们不这样处理。
110 |
111 | #### 第二步 — 原型制作
112 |
113 | 对于这个日志程序我们准备使用 React-Bootstrap 。它可以很方便地使用 Bootstrap 来创建 React 应用。这种方式易于上手,并且保持了模块化,正如我们所愿。
114 |
115 | 让我们设置好并添加一个简单的表单。
116 |
117 | 首先让我们为项目添加 `react-bootstrap`
118 |
119 | ``` bash
120 | npm install react-bootstrap
121 | ```
122 |
123 | 因为 React-Bootstrap 并不依赖任何特定的 Bootstrap 库,所以我们需要自行添加,现在让我们添加 Twitter 的官方 Meteor 包。
124 |
125 | ``` js
126 | meteor add twbs:bootstrap
127 | ```
128 |
129 | 首先我们用 React-Bootstrap 来修改 MainLayout.jsx 文件的内容如下:
130 |
131 | ``` jsx
132 | import React from 'react';
133 | import {Grid, Row} from 'react-bootstrap';
134 | const Layout = ({content = () => null }) => (
135 |
136 |
137 | Journal
138 | {content()}
139 |
140 |
141 | );
142 | export default Layout;
143 | ```
144 |
145 | 在这里,我们从 react-boostrap 包中引入 Grid 和 Row 组件,并且像使用 div 一样为它们添加合适的 bootstrap 类。想要了解更多关于这个优秀的包的工作原理,可以在[这里](https://react-bootstrap.github.io/components.html)查看组件列表。
146 |
147 | 现在让我们修改 NewUser 和 Login UI 的组件让他们更友好地贴近 Bootstrap 。打开 NewUser.jsx 文件进行如下修改:
148 |
149 | ``` jsx
150 | import React from 'react';
151 | import { Col, Panel, Input, ButtonInput, Glyphicon } from 'react-bootstrap';
152 | class NewUser extends React.Component {
153 | render() {
154 | const {error} = this.props;
155 | return (
156 |
157 |
158 | Register
159 | {error ? {error}
: null}
160 |
165 |
166 |
167 | )
168 | }
169 | createUser(e) {
170 | e.preventDefault();
171 | const {create} = this.props;
172 | const {email, password} = this.refs;
173 | create(email.getValue(), password.getValue());
174 | email.getInputDOMNode().value = '';
175 | password.getInputDOMNode().value = '';
176 | }
177 | }
178 | export default NewUser;
179 | ```
180 |
181 | 这个表单十分简单,它仅仅负责显示自身并调用 create 方法。这里我们简单介绍一下。
182 |
183 | 在我们的 actions 文件夹中,它们负责处理我们应用的逻辑,下面这一行
184 |
185 | ``` js
186 | create(email.getValue(), password.getValue());
187 | ```
188 |
189 | 将调用该方法并创建实际用户。 Mantra 重点强调了希望把一切分离成单独的文件。因此,我们将文件分为展示、逻辑、以及这个应用程序的每个组件。
190 |
191 | 现在让我们修改登录表单如下:
192 |
193 | ``` jsx
194 | import React from 'react';
195 | import { Col, Panel, Input, ButtonInput, Glyphicon } from 'react-bootstrap';
196 | class Login extends React.Component {
197 | render() {
198 | const {error} = this.props;
199 | return (
200 |
201 |
202 | Login
203 | {error ? {error}
: null}
204 |
209 |
210 |
211 | )
212 | }
213 | login(e) {
214 | e.preventDefault();
215 | const {loginUser} = this.props;
216 | const {email, password} = this.refs;
217 | loginUser(email.getValue(), password.getValue());
218 | email.getInputDOMNode().value = '';
219 | password.getInputDOMNode().value = '';
220 | }
221 | }
222 | export default Login;
223 | ```
224 |
225 | 这基本上是一个相同的表单,但我们将用登录方法来代替它的逻辑。
226 |
227 | React-Boostrap 非常易于使用,我们只需要安装好项目,使用 import 函数引入每个我们想要引用的组件,就像其他类型一样渲染这些组件。
228 |
229 | 我们处理使用数据的方法则有一些不同,因为它是组件,而不是我们实际需要处理的输入内容,我们需要使用特殊的 React-Bootstrap 函数 getValue() 来帮我们轻松地取值。
230 |
231 | #### 第三步 — 添加条目模块
232 |
233 | 现在,我们将添加新的模块来管理我们的日志条目,首先让我们设置目录和文件。
234 |
235 | ``` bash
236 | mkdir client/modules/entries
237 | cd client/modules/entries
238 | mkdir actions components containers
239 | touch index.js
240 | touch actions/index.js actions/entries.js
241 | touch components/NewEntry.jsx components/Entry.jsx components/EntryList.jsx
242 | touch containers/NewEntry.js containers/Entry.js containers/EntryList.js
243 | ```
244 |
245 | 好了,现在我们有了应用中所需要的所有文件和文件夹。让我们来做一些真正的开发工作吧。
246 |
247 | 首先,让我们再来看一下我们创建的应用结构。这里我们制造了一个简单的 Mantra 模块。我们通过这些目录文件来看看他们是怎么做到交互的。通过这些将会让你很好地理解如何使用 Meteor 1.3 和 Mantra 。
248 |
249 | **索引**
250 |
251 | Mantra 有一个庞大的单一入口。这个索引文件负责导入内容随后导出路由和动作,这样在我们导入模块时即可使用。通过这种方式我们不用担心再单独导入每个文件。
252 |
253 | ``` js
254 | import actions from './actions';
255 | import routes from '../core/routes.jsx';
256 | export default {
257 | routes,
258 | actions
259 | };
260 | ```
261 |
262 | **动作**
263 |
264 | 动作文件夹负责我们应用的所有逻辑。你可以看到我们在这里创建了两个文件。首先是一个索引文件。这是一个类似目的模块的索引文件。我们向里面添加下面的内容。
265 |
266 | ``` js
267 | import entries from './entries';
268 | export default {
269 | entries
270 | };
271 | ```
272 |
273 | 上面所做的就是导入条目文件,在条目文件中有我们的动作逻辑。这只是为了更容易地从其他文件导入我们的逻辑。
274 |
275 | 接下来我们要添加实际逻辑,这些包含了我们的应用逻辑。这里我们要添加一个创建条目的函数方法。
276 |
277 | 你可以通过查看例子中 users 模块的方法文件来了解这是怎么工作的。
278 |
279 | 在 actions.js 中添加下面的内容来补全条目模块。
280 |
281 | ``` js
282 | export default {
283 | create({Meteor, LocalState, FlowRouter}, text) {
284 | if (!text) {
285 | return LocalState.set('CREATE_ENTRY_ERROR', 'Text is required.');
286 | }
287 | LocalState.set('CREATE_ENTRY_ERROR', null);
288 | Meteor.call('entries.create', text, (err) => {
289 | if (err) {
290 | return LocalState.set('CREATE_ENTRY_ERROR', err.message);
291 | }
292 | });
293 | }
294 | };
295 | ```
296 | 当我们填写表格来创建一个新条目时,这就是会被执行的方法,我们就快设置好这些组件了,让我们先别管服务端的东西,为我们的条目创建集合和方法。
297 |
298 | 在 lib 目录中打开 collections.js 文件然后添加条目集合。
299 |
300 | ``` js
301 | export const Entries = new Mongo.Collection('entries');
302 | ```
303 | 现在在 server 目录下的 methods 目录中添加 entries.js 文件,并添加以下内容来创建一个创建新条目的方法。
304 |
305 | ``` js
306 | import {Entries} from '/lib/collections';
307 | import {Meteor} from 'meteor/meteor';
308 | import {check} from 'meteor/check';
309 | export default function () {
310 | Meteor.methods({
311 | 'entries.create'(text) {
312 | check(text, String);
313 | const createdAt = new Date();
314 | const entry = {text, createdAt};
315 | Entries.insert(entry);
316 | }
317 | });
318 | }
319 | ```
320 | 这是一个我们刚创建的将要被调用的方法。
321 |
322 | 我们还需要将下面代码添加到 methods 文件夹中的 index.js 文件。
323 |
324 | ``` js
325 | import entries from './entries';
326 | export default function () {
327 | entries();
328 | }
329 | ```
330 |
331 | **组件**
332 |
333 | 组件目录存放着我们的 UI 组件。这里的组件只负责显示我们的接口内容,他们不操作任何数据,这些是容器组件需要做的。
334 |
335 | 让我们创建 UI 组件,然后我们将建立相应的容器组件。
336 |
337 | ``` jsx
338 | import React from 'react';
339 | import {Grid, Row, Col} from 'react-bootstrap';
340 | const Entry = ({entry}) => (
341 |
342 |
343 |
344 |
345 | {entry.text}
346 |
347 |
348 |
349 |
350 | );
351 | export default Entry;
352 | ```
353 |
354 | 这里获取到的 {entry} 对象是我们容器组件要传递给它属性。它包含了我们的数据。
355 |
356 | 接下来我们创建 NewEntry 组件。
357 |
358 | ``` jsx
359 | import React from 'react';
360 | import { Col, Panel, Input, ButtonInput, Glyphicon } from 'react-bootstrap';
361 | class NewEntry extends React.Component {
362 | render() {
363 | const {error} = this.props;
364 | return (
365 |
366 |
367 | Add a New Entry
368 | {error ? {error}
: null}
369 |
373 |
374 |
375 | )
376 | }
377 | newEntry(e) {
378 | e.preventDefault();
379 | const {create} = this.props;
380 | const {text} = this.refs;
381 | create(text.getValue());
382 | text.getInputDOMNode().value = '';
383 | }
384 | }
385 | export default NewEntry;
386 | ```
387 |
388 | 这里我们使用了更多的 React-Bootstrap 组件,你会留意到为了获取输入的值,我们用了一个特别的 getValue() 方法。这是因为我们的渲染组件实际上并不是输入框,输入框是在这些组件的内部。所以我们需要使用这个函数来访问它。
389 |
390 | 最后,我们创建一个 EntryList 组件。
391 |
392 | ``` jsx
393 | import React from 'react';
394 | import {Grid, Row, Col, Panel} from 'react-bootstrap';
395 | const EntryList = ({entries}) => (
396 |
397 |
398 | {entries.map(entry => (
399 |
400 |
401 | {entry.title}
402 | View Entry
403 |
404 |
405 | ))}
406 |
407 |
408 | );
409 | export default EntryList;
410 | ```
411 |
412 | 接下来,我们通过属性来获取数据,设置一些 React-Bootstrap 组件,并为每个入口映射一个对应专属的面板。
413 |
414 | 现在,让我们来设置这些容器组件,首先从最简单的 NewEntry 容器组件开始。
415 |
416 | ``` js
417 | import NewEntry from '../components/NewEntry.jsx';
418 | import {useDeps, composeWithTracker, composeAll} from 'mantra-core';
419 | export const composer = ({context, clearErrors}, onData) => {
420 | const {LocalState} = context();
421 | const error = LocalState.get('CREATE_ENTRY_ERROR');
422 | onData(null, {error});
423 | return clearErrors;
424 | };
425 | export const depsMapper = (context, actions) => ({
426 | create: actions.entries.create,
427 | clearErrors: actions.entries.clearErrors,
428 | context: () => context
429 | });
430 | export default composeAll(
431 | composeWithTracker(composer),
432 | useDeps(depsMapper)
433 | )(NewEntry);
434 | ```
435 |
436 | 这里你应该已经对 react-komposer 较为熟悉了,我们将用它来创建这一容器组件。它负责创建一个容器组件,用于处理错误、调用合适的动作。在大多数情况下,它还将获取数据并通过属性传给 UI 组件。
437 |
438 | depsMapper 通过 react-komposer 中的 useDeps 函数检索动作及上下文内容并将它们传递给 UI 组件。
439 |
440 | clearErrors 方法负责清除组件卸载时发生的所有错误。
441 |
442 | 让我们在创建条目方法时创建这一方法。
443 |
444 | ``` js
445 | clearErrors({LocalState}) {
446 | return LocalState.set('SAVING_ERROR', null);
447 | }
448 | ```
449 |
450 | 现在我们将要创建 EntryList 组件的容器。这个稍许有些复杂,因为我们会实际上获取一些数据。
451 |
452 | ``` js
453 | import EntryList from '../components/EntryList.jsx';
454 | import {useDeps, composeWithTracker, composeAll} from 'mantra-core';
455 | export const composer = ({context}, onData) => {
456 | const {Meteor, Collections} = context();
457 | if (Meteor.subscribe('entries.list').ready()) {
458 | const entries = Collections.Entries.find().fetch();
459 | onData(null, {entries});
460 | }
461 | };
462 | export default composeAll(
463 | composeWithTracker(composer),
464 | useDeps()
465 | )(EntryList);
466 | ```
467 |
468 | 这也确实与其他容器组件较为相似,但一个重要的区别在于,我们会检查我们的入口集合条目结合,并将它们分配给一个变量。最终我们通过 onData 函数将这个变量传给 UI 组件。
469 |
470 | 让我们在 publications 目录下的 entries.js 文件中设置发布
471 |
472 | ``` js
473 | import {Entries} from '/lib/collections';
474 | import {Meteor} from 'meteor/meteor';
475 | import {check} from 'meteor/check';
476 | export default function () {
477 | Meteor.publish('entries.list', function () {
478 | const selector = {};
479 | const options = {
480 | fields: {_id: 1, text: 1},
481 | sort: {createdAt: -1}
482 | };
483 | return Entries.find(selector, options);
484 | });
485 | }
486 | ```
487 |
488 | 同时我们将要为此发布创建一个 index 文件。
489 |
490 | ``` js
491 | import entries from './entries';
492 | export default function () {
493 | entries();
494 | }
495 | ```
496 |
497 | 我们需要在 server 目录中打开 main.js 文件,取消注释行,导入 publications 和 methods ,所以文件就像这样:
498 |
499 | ``` js
500 | import publications from './publications';
501 | import methods from './methods';
502 |
503 | // publications();
504 | // methods();
505 | ```
506 |
507 | 最后我们将要为独立的 Entry 组件创建容器组件。
508 |
509 | ``` js
510 | import Entry from '../components/Entry.jsx';
511 | import {useDeps, composeWithTracker, composeAll} from 'mantra-core';
512 | export const composer = ({context, entryId}, onData) => {
513 | const {Meteor, Collections} = context();
514 | if (Meteor.subscribe('entries.single', entryId).ready()) {
515 | const entry = Collections.Entries.findOne(entryId);
516 | onData(null, {entry});
517 | } else {
518 | const entry = Collections.Entries.findOne(entryId);
519 | if (entry) {
520 | onData(null, {entry});
521 | } else {
522 | onData();
523 | }
524 | }
525 | };
526 | export default composeAll(
527 | composeWithTracker(composer),
528 | useDeps()
529 | )(Entry);
530 | ```
531 |
532 | 此容器使用了一个 entryId (将通过我们之后设立的一个路由进行传递)并且找到一个合适的入口,来通过属性传递它给UI组件。
533 |
534 | 让我们在之前设置的发布列表中快速设置发布来展示发布条目。
535 |
536 | ``` js
537 | Meteor.publish('entries.single', function (entryId) {
538 | check(entryId, String);
539 | const selector = {_id: entryId};
540 | return Entries.find(selector);
541 | });
542 | ```
543 |
544 | 现在让我们设置我们的路由吧。
545 |
546 | **路由**
547 |
548 | 打开 routes 文件来添加一些新的路由,修改 routes 文件类似如下所示。
549 |
550 | ``` jsx
551 | import React from 'react';
552 | import {mount} from 'react-mounter';
553 | import Layout from './components/MainLayout.jsx';
554 | import Home from './components/Home.jsx';
555 | import NewUser from '../users/containers/NewUser.js';
556 | import Login from '../users/containers/Login.js';
557 | import EntryList from '../entries/containers/EntryList.js';
558 | import Entry from '../entries/containers/Entry.js';
559 | import NewEntry from '../entries/containers/NewEntry.js';
560 | export default function (injectDeps, {FlowRouter}) {
561 | const MainLayoutCtx = injectDeps(Layout);
562 | FlowRouter.route('/', {
563 | name: 'items.list',
564 | action() {
565 | mount(MainLayoutCtx, {
566 | content: () => ()
567 | });
568 | }
569 | });
570 | FlowRouter.route('/entry/:entryId', {
571 | name: 'entries.single',
572 | action({entryId}) {
573 | mount(MainLayoutCtx, {
574 | content: () => ()
575 | });
576 | }
577 | });
578 | FlowRouter.route('/new-entry', {
579 | name: 'newEntry',
580 | action() {
581 | mount(MainLayoutCtx, {
582 | content: () => ()
583 | });
584 | }
585 | });
586 |
587 | FlowRouter.route('/register', {
588 | name: 'users.new',
589 | action() {
590 | mount(MainLayoutCtx, {
591 | content: () => ()
592 | });
593 | }
594 | });
595 | FlowRouter.route('/login', {
596 | name: 'users.login',
597 | action() {
598 | mount(MainLayoutCtx, {
599 | content: () => ()
600 | });
601 | }
602 | });
603 | FlowRouter.route('/logout', {
604 | name: 'users.logout',
605 | action() {
606 | Meteor.logout();
607 | FlowRouter.go('/');
608 | }
609 | });
610 | }
611 | ```
612 |
613 | 在运行我们的应用之前我们还需要做最后一件事,打开 main.js 文件并导入我们的 entries 模块,修改内容如下。
614 |
615 | ``` js
616 | import {createApp} from 'mantra-core';
617 | import initContext from './configs/context';
618 | // modules
619 | import coreModule from './modules/core';
620 | import usersModule from './modules/users';
621 | import entriesModule from './modules/entries';
622 | // init context
623 | const context = initContext();
624 | // create app
625 | const app = createApp(context);
626 | app.loadModule(coreModule);
627 | app.loadModule(usersModule);
628 | app.loadModule(entriesModule);
629 | app.init();
630 | ```
631 |
632 | #### 最后 — 运行成功
633 |
634 | 现在我们设置了我们的所有路由并且应用已经准备好运行,让我们切换目录到根目录并运行
635 |
636 | ``` bash
637 | meteor
638 | ```
639 | 你可以看到应用程序在 Mantra 提供的默认加载效果中启动,让我们添加一个条目,这样我们应该可以在屏幕上看到效果了。
640 |
641 | 访问 `localhost:3000/new-entry` ,填写并提交表单来添加一个条目。
642 |
643 | 然后访问根目录,你应该可以看到一个可以逐个查看链接的的条目列表。
644 |
645 | 希望这个简单的 Mantra 引导以及目前的 Meteor 1.3 beta 版本有助于让你更加了解如何运用它们来构建一个应用。
646 |
647 | 
648 |
--------------------------------------------------------------------------------
/某前端群题目答案参考.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 某前端群题目答案参考
3 | date: 2015-10-30 14:19:48
4 | categories: 前端知识
5 | tags: [前端知识,题目,CSS,JavaScript]
6 | ---
7 | 比较基础的一套题,这里简单解答并记录。
8 | ## 一.CSS(40分)
9 |
10 | ### 1. 什么是盒模型?
11 | 盒模型是网页制作的基础,在网页文档中每个元素都将被描绘为一个矩形盒子来进行渲染。通常来说,一个完整的盒子如下图所示:
12 | 
13 |
14 | ### 2. Doctype 的几种类型?
15 | `Doctype`是 Document Type(文档类型)的简写,在页面中,用来指定页面所使用的 XHTML(或者 HTML)版本。
16 |
17 | 在 XHTML1.0 和 HTML 4.01 中有三种 声明。分别为过渡的(Transitional)、严格的(Strict)和框架的(Frameset)。[^Doctype](http://www.w3school.com.cn/tags/tag_doctype.asp)
18 |
19 | (1)过渡的
20 |
21 | 一种要求不太严格的方式,允许在页面中使用 HTML4.01 的标识(符合 XHTML 语法标准)。过渡的 DTD 的写法如下:
22 |
23 | ``` html
24 |
26 | ```
27 | (2)严格的
28 |
29 | 一种要求严格的 DTD,不允许使用任何表现层的标识和属性,例如`
`等。严格的 DTD 的写法如下:
30 |
31 | ``` html
32 |
34 | ```
35 | (3)框架的
36 |
37 | 一种专门针对框架页面所使用的 DTD,当页面中含有框架元素时,就要采用这种 DTD。框架的 DTD 的写法如下:
38 |
39 | ``` html
40 |
42 | ```
43 |
44 | 在 HTML5 中只有一种:
45 |
46 | ``` html
47 |
48 | ```
49 |
50 | ### 3. 如何布局左不动右边自适应的两列布局?
51 | 思路主要为以下两种:
52 |
53 | 1. 左边定宽,浮动
54 |
55 | 2. 左边绝对定位
56 |
57 | ``` html
58 |
59 |
60 |
61 | 1
62 |
80 |
81 |
82 | left
83 | content
84 |
85 |
86 | ```
87 | [Demo](http://codepen.io/yangzj1992/pen/MyjJrL)
88 | ### 4. 如何布局两列等高?
89 | 主要思路如下:
90 |
91 | 1. 假等高列方法,在列的父元素上使用背景图片在 y 轴上平铺,造成等高列的假象
92 |
93 | 2. table-cell 法
94 |
95 | ``` html
96 |
97 |
98 |
99 |
100 | Document
101 |
119 |
120 |
121 |
122 |
123 |
123
124 |
456
125 |
456
126 |
456
127 |
128 |
8989
129 |
130 |
131 |
132 | ```
133 | [Demo](http://codepen.io/yangzj1992/pen/MyjJrL)
134 | 3. 补丁法
135 |
136 | ``` html
137 |
138 |
139 |
140 |
141 | 两列等高布局(float+margin+position)
142 |
182 |
183 |
184 |
185 |
186 |
1231321321
187 |
1313213
188 |
1313213
189 |
1313213
190 |
1313213
191 |
1313213
192 |
193 |
45645456456
194 |
195 |
196 |
197 | ```
198 | [Demo](http://codepen.io/yangzj1992/pen/QNKdxV)
199 | ### 5. 如何布局右侧定宽,左侧或中间自适应?
200 |
201 | ``` html
202 |
203 |
204 |
205 |
206 | 右侧定宽,左侧自适应宽
207 |
228 |
229 |
230 | Main Content
231 |
232 |
233 |
234 | ```
235 | [Demo](http://codepen.io/yangzj1992/pen/JXREqK)
236 | ### 6. 如何布局三列自适应?
237 | 主要思路如下:
238 |
239 | 1. 绝对定位法(左右两栏绝对定位,固定于页面两侧,主体栏用 margin 撑开距离)
240 |
241 | ``` html
242 |
243 |
244 |
245 |
246 |
247 |
270 |
271 |
272 |
273 |
274 |
275 |
276 | ```
277 | [Demo](http://codepen.io/yangzj1992/pen/mPrRYq)
278 | 2. margin 负值法
279 | 主体采用双层标签,外层 DIV 宽度 100% 显示,且浮动。内层 div 为真正主体内容,左栏与右栏采用 margin 负值定位,左栏左浮动,`margin-left: -100%`,使其正好到达最左端。
280 |
281 | ``` html
282 |
283 |
284 |
285 |
286 | 11
287 |
296 |
297 |
298 |
299 |
302 |
303 |
304 |
305 |
306 | ```
307 | [Demo](http://codepen.io/yangzj1992/pen/ZWpLNg)
308 | 3. 自身浮动法:
309 | 应用标签浮动跟随的特性,左栏左浮动,右栏右浮动,主体直接放后面,实现自适应
310 |
311 | ``` html
312 |
313 |
314 |
315 |
316 | 11
317 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 | ```
332 | [Demo](http://codepen.io/yangzj1992/pen/bpwgPx)
333 | ### 7. gif,png,jpg 的区别?
334 | 参考: [^图片特点比较](http://wenku.baidu.com/view/fef1587c6bd97f192379e95a.html?re=view)
335 |
336 | | 图片类型 | 透明性 | 动画支持 | 损耗性| 显示方式|
337 | | -------- | :-----: | :----: | :----: |:----: |
338 | | GIF | Gif 是一种布尔透明类型,它可以是全透明或全不透明,但是它并没有半透明(alpha 透明)| 支持 |Gif 是一种无损耗的图像格式,这也意味着你可以对 gif 图片做任何操作也不会使图像质量产生损耗;
Gif 使用 LZW 算法进行压缩,当压缩 gif 的过程中,像素是由上到下水平压缩的,这也意味着同等条件下,横向的 gif 图片比竖向的 gif 图片更小。例如500\*10的图片比10\*500的图片更小|Gif 支持可选择性的间隔渐进显示;
只有256种颜色的 gif 图片不适合作为照片,它适合做对颜色要求不高的图形。|
339 | | PNG | Png 是完全支持 alpha 透明的(透明,半透明,不透明) | 不支持 |png 是一种无损耗的图像格式,这意味着 png 图片做任何操作也不会使图像质量产生损耗。这也使得 png 可以作为 jpeg 编辑的过渡格式;
像 GIF 一样,png 也是水平扫描的,这意味着水平重复颜色比垂直重复颜色的图片更小|它支持间隔渐进显示,但是会使图片大小更大|
340 | | JPG | 不支持 | 不支持 |除了一些如旋转(仅仅是90、180、270度旋转),裁切,从标准类型到先进类型,编辑图片的原数据之外,所有其它对 jpeg 图像的处理都会使它的质量产生损失。所以我们在编辑过程一般用 png 作为过渡格式|它支持隔行渐进显示(但是 ie 浏览器并不支持这个属性,但是 ie 会在整个图像信息完全到达的时候显示)。
由上可以看出 Jpeg 是最适 web 上面的摄影图片和数字照相机中。|
341 |
342 |
343 | #### 综述:
344 |
345 | GIF 适合图形,JPEG 适合照片,PNG 系列两种都适合。
346 |
347 | PNG 相比 GIF:
348 |
349 | 对于相同色值,gif 格式图片会比 png32 格式图片小。但 png8 才是最优的选择。
350 |
351 | PNG8 除了不支持动画外,PNG8 有 GIF 所有的特点,但是比 GIF 更加具有优势的是它支持 alpha 透明和更优的压缩。所以,大多数情况下,你都应该用 PNG8 不是 GIF(除了非常小的图片 GIF 会有更好的压缩外)。
352 |
353 | PNG 相比 JPEG:
354 |
355 | JPEG 比全色 PNG 具有更加好的压缩,因此也使得 JPEG 适合照片,但是编辑 JPEG 过程中容易造成质量的损失,所以全色 PNG 适合作为编辑 JPEG 的过渡格式.
356 |
357 | ### 8. 什么是 css sprite?优缺点?
358 |
359 | CSS Sprites(雪碧图)就是把网页中用到的一些背景图片整合到一张图片文件中,利用 CSS 的 `background-image`,`background-repeat`,`background-position` 来组合进行背景定位。
360 |
361 | 优点: 减少网页的 http 请求,大大提高页面性能。
362 |
363 | 缺点:在图片合并的时候,你要把多张图片有序的合理的合并成一张图片,还要留好足够的空间,防止板块内出现不必要的背景。修改编辑图片时也相对较为繁琐。
364 |
365 | ### 9. 制作细线表格?
366 |
367 | 定义 `CSS:border-collapse` 属性,将值设为 `collapse`,合并边框
368 |
369 | ### 10. position:relative,absolute,fixed区别与联系?
370 |
371 | position 一共有四个可选属性:`static|relative|absolute|fixed`,默认是 `static`。
372 |
373 | 一:`relative`
374 |
375 | `relative` 会导致元素自身位置的相对变化,但不会影响其它元素的位置、大小。
376 |
377 | 二:`absolute`
378 |
379 | ①`absolute` 元素脱离文档结构(和 float 元素类似,float 元素也会脱离文档结构)
380 |
381 | ②`absolute` 元素具有“包裹性”。会使其之前的块级元素的宽度变为内容的宽度。
382 |
383 | ③`absolute` 元素具有“跟随性”。没有设置 top、left 的值时,与其前一个元素的 top,left 值为 0。
384 |
385 | 如果为 `absolute` 设置了 top、left,absolute 元素会根据最近的定位上下文确定位置,浏览器会递归查找该元素的所有父元素,如果找到一个设置了 `position: relative/absolute/fixed` 的元素,就以该元素为基准定位,如果没找到,就以浏览器边界定位。
386 |
387 | 三:`fixed`
388 |
389 | 其实 `fixed` 和 `absolute` 是一样的,唯一的区别在于:`absolute` 元素是根据最近的定位上下文确定位置,而 `fixed` 永远根据浏览器确定位置.
390 |
391 | ### 11. 如何居中一个 float:left 的元素
392 | 主要思路如下:
393 |
394 | 1. 定义父级元素为行级元素,在父级元素之上设定 `text-align: center`
395 |
396 | ``` html
397 |
398 |
399 |
400 |
401 | Document
402 |
403 |
404 |
405 |
406 |
407 | 子元素1
408 |
409 |
410 | 子元素2
411 |
412 |
413 |
414 |
415 |
416 | ```
417 |
418 | 2. 父元素浮动法
419 | 在外层包裹一个父元素,使他浮动,相对定位 `left: 50%`;内部元素同样相对定位 `left: -50%`;
420 |
421 | ``` html
422 |
423 |
424 |
425 |
426 | Document
427 |
428 |
429 |
430 |
431 | 子元素1
432 |
433 |
434 | 子元素2
435 |
436 |
437 |
438 |
439 | ```
440 |
441 | ### 12. Css 在各浏览器下的兼容问题你通常是怎么来解决的,请分享你的经验;
442 |
443 | 解决办法一般都是平时为知笔记记录,搜索时熟悉内容找笔记内容+不熟悉的找谷歌。
444 |
445 | ## 二. Js 30分
446 | ### 1. parseInt 与 parseFloat 的区别?
447 |
448 | `parseInt` 与 `parseFloat` 是把字符串转换成整数和浮点数的方法,
449 |
450 | 二者语法如下:
451 |
452 | `parseInt(string, radix)`
453 |
454 | `parseFloat(string)`
455 |
456 | `parseInt` 多一个可选的 `radix` 数字基数。
457 |
458 | 两者的一些参数执行返回结果如下
459 |
460 | | str | parseInt(str) | parseFloat(str) |
461 | | -------- | :-----:| :----:|
462 | | 零长度字符串 | NaN | NaN |
463 | | null | NaN | NaN |
464 | | undefined | NaN | NaN |
465 | | .3 | 0 | 0.3 |
466 | | -.3 | NaN | -0.3 |
467 | | -3 | -3 | -3 |
468 | | 3e7 | 3 | 30000000 |
469 | | 0xFF | 255 | 0 |
470 | | 0x3e7 | 999 | 0 |
471 | | 08 | 8 | 8 |
472 | | 3 | 3 | 3 |
473 | | 123abc | 123 | 123 |
474 |
475 | ### 2. valueof 与 toString 的区别?
476 |
477 | 基本上,所有的 JS 数据类型都拥有这两种方法(NULL 除外),他们俩解决了 JS 值的运算和显示问题。
478 |
479 | 在《JavaScript 高级程序设计》一书中描述如下:
480 |
481 | `toString()`——返回对象的原始字符串表示
482 |
483 | `valueOf()`——返回最适合该对象的原始值
484 |
485 | 一个例子:
486 |
487 | ``` html
488 |
519 | ```
520 |
521 | 这里隐式调用了 `valueOf` 跟 `toString`,二者并存的情况下,在数值运算中,优先调用了 `valueOf`,字符串运算中,优先调用了 `toString`。比如 money1 + money2,调用的就是两者 `valueOf` 之后的值相加,而 alert 的时候,把 money3 先 `toString` 了一下。
522 |
523 | | str | valueOf(str) | toString(str) |
524 | | -------- | :-----:| :----:|
525 | | Array | Array 数组的元素被转换为字符串,这些字符串由逗号分隔,连接在一起。其操作与 Array.toString 和 Array.join 方法相同。| Array 将 Array 的元素转换为字符串。结果字符串由逗号分隔,且连接起来。|
526 | | Boolean | Boolean 的原始值| Boolean 如果 Boolean 值是 true,则返回 “true”。否则,返回 “false”。|
527 | | Date | Date 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。| Date 返回日期的文字表示法。Error 返回一个包含相关错误消息的字符串。|
528 | | Function | Function 函数本身。| Function 返回如下格式的字符串,其中 functionname 是被调用 toString 方法函数的名称: function functionname( ) { [native code] }|
529 | | Number | Number 数字值。|Number 返回数字的文字表示。|
530 | | Object | Object 对象本身。这是默认情况。 | Object 默认返回 “[object objectname]”,其中 objectname 是对象类型的名称。 |
531 | | String | String 字符串值。 | String 返回 String 对象的值。|
532 |
533 | ### 3. 全等与相等?
534 | 相等(==) 当且仅当两个运算符相等时,返回 true。在确定两个运算符时会进行类型转换。
535 |
536 | 执行类型转换的规则如下:
537 |
538 | - 如果一个运算数是 Boolean 值,在检查相等性之前,把它转换成数字值。false 转换成 0,true 为 1。
539 | - 如果一个运算数是字符串,另一个是数字,在检查相等性之前,要尝试把字符串转换成数字。
540 | - 如果一个运算数是对象,另一个是字符串,在检查相等性之前,要尝试把对象转换成字符串。
541 | - 如果一个运算数是对象,另一个是数字,在检查相等性之前,要尝试把对象转换成数字。
542 |
543 | 全等(===) 只有在无需类型转换运算数就相等的情况下,才返回 true。
544 |
545 | ### 4. break 与 continue 的区别?
546 | break 表示跳出循环,continue 表示结束本次循环
547 |
548 | ### 5. js 的作用域?
549 | js 的作用域以函数 function 来划分,根据作用域,使得变量存在作用域的概念分为全局变量和局部变量。
550 |
551 | ### 6. Array 的 slice 与 splice 的区别?
552 |
553 | - arrayObject.slice(start,end)
554 |
555 | `slice()` 方法从已有的数组中返回选定的元素,其接收1或2个参数,返回一个新的数组,包含数组下标从 `start` 到 `end`(不包括该元素)的 `arrayObject` 中的元素。(如果没有指定 `end` 参数,那么切分的数组包含从 `start` 到数组结束的所有元素)
556 | 如:
557 |
558 | ``` js
559 | var aCloros = ["red","green","blue","yellow","purple"];
560 | var aCloros2 = aCloros.slice(1);
561 | var aCloros3 = aCloros.slice(1,4);
562 | alert(aCloros2);//output “green,blue,yellow,purple”
563 | alert(aCloros3);//output “green,blue,yellow”
564 | ```
565 |
566 | - arrayObject.splice(index,howmany,item1,.....,itemX)
567 |
568 | `splice()` 方法可从数组中添加或删除项目,然后返回被删除的项目。
569 |
570 | `splice()` 方法可删除从 `index` 处开始的零个或多个元素,`howmany` 表示删除数量,后面的参数表示向数组中添加的新项目。
571 |
572 | ``` js
573 | var arr = new Array(6)
574 | arr[0] = "red"
575 | arr[1] = "green"
576 | arr[2] = "blue"
577 | arr[3] = "yellow"
578 | arr[4] = "purple"
579 |
580 | arr.splice(2,0,"white") //output red,green,white,blue,yellow,purple
581 | arr.splice(2,1,"white") //output red,green,white,yellow,purple
582 |
583 | ```
584 |
585 | ### 7. 正则中 test 与 match 的区别?
586 | - RegExpObject.test(str)
587 |
588 | `test()` 方法用于检测一个字符串是否匹配某个模式.如果字符串 `str` 中含有与 `RegExpObject` 匹配的文本,则返回 true,否则返回 false。
589 |
590 | ``` js
591 | //匹配正整数
592 | var patten = /^[1-9]\d*$/;
593 | patten.test("1498924") //true
594 | patten.test("-1498924") //false
595 | ```
596 |
597 | - stringObject.match(regexp)
598 |
599 | `match()` 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。返回指定的值。
600 |
601 | ``` js
602 | var str="1 + 2 * 3 / 4"
603 | str.match(/\d+/g) //["1", "2", "3", "4"]
604 | ```
605 |
606 | ### 8. 如何在 js 中创建一个对象并继承他?
607 |
608 | ``` js
609 | function Person(name) {
610 | this.name = name;
611 | }
612 | Person.prototype = {
613 | constructor: Person, //强制声明构造函数,默认是Object
614 | sayName : function() {
615 | alert(this.name);
616 | }
617 | }
618 | var Person1 = new Person("xiaoming");
619 | ```
620 |
621 | ### 9. 什么是 prototype ? 什么是原型链?
622 |
623 | JavaScript 中每个对象都有一个内部的链接指向另一个对象,这个对象就是原对象的原型(`prototype`)。这个原型对象也有自己的原型,直到对象的原型为 null 为止。这种一级一级的链结构就是原型链。
624 |
625 | 如通过 new 运算符创建的对象,`构造函数.prototype` 的值就是该对象的原型对象。
626 |
627 | ``` js
628 | var now = new Date();
629 | ```
630 |
631 | `now` 对象的原型对象就是 `Date.prototype`。
632 |
633 | 通过 new 一个对象就可以调用里面的公开的方法,属性。
634 |
635 | ### 10. setTimeout 与 setInterval 的区别?
636 |
637 | `setTimeout()` 方法用于在指定的毫秒数后调用函数或计算表达式。
638 |
639 | `setInterval()` 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。它会不停地调用函数,直到 `clearInterval()` 被调用或窗口被关闭。由 `setInterval()` 返回的 ID 值可用作 `clearInterval()` 方法的参数。
640 |
641 | ### 11. cssText?
642 | cssText 的本质就是设置 HTML 元素的 style 属性值。
643 |
644 | ``` js
645 | document.getElementById("d1").style.cssText = "color:red; font-size:13px;";
646 | ```
647 |
648 | ### 12. offsetWidth, scrollLeft, scrollHeight?
649 | - offsetWidth:元素的宽度(width + padding + border)
650 | - scrollLeft:返回和设置当前横向滚动条的坐标值
651 | - scrollWidth:获取对象的滚动高度。
652 |
653 | ### 13. IE 的事件与 w3c 事件的区别?
654 | - w3c 事件流:
655 |
656 | 从根文档(html)开始遍历所有子节点,如果目标事件的父节点设置为捕获时触发,则执行该事件,直到目标被执行,然后再事件冒泡(设置为捕获时触发的事件不再被执行)。
657 |
658 | - ie 事件流:
659 |
660 | 从目标事件被执行,然后再冒泡父节点的事件,直到根文档。
661 |
662 | ## 三. 高级:30分
663 | ### 1. 除了 jQuery 以外,请写出你所知道的 js 框架,试说出不同框架的特点;
664 |
665 | - Zeptojs:轻量级的适于移动端的针对高级浏览器的 JavaScript 库(类jQuery)
666 |
667 | - Underscore.js:一个 JavaScript 实用库,提供了一整套函数式编程的实用功能,没有扩展任何 JavaScript 内置对象,弥补了部分 jQuery 没有实现的功能。
668 |
669 | - Bootstrap:拥有丰富的 Web 组件。
670 |
671 | - Node.js: 跨平台、可用于服务器端和网络应用的运行环境;具有事件驱动、单线程运行、非阻塞 I/O 调 用、V8 引擎执行代码的特性。Node.js 含有一系列内置模块,使得程序可以作为独立服务器运行,适合在分布式设备上运行的数据密集型的实时应用.
672 |
673 | - AngularJS: MVVM、模块化、自动化双向数据绑定、语义化标签、依赖注入等等,适用于单页面、CRUD 应用。
674 |
675 | - ReactJs:基于组件化开发,而且所有组件有其状态。将 DOM 封装为可以相互组合的 Component, 并且将DOM操作抽象为状态的改变。
676 |
677 | - Vue:MVVM、双向数据绑定连接视图和模型、保持状态和视图同步、易于组件化和模块化、简单灵活的 API ,易上手。
678 |
679 | ### 2. 对于 Javascript 中 OOP 的理念,你认为在项目开发中有没有必要,什么样的项目适合前端 OOP,请谈谈你的看法;
680 | 我个人认为不太重要,在前端页面不需要刻意的去追求面向对象。
681 |
682 | 因为对于面向对象我们需要注意“继承、多态、封装、组合”等特性,它们的出发点都是“分离减少关注点”。使程序以最小的代价来适应“关注点”的变化。
683 | 但对于前端代码需要关注的东西与后端存在许多不同,后端程序大多只需关注项目的“数据+行为”——关注点不多且容易预测。而前端则是“数据+行为+展现+交互”,其中多出来的“展现+交互”决定了前端的关注点更多且更加无从预测,除非人为限制减少相关的关注点,让 UI 和交互套在一个相对死的范围内进行,而这样难免会影响到 UI 的灵活性,不可避免的面对:“丑,慢,大”等特性。
684 |
685 | 结合以上,前端层面上做好代码的分层、解耦、结构化则更优,但做这些事和追求面向对象没有必然关系。
686 |
687 | ### 3. 在 Javascript 开发中,关于性能优化,分享一下你相关的经验?
688 | 对于 JS 开发个人总结大概如下:
689 | 基本原则:
690 |
691 | - 尽可能的减少请求。
692 | - 代码开发遵守规范
693 |
694 | 具体细节:
695 | 基本的代码规范,如 JS 脚本放在底部加载,尽量写为外部文件,基本的上线压缩等
696 | 其余一些细节包括
697 |
698 | 1. 资源加载
699 | - 资源加载:首页加载性能提升
700 | - 按需加载:静态资源依赖关系表,lazyload
701 | 2. 开发规范
702 | - AJAX:缓存 AJAX,请求:GET
703 | - 减少 DOM 操作
704 | - 避免全局变量,尽量使用语言本身的构造和内建函数等。
705 |
706 | 具体可参考[前端性能优化指南](https://github.com/yangzj1992/web_performance_optimization)
707 |
708 | ### 4. 对于模块开发你是怎么看的?
709 | 前端采用模块化开发,使得开发体验大大增强,也使代码管理更加清晰、规范。主要表现为以下几点:
710 |
711 | - 减少命名冲突,消除全局变量
712 | - 一个模块一个文件,组织更清晰
713 | - 依赖自动加载,按需加载
714 | - 其中文件按需加载,依赖自动管理,使得更多精力去关注模块代码本身,开发时不需要在页面上写一大堆 script 引用,一个 require 初始化模块就搞定。
715 |
716 | 前端模块化规范标准:
717 |
718 | - CommonJs (Node.js)
719 | - AMD (RequireJS)
720 | - CMD (SeaJS)
721 |
722 | 也可参考这里的前部分[前端模块化开发的价值](https://github.com/seajs/seajs/issues/547)
723 |
724 | ### 5. 对于 Javascript MVC 开发你是怎么看的?分享一下你了解的相关信息?
725 | 简单了解的采用MVC开发模式的库包括:
726 |
727 | - Backbone.js——优点:社区较好,缺点:抽象较弱。
728 | - Ember.js——优点:丰富的模板系统,拥有复合视图和 UI 绑定;缺点:相对较新,还不够成熟
729 | - Angular.js——优点:对模板范围和控制器设计有很好的考虑,拥有依赖注入系统,支持丰富的 UI 绑定语法。缺点:代码的模块性不强,视图的模块化也不够。
730 |
731 | 其余了解的不多
732 |
733 | 但平常做项目的时候,特别是大项目,一定要把 MVC 的概念放在业务场景里多想想。MVC 并不是终极思想。它有它适合的地方,但也有其局限的一面。还有前端 MVC 的话,对应的后台最好是 REST 风格的接口。还有并不是越复杂的前端业务,越紧迫地需要MVC。一定要多想多看。
734 |
735 | MVC 的一大问题在于:MVC 开发模式会将本身简单的系统大大增加结构的复杂性,并且降低效率。
736 |
737 | 但整套前端开发其实本质上可以看做一个大大的 MVC 架构:
738 |
739 | - Model: HTML/XHTML中的信息
740 | - View: Style sheet
741 | - Controller: ECMAScripts等等脚本
742 |
743 | ### 6. AJAX 是什么? AJAX 跨域的解决办法?
744 | AJAX 即 “Asynchronous JavaScript and XML”(异步的 JavaScript 与 XML 技术),是一种创建交互式网页应用的网页开发技术。通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新。意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
745 |
746 | AJAX 跨域解决:
747 |
748 | 1. 使用中间层过渡的方式:
749 | 就是在 AJAX 与不同域的服务器进行通讯的中间加一层过渡,这一层过渡可以是 PHP、JSP、C++ 等任何具备网络通讯功能的语言,由中间层向不同域的服务器进行读取数据的操作。
750 |
751 | 2. 使用 <script> 标签
752 | 利用 <script> 标签中 src 来请求,因为 <script> 标签的 src 属性不存在跨域的问题。
753 |
754 | 3. jsonp
755 |
756 | --------------------
757 | Questions by [豪情](https://github.com/jikeytang)
--------------------------------------------------------------------------------