Conceptos básicos de front-end: una breve discusión sobre funciones y programación funcional

Si observamos todos los conocimientos clave que se deben dominar en JavaScript, las funciones son los puntos de conocimiento que se pasan por alto más fácilmente cuando aprendemos por primera vez . Puede que haya muchas personas y muchos artículos que le digan que la orientación a objetos es importante y que los prototipos son importantes, pero pocas personas le dicen que casi todos los puntos clave y difíciles de la orientación a objetos están estrechamente relacionados con las funciones.

Incluyendo el contexto de ejecución , los objetos variables, los cierres, esto, etc. introducidos en mis artículos anteriores , todos giran en torno a los detalles de la función.

Muchas personas están ansiosas por comenzar a aprender orientado a objetos, aprender módulos y aprender marcos populares lo antes posible mientras aprenden, y convertirse rápidamente en maestros. Pero puedo decirle con responsabilidad que si no comprende hasta cierto punto estos aspectos básicos de las funciones, su progreso en el aprendizaje definitivamente será difícil.

¡Por lo tanto, todos deben prestar atención a las funciones!

Por supuesto, los puntos clave y las dificultades de las funciones se han discutido en artículos anteriores. Este artículo resume principalmente el conocimiento básico de las funciones e inicialmente aprende el pensamiento de la programación funcional .

1. Declaración de función, expresión de función, función anónima y función autoejecutable

Con respecto a la aplicación de funciones en el desarrollo real, se puede resumir aproximadamente en declaraciones de funciones, expresiones de funciones, funciones anónimas y funciones autoejecutables.

declaración de función

En JavaScript, hay dos métodos de declaración, uno es var/let/constla declaración de variable utilizada y el otro es functionla declaración de función utilizada.

En Conceptos básicos avanzados de front-end (3): explicación detallada de objetos variables [1], mencioné que durante el proceso de creación de objetos variables, las declaraciones de funciones tienen una mayor prioridad en el orden de ejecución que las declaraciones de variables, es decir, las declaraciones de funciones que A menudo se menciona de antemano. Por lo tanto, no importa dónde declaremos la función en el contexto de ejecución, podemos usar la función directamente en el mismo contexto de ejecución.

fn();  // functionfunction fn() {
   
       console.log("function");}

expresión de función

A diferencia de la declaración de función, la expresión de función se declara usando var/let/const, por lo que cuando confirmamos si se puede usar correctamente, debemos juzgar de acuerdo con las reglas de var/let/const, es decir, declaración de variable. Sabemos que usar var para declarar variables es en realidad una operación de dos pasos.

// 变量声明var a = 20;// 实际执行顺序var a = undefined;  // 变量声明,初始值undefined,变量提升,提升顺序次于function声明a = 20;  // 变量赋值,该操作不会提升

De la misma manera, cuando usamos la declaración de variables para declarar una función, es lo que a menudo llamamos expresión de función. Las expresiones de funciones se elevan de la misma manera que las declaraciones de variables.

fn(); // 报错var fn = function() {
   
       console.log("function");}

La secuencia de ejecución del ejemplo anterior es:

var fn = undefined;   // 变量声明提升fn();    // 执行报错fn = function() {   // 赋值操作,此时将后边函数的引用赋值给fn    console.log("function");}

Debido a los diferentes métodos de declaración, existen algunas diferencias en el uso de declaraciones de funciones y expresiones de funciones a las que debemos prestar atención, aparte de eso, no hay diferencia en el uso de estas dos formas de funciones.

Con respecto al ejemplo anterior, la operación de asignación en expresiones de función también se usa con frecuencia en otros lugares, solo debemos tener clara la relación.

// 在构造函数中添加方法function Person(name) {
   
       this.name = name;    this.age = age;    // 在构造函数内部中添加方法    this.getAge = function() {
   
           return this.age;    }    this.}// 给原型添加方法Person.prototype.getName = function() {
   
       return this.name;}// 在对象中添加方法var a = {
   
       m: 20,    getM: function() {
   
           return this.m;    }}

función anónima

