Cómo diseñar mejores API de JavaScript

 

 

 

  • Implemente rápidamente. Implementar inteligentemente
  • Accesibilidad para diseñadores, con Stéphanie Walter

  • Índice
    1. Tabla de contenido
  • Interfaz fluida
    1. Encadenamiento de métodos
    2. Separación de consultas de comando
    3. Hablar con fluidez
  • Consistencia
    1. Nombrar cosas
  • Manejo de argumentos
    1. Tipos de manejo
    2. Tratar undefinedcomo un valor esperado
    3. Argumentos nombrados
    4. Cómo lo hacen otros
    5. Mapas de argumentos
    6. Valores de argumento predeterminados
    7. Buenas intenciones, también conocidas como "trampas"
  • Extensibilidad
    1. Devoluciones de llamada
    2. Eventos
  • Hooks
  • En esta publicación, Rodney Rehm se centra en cómo hacer que su código sea accesible para otros desarrolladores. Descubra los aspectos más importantes que deberá considerar antes y mientras escribe sus propias utilidades y bibliotecas.

     

    En algún momento u otro, te encontrarás escribiendo código JavaScript que excede el par de líneas de un complemento jQuery. Tu código hará muchas cosas; (idealmente) será utilizado por muchas personas que abordarán su código de manera diferente. Tienen diferentes necesidades, conocimientos y expectativas.

    Este artículo cubre los aspectos más importantes que deberá considerar antes y mientras escribe sus propias utilidades y bibliotecas. Nos centraremos en cómo hacer que su código sea accesible para otros desarrolladores. Se abordarán un par de temas sobre jQuery a modo de demostración, pero este artículo no trata sobre jQuery ni sobre cómo escribir complementos para él.

    Peter Drucker dijo una vez: "La computadora es un imbécil". ¡No escribas código para idiotas, escribe para humanos! Profundicemos en el diseño de las API que a los desarrolladores les encantará usar .

    Tabla de contenido

    • Interfaz fluida
    • Consistencia
    • Manejo de argumentos
    • Extensibilidad
    • Manos
    • Generando accesorios
    • El terror de referencia
    • El problema de la continuación
    • Manejo de errores
    • Volverse asíncrono
    • Depuración de interfaces fluidas
    • Documentar las API
    • Conclusión

    Interfaz fluida

    A menudo se hace referencia a Fluent Interface como encadenamiento de métodos (aunque eso es sólo la mitad de la verdad). Para los principiantes, parece el estilo jQuery . Si bien creo que el estilo API fue un ingrediente clave en el éxito de jQuery, no fue inventado por ellos; los créditos parecen ser para Martin Fowler, quien acuñó el término en 2005, aproximadamente un año antes de que se lanzara jQuery. Sin embargo, Fowler solo le dio un nombre: Fluent Interfaces existe desde hace mucho más tiempo.

    Aparte de importantes simplificaciones, jQuery ofreció igualar diferencias severas entre navegadores. Siempre ha sido la Fluent Interface lo que más me ha gustado de esta biblioteca de gran éxito. He llegado a disfrutar tanto de este estilo de API en particular que de inmediato se hizo evidente que también quería este estilo para URI.js. Mientras ajustaba la API URI.js, revisaba constantemente el código fuente de jQuery para encontrar pequeños trucos que hicieran que mi implementación fuera lo más simple posible. Descubrí que no estaba solo en este esfuerzo. Lea Verou creó chainvas , una herramienta para envolver las API getter/setter habituales en interfaces fluidas y atractivas. El guión bajo _.chain()hace algo similar. De hecho, la mayoría de las bibliotecas de nueva generación admiten el encadenamiento de métodos.

    Encadenamiento de métodos

    La idea general del encadenamiento de métodos es lograr un código que sea legible con la mayor fluidez posible y, por tanto, más rápido de entender. Con Method Chaining podemos formar código en secuencias similares a oraciones, lo que hace que el código sea fácil de leer y, al mismo tiempo, reduce el ruido en el proceso:

     

    // regular API calls to change some colors and add an event-listenervar elem = document.getElementById("foobar");elem.style.background = "red";elem.style.color = "green";elem.addEventListener(’click’, function(event) { alert("hello world!");}, true);// (imaginary) method chaining APIDOMHelper.getElementById(’foobar’) .setStyle("background", "red") .setStyle("color", "green") .addEvent("click", function(event) { alert("hello world"); });

    Observe que no tuvimos que asignar la referencia del elemento a una variable y repetirlo una y otra vez.

    Separación de consultas de comando

    La separación de comandos y consultas (CQS) es un concepto heredado de la programación imperativa. Las funciones que cambian el estado (valores internos) de un objeto se denominan comandos , las funciones que recuperan valores se denominan consultas . En principio, las consultas devuelven datos, los comandos cambian el estado, pero tampoco ambas cosas. Este concepto es uno de los fundamentos de los métodos getter y setter cotidianos que vemos en la mayoría de las bibliotecas actuales. Dado que Fluent Interfaces devuelve una autorreferencia para encadenar llamadas a métodos, ya estamos rompiendo la regla para los comandos , ya que se supone que no deben devolver nada. Además de este rasgo (fácil de ignorar), rompemos (deliberadamente) con este concepto para mantener las API lo más simples posible. Un excelente ejemplo de esta práctica es el método de jQuery css():

    var $elem = jQuery("#foobar");// CQS - command$elem.setCss("background", "green");// CQS - query$elem.getCss("color") === "red";// non-CQS - command$elem.css("background", "green");// non-CQS - query$elem.css("color") === "red";

    Como puede ver, los métodos getter y setter se fusionan en un solo método. La acción a realizar (es decir, consulta o comando ) se decide por la cantidad de argumentos pasados ​​a la función, en lugar de por qué función se llamó. Esto nos permite exponer menos métodos y, a su vez, escribir menos para lograr el mismo objetivo.

    No es necesario comprimir captadores y definidores en un solo método para crear una interfaz fluida; todo se reduce a las preferencias personales. Su documentación debe ser muy clara con el enfoque que ha decidido. Me ocuparé de la documentación de las API más adelante, pero en este punto me gustaría señalar que las firmas de funciones múltiples pueden ser más difíciles de documentar.

    Hablar con fluidez

    Si bien el encadenamiento de métodos ya hace la mayor parte del trabajo para lograr fluidez, aún no estás libre de responsabilidad. Para ilustrar el siguiente paso de fluidez , pretendemos escribir una pequeña biblioteca que maneje intervalos de fechas. Un intervalo comienza con una fecha y termina con una fecha. Una fecha no está necesariamente relacionada con un intervalo. Entonces se nos ocurrió este constructor simple:

    // create new date intervalvar interval = new DateInterval(startDate, endDate);// get the calculated number of days the interval spansvar days = interval.days();

    Si bien esto parece correcto a primera vista, este ejemplo muestra lo que está mal:

    var startDate = new Date(2012, 0, 1);var endDate = new Date(2012, 11, 31)var interval = new DateInterval(startDate, endDate);var days = interval.days(); // 365

    Estamos escribiendo un montón de variables y cosas que probablemente no necesitaremos. Una buena solución al problema sería agregar una función al objeto Fecha para devolver un intervalo:

     

    // DateInterval creator for fluent invocationDate.prototype.until = function(end) { // if we weren’t given a date, make one if (!(end instanceof Date)) { // create date from given arguments, // proxy the constructor to allow for any parameters // the Date constructor would’ve taken natively end = Date.apply(null, Array.prototype.slice.call(arguments, 0) ); } return new DateInterval(this, end);};

    Ahora podemos crearlo DateIntervalde forma fluida y fácil de escribir y leer:

    var startDate = new Date(2012, 0, 1);var interval = startDate.until(2012, 11, 31);var days = interval.days(); // 365// condensed fluent interface call:var days = (new Date(2012, 0, 1)) .until(2012, 11, 31) // returns DateInterval instance .days(); // 365

    Como puede ver en este último ejemplo, hay menos variables que declarar, menos código que escribir y la operación casi se lee como una oración en inglés. Con este ejemplo deberías haberte dado cuenta de que el encadenamiento de métodos es sólo una parte de una interfaz fluida y, como tal, los términos no son sinónimos. Para brindar fluidez, debe pensar en los flujos de código: de dónde viene y hacia dónde se dirige.

    Este ejemplo ilustró la fluidez al extender un objeto nativo con una función personalizada. Esto es tanto una religión como usar punto y coma o no. En Ampliación de objetos nativos integrados. ¿Mal o no? kangax explica los altibajos de este enfoque. Si bien todos tienen sus opiniones al respecto, en lo único en lo que todos están de acuerdo es en mantener la coherencia. Además, incluso los seguidores de "No contaminar objetos nativos con funciones personalizadas" probablemente dejarían pasar el siguiente truco, todavía algo fluido:

    String.prototype.foo = function() { return new Foo(this);}"I’m a native object".foo() .iAmACustomFunction();

    Con este enfoque, sus funciones todavía están dentro de su espacio de nombres, pero son accesibles a través de otro objeto. Asegúrese de que su equivalente .foo()sea un término no genérico, algo que es muy poco probable que colisione con otras API. Asegúrese de proporcionar .valueOf()los .toString()métodos adecuados para volver a convertir a los tipos primitivos originales.

    Consistencia

    Jake Archibald una vez tuvo un deslizamiento al definir la coherencia . Simplemente decía No PHP . Hacer. No. Alguna vez. Búscate nombrando funciones como str_repeat() , strpos() , substr() . Además, nunca cambies las posiciones de los argumentos. Si lo declaraste find_in_array(haystack, needle)en algún momento, la presentación findInString(needle, haystack)invitará a una multitud enojada de zombis a levantarse de sus tumbas para cazarte y obligarte a escribir Delphi por el resto de tu vida.

    Nombrar cosas

    "Sólo hay dos problemas difíciles en informática: la invalidación de la caché y el nombramiento de cosas". —Phil Karlton

    He asistido a numerosas charlas y sesiones tratando de enseñarme los detalles más finos de nombrar cosas. No he dejado ninguno de ellos sin haber escuchado la cita anterior, ni haber aprendido a nombrar las cosas. Mi consejo se reduce a que sea breve pero descriptivo y siga sus instintos . Pero, sobre todo, mantén la coherencia.

     

    El DateIntervalejemplo anterior introdujo un método llamado until(). Podríamos haber nombrado esa función interval(). Este último habría estado más cerca del valor devuelto, mientras que el primero es más legible humanamente . Encuentra una línea de redacción que te guste y apégate a ella. La coherencia es el 90% de lo que importa. Elija un estilo y manténgalo, incluso si en algún momento en el futuro no le gusta.

    Manejo de argumentos

    La forma en que sus métodos aceptan datos es más importante que hacerlos encadenables. Si bien el encadenamiento de métodos es algo bastante genérico que puedes hacer que tu código haga fácilmente, el manejo de argumentos no lo es. Deberá pensar en cómo es más probable que se utilicen los métodos que proporcione. ¿Es probable que el código que utiliza su API repita ciertas llamadas a funciones? ¿Por qué se repiten estos llamados? ¿Cómo podría su API ayudar al desarrollador a reducir el ruido de las llamadas repetidas a métodos?

    El método de jQuery css()puede establecer estilos en un elemento DOM:

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

    ¡Hay un patrón aquí! Cada invocación de método nombra un estilo y especifica un valor para él. Esto requiere que el método acepte un mapa:

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

    El método de jQuery on()puede registrar controladores de eventos. Acepta css()un mapa de eventos, pero va aún más lejos al permitir que un único controlador se registre para múltiples eventos:

    // binding events by passing a mapjQuery("#some-selector").on({ "click" : myClickHandler, "keyup" : myKeyupHandler, "change" : myChangeHandler});// binding a handler to multiple events:jQuery("#some-selector").on("click keyup change", myEventHandler);

    Puede ofrecer las firmas de funciones anteriores utilizando el siguiente patrón de método :

    DateInterval.prototype.values = function(name, value) { var map; if (jQuery.isPlainObject(name)) { // setting a map map = name; } else if (value !== undefined) { // setting a value (on possibly multiple names), convert to map keys = name.split(" "); map = {}; for (var i = 0, length = keys.length; i length; i++) { map[keys[i]] = value; } } else if (name === undefined) { // getting all values return this.values; } else { // getting specific value return this.values[name]; } for (var key in map) { this.values[name] = map[key]; } return this;};

    Si está trabajando con colecciones, piense en lo que puede hacer para reducir la cantidad de bucles que probablemente tendría que realizar un usuario de API. Digamos que tenemos varios inputelementos para los que queremos establecer el valor predeterminado:

    input type="text" value="" data-default="foo"input type="text" value="" data-default="bar"input type="text" value="" data-default="baz"

    Probablemente haríamos esto con un bucle: SEO y posicionamiento SEO

     

    jQuery("input").each(function() { var $this = jQuery(this); $this.val($this.data("default"));});

    ¿Qué pasaría si pudiéramos omitir ese método con una simple devolución de llamada que se aplique a cada uno de los inputmiembros de la colección? Los desarrolladores de jQuery han pensado en eso y nos permiten escribir menos™:

    jQuery("input").val(function() { return jQuery(this).data("default");});

    Son las pequeñas cosas como aceptar mapas, devoluciones de llamadas o nombres de atributos serializados, las que hacen que el uso de su API no solo sea más limpio, sino también más cómodo y eficiente. Obviamente, no todos los métodos de sus API se beneficiarán de este patrón de método; depende de usted decidir dónde todo esto tiene sentido y dónde es solo una pérdida de tiempo. Trate de ser lo más coherente posible en esto. Reduzca la necesidad de código repetitivo con los trucos que se muestran arriba y la gente lo invitará a tomar una copa.

    Tipos de manejo

    Siempre que define una función que aceptará argumentos, usted decide qué datos acepta esa función. Una función para calcular el número de días entre dos fechas podría verse así:

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

    Como puede ver, la función espera una entrada numérica (una marca de tiempo de milisegundos, para ser exactos). Si bien la función hace lo que pretendíamos, no es muy versátil. ¿Qué pasa si trabajamos con Dateobjetos o una representación de cadena de una fecha? ¿Se supone que el usuario de esta función debe transmitir datos todo el tiempo? ¡No! Simplemente verificar la entrada y transmitirla a lo que necesitemos debe hacerse en un lugar central, no abarrotado en todo el código usando nuestra API:

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

    Al agregar estas seis líneas, le hemos dado a la función el poder de aceptar un objeto Fecha, una marca de tiempo numérica o incluso una representación de cadena como Sat Sep 08 2012 15:34:35 GMT+0200 (CEST). No sabemos cómo y para qué la gente va a utilizar nuestro código, pero con un poco de previsión, podemos asegurarnos de que la integración de nuestro código sea un poco complicada.

    El desarrollador experimentado puede detectar otro problema en el código de ejemplo. Suponemos que startviene antes end. Si el usuario de la API accidentalmente intercambiara las fechas, se le daría un valor negativo para la cantidad de días entre starty end. Detente y piensa detenidamente en estas situaciones. Si has llegado a la conclusión de que un valor negativo no tiene sentido, corrígelo:

    DateInterval.prototype.days = function(start, end) { if (!(start instanceof Date)) { start = new Date(start); } if (!(end instanceof Date)) { end = new Date(end); } return Math.abs(Math.floor((end.getTime() - start.getTime()) / 86400000));};

    JavaScript permite la conversión de tipos de varias maneras. Si se trata de primitivos (cadena, número, booleano), puede resultar tan simple (como en "corto") como:

     

    function castaway(some_string, some_integer, some_boolean) { some_string += ""; some_integer += 0; // parseInt(some_integer, 10) is the safer bet some_boolean = !!some_boolean;}

    No estoy recomendando que hagas esto en todas partes y en todo momento. Pero estas líneas de apariencia inocente pueden ahorrarle tiempo y algo de sufrimiento al integrar su código.

    Tratar undefinedcomo un valor esperado

    Llegará un momento en el que undefinedsu API realmente espera que se le proporcione un valor para establecer un atributo. Esto podría suceder para "desarmar" un atributo o simplemente para manejar con elegancia entradas incorrectas, lo que hace que su API sea más sólida. Para identificar si undefinedsu método realmente pasó el valor , puede verificar el argumentsobjeto:

    function testUndefined(expecting, someArgument) { if (someArgument === undefined) { console.log("someArgument was undefined"); } if (arguments.length 1) { console.log("but was actually passed in"); }}testUndefined("foo");// prints: someArgument was undefinedtestUndefined("foo", undefined);// prints: someArgument was undefined, but was actually passed in

    Argumentos nombrados

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

    La firma de función de Event.initMouseEvent es una pesadilla hecha realidad. No hay posibilidad de que ningún desarrollador recuerde lo que 1significa ese (penúltimo parámetro) sin buscarlo en la documentación. No importa qué tan buena sea tu documentación, ¡haz lo que puedas para que la gente no tenga que buscar cosas!

    Cómo lo hacen otros

    Mirando más allá de nuestro querido lenguaje, encontramos que Python conoce un concepto llamado argumentos con nombre . Le permite declarar una función que proporciona valores predeterminados para los argumentos, lo que permite que los nombres de sus atributos se indiquen en el contexto de llamada:

    function namesAreAwesome(foo=1, bar=2) { console.log(foo, bar);}namesAreAwesome();// prints: 1, 2namesAreAwesome(3, 4);// prints: 3, 4namesAreAwesome(foo=5, bar=6);// prints: 5, 6namesAreAwesome(bar=6);// prints: 1, 6

    Dado este esquema, initMouseEvent() podría haber parecido una llamada de función que se explica por sí misma:

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

    En JavaScript esto no es posible hoy en día. Si bien “la próxima versión de JavaScript” (frecuentemente llamada ES.next, ES6 o Harmony) tendrá valores de parámetros predeterminados y parámetros restantes , todavía no hay señales de parámetros con nombre.

    Mapas de argumentos

    Al no ser JavaScript Python (y ES.next estar a años luz de distancia), nos quedan menos opciones para superar el obstáculo de los "bosques de argumentos". jQuery (y casi todas las demás API decentes que existen) optaron por trabajar con el concepto de "objetos de opción". La firma de jQuery.ajax() proporciona un ejemplo bastante bueno. En lugar de aceptar numerosos argumentos, sólo aceptamos un objeto:

     

    function nightmare(accepts, async, beforeSend, cache, complete, /* and 28 more */) { if (accepts === "text") { // prepare for receiving plain text }}function dream(options) { options = options || {}; if (options.accepts === "text") { // prepare for receiving plain text }}

    Esto no solo evita firmas de funciones increíblemente largas, sino que también hace que llamar a la función sea más descriptivo:

    nightmare("text", true, undefined, false, undefined, /* and 28 more */);dream({ accepts: "text", async: true, cache: false});

    Además, no tenemos que tocar la firma de la función (agregando un nuevo argumento) si introducimos una nueva característica en una versión posterior.

    Valores de argumento predeterminados

    jQuery.extend() , _.extend() y Object.extend de Protoype son funciones que te permiten fusionar objetos, permitiéndote agregar tu propio objeto de opciones preestablecidas a la mezcla:

    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);}// make defaults publicdream.default_options = default_options;dream({ async: false });// prints: "text"

    Obtendrá puntos de bonificación por hacer que los valores predeterminados sean accesibles públicamente. Con esto, cualquiera puede cambiar acceptsa “json” en un lugar central, y así evitar especificar esa opción una y otra vez. Tenga en cuenta que el ejemplo siempre se agregará || {}a la lectura inicial del objeto de opción. Esto le permite llamar a la función sin dar un argumento.

    Buenas intenciones, también conocidas como "trampas"

    Ahora que sabes cómo ser verdaderamente flexible al aceptar argumentos, debemos volver a un viejo dicho:

    "¡Con un gran poder viene una gran responsabilidad!"

    - Voltaire

    Como ocurre con la mayoría de los lenguajes de tipo débil, JavaScript realiza conversión automática cuando es necesario. Un ejemplo sencillo es probar la veracidad:

    var foo = 1;var bar = true;if (foo) { // yep, this will execute}if (bar) { // yep, this will execute}

    Estamos bastante acostumbrados a este casting automático. Estamos tan acostumbrados que olvidamos que aunque algo sea veraz, puede que no sea la verdad booleana. Algunas API son tan flexibles que resultan demasiado inteligentes para su propio bien. Eche un vistazo a las firmas de jQuery.toggle() :

    .toggle( /* int */ [duration] [, /* function */ callback] ).toggle( /* int */ [duration] [, /* string */ easing] [, /* function */ callback] ).toggle( /* bool */ showOrHide )

    Nos llevará algún tiempo descifrar por qué se comportan de manera completamente diferente:

    var foo = 1;var bar = true;var $hello = jQuery(".hello");var $world = jQuery(".world");$hello.toggle(foo);$world.toggle(bar);

    Esperábamos utilizar la firma enshowOrHide ambos casos. Pero lo que realmente pasó es $hellohacer un cambio con durationun milisegundo. Esto no es un error en jQuery, es un caso simple de expectativa no cumplida . Incluso si eres un desarrollador experimentado de jQuery, te toparás con esto de vez en cuando.

     

    Eres libre de agregar tanta comodidad/azúcar como quieras, pero no sacrifiques una API limpia y (en su mayoría) robusta en el camino. Si se encuentra proporcionando algo como esto, piense en proporcionar un método separado como .toggleIf(bool)este. Cualquiera que sea la elección que hagas, ¡mantén tu API consistente!

    Extensibilidad

    Con los objetos de opción, hemos cubierto el tema de la configuración extensible. Hablemos de permitir que los usuarios de API extiendan el núcleo y la propia API. Este es un tema importante, ya que permite que su código se centre en las cosas importantes, mientras que los usuarios de API implementan ellos mismos los casos extremos. Las buenas API son API concisas. Tener un montón de opciones de configuración está bien, pero tener un par de docenas de ellas hace que tu API se sienta inflada y opaca. Concéntrese en los casos de uso principal, haga solo las cosas que la mayoría de los usuarios de su API necesitarán. Todo lo demás debería dejarse en manos de ellos. Para permitir que los usuarios de API extiendan su código para adaptarlo a sus necesidades, tiene un par de opciones...

    Devoluciones de llamada

    Las devoluciones de llamada se pueden utilizar para lograr extensibilidad mediante configuración. Puede utilizar devoluciones de llamada para permitir que el usuario de la API anule ciertas partes de su código. Cuando crea que tareas específicas pueden manejarse de manera diferente a su código predeterminado, refactorice ese código en una función de devolución de llamada configurable para permitir que un usuario de API lo anule fácilmente:

    var default_options = { // ... position: function($elem, $parent) { $elem.css($parent.position()); }};function Widget(options) { this.options = jQuery.extend({}, default_options, options || {}); this.create();};Widget.prototype.create = function() { this.$container = $("div/div").appendTo(document.body); this.$thingie = $("div/div").appendTo(this.$container); return this;};Widget.protoype.show = function() { this.options.position(this.$thingie, this.$container); this.$thingie.show(); return this;};
    var widget = new Widget({ position: function($elem, $parent) { var position = $parent.position(); // position $elem at the lower right corner of $parent position.left += $parent.width(); position.top += $parent.height(); $elem.css(position); }});widget.show();

    Las devoluciones de llamada también son una forma genérica de permitir a los usuarios de API personalizar los elementos que ha creado su código:

    // default create callback doesn’t do anythingdefault_options.create = function($thingie){};Widget.prototype.create = function() { this.$container = $("div/div").appendTo(document.body); this.$thingie = $("div/div").appendTo(this.$container); // execute create callback to allow decoration this.options.create(this.$thingie); return this;};
    var widget = new Widget({ create: function($elem) { $elem.addClass(’my-style-stuff’); }});widget.show();

    Siempre que acepte devoluciones de llamadas, asegúrese de documentar su firma y proporcionar ejemplos para ayudar a los usuarios de API a personalizar su código. Asegúrese de ser coherente con el contexto (hacia dónde thisapunta) en el que se ejecutan las devoluciones de llamada y los argumentos que aceptan.

     

    Eventos

    Los eventos surgen naturalmente cuando se trabaja con el DOM. En aplicaciones más grandes utilizamos eventos en varias formas (por ejemplo, PubSub) para permitir la comunicación entre módulos. Los eventos son particularmente útiles y se sienten más naturales cuando se trata de widgets de interfaz de usuario. Bibliotecas como jQuery ofrecen interfaces simples que le permiten conquistar fácilmente este dominio.

    Los eventos interactúan mejor cuando sucede algo, de ahí el nombre. Mostrar y ocultar un widget podría depender de circunstancias fuera de su alcance. Actualizar el widget cuando se muestra también es algo muy común. Ambos se pueden lograr con bastante facilidad utilizando la interfaz de eventos de jQuery, que incluso permite el uso de eventos delegados:

    Widget.prototype.show = function() { var event = jQuery.Event("widget:show"); this.$container.trigger(event); if (event.isDefaultPrevented()) { // event handler prevents us from showing return this; } this.options.position(this.$thingie, this.$container); this.$thingie.show(); return this;};
    // listen for all widget:show events$(document.body).on(’widget:show’, function(event) { if (Math.random() 0.5) { // prevent widget from showing event.preventDefault(); } // update widget’s data $(this).data("last-show", new Date());});var widget = new Widget();widget.show();

    You can freely choose event names. Avoid using native events for proprietary things and consider namespacing your events. jQuery UI’s event names are comprised of the widget’s name and the event name dialogshow. I find that hard to read and often default to dialog:show, mainly because it is immediately clear that this is a custom event, rather than something some browser might have secretly implemented.

    Hooks

    Traditional getter and setter methods can especially benefit from hooks. Hooks usually differ from callbacks in their number and how they’re registered. Where callbacks are usually used on an instance level for a specific task, hooks are usually used on a global level to customize values or dispatch custom actions. To






    Tal vez te puede interesar:

    1. Diseño de un cuadro de texto, íntegro
    2. Diseño y construcción de una aplicación web progresiva sin marco (Parte 3)
    3. Escribir un motor de aventuras de texto multijugador en Node.js: diseño del servidor Game Engine (Parte 2)
    4. Componentes de diseño en React

    Cómo diseñar mejores API de JavaScript

    Cómo diseñar mejores API de JavaScript

    Implemente rápidamente. Implementar inteligentemente Accesibilidad para diseñadores, con Stéphanie Walter Índice

    programar

    es

    https://pseint.es/static/images/programar-como-disenar-mejores-api-de-javascript-806-0.jpg

    2024-04-04

     

    Cómo diseñar mejores API de JavaScript
    Cómo diseñar mejores API de JavaScript

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20