您的位置:首页 > Web前端 > JavaScript

JavaScript API 设计原则

2016-10-04 21:11 267 查看
一、接口的流畅性


好的接口是流畅易懂的,他主要体现如下几个方面:

1.简单


操作某个元素的css属性,下面是原生的方法:

?
封装之后

?
从几十个字母长长的一行到简简单单的一个函数调用,体现了api简单易用

2.可阅读性

a('#a', 'red')是个好函数,帮助我们简单实用地改变某个元素,但问题来了,如果第一次使用改函数的人来说会比较困惑,a函数是啥函数,没有人告诉他。开发接口有必要知道一点,人都是懒惰的,从颜色赋值这个函数来说,虽然少写了代码,但是增加了记忆成本。每次做这件事情的时候都需要有映射关系。 a---->color. 如果是简单的几个无所谓,但是通常一套框架都有几十甚至上百的api,映射成本增加会使得程序员哥哥崩溃。 我们需要的就是使得接口有意义,下面我们改写一下a函数:

?
letSomeElementChangeColor相对于a来说被赋予了语言意义,任何人都会知道它的意义

3.减少记忆成本

我们刚刚的函数也是这样的它太长了letSomeElementChangeColor虽然减少了映射成本,但是增加了记忆成本。要知道,包括学霸在内,任何人都不喜欢被单词。原生获取dom的api也同样有这个问题 document.getElementsByClassName; document.getElementsByName; document.querySelectorAll;这些api给人的感觉就是单词太长了,虽然他给出的意义是很清晰,然而这种做法是建立在牺牲简易性的基础上进行的。于是我们又再次改写这个之前函数

?
在意义不做大的变化前提下,缩减函数名称。使得它易读易记易用;

4.可延伸


所谓延伸就是指函数的使用像流水一样按照书写的顺序执行形成执行链条:

?
用我们之前的之前的方法是再次封装两个函数 setFontSize, setbackgroundColor; 然后执行它们 setColor('id', 'red');setFontSiez('id', '12px'); setbackgroundColor('id', 'pink'); 显然,这样的做法没有懒出境界来;id元素每次都需要重新获取,影响性能,失败;每次都需要添加新的方法 失败 每次还要调用这些方法,还是失败。下面我们将其改写为可以延伸的函数 首先将获取id方法封装成对象,然后再对象的每个方法中返回这个对象:

?
简单、流畅、易读后面我们会在参数里面讲到如何继续优化。所以,大家都比较喜欢用jquery的api,虽然一个$符号并不代表任何现实意义,但简单的符号有利于我们的使用。它体现了以上的多种原则,简单,易读,易记,链式写法,多参处理。

nightware:

?
dream:

?
二、一致性

1.接口的一致性

相关的接口保持一致的风格,一整套 API 如果传递一种熟悉和舒适的感觉,会大大减轻开发者对新工具的适应性。 命名这点事:既要短,又要自描述,最重要的是保持一致性 “在计算机科学界只有两件头疼的事:缓存失效和命名问题” — Phil Karlton 选择一个你喜欢的措辞,然后持续使用。选择一种风格,然后保持这种风格。

Nightware:

setColor,

letBackGround

changefontSize

makedisplay

dream:


setColor;

setBackground;

setFontSize

set.........

尽量地保持代码风格和命名风格,使人读你的代码像是阅读同一个人写的文章一样。

三、参数的处理

1.参数的类型


判断参数的类型为你的程序提供稳定的保障

?
2.使用json方式传参


使用json的方式传值很多好处,它可以给参数命名,可以忽略参数的具体位置,可以给参数默认值等等 比如下面这种糟糕的情况:

function fn(param1, param2...............paramN)

你必须对应地把每一个参数按照顺序传入,否则你的方法就会偏离你预期去执行,正确的方法是下面的做法。

?
这段函数代码,即便你不传任何参数进来,他也会预期运行。因为在声明的时候,你会根据具体的业务决定参数的缺省值。