Las funciones anónimas, como sugiere el nombre, se refieren a funciones a las que no se les asignan valores explícitamente. Su escenario de uso se pasa principalmente a otra función como parámetro.

var a = 10;var fn = function(bar, num) {
   
       return bar() + num;}fn(function() {
   
       return a;}, 20)

En el ejemplo anterior, se pasa una función anónima como primer parámetro de fn. Aunque la función anónima no realiza una operación de asignación explícita y no tenemos forma de hacer referencia a ella en el contexto de ejecución externo, dentro de la función fn, asignamos la función anónima a la barra de variables y la guardamos en el objeto de argumentos de la variable fn. objeto. .

// 变量对象在fn上下文执行过程中的创建阶段VO(fn) = {
   
       arguments: {
   
           bar: undefined,        num: undefined,        length: 2    }}// 变量对象在fn上下文执行过程中的执行阶段// 变量对象变为活动对象,并完成赋值操作与执行可执行代码VO -> AOAO(fn) = {
   
       arguments: {
   
           bar: function() { return a },        num: 20,        length: 2    }}

Debido a que una vez que la función anónima se pasa a otra función, eventualmente se ejecutará en otra función, por lo que a menudo llamamos a esta función anónima función de devolución de llamada . Explicaré más sobre las funciones anónimas con más detalle en mi próximo artículo que profundizará en el curry.

Este escenario de aplicación de funciones anónimas adopta casi todos los puntos de conocimiento de funciones difíciles de entender , por lo que debemos comprender estos detalles con suficiente claridad. Si aún no comprende la evolución de los objetos variables, debe regresar. Ir a leer este artículo: Conceptos básicos avanzados de front-end (3): explicación detallada de los objetos variables [2]

Función de autoejecución y alcance a nivel de bloque.

En ES5, no existe un alcance a nivel de bloque, por lo que a menudo utilizamos la autoejecución de funciones para imitar el alcance a nivel de bloque, lo que proporciona un contexto de ejecución independiente y, combinado con cierres, proporciona la base para la modularización. La autoejecución de funciones es en realidad una aplicación de funciones anónimas.

(function() {
   
      // ...})();

Un módulo suele incluir: variables privadas, métodos privados, variables públicas y métodos públicos.

De acuerdo con el acceso unidireccional de la cadena de alcance, puede ser fácil para los externos saber que en este módulo independiente, el entorno de ejecución externo no puede acceder a ninguna variable y método interno, por lo que podemos crear fácilmente variables privadas y métodos privados que pertenezcan a este módulo.método.

(function() {
   
       // 私有变量    var age = 20;    var name = "Tom";    // 私有方法    function getName() {
   
           return `your name is ` + name;    }})();

Pero, ¿qué debemos hacer con los métodos y variables compartidos? ¿Aún recuerdas las características de los cierres de las que hablamos antes ? Sí, usando cierres podemos acceder a las variables y métodos dentro del contexto de ejecución, por lo tanto, solo necesita crear un cierre basado en la definición del cierre y abrir las variables y métodos que crea que deben hacerse públicos.

(function() {
   
       // 私有变量    var age = 20;    var name = "Tom";    // 私有方法    function getName() {
   
           return `your name is ` + name;    }    // 共有方法    function getAge() {
   
           return age;    }    // 将引用保存在外部执行环境的变量中,形成闭包,防止该执行环境被垃圾回收    window.getAge = getAge;})();

Por supuesto, hemos enfatizado el importante papel de los cierres en los módulos cuando explicamos los cierres, pero este punto de conocimiento es realmente importante y requiere que lo comprendamos repetidamente y lo dominemos a fondo.

Para ayudarlo a comprender mejor los cierres, echemos un vistazo a cómo se usan los módulos y cierres en jQuery.

// 使用函数自执行的方式创建模块(function(window, undefined) {
   
       // 声明jQuery构造函数     var jQuery = function(name) {
   
           // 主动在构造函数中,返回一个jQuery实例         return new jQuery.fn.init(name);     }    // 添加原型方法     jQuery.prototype = jQuery.fn = {
   
            constructor: jQuery,         init:function() { ... },         css: function() { ... }     }     jQuery.fn.init.prototype = jQuery.fn;    // 将jQuery改名为$,并将引用保存在window上,形成闭包,对外开放jQuery构造函数,这样我们就可以访问所有挂载在jQuery原型上的方法了     window.jQuery = window.$ = jQuery; })(window);// 在使用时,直接执行了构造函数,因为在jQuery的构造函数中通过一些手段,返回的是jQuery的实例,所以我们就不用再每次用的时候自己new一个实例$("#div1");

