zoukankan      html  css  js  c++  java
  • 【JS】327- javascript 的 api 设计原则

    640?wx_fmt=png

    点击上方“前端自习课”关注,学习起来~


    前言


    本篇博文来自一次公司内部的前端分享,从多个方面讨论了在设计接口时遵循的原则,总共包含了七个大块。系卤煮自己总结的一些经验和教训。本篇博文同时也参考了其他一些文章,相关地址会在后面贴出来。很难做到详尽充实,如果有好的建议或者不对的地方,还望不吝赐教斧正。

    一、接口的流畅性

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

    1. 简单

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

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

    封装之后

    1function a(selector, color) {
    2  document.querySelector(selector).style.color = color
    3}
    4a('#a', 'red');
    

    从几十个字母长长的一行到简简单单的一个函数调用,体现了 api 设计原则之一:简单易用。

    2. 可阅读性

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

    1function letSomeElementChangeColor(selector, color) {
    2  document.querySelectorAll(selector, color).style.color = color; 
    3}
    

    letSomeElementChangeColor 相对于 a 来说被赋予了现实语言上的意义,任何人都不需要看说明也能知道它的功能。

    3. 减少记忆成本

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

    1function setColor(selector, color) {
    2 xxxxxxxxxxxx
    3}
    

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

    4. 可延伸

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

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

    如果我们需要实现像以上有强关联性的业务时,用我们之前的之前的方法是再次封装两个函数 setFontSize, setbackgroundColor; 然后执行它们

    • setColor('id', 'red');

    • setFontSiez('id', '12px');

    • setbackgroundColor('id', 'pink');

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

     1function getElement(selector) {
     2  this.style = document.querySelecotrAll(selector).style;
     3}
     4
     5getElement.prototype.color = function(color) {
     6  this.style.color = color;
     7  return this;
     8}
     9getElement.prototype.background = function(bg) {
    10  this.style.backgroundColor = bg;
    11  return this;
    12}
    13getElement.prototype.fontSize = function(size) {
    14  this.style.fontSize = size;
    15  return this;
    16}
    17
    18//调用
    19var el = new getElement('#id')
    20el.color('red').background('pink').fontSize('12px');
    

    简单、流畅、易读,它们看起来就像行云流水一样,即在代码性能上得到了提升优化,又在视觉上悦目。后面我们会在参数里面讲到如何继续优化。

    所以,大家都比较喜欢用 jquery 的 api,虽然一个 $ 符号并不代表任何现实意义,但简单的符号有利于我们的使用。它体现了以上的多种原则,简单,易读,易记,链式写法,多参处理。

    1// nightmare
    2document.getElementById('id').style.color = 'red';
    3document.getElementById('id').style.fontSize = '12px';
    4document.getElementById('id').style.backgourdColor = 'pink';
    5
    6// dream
    7$('id').css({color:'red', fontSize:'12px', backgroundColor:'pink'})
    

    二、一致性

    1. 接口的一致性

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

    Nightmare:

    1setColor,
    2letBackGround
    3changefontSize
    4makedisplay
    

    dream:

    1setColor;
    2setBackground;
    3setFontSize
    4set.........
    

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

    三、参数的处理

    1. 参数的类型

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

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

    2. 使用 json 方式传参

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

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

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

    1function fn(json) {
    2//为必须的参数设置默认值
    3   var default = extend({
    4    param: 'default',
    5    param1: 'default'
    6    ......
    7   },json)
    8}
    

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

    四、可扩展性

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

     1//需要同时改变某个元素的字体和背景
     2// Nightmare:
     3function set(selector, color) {
     4  document.querySelectroAll(selector).style.color = color;
     5  document.querySelectroAll(selector).style.backgroundColor = color;
     6}
     7
     8//无法扩展改函数,如果需要再次改变字体的大小的话,只能修改此函数,在函数后面填加改变字体大小的代码
     9
    10//Dream
    11function set(selector, color) {
    12  var el = document.querySelectroAll(selector);
    13  el.style.color = color;
    14  el.style.backgroundColor = color;
    15  return el;
    16}
    17
    18//需要设置字体、背景颜色和大小
    19function setAgain (selector, color, px) {
    20  var el = set(selector, color)
    21  el.style.fontSize = px;
    22  return el;
    23}
    

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

    2.this 的运用

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

    1function sayBonjour() {
    2  alert(this.a)
    3}
    4
    5obj.a = 1;
    6obj.say = sayBonjour;
    7obj.say();//1
    8//or
    9sayBonjour.call || apply(obj);//1
    

    五、对错误的处理

    1. 预见错误

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

    2. 抛出错误

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

    1function error (a) {
     2  if(typeof a !== 'string') {
     3    console.error('param a must be type of string')
     4  }
     5}
     6
     7function error() {
     8  try {
     9    // some code excucete here maybe throw wrong
    10  }catch(ex) {
    11    console.wran(ex);
    12  }
    13}
    

    六、可预见性

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

     1//nighware
     2function set(selector, color) {
     3  document.getElementById(selector).style.color = color;
     4}
     5
     6//dream
     7zepto.init = function(selector, context) {
     8  var dom
     9  // If nothing given, return an empty Zepto collection
    10  if (!selector) return zepto.Z()
    11  // Optimize for string selectors
    12  else if (typeof selector == 'string') {
    13    selector = selector.trim()
    14    // If it's a html fragment, create nodes from it
    15    // Note: In both Chrome 21 and Firefox 15, DOM error 12
    16    // is thrown if the fragment doesn't begin with <
    17    if (selector[0] == '<' && fragmentRE.test(selector))
    18      dom = zepto.fragment(selector, RegExp.$1, context), selector = null
    19    // If there's a context, create a collection on that context first, and select
    20    // nodes from there
    21    else if (context !== undefined) return $(context).find(selector)
    22    // If it's a CSS selector, use it to select nodes.
    23    else dom = zepto.qsa(document, selector)
    24  }
    25  // If a function is given, call it when the DOM is ready
    26  else if (isFunction(selector)) return $(document).ready(selector)
    27  // If a Zepto collection is given, just return it
    28  else if (zepto.isZ(selector)) return selector
    29  else {
    30    // normalize array if an array of nodes is given
    31    if (isArray(selector)) dom = compact(selector)
    32    // Wrap DOM nodes.
    33    else if (isObject(selector))
    34      dom = [selector], selector = null
    35    // If it's a html fragment, create nodes from it
    36    else if (fragmentRE.test(selector))
    37      dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
    38    // If there's a context, create a collection on that context first, and select
    39    // nodes from there
    40    else if (context !== undefined) return $(context).find(selector)
    41    // And last but no least, if it's a CSS selector, use it to select nodes.
    42    else dom = zepto.qsa(document, selector)
    43  }
    44  // create a new Zepto collection from the nodes found
    45  return zepto.Z(dom, selector)
    46}
    

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

    七、注释和文档的可读性

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

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

    最后

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

    640?wx_fmt=png

    卤煮在此也推荐几个在线编辑的网站。诸君可自行前往练习使用。

    https://www.zybuluo.com/mdeditor

    http://mahua.jser.me/

    原文作者:卖烧烤夫斯基,

    地址 https://www.cnblogs.com/constantince/p/5580003.html

    640?wx_fmt=png

    如果您觉得本文不错,

    请点击文章底部广告,支持一下我啦!


    原创系列推荐

    1. JavaScript 重温系列(22篇全)

    2. ECMAScript 重温系列(10篇全)

    3. JavaScript设计模式 重温系列(9篇全)

    4. 正则 / 框架 / 算法等 重温系列(16篇全)

    5. Webpack4 入门手册(共 18 章)(上)

    6. Webpack4 入门手册(共 18 章)(下)

    7. 59篇原创系列汇总

    640?wx_fmt=png

    640?wx_fmt=png

    点这,与大家一起分享本文吧~

    个人博客:http://www.pingan8787.com 微信公众号【前端自习课】和千万网友一起,每日清晨,享受一篇前端优秀文章。 目前已连续推送文章 600+ 天,愿每个人的初心都能一直坚持下去!
  • 相关阅读:
    软件杯_视频全量目标分析和建模_初步分析
    《一线架构师实践指南》第三部分阅读笔记
    eclipse配置Struts2至Tomcat8.5
    《一线架构师实践指南》阅读笔记02
    Java中对list集合使用HashSet去重不成功
    学习02
    《一线架构师实践指南》阅读笔记01
    通过DOS命令将txt文件导入mysql数据库
    zookeeper集群环境搭建详细图文教程
    SSO之CAS单点登录详细搭建教程
  • 原文地址:https://www.cnblogs.com/pingan8787/p/11838116.html
Copyright © 2011-2022 走看看