前端学堂
学有所用

前端技能总结

如果让你作为前端负责人,你认为前端必备技能有哪些呢?

前端技能总结

编码能力

编码规范

学习编码规范,最好就是先阅读一下现有的规范。前端编码规范
然后可以指定自己团队的编码规范,制定完之后可以通过配置eslint来检验你的编码是否符合规范。

关于规范还可以参考:https://github.com/fedesigner/styleguide

ESlint使用: https://gcdn.gcpowertools.com.cn/showtopic-36912-1-3.html?utm_source=segmentfault&utm_medium=referral&utm_campaign=20170417&utm_content=36912

原型与类与对象

先上一段代码,仔细看一下,能理解多少。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//对象构造函数
function Atest(name){
//私有属性,只能在对象构造函数内部使用
var className = "Atest";
//公有属性,在对象实例化后调用
this.name = name;
//对象方法
this.hello = function(){
alert(this.name);
alert(this.msg());//使用原型方法扩充的方法可以在类内部使用
alert(this.sex);//使用原型方法扩充的属性可以在类内部使用
alert(Atest.age);//静态属性调用时格式为[对象.静态属性]
}
}
//类方法 (实际是静态方法直接调用) 位置:Person类的外部 语法格式:类名称.方法名称 = function([参数...]){ 语句行; }
Atest.Run = function(){
alert("我是类方法 Run");
}
//原型方法
Atest.prototype.msg = function(){
alert("我的名字是:"+this.name);//如果原型方法当作静态方法直接调用时,this.name无法被调用
}
//公有静态属性 在类的外部
Atest.age = 20;//公有静态属性不能使用 【this.属性】,只能使用 【对象.属性】 调用
//原型属性,当作是类内部的属性使用【this.原型属性】,也可以当成公有静态属性使用【对象.prototype.原型属性】
Atest.prototype.sex = "男";
Atest.Run(); //类方法也是静态方法,可以直接使用 【对象.静态方法()】
Atest.prototype.msg();//原型方法当成静态方法使用时【对象.prototype.方法()】
alert(Atest.prototype.sex);//原型属性当作静态属性使用时【对象.prototype.方法()】
var a = new Atest("zhangsan");//对象方法和原型方法需要实例化对象后才可以使用
a.hello();//对象方法必须实例化对象
a.msg();//原型方法必须实例化对象
alert(a.age)://错误,公有静态属性只能使用 【对象.属性】调用
//ps:尽量将方法定义为原型方法,原型方法避免了每次调用构造函数时对属性或方法的构造,节省空间,创建对象快.

DOM树

基础知识这里就不做多介绍了,不过我还是比较注重对于浏览器渲染网页的机制的理解。先看下这张图:
前端技能总结
当浏览器获得一个html文件时,会“自上而下”加载,并在加载过程中进行解析渲染。
解析:

  1. 浏览器会将HTML解析成一个DOM树,DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。
  2. 将CSS解析成 CSS Rule Tree 。
  3. 根据DOM树和CSSOM来构造 Rendering Tree。注意:Rendering Tree 渲染树并不等同于 DOM 树,因为一些像 Header 或 display:none 的东西就没必要放在渲染树中了。
  4. 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作称之为Layout,顾名思义就是计算出每个节点在屏幕中的位置。
  5. 再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。
    上述这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

(1)Reflow(回流):浏览器要花时间去渲染,当它发现了某个部分发生了变化影响了布局,那就需要倒回去重新渲染。
(2)Repaint(重绘):如果只是改变了某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的repaint,重画某一部分。
Reflow要比Repaint更花费时间,也就更影响性能。所以在写代码的时候,要尽量避免过多的Reflow。

reflow的原因:

(1)页面初始化的时候;
(2)操作DOM时;
(3)某些元素的尺寸变了;
(4)如果 CSS 的属性发生变化了。

减少 reflow/repaint

 (1)不要一条一条地修改 DOM 的样式。与其这样,还不如预先定义好 css 的 class,然后修改 DOM 的 className。
 (2)不要把 DOM 结点的属性值放在一个循环里当成循环里的变量。
 (3)为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是不会 reflow 的。
 (4)千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。