Aquí solo necesitamos entender las partes de cierre y módulo. En cuanto a cómo se enrolla la cadena interna del prototipo y por qué está escrita así, lo analizaremos lentamente para todos cuando hablemos de orientado a objetos. El propósito de dar este ejemplo es esperar que todos puedan prestar atención a las funciones, que en el desarrollo real están en todas partes.

A continuación quiero compartirles una aplicación de un módulo avanzado y muy útil. A medida que nuestros proyectos se hacen cada vez más grandes, es necesario guardar más y más datos y estados. Por lo tanto, necesitamos un módulo especial para mantener estos datos. En este momento, surge algo llamado administrador de estados. En cuanto al administrador estatal, creo que el más famoso es redux. Aunque para todos los que todavía están aprendiendo, redux es un poco impredecible, antes de aprender, primero podemos usar una forma simple para que todos tengan una comprensión general del principio de implementación del administrador estatal, lo que nos proporcionará aprendizaje futuro. Construya una base sólida.

Primero veamos directamente el código.

// 自执行创建模块(function() {
   
       // states 结构预览    // states = {
   
       //     a: 1,    //     b: 2,    //     m: 30,      //     o: {}    // }    var states = {};  // 私有变量,用来存储状态与数据    // 判断数据类型    function type(elem) {
   
           if(elem == null) {
   
               return elem + "";        }        return toString.call(elem).replace(/[\[\]]/g, "").split(" ")[1].toLowerCase();    }    /**     * @Param name 属性名     * @Description 通过属性名获取保存在states中的值    */    function get(name) {
   
           return states[name] ? states[name] : "";    }    function getStates() {
   
           return states;    }    /*    * @param options {object} 键值对    * @param target {object} 属性值为对象的属性,只在函数实现时递归中传入    * @desc 通过传入键值对的方式修改state树,使用方式与小程序的data或者react中的setStates类似    */    function set(options, target) {
   
           var keys = Object.keys(options);        var o = target ? target : states;        keys.map(function(item) {
   
               if(typeof o[item] == "undefined") {
   
                   o[item] = options[item];            }            else {
   
                   type(o[item]) == "object" ? set(options[item], o[item]) : o[item] = options[item];            }            return item;        })    }    // 对外提供接口    window.get = get;    window.set = set;    window.getStates = getStates;})()// 具体使用如下set({ a: 20 });     // 保存 属性aset({ b: 100 });    // 保存属性bset({ c: 10 });     // 保存属性c// 保存属性o, 它的值为一个对象set({
   
       o: {
   
           m: 10,        n: 20    }})// 修改对象o 的m值set({
   
       o: {
   
           m: 1000    }})// 给对象o中增加一个c属性set({
   
       o: {
   
           c: 100    }})console.log(getStates())

Dirección en línea de la instancia de demostración[3]

La razón por la que digo que esta es una aplicación avanzada es porque en una aplicación de una sola página, es probable que usemos esta idea. Con base en el conocimiento que hemos mencionado, en realidad es muy sencillo entender este ejemplo. La dificultad probablemente esté en el procesamiento del método establecido. Para tener una mayor aplicabilidad, se han realizado muchas adaptaciones y conocimientos como la recursividad. ha sido usado. Si aún no lo comprende, no importa, solo sepa cómo usarlo. El código anterior se puede aplicar directamente al desarrollo práctico. Recuerde, cuando necesite guardar demasiado estado, simplemente piense en este fragmento de código.

Hay varias otras formas de escribir funciones para autoejecución, como !function(){}(),+function(){}()

2. Método de paso de parámetros de función: paso por valor

