nodejs herencia example es6 ejemplos ecmascript clases javascript ecmascript-6 es6-class

herencia - prototype javascript ejemplos



¿Cómo verifica la diferencia entre una clase y función de ECMAScript 6? (7)

En ECMAScript 6 el tipo de clases es, de acuerdo con la especificación, ''function'' .

Sin embargo, también según la especificación, no se le permite llamar al objeto creado a través de la sintaxis de clase como una llamada de función normal. En otras palabras, debe usar la new palabra clave, de lo contrario se lanzará un TypeError.

TypeError: Classes can''t be function-called

Entonces, sin usar try catch, que sería muy feo y destruiría el rendimiento, ¿cómo puedes verificar si una función proviene de la sintaxis de class o de la function ?


Aunque no está directamente relacionado, pero si usted genera la clase, el constructor o la función y desea saber si debe llamar a la función o crear una instancia de un objeto con una nueva palabra clave, puede hacerlo agregando una marca personalizada en el prototipo de El constructor o clase. Ciertamente, puede distinguir una clase de una función usando los métodos mencionados en otras respuestas (como toString ). Sin embargo, si su código se transpila usando babel, ciertamente sería un problema.

Para hacerlo más simple, puedes probar el siguiente código:

class Foo{ constructor(){ this.someProp = ''Value''; Foo.prototype.isClass = true; } }

o si usa la función de constructor

function Foo(){ this.someProp = ''Value''; Foo.prototype.isClass = true; }

y puede verificar si es clase o no verificando la propiedad del prototipo.

if(Foo.prototype.isClass){ //It''s a class }

Obviamente, este método no funcionará si usted no crea la clase o la función. React.js utiliza este método para verificar si el React Component es un componente de clase o un componente de función. Esta respuesta está tomada de la publicación del blog de Dan Abramov.


Creo que la forma más sencilla de verificar si la función es la clase ES6 es verificar el resultado del método .toString() . Según la especificación de es2015 :

La representación de la cadena debe tener la sintaxis de FunctionDeclaration FunctionExpression, GeneratorDeclaration, GeneratorExpression, ClassDeclaration, ClassExpression, ArrowFunction, MethodDefinition o GeneratorMethod dependiendo de las características reales del objeto

Así que la función de verificación parece bastante simple:

function isClass(func) { return typeof func === ''function'' && /^class/s/.test(Function.prototype.toString.call(func)); }


Dado que las respuestas existentes abordan este problema desde una perspectiva del entorno ES5, pensé que valdría la pena ofrecer una respuesta desde una perspectiva ES2015 +; La pregunta original no especifica y hoy en día muchas personas ya no necesitan transcribir las clases, lo que altera un poco la situación.

En particular, quería señalar que es posible responder de manera definitiva a la pregunta "¿se puede construir este valor?" Es cierto que eso no suele ser útil por sí solo; los mismos problemas fundamentales continúan existiendo si necesita saber si se puede llamar un valor.

¿Es algo construible?

Para empezar, creo que necesitamos aclarar algunos términos porque preguntar si un valor es un constructor puede significar más de una cosa:

  1. Literalmente, ¿este valor tiene una ranura [[construct]]? Si lo hace, es construible. Si no lo hace, no es construible.
  2. ¿Se pretendía construir esta función? Podemos producir algunos aspectos negativos: las funciones que no se pueden construir no estaban destinadas a construirse. Pero tampoco podemos decir (sin recurrir a controles heurísticos) si una función que es construible no fue usada como un constructor.

Lo que hace que 2 no puedan responderse es que las funciones creadas solo con la palabra clave de function son construibles y invocables, pero estas funciones a menudo están destinadas solo para uno de estos propósitos. Como algunos otros han mencionado, 2 es también una pregunta sospechosa: es como preguntar "¿en qué estaba pensando el autor cuando escribió esto?" No creo que la IA esté ahí todavía :) Mientras que en un mundo perfecto tal vez todos los autores reserven PascalCase para los constructores (consulte la función de isConventionalClass convencional de isConventionalClass ), en la práctica no sería inusual encontrar falsos positivos / negativos con esta prueba.

Con respecto a la primera versión de esta pregunta, sí, podemos saber si una función es construible. Lo obvio es intentar construirlo. Sin embargo, esto no es realmente aceptable porque no sabemos si hacerlo tendría efectos secundarios; parece un hecho que no sabemos nada sobre la naturaleza de la función, ya que si lo hiciéramos, no lo necesitaríamos. este cheque). Afortunadamente, hay una manera de construir un constructor sin realmente construirlo:

const isConstructable = fn => { try { new new Proxy(fn, { construct: () => ({}) }); return true; } catch (err) { return false; } };

El construct Proxy handler puede anular un valor de proxy [[construct]], pero no puede hacer que un valor no construible se pueda construir. Así que podemos "simular una instancia" de la entrada para probar si esto falla. Tenga en cuenta que la trampa de construcción debe devolver un objeto.

isConstructable(class {}); // true isConstructable(class {}.bind()); // true isConstructable(function() {}); // true isConstructable(function() {}.bind()); // true isConstructable(() => {}); // false isConstructable((() => {}).bind()); // false isConstructable(async () => {}); // false isConstructable(async function() {}); // false isConstructable(function * () {}); // false isConstructable({ foo() {} }.foo); // false isConstructable(URL); // true

Observe que las funciones de flecha, las funciones asíncronas, los generadores y los métodos no tienen doble función en la forma en que lo son las declaraciones y expresiones de funciones "heredadas". A estas funciones no se les asigna una ranura de [[construcción]] (creo que no muchos se dan cuenta de que la sintaxis del "método abreviado" hace algo, no es solo azúcar).

Entonces, para recapitular, si su pregunta es realmente "es construible", lo anterior es concluyente. Lamentablemente nada más lo será.

¿Es algo llamable?

Tendremos que aclarar la pregunta otra vez, porque si estamos siendo muy literales, la siguiente prueba realmente funciona *:

const isCallable = fn => typeof fn === ''function'';

Esto se debe a que ES actualmente no le permite crear una función sin una ranura [[call]] (bueno, las funciones enlazadas no tienen una directamente, pero representan una función que sí lo hace).

Esto puede parecer falso porque los constructores creados con sintaxis de clase lanzan si intenta llamarlos en lugar de construirlos. Sin embargo, son invocables, es solo que su [[call]] slot está definido como una función que lanza! Oy.

Podemos probar esto convirtiendo nuestra primera función a su imagen reflejada.

// Demonstration only, this function is useless: const isCallable = fn => { try { new Proxy(fn, { apply: () => undefined })(); return true; } catch (err) { return false; } }; isCallable(() => {}); // true isCallable(function() {}); // true isCallable(class {}); // ... true!

Tal función no es útil, pero quería mostrar estos resultados para enfocar la naturaleza del problema. La razón por la que no podemos verificar fácilmente si una función es "solo nueva" es que la respuesta no se basa en el modelo de "ausencia de llamada", como se modela "nunca nuevo" en la "ausencia de construcción". Lo que nos interesa saber está enterrado en un método que no podemos observar, excepto a través de su evaluación, por lo que todo lo que podemos hacer es usar los controles heurísticos como un proxy de lo que realmente queremos saber.

Opciones heurísticas

Podemos empezar por reducir los casos que son ambiguos. Cualquier función que no sea construible es inequívocamente invocable en ambos sentidos: si typeof fn === ''function'' pero isConstructable(fn) === false , tenemos una función de solo llamada, como una flecha, un generador o un método.

Así que los cuatro casos de interés son la class {} y la function() {} más las formas encuadernadas de ambos. Todo lo demás que podemos decir es solo llamable. Tenga en cuenta que ninguna de las respuestas actuales menciona funciones vinculadas, pero estas introducen problemas significativos en cualquier comprobación heurística.

Como señala Balupton, la presencia o ausencia de un descriptor de propiedad para la propiedad ''llamante'' puede actuar como un indicador de cómo se creó una función. Un objeto exótico de función enlazada no tendrá esta propiedad propia incluso si la función que envuelve tiene. La propiedad existirá a través de la herencia de Function.prototype , pero esto es cierto también para los constructores de clases.

Del mismo modo, toString para un BFEO normalmente comenzará la "función" incluso si la función enlazada se creó con la clase. Ahora, una heurística para detectar BFEOs en sí misma sería ver si su nombre comienza ''atado'', pero desafortunadamente esto es un callejón sin salida; Todavía no nos dice nada sobre lo que estaba vinculado, esto es opaco para nosotros.

Sin embargo, si toString devuelve ''clase'' (lo cual no será cierto para, por ejemplo, los constructores de DOM), esa es una señal bastante sólida de que no se puede llamar.