四、可扩展性


软件设计最重要的原则之一:永远不修改接口,指扩展它!可扩展性同时会要求接口的职责单一,多职责的接口很难扩展。 举个栗子:

?
以上只是简单的添加颜色,业务复杂而代码又不是你写的时候,你就必须去阅读之前的代码再修改它,显然是不符合开放-封闭原则的。修改后的function是返回了元素对象,使得下次需要改变时再次得到返回值做处理。

2.this的运用


可扩展性还包括对this的以及call和apply方法的灵活运用:

?
五、对错误的处理

1.预见错误

可以用 类型检测 typeof 或者try...catch。 typeof 会强制检测对象不抛出错误,对于未定义的变量尤其有用。

2.抛出错误

大多数开发者不希望出错了还需要自己去找带对应得代码,最好方式是直接在console中输出,告诉用户发生了什么事情。我们可以用到浏览器的输出api:console.log/warn/error。你还可以为自己的程序留些后路: try...catch。

?
六、可预见性


可预见性味程序接口提供健壮性,为保证你的代码顺利执行,必须为它考虑到非正常预期的情况。我们看下不可以预见的代码和可预见的代码的区别用之前的setColor

?
以上是zepto的源码,可以看见,作者在预见传入的参数时做了很多的处理。其实可预见性是为程序提供了若干的入口,无非是一些逻辑判断而已。zepto在这里使用了很多的是非判断,同时导致了代码的冗长,不适合阅读。总之,可预见性真正需要你做的事多写一些对位置实物的参数。把外部的检测改为内部检测。是的使用的人用起来舒心放心开心。呐!做人嘛最重要的就是海森啦。

七、注释和文档的可读性


一个最好的接口是不需要文档我们也会使用它,但是往往接口量一多和业务增加,接口使用起来也会有些费劲。所以接口文档和注释是需要认真书写的。注释遵循简单扼要地原则,给多年后的自己也给后来者看:

?
最后

推荐markdown语法书写API文档,github御用文档编写语法。简单、快速,代码高亮、话不多说上图





以上所述是小编给大家介绍的JavaScript的API设计原则的全部叙述,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

设计良好的 API ,开发者可以快速上手,没必要经常抱着手册和文档,也没必要频繁光顾技术支持社区。

流畅的接口

方法链:流畅易读,更易理解

JavaScript

// 常见的 API 调用方式:改变一些颜色,添加事件监听
var elem = document.getElementById("foobar");
elem.style.background = "red";
elem.style.color = "green";
elem.addEventListener('click', function(event) {
alert("hello world!");
}, true);

