Mejore su conocimiento de JavaScript leyendo el código fuente
Publicado: 2022-03-10¿Recuerda la primera vez que profundizó en el código fuente de una biblioteca o marco que usa con frecuencia? Para mí, ese momento llegó durante mi primer trabajo como desarrollador frontend hace tres años.
Acabábamos de terminar de reescribir un marco heredado interno que usamos para crear cursos de aprendizaje electrónico. Al comienzo de la reescritura, dedicamos tiempo a investigar varias soluciones diferentes, incluidas Mithril, Inferno, Angular, React, Aurelia, Vue y Polymer. Como era un principiante (acababa de pasar del periodismo al desarrollo web), recuerdo sentirme intimidado por la complejidad de cada marco y no entender cómo funcionaba cada uno.
Mi comprensión creció cuando comencé a investigar nuestro marco elegido, Mithril, en mayor profundidad. Desde entonces, mi conocimiento de JavaScript, y de la programación en general, se ha visto muy favorecido por las horas que he dedicado a profundizar en las entrañas de las bibliotecas que uso a diario, ya sea en el trabajo o en mis propios proyectos. En esta publicación, compartiré algunas de las formas en que puede tomar su biblioteca o marco favorito y usarlo como una herramienta educativa.

Los beneficios de leer el código fuente
Uno de los principales beneficios de leer el código fuente es la cantidad de cosas que puede aprender. Cuando investigué por primera vez el código base de Mithril, tenía una vaga idea de lo que era el DOM virtual. Cuando terminé, me di cuenta de que el DOM virtual es una técnica que implica la creación de un árbol de objetos que describen cómo debería verse la interfaz de usuario. Luego, ese árbol se convierte en elementos DOM utilizando API DOM como document.createElement
. Las actualizaciones se realizan creando un nuevo árbol que describe el estado futuro de la interfaz de usuario y luego comparándolo con los objetos del árbol anterior.
Había leído todo esto en varios artículos y tutoriales, y aunque fue útil, poder observarlo en funcionamiento en el contexto de una aplicación que habíamos enviado fue muy esclarecedor para mí. También me enseñó qué preguntas hacer al comparar diferentes marcos. En lugar de mirar las estrellas de GitHub, por ejemplo, ahora sabía hacer preguntas como: "¿Cómo afecta la forma en que cada marco realiza las actualizaciones el rendimiento y la experiencia del usuario?"
Otro beneficio es un aumento en su apreciación y comprensión de una buena arquitectura de aplicaciones. Si bien la mayoría de los proyectos de código abierto generalmente siguen la misma estructura con sus repositorios, cada uno de ellos contiene diferencias. La estructura de Mithril es bastante plana y si está familiarizado con su API, puede hacer conjeturas informadas sobre el código en carpetas como render
, router
y request
. Por otro lado, la estructura de React refleja su nueva arquitectura. Los mantenedores han separado el módulo responsable de las actualizaciones de la interfaz de usuario ( react-reconciler
) del módulo responsable de representar los elementos DOM ( react-dom
).
Uno de los beneficios de esto es que ahora es más fácil para los desarrolladores escribir sus propios renderizadores personalizados conectándolos al paquete react-reconciler
. Parcel, un paquete de módulos que he estado estudiando recientemente, también tiene una carpeta de packages
como React. El módulo clave se denomina parcel-bundler
y contiene el código responsable de crear paquetes, activar el servidor del módulo activo y la herramienta de línea de comandos.