Lo mejor que podemos hacer es algo como esto:

const isDefinitelyCallable = fn => typeof fn === ''function'' && !isConstructable(fn); isDefinitelyCallable(class {}); // false isDefinitelyCallable(class {}.bind()); // false isDefinitelyCallable(function() {}); // false <-- callable isDefinitelyCallable(function() {}.bind()); // false <-- callable isDefinitelyCallable(() => {}); // true isDefinitelyCallable((() => {}).bind()); // true isDefinitelyCallable(async () => {}); // true isDefinitelyCallable(async function() {}); // true isDefinitelyCallable(function * () {}); // true isDefinitelyCallable({ foo() {} }.foo); // true isDefinitelyCallable(URL); // false const isProbablyNotCallable = fn => typeof fn !== ''function'' || fn.toString().startsWith(''class'') || Boolean( fn.prototype && !Object.getOwnPropertyDescriptor(fn, ''prototype'').writable // or your fave ); isProbablyNotCallable(class {}); // true isProbablyNotCallable(class {}.bind()); // false <-- not callable isProbablyNotCallable(function() {}); // false isProbablyNotCallable(function() {}.bind()); // false isProbablyNotCallable(() => {}); // false isProbablyNotCallable((() => {}).bind()); // false isProbablyNotCallable(async () => {}); // false isProbablyNotCallable(async function() {}); // false isProbablyNotCallable(function * () {}); // false isProbablyNotCallable({ foo() {} }.foo); // false isProbablyNotCallable(URL); // true

Los casos con flechas indican dónde obtenemos respuestas que no nos gustan especialmente.

En la función isProbablyNotCallable, la última parte de la condición podría reemplazarse con otras verificaciones de otras respuestas; Elegí el de Miguel Mota aquí, ya que también funciona con (¿la mayoría?) Los constructores de DOM, incluso aquellos definidos antes de que se introdujeran las clases de ES. Pero en realidad no importa: cada posible control tiene un inconveniente y no hay un combo mágico.

Lo anterior describe, a mi entender, lo que es y no es posible en los ES contemporáneos. No responde a las necesidades que son específicas de ES5 y anteriores, aunque realmente en ES5 y anteriores, la respuesta a ambas preguntas siempre es "verdadera" para cualquier función.

El futuro

Hay una propuesta para una prueba nativa que haría que la ranura [[FunctionKind]] sea observable en la medida en que revele si una función se creó con la class :

https://github.com/caitp/TC39-Proposals/blob/master/tc39-reflect-isconstructor-iscallable.md

Si esta propuesta o algo parecido avanza, obtendríamos una manera de resolver este problema de manera concreta class al menos cuando se trata de class .

* Ignorando el caso del Anexo B [[IsHTMLDDA]].


Ejecutó algunos puntos de referencia de rendimiento en los diferentes enfoques mencionados en este hilo, aquí hay una descripción general:

Clase Nativa: Método de Props (el más rápido por 56x en ejemplos grandes y 15x en ejemplos triviales):

function isNativeClass (thing) { return typeof thing === ''function'' && thing.hasOwnProperty(''prototype'') && !thing.hasOwnProperty(''arguments'') }

Lo que funciona porque lo siguiente es cierto:

> Object.getOwnPropertyNames(class A {}) [ ''length'', ''name'', ''prototype'' ] > Object.getOwnPropertyNames(class A { constructor (a,b) {} }) [ ''length'', ''name'', ''prototype'' ] > Object.getOwnPropertyNames(class A { constructor (a,b) {} a (b,c) {} }) [ ''length'', ''name'', ''prototype'' ] > Object.getOwnPropertyNames(function () {}) [ ''length'', ''name'', ''arguments'', ''caller'', ''prototype'' ] > Object.getOwnPropertyNames(() => {}) > [ ''length'', ''name'' ]

Clase nativa: método de cadena (más rápido que el método de expresiones regulares en aproximadamente un 10%):

/** * Is ES6+ class * @param {any} value * @returns {boolean} */ function isNativeClass (value /* :mixed */ ) /* :boolean */ { return typeof value === ''function'' && value.toString().indexOf(''class'') === 0 }

Esto también puede ser útil para determinar una Clase Convencional:

// Character positions const INDEX_OF_FUNCTION_NAME = 9 // "function X", X is at index 9 const FIRST_UPPERCASE_INDEX_IN_ASCII = 65 // A is at index 65 in ASCII const LAST_UPPERCASE_INDEX_IN_ASCII = 90 // Z is at index 90 in ASCII /** * Is Conventional Class * Looks for function with capital first letter MyClass * First letter is the 9th character * If changed, isClass must also be updated * @param {any} value * @returns {boolean} */ function isConventionalClass (value /* :any */ ) /* :boolean */ { if ( typeof value !== ''function'' ) return false const c = value.toString().charCodeAt(INDEX_OF_FUNCTION_NAME) return c >= FIRST_UPPERCASE_INDEX_IN_ASCII && c <= LAST_UPPERCASE_INDEX_IN_ASCII }

También recomendaría revisar mi TypeChecker que incluye los casos de uso de lo anterior, a través del método isConventionalClass , el método isConventionalClass y un método isClass que comprueba ambos tipos.


Investigué un poco y descubrí que el objeto prototipo [ espec. 19.1.2.16 ] de las clases de ES6 parece que no se puede escribir , no se puede enumerar , no se puede configurar .

Aquí hay una manera de comprobar:

class F { } console.log(Object.getOwnPropertyDescriptor(F, ''prototype'')); // {"value":{},"writable":false,"enumerable":false,"configurable":false

Una función regular por defecto es de escritura , no enumerable , no configurable .

function G() { } console.log(Object.getOwnPropertyDescriptor(G, ''prototype'')); // {"value":{},"writable":true,"enumerable":false,"configurable":false}

ES6 Fiddle: http://www.es6fiddle.net/i7d0eyih/

Por lo tanto, un descriptor de clase ES6 siempre tendrá esas propiedades establecidas en falso y arrojará un error si intenta definir los descriptores.

// Throws Error Object.defineProperty(F, ''prototype'', { writable: true });

Sin embargo, con una función regular todavía puede definir esos descriptores.

// Works Object.defineProperty(G, ''prototype'', { writable: false });

No es muy común que los descriptores se modifiquen en las funciones regulares, por lo que probablemente pueda usar eso para verificar si es una clase o no, pero, por supuesto, esta no es una solución real.

El método de @alexpods para clasificar la función y verificar la palabra clave de la clase es probablemente la mejor solución en este momento.


Mirando el código compilado generado por Babel , creo que no hay forma de saber si una función se usa como clase. En el pasado, JavaScript no tenía clases, y cada constructor era solo una función. La palabra clave de clase de JavaScript de hoy no introduce un nuevo concepto de "clases", es más bien una sintaxis de azúcar.

Código ES6:

// ES6 class A{}

ES5 generado por Babel :

// ES5 "use strict"; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var A = function A() { _classCallCheck(this, A); };

Por supuesto, si te gustan las convenciones de codificación, puedes analizar la función (la clase) y verificar si su nombre comienza con una letra mayúscula.

function isClass(fn) { return typeof fn === ''function'' && /^(?:class/s+|function/s+(?:_class|_default|[A-Z]))/.test(fn); }

EDITAR:

Los navegadores que ya admiten la palabra clave de clase pueden usarla al analizar. De lo contrario, estás atascado con la letra mayúscula uno.

EDITAR:

Como señaló Balupton, Babel genera la function _class() {} para las clases anónimas. Mejora de expresiones regulares basadas en eso.

EDITAR:

Se agregó _default a la expresión regular, para detectar clases como export default class {}

Advertencia

BabelJS está en gran medida en desarrollo, y no hay garantía de que no cambien los nombres de las funciones predeterminadas en estos casos. Realmente, no debes confiar en eso.


Si entendí ES6 correctamente, la class tiene el mismo efecto que si estuviera escribiendo

var Foo = function(){} var Bar = function(){ Foo.call(this); } Bar.prototype = Object.create(Foo.prototype); Bar.prototype.constructor = Bar;

el error de sintaxis al escribir MyClass() sin keyword new es solo para evitar la contaminación del espacio global con variables destinadas a ser utilizadas por un objeto.

var MyClass = function(){this.$ = "my private dollar"; return this;}

si usted tiene

// $ === jquery var myObject = new MyClass(); // $ === still jquery // myObject === global object

pero si lo haces

var myObject = MyClass(); // $ === "My private dollar"

porque this en el constructor llamado como función se refiere a un objeto global, pero cuando se le llama con una palabra clave, Javascript primero crea un nuevo objeto vacío y luego llama al constructor en él.