¿Recuerda la diferencia en replicación entre tipos de datos primitivos y de referencia? La copia de tipos de datos básicos significa que los valores se copian directamente, por lo que después de los cambios, no se afectan entre sí. Sin embargo, al copiar un tipo de datos de referencia, se copia la referencia almacenada en el objeto variable, por lo que las dos referencias después de la copia en realidad acceden al valor en la misma memoria dinámica. Cuando uno cambia, el otro naturalmente también cambiará. Tomemos el siguiente ejemplo.

var a = 20;var b = a;b = 10;console.log(a);  // 20var m = { a: 1, b: 2 }var n = m;n.a = 5;console.log(m.a) // 5

La misma diferencia ocurre cuando los valores se pasan dentro de funciones como argumentos. Sabemos que los parámetros de la función en realidad se almacenan en el objeto variable de la función después de ingresar a la función, por lo que se produce una copia en este momento. Tomemos el siguiente ejemplo.

var a = 20;function fn(a) {
   
       a = a + 10;    return a;}fn(a);console.log(a); // 20
var a = { m: 10, n: 20 }function fn(a) {
   
       a.m = 20;    return a;}fn(a);console.log(a);   // { m: 20, n: 20 }

Es precisamente por esta diferencia que muchas personas tienen mucha confusión al comprender cómo se pasan los parámetros de una función. ¿Se pasa por valor o por referencia? De hecho, la conclusión todavía se pasa por valor, pero cuando esperamos pasar un tipo de referencia, lo que realmente se pasa es solo la referencia de este tipo de referencia almacenada en el objeto variable. Para ilustrar este problema, veamos el siguiente ejemplo.

var person = {
   
       name: "Nicholas",    age: 20}function setName(obj) {  // 传入一个引用    obj = {};   // 将传入的引用指向另外的值    obj.name = "Greg";  // 修改引用的name属性}setName(person);console.log(person.name);  // Nicholas 未被改变

En el ejemplo anterior, si se pasa persona por referencia, persona se modificará automáticamente para que apunte a un nuevo objeto cuyo valor de atributo de nombre sea Gerg. Pero vemos en los resultados que el objeto persona no ha cambiado de ninguna manera, por lo que solo se ha modificado la referencia dentro de la función.

4. Programación funcional

Aunque JavaScript no es un lenguaje de programación puramente funcional, utiliza muchas características de la programación funcional. Por tanto, comprender estas características puede ayudarnos a comprender mejor el código que escribimos.

Cuando queremos utilizar una función, normalmente queremos encapsular algunas funciones, lógica, etc. Creo que todo el mundo está familiarizado con el concepto de encapsulación.

Normalmente logramos una cosa mediante la encapsulación de funciones. Por ejemplo, si queremos calcular la suma de tres números cualesquiera, podemos encapsular una función simple usando estos tres números como parámetros.

function add(a, b, c) {
   
     return a + b + c;}

Cuando queramos calcular la suma de tres números, podemos llamar a este método directamente.

add(1, 2, 3); // 6

Por supuesto, cuando lo que desea hacer es relativamente simple, es posible que no vea la conveniencia que brinda encapsularlo en una función. ¿Y si lo que queremos hacer es un poco más complicado? Por ejemplo, quiero calcular la suma de todos los subelementos de una matriz.

function mergeArr(arr) {
   
       var result = 0;    for(var i = 0; i < arr.length; i++) { result  += arr[i] }    return result;}

Si no utilizamos la encapsulación de funciones, tendremos que reutilizar el bucle for cada vez que queramos implementar esta función, la consecuencia de esto es que nuestro código se llena con cada vez más códigos repetidos. Después de la encapsulación, cuando queramos hacerlo nuevamente, solo necesitamos una oración.

mergeArr([1, 2, 3, 4, 5]);

Por supuesto, creo que todos deberían tener una comprensión muy clara del significado de encapsulación de funciones, pero el problema que tenemos que enfrentar es: cuando queremos encapsular una función, ¿cuál es la mejor práctica?

La programación funcional puede darnos la respuesta.

Cuando aprendemos por primera vez, a menudo utilizamos involuntariamente el estilo de programación imperativo para lograr lo que queremos hacer. Porque la programación imperativa es más simple y directa. Por ejemplo, ahora tenemos una matriz array = [1, 3, "h", 5, "m", "4"]y ahora queremos encontrar todos los hijos de tipo número en esta matriz. Cuando utilizamos el pensamiento de programación imperativo, podemos hacerlo directamente.

var array = [1, 3, "h", 5, "m", "4"];var res = [];for(var i = 0; i < array.length; i ++) {
   
       if (typeof array[i] === "number") {
   
           res.push(array[i]);    }}

Con esta implementación conseguimos nuestro objetivo de forma sencilla. El problema con esto es que cuando queremos encontrar todos los hijos en otra matriz en otro momento, tenemos que escribir la misma lógica nuevamente. A medida que aumenta el número de apariciones, nuestro código empeora y es más difícil de mantener.

El pensamiento de la programación funcional sugiere que encapsulamos esta función que aparecerá muchas veces para prepararnos para las llamadas.

function getNumbers(array) {
   
       var res = [];    array.forEach(function(item) {
   
           if (typeof item === "number") {
   
               res.push(item);        }    })    return res;}// 以上是我们的封装,以下是功能实现var array = [1, 3, "h", 5, "m", "4"];var res = getNumbers(array);

Después de encapsular la función, solo necesitamos escribir una línea de código para implementar la misma función. Y si las necesidades cambian o se modifican ligeramente en el futuro, sólo necesitamos ajustar el método getNumbers. Y cuando lo usamos, solo debemos preocuparnos de lo que este método puede hacer, no de cómo se implementa. Ésta es también una de las diferencias entre el pensamiento de programación funcional y la programación imperativa.

El pensamiento de programación funcional también tiene las siguientes características.

Las funciones son ciudadanos de primera.

El llamado "ciudadano de primera clase" significa que las funciones están en pie de igualdad con otros tipos de datos: pueden asignarse a otras variables, pasarse como parámetros a otra función o devolverse mediante otras funciones. Deberíamos haber visto muchas de estas escenas.

var a = function foo() {}  // 赋值function fn(function() {}, num) {}   // 函数作为参数// 函数作为返回值function var() {
   
       return function() {
   
           ... ...    }}