Otro beneficio más, que fue una grata sorpresa para mí, es que te sientes más cómodo leyendo la especificación oficial de JavaScript que define cómo funciona el lenguaje. La primera vez que leí la especificación fue cuando estaba investigando la diferencia entre throw Error
y throw new Error
(alerta de spoiler, no hay ninguna). Investigué esto porque noté que Mithril usaba throw Error
en la implementación de su función m
y me preguntaba si había algún beneficio en usarlo sobre throw new Error
. Desde entonces, también he aprendido que los operadores lógicos &&
y ||
no necesariamente devuelva valores booleanos, encontró las reglas que rigen cómo el operador de igualdad ==
coacciona los valores y la razón por la que Object.prototype.toString.call({})
devuelve '[object Object]'
.
Técnicas para leer el código fuente
Hay muchas formas de abordar el código fuente. Descubrí que la forma más fácil de comenzar es seleccionando un método de la biblioteca elegida y documentando lo que sucede cuando lo llama. No documente cada paso, pero trate de identificar su flujo y estructura general.
Hice esto recientemente con ReactDOM.render
y, en consecuencia, aprendí mucho sobre React Fiber y algunas de las razones detrás de su implementación. Afortunadamente, como React es un marco popular, encontré muchos artículos escritos por otros desarrolladores sobre el mismo problema y esto aceleró el proceso.
Esta inmersión profunda también me presentó los conceptos de programación cooperativa, el método window.requestIdleCallback
y un ejemplo del mundo real de listas vinculadas (React maneja las actualizaciones colocándolas en una cola que es una lista vinculada de actualizaciones priorizadas). Al hacer esto, es recomendable crear una aplicación muy básica utilizando la biblioteca. Esto facilita la depuración porque no tiene que lidiar con los seguimientos de pila causados por otras bibliotecas.
Si no estoy haciendo una revisión en profundidad, abriré la carpeta /node_modules
en un proyecto en el que estoy trabajando o iré al repositorio de GitHub. Esto suele suceder cuando me encuentro con un error o una función interesante. Al leer código en GitHub, asegúrese de leer desde la última versión. Puede ver el código de las confirmaciones con la etiqueta de la última versión haciendo clic en el botón que se usa para cambiar las ramas y seleccionando "etiquetas". Las bibliotecas y los marcos siempre están experimentando cambios, por lo que no desea aprender sobre algo que puede eliminarse en la próxima versión.
Otra forma menos complicada de leer el código fuente es lo que me gusta llamar el método de "vistazo superficial". Al principio, cuando comencé a leer el código, instalé express.js , abrí su carpeta /node_modules
y revisé sus dependencias. Si el README
no me proporciona una explicación satisfactoria, leo la fuente. Hacer esto me llevó a estos hallazgos interesantes:

- Express depende de dos módulos que fusionan objetos pero lo hacen de formas muy diferentes.
merge-descriptors
solo agrega propiedades que se encuentran directamente en el objeto de origen y también fusiona propiedades no enumerables, mientras queutils-merge
solo itera sobre las propiedades enumerables de un objeto, así como las que se encuentran en su cadena de prototipo.merge-descriptors
usaObject.getOwnPropertyNames()
yObject.getOwnPropertyDescriptor()
mientras queutils-merge
usafor..in
; - El módulo
setprototypeof
proporciona una forma multiplataforma de configurar el prototipo de un objeto instanciado; -
escape-html
es un módulo de 78 líneas para escapar de una cadena de contenido para que pueda interpolarse en contenido HTML.
Si bien es probable que los hallazgos no sean útiles de inmediato, es útil tener una comprensión general de las dependencias utilizadas por su biblioteca o marco.
Cuando se trata de depurar el código front-end, las herramientas de depuración de su navegador son su mejor amigo. Entre otras cosas, te permiten detener el programa en cualquier momento e inspeccionar su estado, omitir la ejecución de una función o entrar o salir de ella. A veces, esto no será posible inmediatamente porque el código se ha minimizado. Tiendo a desminificarlo y copiar el código sin desminificar en el archivo correspondiente en la carpeta /node_modules
.

