javascript API设计原则

优秀的项目离不开优秀的代码,而优秀的代码离不开优秀的接口设计。在javascript中,若没有管理好接口,则代码必然会变得一团糟。下面总结一下javascript API设计原则。

接口的流畅性

优秀的接口一定是让人通熟易懂的,这主要体现在一下几个方面。

简单

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

document.querySelectorAll('#id').style.color = 'red';

封装之后

function a(selector, color) {
document.querySelectorAll(selector)[0].style.color = color
}
a('#a', 'red');

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

可阅读性

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

function letSomeElementChangeColor(selector, color) {
document.querySelectorAll(selector, color);
}

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

减少记忆成本

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

function setColor(selector, color) {
    xxx
}

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

可延伸

所谓延伸就是指函数的链式调用:

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 = color;
    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');

例如最典型的js框架jquery,它也采用了链式调用的写法,它还体现了以上的多种原则,简单,易读,易记,多参处理。这也是大家都喜欢使用它的其中一个原因。

$('id').css({color:'red', fontSize:'12px', backgroundColor:'pink'})

一致性

接口的一致性

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

命名既要短,又要自描述,最重要的是保持一致性。
命名就像是写字,api就像是文章,如何让一篇文章读起来顺心,除了字体好看以外,还要保持一个字体风格,让它看起来像是一个人写的一样。

Nightmare

setColor,
letBackGround
changefontSize
makedisplay

Dream

setColor;
setBackground;
setFontSize
set.........

选择一个喜欢的命名规范,然后持续使用。

参数的处理

参数的类型

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

//我们规定,color接受字符串类型
function setColor(color) {
if(typeof color !== 'string') return;
    // dosomething
}

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

//原来的代码
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);
};

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

使用json方式传参

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

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

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

function fn(json) {
    //为必须的参数设置默认值
    var default = Object.assign({
        param: 'default',
        param1: 'default'
        ......
    },json)
}

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

处理undefined

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

可扩展性

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

//需要同时改变某个元素的字体和背景
// 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是返回了元素对象,使得下次需要改变时再次得到返回值做处理。

通过配置可以使用回调来实现可扩展性。您可以使用回调来允许API用户覆盖代码的某些部分。当您觉得特定任务的处理方式可能与您的默认代码不同时,请将该代码重构为可配置的回调函数,以便API用户可以轻松地覆盖该回调函数

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

function sayBonjour() {
    alert(this.a)
}

obj.a = 1;
obj.say = sayBonjour;
obj.say();//1
//or
sayBonjour.call||apply(obj);

对错误的处理

预见错误

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

抛出错误

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

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);
    }
}

可预见性

可预见性味程序接口提供健壮性,为保证你的代码顺利执行,必须为它考虑到非正常预期的情况。

可预见性真正需要你做的是多写一些对位置实物的参数。把外部的检测改为内部检测。使得使用的人用起来舒心放心开心。

相称性

它是用来衡量一个作品中元素的大小和数量的。与其说一个好的API是一个小的api,相称性是相对于用途的大小。一个相称的API它的API表面和它的能力范围相匹配。
例如, Moment.js,一个流行的日期转换和格式化类库,可以把它视为具有相称性,因为它的API表层是紧凑的,它和类库的目的明确的匹配。

强调性

在艺术作品中,强调是利用对比来使作品中某一方面脱颖而出形成一个焦点。在许多API中,焦点可能是一个通道或者类库主要方法的锚点。另外一个关于强调性的例子可以参考“链接”方式或者fluent API,它通过增加强调性效果突出了类库中心对象。

注释和文档的可读性