//(设想的)方法链 API
DOMHelper.getElementById('foobar')
.setStyle("background", "red")
.setStyle("color", "green")
.addEvent("click", function(event) {
alert("hello world");
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// 常见的 API 调用方式:改变一些颜色,添加事件监听

var elem=
document.getElementById("foobar");
elem.style.background=
"red";

elem.style.color=
"green";
elem.addEventListener('click',function(event){

  alert("hello world!");
},true);

 
//(设想的)方法链 API

DOMHelper.getElementById('foobar')
  .setStyle("background","red")

  .setStyle("color","green")
  .addEvent("click",function(event){

    alert("hello world");
  });

设置和获取操作,可以合二为一;方法越多,文档可能越难写

JavaScript

var $elem = jQuery("#foobar");

//setter
$elem.setCss("background", "green");
//getter
$elem.getCss("color") === "red";

//getter, setter 合二为一
$elem.css("background", "green");
$elem.css("color") === "red";

1
2
3
4
5
6
7
8
9
10

var$elem=
jQuery("#foobar");

 
//setter

$elem.setCss("background","green");
//getter

$elem.getCss("color")===
"red";
 

//getter, setter 合二为一
$elem.css("background","green");

$elem.css("color")===
"red";

一致性

相关的接口保持一致的风格,一整套 API 如果传递一种熟悉和舒适的感觉,会大大减轻开发者对新工具的适应性。

命名这点事:既要短,又要自描述,最重要的是保持一致性

“There are only two hard problems in computer science: cache-invalidation and naming things.”

“在计算机科学界只有两件头疼的事:缓存失效和命名问题”

— Phil Karlton

选择一个你喜欢的措辞,然后持续使用。选择一种风格,然后保持这种风格。

处理参数

需要考虑大家如何使用你提供的方法,是否会重复调用?为何会重复调用?你的 API 如何帮助开发者减少重复的调用?

接收 map 映射参数,回调或者序列化的属性名,不仅让你的 API 更干净,而且使用起来更舒服、高效。

jQuery 的 css()  方法可以给
DOM 元素设置样式:

JavaScript

jQuery("#some-selector")
.css("background", "red")
.css("color", "white")
.css("font-weight", "bold")
.css("padding", 10);

1
2
3
4
5

jQuery("#some-selector")

  .css("background","red")
  .css("color","white")

  .css("font-weight","bold")
  .css("padding",10);

这个方法可以接受一个 JSON 对象:

JavaScript

jQuery("#some-selector").css({
"background" : "red",
"color" : "white",
"font-weight" : "bold",
"padding" : 10
});

// 通过传一个 map 映射绑定事件
jQuery("#some-selector").on({
"click" : myClickHandler,
"keyup" : myKeyupHandler,
"change" : myChangeHandler
});

// 为多个事件绑定同一个处理函数
jQuery("#some-selector").on("click keyup change", myEventHandler);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

jQuery("#some-selector").css({

  "background":
"red",
  "color":
"white",

  "font-weight":
"bold",
  "padding":
10

});
 

// 通过传一个 map 映射绑定事件
jQuery("#some-selector").on({

  "click":
myClickHandler,
  "keyup":
myKeyupHandler,

  "change":
myChangeHandler
});

 
// 为多个事件绑定同一个处理函数

jQuery("#some-selector").on("click
keyup change",
myEventHandler);

 处理类型

定义方法的时候,需要决定它可以接收什么样的参数。我们不清楚人们如何使用我们的代码,但可以更有远见,考虑支持哪些参数类型。

JavaScript

// 原来的代码
DateInterval.prototype.days = function(start, end) {
return Math.floor((end - start) / 86400000);
};

// 修改后的代码
DateInterval.prototype.days = function(start, end) {
if (!(start instanceof Date)) {
start = new Date(start);
}
if (!(end instanceof Date)) {
end = new Date(end);
}

return Math.floor((end.getTime() - start.getTime()) / 86400000);
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// 原来的代码

DateInterval.prototype.days=
function(start,end){
  returnMath.floor((end-
start)/
86400000);

};
 

// 修改后的代码
DateInterval.prototype.days=
function(start,end){

  if(!(startinstanceof
Date)){
    start=
newDate(start);

  }
  if(!(endinstanceof
Date)){

    end=
newDate(end);
  }

 
  returnMath.floor((end.getTime()-
start.getTime())/
86400000);

};

加了短短的 6 行代码,我们的方法强大到可以接收 Date 对象,数字的时间戳,甚至像Sat
Sep08
201215:34:35GMT+0200(CEST)  这样的字符串。

如果你需要确保传入的参数类型(字符串,数字,布尔),可以这样转换:

JavaScript

function castaway(some_string, some_integer, some_boolean) {
some_string += "";
some_integer += 0; // parseInt(some_integer, 10) 更安全些
some_boolean = !!some_boolean;
}

1
2
3
4
5

functioncastaway(some_string,some_integer,some_boolean){

  some_string+=
"";
  some_integer+=
0;// parseInt(some_integer, 10) 更安全些

  some_boolean=
!!some_boolean;
}

 处理 undefined

为了使你的 API 更健壮,需要鉴别是否真正的
undefined  值被传递进来,可以检查 arguments  对象:

JavaScript

function testUndefined(expecting, someArgument) {
if (someArgument === undefined) {
console.log("someArgument 是 undefined");
}
if (arguments.length > 1) {
console.log(" 然而它实际是传进来的 ");
}
}

testUndefined("foo");
// 结果: someArgument 是 undefined
testUndefined("foo", undefined);
// 结果: someArgument 是 undefined , 然而它实际是传进来的

1
2
3
4
5
6
7
8
9
10
11
12
13

functiontestUndefined(expecting,someArgument){

  if(someArgument===
undefined){
    console.log("someArgument
是 undefined");

  }
  if(arguments.length>
1){

    console.log(" 然而它实际是传进来的 ");
  }

}
 

testUndefined("foo");
// 结果: someArgument 是 undefined

testUndefined("foo",undefined);
// 结果:  someArgument 是 undefined , 然而它实际是传进来的

给参数命名

JavaScript

event.initMouseEvent(
"click", true, true, window,
123, 101, 202, 101, 202,
true, false, false, false,
1, null);

1
2
3
4
5

event.initMouseEvent(

  "click",true,true,window,
  123,101,202,101,202,

  true,false,false,false,
  1,null);

Event.initMouseEvent 这个方法简直丧心病狂,不看文档的话,谁能说出每个参数是什么意思?

给每个参数起个名字,赋个默认值,可好

JavaScript

event.initMouseEvent(
type="click",
canBubble=true,
cancelable=true,
view=window,
detail=123,
screenX=101,
screenY=202,
clientX=101,
clientY=202,
ctrlKey=true,
altKey=false,
shiftKey=false,
metaKey=false,
button=1,
relatedTarget=null);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

event.initMouseEvent(

  type="click",
  canBubble=true,

  cancelable=true,
  view=window,

  detail=123,
  screenX=101,

  screenY=202,
  clientX=101,

  clientY=202,
  ctrlKey=true,

  altKey=false,
  shiftKey=false,

  metaKey=false,
  button=1,

  relatedTarget=null);

ES6, 或者 Harmony 就有 默认参数值 和 rest
参数 了。

参数接收 JSON 对象

与其接收一堆参数,不如接收一个 JSON 对象:

JavaScript

function nightmare(accepts, async, beforeSend, cache, complete, /* 等 28 个参数 */) {
if (accepts === "text") {
// 准备接收纯文本
}
}

function dream(options) {
options = options || {};
if (options.accepts === "text") {
// 准备接收纯文本
}
}

1
2
3
4
5
6
7
8
9
10
11
12

functionnightmare(accepts,async,beforeSend,cache,complete,/*
等 28 个参数 */){

  if(accepts===
"text"){
    // 准备接收纯文本

  }
}

 
functiondream(options){

  options=
options||
{};
  if(options.accepts===
"text"){

    // 准备接收纯文本
  }

}

调用起来也更简单了:

JavaScript

nightmare("text", true, undefined, false, undefined, /* 等 28 个参数 */);

dream({
accepts: "text",
async: true,
cache: false
});

1
2
3
4
5
6
7

nightmare("text",true,undefined,false,undefined,/*
等 28 个参数 */);

 
dream({

  accepts:"text",
  async:true,

  cache:false
});

参数默认值

参数最好有默认值,通过 jQuery.extend() , _.extend() 和 Protoype 的 Object.extend,可以覆盖预设的默认值。

JavaScript

var default_options = {
accepts: "text",
async: true,
beforeSend: null,
cache: false,
complete: null,
// …
};

function dream(options) {
var o = jQuery.extend({}, default_options, options || {});
console.log(o.accepts);
}

dream({ async: false });
// prints: "text"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

vardefault_options
= {

  accepts:"text",
  async:true,

  beforeSend:null,
  cache:false,

  complete:null,
  // …

};
 

function dream(options){
  varo
=jQuery.extend({},default_options,options
||{});

  console.log(o.accepts);
}

 
dream({async:false
});

// prints: "text"

扩展性

回调(callbacks)

通过回调, API 用户可以覆盖你的某一部分代码。把一些需要自定义的功能开放成可配置的回调函数,允许 API 用户轻松覆盖你的默认代码。

API 接口一旦接收回调,确保在文档中加以说明,并提供代码示例。

事件(events)

事件接口最好见名知意,可以自由选择事件名字,避免与原生事件 重名。

处理错误

不是所有的错误都对开发者调试代码有用:

JavaScript

// jQuery 允许这么写
$(document.body).on('click', {});

// 点击时报错
// TypeError: ((p.event.special[l.origType] || {}).handle || l.handler).apply is not a function
// in jQuery.min.js on Line 3

1
2
3
4
5
6

// jQuery 允许这么写

$(document.body).on('click',{});
 

// 点击时报错
//   TypeError: ((p.event.special[l.origType] || {}).handle || l.handler).apply is not a function

//   in jQuery.min.js on Line 3

这样的错误调试起来很痛苦,不要浪费开发者的时间,直接告诉他们犯了什么错:

JavaScript

if (Object.prototype.toString.call(callback) !== '[object Function]') { // 看备注
throw new TypeError("callback is not a function!");
}

1
2
3

if(Object.prototype.toString.call(callback)!==
'[object Function]'){
// 看备注

  thrownew
TypeError("callback is not a function!");
}

 备注:typeofcallback
==="function"  在老的浏览器上会有问题,object 会当成个 function 。

可预测性

好的 API 具有可预测性,开发者可以根据例子推断它的用法。

Modernizr’s 特性检测 是个例子:

a) 它使用的属性名完全与 HTML5、CSS 概念和 API 相匹配

b) 每一个单独的检测一致地返回 true 或 false 值

JavaScript

// 所有这些属性都返回 'true' 或 'false'
Modernizr.geolocation
Modernizr.localstorage
Modernizr.webworkers
Modernizr.canvas
Modernizr.borderradius
Modernizr.boxshadow
Modernizr.flexbox

1
2
3
4
5
6
7
8

// 所有这些属性都返回 'true' 或 'false'

Modernizr.geolocation
Modernizr.localstorage

Modernizr.webworkers
Modernizr.canvas

Modernizr.borderradius
Modernizr.boxshadow

Modernizr.flexbox

依赖于开发者已熟悉的概念也可以达到可预测的目的。

jQuery’s 选择器语法 就是一个显著的例子,CSS1-CSS3 的选择器可直接用于它的 DOM 选择器引擎。

JavaScript

$("#grid") // Selects by ID
$("ul.nav > li") // All LIs for the UL with class "nav"
$("ul li:nth-child(2)") // Second item in each list

1
2
3

$("#grid")// Selects
by ID

$("ul.nav > li")// All LIs for the UL with class "nav"
$("ul li:nth-child(2)")//
Second item in each list

比例协调

好的 API 并不一定是小的 API,API 的体积大小要跟它的功能相称。

比如 Moment.js,著名的日期解析和格式化的库,可以称之为均衡,它的 API 既简洁又功能明确。

像 Moment.js 这样特定功能的库,确保 API 的专注和小巧非常重要。

编写 API 文档

软件开发最艰难的任务之一是写文档,实际上每个人都恨写文档,怨声载道的是没有一个好用的文档工具。

以下是一些文档自动生成工具:

YUIDoc (requires Node.js, npm)
JsDoc Toolkit (requires Node.js, npm)
Markdox (requires Node.js, npm)
Dox (requires Node.js, npm)
Docco (requires Node.js, Python, CoffeeScript)
JSDuck (reqires Ruby, gem)
JSDoc 3 (requires Java)
最重要的是:确保文档跟代码同步更新。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: