JavaScript API 设计核心原则:流畅性、一致性与可扩展性

作者:函数茶馆

时间:2026-10-30

浏览:227

本文深入解析 JavaScript API 设计的七大核心原则。涵盖如何通过链式调用提升流畅性,保持命名与风格的一致性以降低记忆成本,利用对象传参优化参数处理。同时探讨接口的可扩展性、错误预见与处理机制,以及文档可读性的重要性,旨在帮助开发者构建易用、健壮且易于维护的 JavaScript 接口。

文章目录

接口的流畅性

一、接口的流畅性

优秀的接口应当具备流畅且易懂的特性,这主要体现在以下几个维度:

1. 简洁性

以操作元素 CSS 属性为例,原生 JavaScript 的实现方式如下:


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

经过封装后的实现:


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

从冗长复杂的数十个字符代码简化为单一的函数调用,这直观地体现了 API 设计的简洁与易用原则。

2. 可读性

例如 `a('#a', 'red')` 是一个简洁的函数,用于改变元素颜色。然而,对于初次使用者而言,`a` 函数的含义不明,缺乏上下文说明。开发者往往倾向于简化代码,但这可能增加记忆负担。如果一套框架包含数十甚至上百个 API,这种映射成本会让开发者感到困惑。因此,接口应具备明确的语义。改进后的 `a` 函数如下:


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

`letSomeElementChangeColor` 相比 `a` 赋予了明确的语义,任何人都能理解其功能。

3. 降低记忆成本

虽然 `letSomeElementChangeColor` 减少了映射成本,但其名称过长增加了记忆负担。即便是经验丰富的开发者也不喜欢冗长的命名。原生 DOM API 如 `document.getElementsByClassName`、`document.getElementsByName` 和 `document.querySelectorAll` 也存在类似问题:虽然语义清晰,但牺牲了简洁性。因此,我们需要在保持语义的前提下再次优化该函数:


function setColor(selector, color) {
xxxxxxxxxxxx
} 

在语义变化不大的前提下,缩短函数名称,使其更易于阅读、记忆和使用。

4. 可扩展性(链式调用)

所谓可扩展性,是指函数的执行如同流水般按书写顺序形成链式调用:


document.getElementById('id').style.color = 'red';
document.getElementById('id').style.fontSize = 'px';
document.getElementById('id').style.backgourdColor = 'pink'; 

若使用之前的方法,需分别封装 `setFontSize` 和 `setBackgroundColor` 函数,然后依次执行 `setColor('id', 'red')`、`setFontSize('id', '12px')` 和 `setBackgroundColor('id', 'pink')`。这种做法存在诸多缺陷:每次都需要重新获取元素 ID,影响性能;每次新增功能都需添加新方法;每次调用都需显式执行。因此,我们将其改写为支持链式调用的函数:首先将获取元素的方法封装为对象,然后在对象的每个方法中返回该对象本身:


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('px'); 

简洁、流畅且易读。后续章节将讨论如何通过参数进一步优化。这也是 jQuery API 广受欢迎的原因,尽管 `$` 符号本身无具体语义,但其简洁性便于使用。它体现了上述多种原则:简洁、易读、易记、支持链式写法及多参数处理。

传统写法(Nightware):


document.getElementById('id').style.color = 'red';
document.getElementById('id').style.fontSize = 'px';
document.getElementById('id').style.backgourdColor = 'pink'; 

理想写法(Dream):


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

接口的一致性

二、一致性

1. 接口风格的一致性

相关接口应保持统一的风格。一套 API 若能传递熟悉和舒适的感觉,将大大降低开发者对新工具的适应成本。关于命名:既要简短,又要具备自描述性,最重要的是保持一致性。“在计算机科学界只有两件头疼的事:缓存失效和命名问题”——Phil Karlton。选择一种措辞风格,并持续使用;选择一种命名规范,并保持下去。

传统写法(Nightware):

setColor, letBackGround, changefontSize, makedisplay

理想写法(Dream):

setColor, setBackground, setFontSize, set...

尽量保持代码风格和命名风格的一致性,使阅读你的代码如同阅读同一个人撰写的文章一样顺畅。

参数的处理

三、参数的处理

1. 参数类型检查

对参数类型进行判断,为程序的稳定性提供保障。


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

2. 使用 JSON 对象传参

使用 JSON 对象传递参数具有诸多优势:可以为参数命名、忽略参数顺序、设置默认值等。以下是一种糟糕的参数传递方式:

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

你必须严格按照顺序传入每个参数,否则方法执行将偏离预期。正确的方法如下所示:


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

即使不传入任何参数,该函数也能按预期运行。因为在声明时,你可以根据具体业务逻辑为参数设定默认值。

接口的可扩展性

四、可扩展性

软件设计最重要的原则之一:绝不修改现有接口,而是对其进行扩展!可扩展性还要求接口职责单一,多职责的接口难以扩展。举例说明:


//需要同时改变某个元素的字体和背景 
// Nightware:
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;
} 

上述代码仅实现了简单的颜色添加。当业务复杂且代码非你编写时,你必须阅读原有代码才能进行修改,这显然违背了开放-封闭原则。修改后的函数返回元素对象,使得后续需要再次修改时,可以获取返回值继续处理。

2. 灵活运用 this、call 和 apply

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


function sayBonjour() {
alert(this.a)
}
obj.a = ;
obj.say = sayBonjour;
obj.say();//
//or
sayBonjour.call||apply(obj);// 

错误的处理与预见性

五、对错误的处理

1. 预见错误

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

2. 抛出错误

大多数开发者不希望出错后还需自行查找对应代码。最佳方式是在 `console` 中直接输出,告知用户发生了何事。我们可以利用浏览器的输出 API:`console.log`、`console.warn` 或 `console.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);
}
} 

六、可预见性

可预见性为程序接口提供了健壮性。为保证代码顺利执行,必须考虑非正常预期的情况。我们对比不可预见和可预见的代码,以之前的 `setColor` 为例:


//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 and Firefox , DOM error 
// is thrown if the fragment doesn't begin with <
if (selector[] == '<' && fragmentRE.test(selector))
dom = zepto.fragment(selector, RegExp.$, 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.$, 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 在此处使用了大量非空判断,导致代码冗长,不利于阅读。总之,可预见性要求开发者多编写针对实际参数的检测逻辑,将外部检测转为内部检测,使使用者使用更舒心、放心。

文档与注释的可读性

七、注释和文档的可读性

最好的接口是不需要文档也能使用,但随着接口数量增加和业务复杂化,接口使用会变得困难。因此,接口文档和注释需要认真编写。注释应遵循简单扼要的原则,既为多年后的自己,也为后来者提供参考:


//注释接口,为了演示PPT用
function commentary() {
//如果你定义一个没有字面意义的变量时,最好为它写上注释:a:没用的变量,可以删除
var a;
//在关键和有歧义的地方写上注释,犹如画龙点睛:路由到hash界面后将所有的数据清空结束函数
return go.Navigate('hash', function(){
data.clear();
});
} 

最后

推荐使用 Markdown 语法编写 API 文档,这是 GitHub 官方推荐的文档编写语法。其特点包括简单、快速、支持代码高亮。具体效果如下图所示:

接口的流畅性

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

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章