Por supuesto, todos estos son conceptos básicos de JavaScript. Pero creo que muchas personas, incluso usted mismo, que lee esto, pueden ignorar estos conceptos. Puedes verificar esto con un ejemplo simple.

Primero personalicemos dicha función.

function delay() {
   
       console.log("5000ms之后执行该方法.");}

Lo que debe hacer ahora es, si se le pide que combine el método setTimeout para retrasar la ejecución del método de retardo en 5000 ms, ¿qué debe hacer?

En realidad es muy simple, ¿verdad? Simplemente hazlo de esta manera.

var timer = setTimeout(function() {
   
       delay();}, 5000);

Ahora viene la pregunta: si tienes un conocimiento profundo de las funciones como ciudadanos de primera clase, creo que encontrarás que en realidad hay algunos problemas con la forma de escribir anterior. Entonces piénselo, ¿cuál es el problema?

Dado que una función se puede pasar a otra función como parámetro, ¿podemos usar directamente el retraso como primer parámetro de setTimeout sin agregar una capa adicional de funciones anónimas?

Por tanto, de hecho, la solución más correcta debería escribirse así.

var timer = setTimeout(delay, 5000);

Por supuesto, si ha pensado en hacer esto con anticipación, felicidades, significa que tiene más talento en JavaScript que la gente común. De hecho, muchas personas, incluidas personas con muchos años de experiencia laboral, utilizan el primer mal método y no lo han evitado por completo. Y ni siquiera saben todavía cuál es su problema.

En la práctica futura, encontrará más escenarios similares. Para verificar la comprensión de los lectores, también podríamos pensar en cómo optimizar el siguiente código.