Estudio de caso: función de conexión de Redux
React-Redux es una biblioteca utilizada para administrar el estado de las aplicaciones React. Cuando trato con bibliotecas populares como estas, empiezo buscando artículos que se hayan escrito sobre su implementación. Al hacerlo para este estudio de caso, encontré este artículo. Esta es otra cosa buena de leer el código fuente. La fase de investigación generalmente lo lleva a artículos informativos como este que solo mejoran su propio pensamiento y comprensión.
connect
es una función de React-Redux que conecta los componentes de React a la tienda Redux de una aplicación. ¿Cómo? Bueno, según los documentos, hace lo siguiente:
"... devuelve una nueva clase de componente conectado que envuelve el componente que pasó".
Después de leer esto, me gustaría hacer las siguientes preguntas:
- ¿Conozco algún patrón o concepto en el que las funciones toman una entrada y luego devuelven esa misma entrada envuelta con funcionalidad adicional?
- Si conozco alguno de estos patrones, ¿cómo implementaría esto según la explicación dada en los documentos?
Por lo general, el siguiente paso sería crear una aplicación de ejemplo muy básica que use connect
. Sin embargo, en esta ocasión opté por usar la nueva aplicación React que estamos creando en Limejump porque quería comprender la connect
dentro del contexto de una aplicación que eventualmente se irá a un entorno de producción.
El componente en el que me estoy enfocando se ve así:
class MarketContainer extends Component { // code omitted for brevity } const mapDispatchToProps = dispatch => { return { updateSummary: (summary, start, today) => dispatch(updateSummary(summary, start, today)) } } export default connect(null, mapDispatchToProps)(MarketContainer);
Es un componente contenedor que envuelve cuatro componentes conectados más pequeños. Una de las primeras cosas que encuentra en el archivo que exporta el método de connect
es este comentario: connect es una fachada sobre connectAdvanced . Sin ir muy lejos, tenemos nuestro primer momento de aprendizaje: una oportunidad para observar el patrón de diseño de la fachada en acción . Al final del archivo vemos que connect
exporta una invocación de una función llamada createConnect
. Sus parámetros son un montón de valores predeterminados que se han desestructurado así:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {})
De nuevo, nos encontramos con otro momento de aprendizaje: exportar funciones invocadas y desestructurar argumentos de funciones por defecto . La parte de desestructuración es un momento de aprendizaje porque si el código se hubiera escrito así:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory })
Habría resultado en este error Uncaught TypeError: Cannot destructure property 'connectHOC' of 'undefined' or 'null'.
Esto se debe a que la función no tiene un argumento predeterminado al que recurrir.
Nota : Para obtener más información sobre esto, puede leer el artículo de David Walsh. Algunos momentos de aprendizaje pueden parecer triviales, dependiendo de su conocimiento del idioma, por lo que podría ser mejor concentrarse en cosas que no ha visto antes o sobre las que necesita aprender más.
createConnect
en sí no hace nada en el cuerpo de su función. Devuelve una función llamada connect
, la que usé aquí:
export default connect(null, mapDispatchToProps)(MarketContainer)
Toma cuatro argumentos, todos opcionales, y cada uno de los primeros tres argumentos pasa por una función de match
que ayuda a definir su comportamiento según si los argumentos están presentes y su tipo de valor. Ahora, debido a que el segundo argumento provisto para match
es una de las tres funciones importadas en connect
, tengo que decidir qué hilo seguir.
Hay momentos de aprendizaje con la función de proxy utilizada para envolver el primer argumento para connect
si esos argumentos son funciones, la utilidad isPlainObject
utilizada para verificar objetos simples o el módulo de warning
que revela cómo puede configurar su depurador para interrumpir todas las excepciones. Después de las funciones de coincidencia, llegamos a connectHOC
, la función que toma nuestro componente React y lo conecta a Redux. Es otra invocación de función que devuelve wrapWithConnect
, la función que en realidad maneja la conexión del componente a la tienda.
Mirando la implementación de connectHOC
, puedo apreciar por qué necesita connect
para ocultar sus detalles de implementación. Es el corazón de React-Redux y contiene lógica que no necesita exponerse a través de connect
. Aunque terminaré la inmersión profunda aquí, si hubiera continuado, este habría sido el momento perfecto para consultar el material de referencia que encontré anteriormente, ya que contiene una explicación increíblemente detallada del código base.
Resumen
Leer el código fuente es difícil al principio, pero como todo, se vuelve más fácil con el tiempo. El objetivo no es entender todo, sino salir con una perspectiva diferente y nuevos conocimientos. La clave es ser deliberado sobre todo el proceso e intensamente curioso sobre todo.
Por ejemplo, encontré interesante la función isPlainObject
porque usa esto if (typeof obj !== 'object' || obj === null) return false
para asegurarse de que el argumento dado sea un objeto simple. Cuando leí su implementación por primera vez, me pregunté por qué no usaba Object.prototype.toString.call(opts) !== '[object Object]'
, que es menos código y distingue entre objetos y subtipos de objetos como la Fecha objeto. Sin embargo, leer la siguiente línea reveló que, en el caso extremadamente improbable de que un desarrollador que use connect
devuelva un objeto Date, por ejemplo, esto será manejado por Object.getPrototypeOf(obj) === null
.
Otro poco de intriga en isPlainObject
es este código:
while (Object.getPrototypeOf(baseProto) !== null) { baseProto = Object.getPrototypeOf(baseProto) }
Algunas búsquedas en Google me llevaron a este hilo de StackOverflow y al problema de Redux que explica cómo ese código maneja casos como la verificación de objetos que se originan en un iFrame.
Enlaces útiles sobre la lectura del código fuente
- "Cómo aplicar ingeniería inversa a los marcos", Max Koretskyi, Medium
- "Cómo leer código", Aria Stewart, GitHub