本文深入解析 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 设计原则的全部叙述,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对本站网站的支持!