function getUser(path, callback) {
   
       return $.get(path, function(info) {
   
           return callback(info);    })}getUser("/api/user", function(resp) {
   
       // resp为成功请求之后返回的数据    console.log(resp);})

El principio de optimización es exactamente el mismo que el del ejemplo de setTimeout. Lo mantendré en secreto aquí y no tengo la intención de contarles la conclusión. Solo les recuerdo que después de optimizar getUser, solo hay una línea. de código. Es hora de poner a prueba los logros de aprendizaje de todos ^ ^.

Utilice únicamente "expresión" en lugar de "declaración"

Una "expresión" es un proceso de operación simple que siempre devuelve un valor; una "declaración" realiza una determinada operación y no tiene valor de retorno. La programación funcional requiere el uso únicamente de expresiones, no declaraciones. En otras palabras, cada paso es una operación sencilla y tiene un valor de retorno.

Supongamos que en nuestro proyecto necesitamos cambiar el color de fondo de un elemento en muchos lugares. Entonces podemos resumirlo así.

var ele = document.querySelector(".test");function setBackgroundColor(color) {
   
       ele.style.backgroundColor = color;}// 多处使用setBackgroundColor("red");setBackgroundColor("#ccc");

Podemos sentir claramente que setBackgroundColor encapsula solo una declaración. Este no es un efecto ideal. La programación funcional espera que una función tenga entradas y salidas. Entonces los buenos hábitos deben ser los siguientes.

function setBackgroundColor(ele, color) {
   
       ele.style.backgroundColor = color;    return color;}// 多处使用var ele = document.querySelector(".test");setBackgroundColor(ele, "red");setBackgroundColor(ele, "#ccc");

Comprender esto puede ayudarnos a desarrollar buenos hábitos a la hora de encapsular funciones.

función pura

Una función que siempre produce la misma salida para la misma entrada y no tiene efectos secundarios es una función pura.

El llamado "efecto secundario" se refiere a la interacción entre el interior y el exterior de una función (el caso más típico es modificar el valor de una variable global), produciendo resultados distintos a las operaciones.

La programación funcional enfatiza que no hay "efectos secundarios", lo que significa que la función debe permanecer independiente. Todas las funciones deben devolver un nuevo valor y no tener otras acciones, especialmente los valores de las variables externas no deben modificarse.

Es decir: siempre que se pasen los mismos parámetros, los resultados devueltos deben ser iguales.

Por ejemplo, esperamos encapsular una función que pueda obtener el último elemento de la matriz pasada. Entonces se puede lograr de las dos maneras siguientes.

function getLast(arr) {
   
       return arr[arr.length];}function getLast_(arr) {
   
       return arr.pop();}var source = [1, 2, 3, 4];var last = getLast(source); // 返回结果4 原数组不变var last_ = getLast_(source); // 返回结果4 原数据最后一项被删除

Aunque getLast y getLast_ también pueden obtener el último valor de la matriz, getLast_ cambia la matriz original. Y cuando se cambia la matriz original, cuando volvemos a llamar al método, el resultado será diferente. En nuestra opinión, un método de embalaje tan impredecible es muy malo. Estropeará mucho nuestros datos. Entre los métodos de datos admitidos de forma nativa por JavaScript, también hay muchos métodos impuros. Debemos estar muy atentos al usarlos y saber claramente si los cambios en los datos originales dejarán peligros ocultos.

var source = [1, 2, 3, 4, 5];source.slice(1, 3); // 纯函数 返回[2, 3] source不变source.splice(1, 3); // 不纯的 返回[2, 3, 4] source被改变source.pop(); // 不纯的source.push(6); // 不纯的source.shift(); // 不纯的source.unshift(1); // 不纯的source.reverse(); // 不纯的// 我也不能短时间知道现在source被改变成了什么样子,干脆重新约定一下source = [1, 2, 3, 4, 5];source.concat([6, 7]); // 纯函数 返回[1, 2, 3, 4, 5, 6, 7] source不变source.join("-"); // 纯函数 返回1-2-3-4-5 source不变

Reimpreso de: Weidian Reading    https://www.weidianyuedu.com

Supongo que te gusta

Origin blog.csdn.net/hdxx2022/article/details/132661428
Recomendado
Clasificación