编写CSS时应该注意:

  1. dom深度尽量浅, CSS选择符是从右到左进行匹配的。
  2. 减少inline javascript、css的数量。
  3. 使用现代合法的css属性。
  4. 不要为id选择器指定类名或是标签,因为id可以唯一确定一个元素。
  5. 避免后代选择符,尽量使用子选择符。原因:子元素匹配符的概率要大于后代元素匹配符。后代选择符;#tp p{} 子选择符:#tp>p{}
  6. 避免使用通配符,举一个例子,.mod .hd *{font-size:14px;} 根据匹配顺序,将首先匹配通配符,也就是说先匹配出通配符,然后匹配.hd(就是要对dom树上的所有节点进行遍历他的父级元素),然后匹配.mod,这样的性能耗费可想而知.

渲染树

在DOM树构建的同时,浏览器会构建渲染树(render tree)。渲染树的节点(渲染器),在Gecko中称为frame,而在webkit中称为renderer。渲染器是在文档解析和创建DOM节点后创建的,会计算DOM节点的样式信息。 在webkit中,renderer是由DOM节点调用attach()方法创建的。attach()方法计算了DOM节点的样式信息。attach()是自上而下的递归操作。也就是说,父节点总是比子节点先创建自己的renderer。销毁的时候,则是自下而上的递归操作,也就是说,子节点总是比父节点先销毁。 如果元素的display属性被设置成了none,或者如果元素的子孙继承了display:none,renderer不会被创建。节点的子类和display属性一起决定为该节点创建什么样的渲染器。但是visibility:hidden的元素会被创建。

在webkit中,根据display属性的不同,创建不同的renderer,比如:

  1. display:inline,创建的是RenderInline类型。
  2. display:block,创建的是RenderBlock类型。
  3. display:inline-block,创建的是RenderBlock类型。
  4. display:list-item,创建的是RenderListItem类型。
    position:relative和position:absolute的元素在渲染树中的位置与DOM节点在DOM树中的位置是不一样的。

DOM树和渲染树的对应关系如下图:
前端技能总结

为了简化样式计算,Firefox 还采用了另外两种树:规则树和样式上下文树。WebKit 也有样式对象,但它们不是保存在类似样式上下文树这样的树结构中,只是由 DOM 节点指向此类对象的相关样式。
前端技能总结

所有匹配的规则都存储在树中。路径中的底层节点拥有较高的优先级。规则树包含了所有已知规则匹配的路径。规则的存储是延迟进行的。规则树不会在开始的时候就为所有的节点进行计算,而是只有当某个节点样式需要进行计算时,才会向规则树添加计算的路径。

在计算某个特定元素的样式上下文时,我们首先计算规则树中的对应路径,或者使用现有的路径。然后我们沿此路径应用规则,在新的样式上下文中填充结构。我们从路径中拥有最高优先级的底层节点(通常也是最特殊的选择器)开始,并向上遍历规则树,直到结构填充完毕。如果该规则节点对于此结构没有任何规范,那么我们可以实现更好的优化:寻找路径更上层的节点,找到后指定完整的规范并指向相关节点即可。这是最好的优化方法,因为整个结构都能共享。这可以减少端值的计算量并节约内存。 如果我们找到了部分定义,就会向上遍历规则树,直到结构填充完毕。如果我们找不到结构的任何定义,那么假如该结构是“继承”类型,我们会在上下文树中指向父代的结构,这样也可以共享结构。如果是 reset 类型的结构,则会使用默认值。如果最特殊的节点确实添加了值,那么我们需要另外进行一些计算,以便将这些值转化成实际值。然后我们将结果缓存在树节点中,供子代使用。如果某个元素与其同级元素都指向同一个树节点,那么它们就可以共享整个样式上下文。

让我们来看一个例子,假设我们有如下 HTML 代码:

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<body>
<div class="err" id="div1">
<p>
this is a <span class="big"> big error </span>
this is also a
<span class="big"> very big error</span> error
</p>
</div>
<div class="err" id="div2">another error</div>
</body>
</html>

css代码:

1
2
3
4
5
6
div {margin:5px;color:black}
.err {color:red}
.big {margin-top:3px}
div span {margin-bottom:4px}
#div1 {color:blue}
#div2 {color:green}

为了简便起见,我们只需要填充两个结构:color 结构和 margin 结构。color 结构只包含一个成员(即“color”),而 margin 结构包含四条边。
形成的规则树如下图所示(节点的标记方式为“节点名 : 指向的规则序号”):
前端技能总结
上下文树如下图所示(节点名 : 指向的规则节点):
前端技能总结
假设我们解析 HTML 时遇到了第二个 /

