本篇博文来自一次公司内部的前端分享,从多个方面讨论了在设计接口时遵循的原则,总共包含了七个大块。系卤煮本身总结的一些经验和教训。本篇博文同时也参考了其余一些文章,相关地址会在后面贴出来。很难作到详尽充实,若是有好的建议或者不对的地方,还望不吝赐教斧正。javascript
好的接口是流畅易懂的,他主要体现以下几个方面:css
1.简单html
操做某个元素的css属性,下面是原生的方法:前端
1
|
document.querySelector(
'#id'
).style.color =
'red'
;
|
封装以后java
1
2
3
4
|
function
a(selector, color) {
document.querySelector(selector).style.color = color
}
a(
'#a'
,
'red'
);
|
从几十个字母长长的一行到简简单单的一个函数调用,体现了api设计原则之一:简单易用。node
2.可阅读性jquery
a('#a', 'red')是个好函数,帮助咱们简单实用地改变某个元素,但问题来了,若是第一次使用该函数的人来讲会比较困惑,a函数是啥函数,没有人告诉他。开发接口有必要知道一点,大多数人都是懒惰的(包括卤煮本身),从颜色赋值这个函数来讲,虽然少写了代码,可是增长了单词字母的个数,使得它再也不好记。每次作这件事情的时候都须要有映射关系: a---->color. 若是是简单的几个api却是无所谓,可是一般一套框架都有几十甚至上百的api,映射成本增长会使得程序员哥哥崩溃。 咱们须要的就是使得接口名称有意义,下面咱们改写一下a函数:git
1
2
|
function
letSomeElementChangeColor(selector, color) {
document.querySelectorAll(selector, color).style.color = color; }
|
letSomeElementChangeColor相对于a来讲被赋予了现实语言上的意义,任何人都不须要看说明也能知道它的功能。程序员
3.减小记忆成本github
咱们刚刚的函数太长了,letSomeElementChangeColor虽然减小了映射成本,有了语言上的意义,可是毫无疑问增长了记忆成本。要知道,包括学霸在内,任何人都不喜欢背单词。不只仅在此处,原生获取dom的api也一样有这个问题: document.getElementsByClassName; document.getElementsByName; document.querySelectorAll;这些api给人的感受就是单词太长了,虽然他给出的意义是很清晰,然而这种作法是创建在牺牲简易性和简忆性的基础上进行的。因而咱们又再次改写这个以前函数
1
2
3
|
function
setColor(selector, color) {
xxxxxxxxxxxx
}
|
在语言意义不作大的变化前提下,缩减函数名称。使得它易读易记易用。
4.可延伸
所谓延伸就是指函数的使用像流水同样按照书写的顺序执行造成执行链条:
1
2
3
|
document.getElementById(
'id'
).style.color =
'red'
;
document.getElementById(
'id'
).style.fontSize =
'12px'
;
document.getElementById(
'id'
).style.backgourdColor =
'pink'
;
|
若是咱们须要实现像以上有强关联性的业务时,用咱们以前的以前的方法是再次封装两个函数 setFontSize, setbackgroundColor; 而后执行它们 setColor('id', 'red');setFontSiez('id', '12px');setbackgroundColor('id', 'pink'); 显然,这样的作法没有懒出境界来;id元素每次都须要从新获取,影响性能,失败;每次都须要添加新的方法,失败; 每次还要调用这些方法,仍是失败。下面咱们将其改写为能够延伸的函数 首先将获取id方法封装成对象,而后再对象的每一个方法中返回这个对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
function
getElement(selector) {
this
.style = document.querySelecotrAll(selector).style;
}
getElement.prototype.color =
function
(color) {
this
.style.color = color;
return
this
;
}
getElement.prototype.background =
function
(bg) {
this
.style.backgroundColor = bg;
return
this
;
}
getElement.prototype.fontSize =
function
(size) {
this
.style.fontSize = size;
return
this
;
}
//调用
var
el =
new
getElement(
'#id'
)
el.color(
'red'
).background(
'pink'
).fontSize(
'12px'
);
|
简单、流畅、易读,它们看起来就像行云流水同样,即在代码性能上获得了提高优化,又在视觉上悦目。后面咱们会在参数里面讲到如何继续优化。
因此,你们都比较喜欢用jquery的api,虽然一个$符号并不表明任何现实意义,但简单的符号有利于咱们的使用。它体现了以上的多种原则,简单,易读,易记,链式写法,多参处理。
nightmare:
1
2
3
|
document.getElementById(
'id'
).style.color =
'red'
;
document.getElementById(
'id'
).style.fontSize =
'12px'
;
document.getElementById(
'id'
).style.backgourdColor =
'pink'
;
|
dream:
1
|
$(
'id'
).css({color:
'red'
, fontSize:
'12px'
, backgroundColor:
'pink'
})
|
1.接口的一致性
相关的接口保持一致的风格,一整套 API 若是传递一种熟悉和温馨的感受,会大大减轻开发者对新工具的适应性。 命名这点事:既要短,又要自描述,最重要的是保持一致性 “在计算机科学界只有两件头疼的事:缓存失效和命名问题” — Phil Karlton 选择一个你喜欢的措辞,而后持续使用。选择一种风格,而后保持这种风格。
Nightmare:
1
2
3
4
|
setColor,
letBackGround
changefontSize
makedisplay
|
dream:
1
2
3
4
|
setColor;
setBackground;
setFontSize
set.........
|
尽可能地保持代码风格和命名风格,使别人读你的代码像是阅读同一我的写的文章同样。
1.参数的类型
判断参数的类型为你的程序提供稳定的保障
1
2
3
4
5
|
//咱们规定,color接受字符串类型
function
setColor(color) {
if
(
typeof
color !==
'string'
)
return
;
dosomething
}
|
2.使用json方式传参
使用json的方式传值不少好处,它能够给参数命名,能够忽略参数的具体位置,能够给参数默认值等等 好比下面这种糟糕的状况:
1
|
function
fn(param1, param2...............paramN)
|
你必须对应地把每个参数按照顺序传入,不然你的方法就会偏离你预期去执行,正确的方法是下面的作法。
1
2
3
4
5
6
7
8
|
function
fn(json) {
//为必须的参数设置默认值
var
default
= extend({
param:
'default'
,
param1:
'default'
......
},json)
}
|
这段函数代码,即使你不传任何参数进来,他也会预期运行。由于在声明的时候,你会根据具体的业务预先决定参数的缺省值。
软件设计最重要的原则之一:永远不修改接口,而是去扩展它!可扩展性同时会要求接口的职责单一,多职责的接口很难扩展。 举个栗子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
//须要同时改变某个元素的字体和背景
// Nightmare:
function
set(selector, color) {
document.querySelectroAll(selector).style.color = color;
document.querySelectroAll(selector).style.backgroundColor = color;
}
//没法扩展改函数,若是须要再次改变字体的大小的话,只能修改此函数,在函数后面填加改变字体大小的代码
//Dream
function
set(selector, color) {
var
el = document.querySelectroAll(selector);
el.style.color = color;
el.style.backgroundColor = color;
return
el;
}
//须要设置字体、背景颜色和大小
function
setAgain (selector, color, px) {
var
el = set(selector, color)
el.style.fontSize = px;
return
el;
}
|
以上只是简单的添加颜色,业务复杂而代码又不是你写的时候,你就必须去阅读以前的代码再修改它,显然是不符合开放-封闭原则的。修改后的function是返回了元素对象,使得下次须要改变时再次获得返回值作处理。
2.this的运用
可扩展性还包括对this的以及call和apply方法的灵活运用:
1
2
3
4
5
6
7
8
9
|
function
sayBonjour() {
alert(
this
.a)
}
obj.a = 1;
obj.say = sayBonjour;
obj.say();
//1
//or
sayBonjour.call||apply(obj);
//1
|
1.预见错误
能够用 类型检测 typeof 或者try...catch。 typeof 会强制检测对象不抛出错误,对于未定义的变量尤为有用。
2.抛出错误
大多数开发者不但愿出错了还须要本身去找带对应得代码,最好方式是直接在console中输出,告诉用户发生了什么事情。咱们能够用到浏览器为咱们提供的api输出这些信息:console.log/warn/error。你还能够为本身的程序留些后路: try...catch。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function
error (a) {
if
(
typeof
a !==
'string'
) {
console.error(
'param a must be type of string'
)
}
}
function
error() {
try
{
// some code excucete here maybe throw wrong
}
catch
(ex) {
console.wran(ex);
}
}
|
可预见性味程序接口提供健壮性,为保证你的代码顺利执行,必须为它考虑到非正常预期的状况。咱们看下不能够预见的代码和可预见的代码的区别用以前的setColor
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
45
46
|
//nighware
function
set(selector, color) {
document.getElementById(selector).style.color = color;
}
//dream
zepto.init =
function
(selector, context) {
var
dom
// If nothing given, return an empty Zepto collection
if
(!selector)
return
zepto.Z()
// Optimize for string selectors
else
if
(
typeof
selector ==
'string'
) {
selector = selector.trim()
// If it's a html fragment, create nodes from it
// Note: In both Chrome 21 and Firefox 15, DOM error 12
// is thrown if the fragment doesn't begin with <
if
(selector[0] ==
'<'
&& fragmentRE.test(selector))
dom = zepto.fragment(selector, RegExp.$1, context), selector =
null
// If there's a context, create a collection on that context first, and select
// nodes from there
else
if
(context !== undefined)
return
$(context).find(selector)
// If it's a CSS selector, use it to select nodes.
else
dom = zepto.qsa(document, selector)
}
// If a function is given, call it when the DOM is ready
else
if
(isFunction(selector))
return
$(document).ready(selector)
// If a Zepto collection is given, just return it
else
if
(zepto.isZ(selector))
return
selector
else
{
// normalize array if an array of nodes is given
if
(isArray(selector)) dom = compact(selector)
// Wrap DOM nodes.
else
if
(isObject(selector))
dom = [selector], selector =
null
// If it's a html fragment, create nodes from it
else
if
(fragmentRE.test(selector))
dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector =
null
// If there's a context, create a collection on that context first, and select
// nodes from there
else
if
(context !== undefined)
return
$(context).find(selector)
// And last but no least, if it's a CSS selector, use it to select nodes.
else
dom = zepto.qsa(document, selector)
}
// create a new Zepto collection from the nodes found
return
zepto.Z(dom, selector)
}
|
以上是zepto的源码,能够看见,做者在预见传入的参数时作了不少的处理。其实可预见性是为程序提供了若干的入口,无非是一些逻辑判断而已。zepto在这里使用了不少的是非判断,这样作的好处固然是代码比以前更健壮,但同时致使了代码的冗长,不适合阅读。总之,可预见性真正须要你作的事多写一些对位置实物的参数。把外部的检测改成内部检测。是的使用的人用起来舒心放心开心。呐!作人嘛最重要的就是海森啦。
一个最好的接口是不须要文档咱们也会使用它,可是每每接口量一多和业务增长,接口使用起来也会有些费劲。因此接口文档和注释是须要认真书写的。注释遵循简单扼要地原则,给多年后的本身也给后来者看:
1
2
3
4
5
6
7
8
9
10
|
//注释接口,为了演示PPT用
function
commentary() {
//若是你定义一个没有字面意义的变量时,最好为它写上注释:a:没用的变量,能够删除
var
a;
//在关键和有歧义的地方写上注释,犹如画龙点睛:路由到hash界面后将全部的数据清空结束函数
return
go.Navigate(
'hash'
,
function
(){
data.clear();
});
}
|
推荐markdown语法书写API文档,github御用文档编写语法。简单、快速,代码高亮、话很少说上图
卤煮在此也推荐几个在线编辑的网站。诸君可自行前往练习使用。
https://www.zybuluo.com/mdeditor