标记,我们需要为此节点创建样式上下文,并填充其样式结构。 经过规则匹配,我们发现该
的匹配规则是第 1、2 和 6 条。这意味着规则树中已有一条路径可供我们的元素使用,我们只需要再为其添加一个节点以匹配第 6 条规则(规则树中的 F 节点)。我们将创建样式上下文并将其放入上下文树中。新的样式上下文将指向规则树中的 F 节点。
/>

在 WebKit 中没有规则树,因此会对匹配的声明遍历 4 次。首先应用非重要高优先级的属性(由于作为其他属性的依据而应首先应用的属性,例如 display),接着是高优先级重要规则,然后是普通优先级非重要规则,最后是普通优先级重要规则。这意味着多次出现的属性会根据正确的层叠顺序进行解析。最后出现的最终生效。

了解上面的流程基本可以大致了解渲染树的渲染过程,具体详情参考: https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/#The_main_flow

呈现器在创建完成并添加到呈现树时,并不包含位置和大小信息。计算这些值的过程称为布局或重排。HTML 采用基于流的布局模型,这意味着大多数情况下只要一次遍历就能计算出几何信息。处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至右、从上至下的顺序遍历文档。但是也有例外情况,比如 HTML 表格的计算就需要不止一次的遍历 (3.5)。坐标系是相对于根框架而建立的,使用的是上坐标和左坐标。

布局是一个递归的过程。它从根呈现器(对应于 HTML 文档的 /

元素)开始,然后递归遍历部分或所有的框架层次结构,为每一个需要计算的呈现器计算几何信息。根呈现器的位置左边是 0,0,其尺寸为视口(也就是浏览器窗口的可见区域)。
所有的呈现器都有一个“layout”或者“reflow”方法,每一个呈现器都会调用其需要进行布局的子代的 layout 方法。/>

异步编程与技巧

说起异步编程,不得不提promise, 其实也有很多实现异步编程的函数类库,比如jQuery,then.js等。当然ES6新增了很多关于异步编程特性。
异步编程主要在nodejs中使用很多,在前端编程中ajax请求中会经常用到。了解promise的处理机制对于养成良好的编程习惯很重要。
可以参考: Javascript异步编程的4种方法
简单实现一个promise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
var Promise = function(){
this.callbacks = [];
}
Promise.prototype = {
constructor: Promise, //需要设置构造函数,参考https://blog.sae.sina.com.cn/archives/1206
complete: function(type, result){
while(this.callbacks[0])
this.callbacks.shift()[type](result);
},
resolve: function(result){
this.complete("resolve",result);
},
reject: function(result){
this.complete("reject",result);
},
then: function(successHandler, failedHandler){
var _p = new Promise();
this.callbacks.push({
resolve: successHandler.bind(_p),
reject: failedHandler.bind(_p)
});
return _p;
}
}
//test example
var aa = new Promise();
aa.then(
function(_da){
console.log(_da);
this.resolve(33)
},function(_da){
console.log(_da);
this.reject(44)
}).then(
function(_da){
console.log(_da);
},function(_da){
console.log(_da);
});
aa.reject(22)
//output:
22
44

详细可以参考: https://segmentfault.com/a/1190000003028634
Promise/A的误区以及实践
JavaScript异步编程的Promise模式

关于技巧,不得不提的是函数节流(throttle)与函数去抖(debounce)。简而言之就是防止一段时间内重复执行同一个操作,需要去抖函数。而节流则是要求在延迟一定时间后执行某个动作,这个动作需要按照一定的频率来触发,以保证整体的流畅度,减少计算量。
关于Throttle: JavaScript 节流函数 Throttle 详解
Debounce and Throttle: a visual explanation
函数节流(throttle)与函数去抖(debounce)

手写一个节流函数吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 节流函数
* @param fn: 需要截流执行的函数
* @param delay: 节流的时间
*/
function throttle(fn, delay){
var timer = null;
return function(){
clearTimeout(timer);
timer = setTimeout(function(){
fn();
}, delay);
};
}
//使用
window.onresize = throttle(testFn, 200);

这时候你会发现,如果用户一直拖拽窗口,那么只有在最后一次用户停下来的时候才会执行需要执行的函数。这个正好满足去抖的要求,如果你需要控制用户快速频繁点击按钮的时候只会执行一次,那么可以用上面的函数。
但有时候你可能需要这个函数至少要每隔200ms执行一次,那么我们需要改造一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 节流函数
* @param fn: 需要截流执行的函数
* @param delay: 节流的时间
* @param min: 毫秒数,最少min毫秒执行一次
*/
function throttle(fn, delay, min){
var timer = null;
var startTime = startTime || new Date();
return function(){
if(new Date().getTime() - startTime.getTime() >= min*1000){
fn();
startTime = new Date();
clearTimeout(timer);
}else{
clearTimeout(timer);
timer = setTimeout(function(){
fn();
}, delay);
}
};
}

关于节流和去抖还可以参考:https://www.css88.com/archives/4648

正则表达式

正则表达式在编写业务逻辑中可能使用的并不平凡,但是在编写工具脚本或者自己写一些基础类库,就会有很大的用处。所以还是需要了解一下基础知识。至少知道igm是什么?什么是贪婪匹配和非贪婪匹配。ES6中也扩展了正则的一些用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
"^" :^会匹配行或者字符串的起始位置,有时还会匹配整个文档的起始位置。
"$" :$会匹配行或字符串的结尾
"/b" :不会消耗任何字符只匹配一个位置,常用于匹配单词边界 如 我想从字符串中"This is Regex"匹配单独的单词 "is" 正则就要写成 "/bis/b"
   /b 不会匹配is 两边的字符,但它会识别is 两边是否为单词的边界
"/d": 匹配数字,
   例如要匹配一个固定格式的电话号码以0开头前4位后7位,如0737-5686123 正则:^0/d/d/d-/d/d/d/d/d/d/d$ 这里只是为了介绍"/d"字符,实际上有更好的写法会在 下面介绍。
"/w":匹配字母,数字,下划线.
   例如我要匹配"a2345BCD__TTz" 正则:"/w+" 这里的"+"字符为一个量词指重复的次数,稍后会详细介绍。
"/s":匹配空格
   例如字符 "a b c" 正则:"/w/s/w/s/w" 一个字符后跟一个空格,如有字符间有多个空格直接把"/s" 写成 "/s+" 让空格重复
".":匹配除了换行符以外的任何字符
   这个算是"/w"的加强版了"/w"不能匹配 空格 如果把字符串加上空格用"/w"就受限了,看下用 "."是如何匹配字符"a23 4 5 B C D__TTz" 正则:".+"
"[abc]": 字符组 匹配包含括号内元素的字符
这个比较简单了只匹配括号内存在的字符,还可以写成[a-z]匹配a至z的所以字母就等于可以用来控制只能输入英文了,
写法很简单改成大写就行了,意思与原来的相反:
"/W" 匹配任意不是字母,数字,下划线 的字符
"/S" 匹配任意不是空白符的字符
"/D" 匹配任意非数字的字符
"/B" 匹配不是单词开头或结束的位置
"[^abc]" 匹配除了abc以外的任意字符
"*"(贪婪) 重复零次或更多
    例如"aaaaaaaa" 匹配字符串中所有的a 正则: "a*" 会出到所有的字符"a"
"+"(懒惰) 重复一次或更多次
   例如"aaaaaaaa" 匹配字符串中所有的a 正则: "a+" 会取到字符中所有的a字符, "a+"与"a*"不同在于"+"至少是一次而"*" 可以是0次,
   稍后会与"?"字符结合来体现这种区别
"?"(占有) 重复零次或一次
   例如"aaaaaaaa" 匹配字符串中的a 正则 : "a?" 只会匹配一次,也就是结果只是单个字符a
"{n}" 重复n次
   例如从"aaaaaaaa" 匹配字符串的a 并重复3次 正则: "a{3}" 结果就是取到3个a字符 "aaa";
"{n,m}" 重复n到m次
   例如正则 "a{3,4}" 将a重复匹配3次或者4次 所以供匹配的字符可以是三个"aaa"也可以是四个"aaaa" 正则都可以匹配到
"{n,}" 重复n次或更多次
   与{n,m}不同之处就在于匹配的次数将没有上限,但至少要重复n次 如 正则"a{3,}" a至少要重复3次

具体参考: https://www.cnblogs.com/China3S/archive/2013/11/30/3451971.html

测试能力

前端开发转移到后端环境,意味着可以适用标准的软件工程流程。

  1. 本地开发(developing)
  2. 静态代码检查(linting)
  3. 单元测试(testing)
  4. 合并进入主干(merging)
  5. 自动构建(building)
  6. 自动发布(publishing)

Continuous integration(简称 CI):开发代码频繁地合并进主干,始终保持可发布状态的这个过程。
测试的类型

  1. 单元测试(unit testing)
  2. 功能测试(feature testing)
  3. 集成测试(integration testing)
  4. 端对端测试 (End-to-End testing)

TDD:测试驱动的开发(Test-Driven Development),基于开发者角度,重点测试函数的输入输出
BDD:行为驱动的开发(Behavior-Driven Development),基于使用者角度,重点测试对用户行为的反应
详细可以参考:前端工程简介

这里说的测试主要包含前端能做的单元测试与集成测试。先介绍几个名词吧:单元测试用用到mocha与chai, 可能也会用到nightmare。集成测试主要就是nightWatch.

mocha

说下mocha,这个用起来比较简单吧,可以在浏览器或者命令行中跑测试。如果测试脚本不涉及依赖和脚本解析问题,可以直接在命令行中跑单元测试,比如一些通用的方法,util类等等。而一些涉及到业务逻辑的,比如写的一个组件,需要引入一些类库,需要解析模板等等,这时候需要在浏览器中跑你的测试脚本了,因为浏览器可以为你加载解析执行你的组价。当然也可以用一些headless浏览器,比如phantomjs(https://phantomjs.org/,作者已放弃维护),Electron,还有headless chrome等等。
关于mocha的使用:
前端测试方法+工具
阮老师文章: https://www.ruanyifeng.com/blog/2015/12/a-mocha-tutorial-of-examples.html

Nightmare

  1. 使用 Electron 模拟真实浏览器环境
  2. 提供大量人性化、易用的 API
  3. 官网:https://nightmarejs.org

nightWatch: 同上

  1. 官网:https://nightwatchjs.org/

测试覆盖率

测试覆盖率是衡量单元测试的一项重要指标,常用的工具是Istanbul,它提供如下测量维度:

  1. 行覆盖率(line coverage):是否每一行都执行了?
  2. 函数覆盖率(function coverage):是否每个函数都调用了?
  3. 分支覆盖率(branch coverage):是否每个if代码块都执行了?
  4. 语句覆盖率(statement coverage):是否每个语句都执行了?
    可以参考: 代码覆盖率工具 Istanbul 入门教程

架构能力

谈及架构,你可能想到技术栈,是啊。React火起来了,这个技术栈的名词也火起来了。相信只要你开发过几个项目,再让你开发新的项目,你都可以照着葫芦画瓢,都能搭建一套基础的项目框架。在搭建前端项目框架之前需要考虑的一些事情:

  1. 业务结构划分,是横向划分按业务,还是纵向划分按功能,还是二者结合。
  2. 业务粒度,组件和模块如何有效组织,如何区分职能。单页应用和非单页应用如何优雅开发?
  3. 项目组件依赖采用什么方式,AMD还是CMD?这也关系到项目如何打包?
  4. 项目编码规范实施,eslint配置。
  5. 模块和组件如何做测试?如何做单元测试与集成测试?测试覆盖率如何检测与保证?前端性能监控,数据统计。
  6. 开发中如何复用?如何优雅的减少开发成本,提升开发质量?
  7. 针对以上问题,可以开发一些脚手架工具。

关于模块化,参考:

  1. JavaScript中的AMD和CMD模块化
  2. AMD与CMD规范-javascript模块化

业务能力

业务能力,我承认我自己的业务能力并不是很好,自己的逻辑思维能力总是有遗漏,这一点可以向测试同学学习。开发业务的时候,如何把业务逻辑写全面?当然写的越是全面,后面出现的bug就越少。毫无疑问,增加测试用例可以提升业务代码完整度。

谢谢!

转载请注明出处://fed123.oss-ap-southeast-2.aliyuncs.com/2017/04/25/2017_frontSkill/
欢迎关注皓眸学问公众号(扫描左侧二维码),每天好文、新技术!任何学习疑问或者工作问题都可以给我留言、互动。T_T 皓眸大前端开发学习 T_T

赞(0) 打赏
一分也是爱,觉得好请我喝杯咖啡吧!前端学堂 » 前端技能总结

一分也是爱,觉得好请我喝杯咖啡吧!

支付宝扫一扫打赏

微信扫一扫打赏