From 65f86ec58a44a8b7df26bfe7709b228a0ce308e0 Mon Sep 17 00:00:00 2001 From: Rong Zeng Date: Wed, 23 Apr 2014 00:03:52 -0500 Subject: [PATCH] latest --- es/index.html | 1411 +++++++++++++++++ site/favicon.ico => favicon.ico | Bin fi/index.html | 1019 ++++++++++++ hu/index.html | 1434 +++++++++++++++++ {site/image => image}/sidebar-icon.png | Bin index.html | 1499 ++++++++++++++++++ it/index.html | 1587 +++++++++++++++++++ ja/index.html | 1127 +++++++++++++ {site/javascript => javascript}/garden.js | 0 {site/javascript => javascript}/html5.js | 0 {site/javascript => javascript}/plugin.js | 0 {site/javascript => javascript}/prettify.js | 0 ko/index.html | 1103 +++++++++++++ pl/index.html | 1441 +++++++++++++++++ ru/index.html | 1062 +++++++++++++ {site/style => style}/garden.css | 0 {site/style => style}/print.css | 0 tr/index.html | 1508 ++++++++++++++++++ zh/index.html | 1255 +++++++++++++++ zhtw/index.html | 1197 ++++++++++++++ 20 files changed, 15643 insertions(+) create mode 100644 es/index.html rename site/favicon.ico => favicon.ico (100%) create mode 100644 fi/index.html create mode 100644 hu/index.html rename {site/image => image}/sidebar-icon.png (100%) create mode 100644 index.html create mode 100644 it/index.html create mode 100644 ja/index.html rename {site/javascript => javascript}/garden.js (100%) rename {site/javascript => javascript}/html5.js (100%) rename {site/javascript => javascript}/plugin.js (100%) rename {site/javascript => javascript}/prettify.js (100%) create mode 100644 ko/index.html create mode 100644 pl/index.html create mode 100644 ru/index.html rename {site/style => style}/garden.css (100%) rename {site/style => style}/print.css (100%) create mode 100644 tr/index.html create mode 100644 zh/index.html create mode 100644 zhtw/index.html diff --git a/es/index.html b/es/index.html new file mode 100644 index 0000000..552b246 --- /dev/null +++ b/es/index.html @@ -0,0 +1,1411 @@ +Jardín de JavaScript

Introducción

Introducción

El Jardín de JavaScript es una guía de documentación acerca de las +partes más peculiares de este lenguaje de programación. Brinda consejos para evitar +los errores más comunes y sutiles, así como problemas de rendimiento y de malas +prácticas que los programadores menos experimentados en JavaScript pueden resolver +en sus esfuerzos por profundizar en el lenguaje.

+

El Jardín de JavaScript no prentende enseñar JavaScript. +Se recomienda un conocimiento sobre el lenguaje para entender los temas tratados en +esta guía. Con el fin de aprender los conceptos básicos del lenguaje, por favor +diríjase a la excelente guía de los desarrolladores de Mozilla.

+

Los autores

+

Esta guía es el trabajo de dos encantadores usuarios del foro Stack Overflow, +Ivo Wetzel (Escrito) y Zhang Yi Jiang (Diseño).

+

Colaboradores

+ +

Hosting

+

JavaScript Garden es hospedado en GitHub, además Cramer Development nos apoya +con un sitio espejo en JavaScriptGarden.info.

+

Licencia

+

El Jardín de JavaScript es publicado bajo la licencia MIT y es hospedado en +GitHub. Si encuentra algún error o errata por favor publique una incidencia o +envie un pull request a nuestro repositorio. También nos puede encontrar en la +sala de chat de JavaScript en Stack Overflow.

+

Objetos

Uso de objetos y propiedades

Todo en JavaScript actúa como un objeto, con las dos únicas excepciones de +null y undefined.

+
false.toString(); // 'false'
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

Un error muy común es el uso de literales númericos como objetos. +Esto se debe a un error en el parser de JavaScript que intenta analizar la +notación de puntos como un literal de punto flotante.

+
2.toString(); // lanza SyntaxError
+

Existe un par de soluciones que pueden utilizarse para hacer que los +literales númericos actúen como objetos.

+
2..toString(); // el segundo punto es reconocido correctamente
+2 .toString(); // observe el espacio a la izquierda del punto
+(2).toString(); // el número 2 se evalúa primero
+

Objetos como un tipo de datos

+

Los objetos en JavaScript también pueden ser utilizados como una Tabla Hash o conocido como Hashmap en inglés, consisten +principalmente en nombres de propiedades, y asignándoles valores a éstas.

+

El uso de un objeto literal - con notación {} - puede crear un +objeto plano. Este nuevo objeto heredado desde Object.prototype +no posee propiedades propias definidas.

+
var foo = {}; // un nuevo objeto vacío
+
+// un nuevo objeto con la propiedad llamada 'test' con el valor 12
+var bar = {test: 12};
+

Acceso a las propiedades

+

Se puede acceder a las propiedades de un objeto de dos maneras, ya sea a través de la +notación de punto o desde la notación de corchetes.

+
var foo = {name: 'kitten'}
+foo.name; // kitten
+foo['name']; // kitten
+
+var get = 'name';
+foo[get]; // kitten
+
+foo.1234; // SyntaxError
+foo['1234']; // ¡funciona!
+

Ambas notaciones son idénticas en su funcionamiento, la única diferencia es la +notación de corchetes permite el ajuste dinámico de las propiedades, así como +el uso de propiedades que de otro modo daría lugar a error de sintaxis.

+

Eliminando propiedades

+

La única manera de eliminar una propiedad desde un objeto es usando el +operador delete; establecer la propiedad a undefined o null solamente +elimina el valor asociado a la propiedad, pero no la key (valor clave).

+
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

Los resultados de la salida son bar undefined y foo null - sólo baz ha +sido removido y por lo tanto no aparece en la salida.

+

Notación de Keys

+
var test = {
+    'case': 'Soy una palabra clave y debo ser anotado como string',
+    delete: 'Soy una palabra clave también' // lanza SyntaxError
+};
+

Las propiedades de los objetos puede ser simbolizados como caracteres planos y como strings. Debido +a otro mal diseño del parser de JavaScript, lo anterior es una excepción +de SyntaxError antes de ECMAScript 5.

+

Este error se debe al eliminar una keyword; por lo tanto, debe ser +anotado como un string literal para asegurarse que será interpretado correctamente +por diversos motores de JavaScript.

+

Prototipo

JavaScript no posee en sus características un sistema clásico de herencia, sino que +utiliza un prototipo para esto.

+

Si bien a menudo se considera uno de los puntos débiles de JavaScript, el +modelo de herencia prototipado es de hecho más poderoso que el modelo clásico. +Por ejemplo, es bastante trivial construir un modelo clásico a partir del modelo prototipado, +mientras que al contrario es una tarea mucho más difícil.

+

Debido al hecho que JavaScript es básicamente el único lenguaje que utiliza +ampliamente la herencia prototipada, se necesita algo de tiempo para adaptarse a +las diferencias entre los dos modelos.

+

La primera gran diferencia es que la herencia en JavaScript se realiza usando +llamadas de cadenas de prototipo (prototype chains).

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// Asigna el prototipo de Bar como una nueva instancia de Foo
+Bar.prototype = new Foo();
+Bar.prototype.foo = 'Hello World';
+
+// Asegura que el constructor sea Bar
+Bar.prototype.constructor = Bar;
+
+var test = new Bar() // crea una nueva instancia de Bar
+
+// Resultado de cadena de prototipos (prototype chain)
+test [instance of Bar]
+    Bar.prototype [instance of Foo] 
+        { foo: 'Hello World' }
+        Foo.prototype
+            { method: ... }
+            Object.prototype
+                { toString: ... /* etc. */ }
+

En el código anterior, el objeto test hereda de Bar.prototype y Foo.prototype; +por lo tanto, tendrá acceso a la función method que se ha definido en Foo. +También se tendrá acceso a a la propiedad value de la única instancia de Foo +que compone su prototipo. Es importante tomar en cuenta que new Bar() no creará una nueva +instancia de Foo, pero retornará lo asignado en su prototipo; de este modo, todas las instancias +de Bar tendrán que compartir el mismo valor de la propiedad.

+ +

Búsqueda de propiedades

+

Cuando se accede a las propiedades de un objeto, JavaScript recorre la cadena de +prototipo hacia arriba hasta encontrar la propiedad con el nombre solicitado.

+

Cuando se llega al final de la cadena - concretamente Object.prototype - y aún +no se ha encontrado la propiedad especificada, se retornará un valor +undefined en su lugar.

+

La propiedad prototype

+

Aunque la propiedad prototype es usada por el lenguaje para construir la cadena +de prototipos, es posible asignar cualquier valor. Aunque los tipos primitivos +serán ignorados cuando se asigne en prototype.

+
function Foo() {}
+Foo.prototype = 1; // no tendrá efecto
+

La asignación de objetos, como se muestra en el ejemplo anterior, funcionará, y permitirá +la creación dinámica de cadena de prototipos.

+

Rendimiento

+

El tiempo tomado en la búsqueda de propiedades es alta y la cadena de prototipo puede +presentar un impacto negativo crítico en el rendimiento en partes del código. Además, +si ha tratado de acceder a propiedades que no existen, esto provoca que se recorra la cadena de prototipo completa.

+

Además, al recorrer en iteración las propiedades de un objeto +, cada propiedad encontrada en la cadena de prototipo será enumerada.

+

Extensión de prototipos nativos

+

Una mala característica que se suele utilizar para extender Object.prototype o cualquier +otro prototipo construido.

+

Esta técnica es conocida en inglés como monkey patching y rompe la encapsulación del código. +Si bien es utilizado en frameworks como Prototype, todavía no existen buenas razones para adoptarlo o integrarlo +como tipos de dato o como funcionalidad no estándar.

+

La única razón coherente para extender un prototipo es para adaptarle nuevas +características de los motores JavaScript más modernos; por ejemplo, +Array.forEach.

+

En conclusión

+

Se debe entender por completo el módelo de herencia prototipado antes de +escribir código complejo que lo utilice. Además, observe la longitud de la +cadena de prototipo y modifíquela si es necesario para evitar posibles problemas de +rendimiento. Con relación a los prototipos nativos, estos nunca deben ser extendidos a +menos que sea para mantener la compatibilidad con nuevas características de JavaScript.

+

hasOwnProperty

Con el fin de comprobar si un objeto posee una propiedad definida en sí mismo y no +en algún lugar de su cadena de prototipo, es necesario utilizar +el método hasOwnProperty ya que todos los objetos herendan de Object.prototype.

+ +

hasOwnProperty es la única utilidad en JavaScript que se ocupa de las propiedades +y no las salta en la cadena de prototipo.

+
// Envenenamiento en Object.prototype
+Object.prototype.bar = 1; 
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // true
+
+foo.hasOwnProperty('bar'); // false
+foo.hasOwnProperty('goo'); // true
+

Sólo hasOwnProperty retornará el resultado correcto y esperado, esto es +ensencial cuando se repite una iteración en las propiedades de cualquier objeto. No hay +otra maner de excluir las propiedades que no están definidas en el mismo objeto, pero +en alguna parte de su cadena de prototipo si.

+

hasOwnProperty como propiedad

+

JavaScript no protege el nombre de la propiedad hasOwnProperty; de este modo, si existe +la posibilidad de que un objeto tenga una propiedad con el mismo nombre, es necesario utilizar +hasOwnProperty como propiedad externa con el fin de obtener resultados correctos.

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Here be dragons'
+};
+
+foo.hasOwnProperty('bar'); // siempre devolverá false
+
+// Utilice otro objeto con hasOwnProperty y llamelo con 'this' para asignarlo a foo
+({}).hasOwnProperty.call(foo, 'bar'); // true
+

En conclusión

+

Cuando se necesite comprobar la existencia de una propiedad en un objeto, hasOwnProperty es +el único método para hacerlo. También se recomienda el uso de hasOwnProperty como +parte de un bucle for in, esto evitará errores desde +extenciones de prototipos nativos.

+

El bucle for in

Al igual que el operador in, el bucle for in también recorre sobre la +cadena de prototipo cuando este se repite en una iteración en las propiedades de un objeto.

+ +
// Envenenamiento en Object.prototype
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // Imprime ambos bar y moo
+}
+

Dado que no es posible cambiar el comportamiento del bucle for in en sí mismo, es +necesario filtrar las propiedades internas no deseadas dentro del bucle, +esto se hace mediante el uso del método hasOwnProperty del +Object.prototype.

+ +

Usando hasOwnProperty para filtrado

+
// Aún es el foo del código de arriba
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

Está versión es la única forma correcta de uso. Esto se debe sólo al uso de +hasOwnProperty que imprimirá moo. Cuando hasOwnProperty se omita, el código es +propenso a errores en los casos de prototipos nativos - ej. Object.prototype - +se ha extendedido.

+

Uno de los frameworks más usado que implementa estas funcionalidades es Prototype. Cuando el +framework es incluido, el bucle for in que no utilicen hasOwnProperty no podrá garantizar que +se interrumpa.

+

En conclusión

+

Se recomienda utilizar siempre el uso de hasOwnProperty. Nunca debe suponer
ningún entorno donde el código se ejecute, o si los prototipos +nativos han sido extendidos o no.

+

Funciones

La declaración de funciones y expresiones

Las funciones en JavaScript son las primeras clases de objetos. Esto significa que se +puede pasar como cualquier otro valor. Un uso común de está característica es pasar de +una función anónima a otra, posiblemente una función asíncrona. Esto se conoce como callback.

+

La declaración function

+
function foo() {}
+

La función anterior se carga así mismo antes de iniciar la ejecución del +programa; por lo tanto, está disponible en todo el scope (ámbito) de la aplicación +donde se ha definido, aunque hubiera sido llamado antes de definirse en el código.

+
foo(); // Funciona porque foo ha sido creado antes que este código se ejecute
+function foo() {}
+

La expresión function

+
var foo = function() {};
+

Este ejemplo asigna una función sin nombre y anónima a la variable foo.

+
foo; // 'undefined'
+foo(); // Lanza TypeError
+var foo = function() {};
+

Debido a la declaración de var, que carga el nombre de la variable foo antes +de la ejecución real del inicio del código, foo ya estará definidido cuando se +ejecute el script.

+

Pero se asigna sólo si ocurre en tiempo de ejecución, el valor de foo de forma +predetermina es undefined antes de que el código se ejecute.

+

Expresión nombre de función

+

Otro caso especial de asignación de nombre de funciones.

+
var foo = function bar() {
+    bar(); // Funciona
+}
+bar(); // ReferenceError
+

Aquí bar no está disponible en el ámbito externo (scope), ya que la función sólo es +asignada a foo; Sin embargo, dentro de bar si está disponible. Esto se debe a la forma +en como trabaja la resolución de nombres en JavaScript, el nombre de +la función esta siempre disponible en el ámbito local de la propia función.

+

Cómo trabaja this

JavaScript tiene un concepto diferente sobre el nombre especial this referido a la +mayoría de lenguajes de programación. Hay exactamente cinco formas distintas en donde +es posible ver el valor de this dentro de lo posible en el lenguaje.

+

El ámbito global (Global Scope)

+
this;
+

Cuando se utiliza this en el ámbito global, simplemente se refiere al objeto global.

+

Llamar a una función

+
foo();
+

Aquí this se refiere al objeto global.

+ +

Llamar a un método

+
test.foo(); 
+

En este ejemplo this se referiere a test.

+

Llamar a un constructor

+
new foo(); 
+

Llamar a una función que esta precedida por la palabra clave new actúa como +un constructor. Dentro de la función, this se refiere +al Objeto recién creado.

+

Ajuste explícito de this

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // array que se apilará
+foo.call(bar, 1, 2, 3); // resultados a = 1, b = 2, c = 3
+

Cuando se utiliza los métodos call o apply en Function.prototype, el valor de +this dentro de la función llamada se ajustará explícitamente al primer argumento +correspondiente a la llamada de la función.

+

Como resultado, el ejemplo anterior sobre los casos de métodos estos no se aplican, y this +dentro de foo puede establecerse en bar.

+ +

Errores comunes

+

Si bien en la mayoría de los casos esto tiene sentido, el primero puede cosiderarse como otro +mal diseño del lenguaje, ya que nunca tiene un uso práctico.

+
Foo.method = function() {
+    function test() {
+        // this es establecido como un objeto global
+    }
+    test();
+}
+

Un error común es que this dentro de test haga referencia a Foo, mientras que en +realidad esto no es así.

+

Con el fin de acceder a Foo desde dentro de test es necesario crear una variable local +dentro del método para referirse a Foo.

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // Use that instead of this here
+    }
+    test();
+}
+

that es justo un nombre normal, pero es comúnmente usado para referenciar a this +de forma externa. En combinación con closures, esto puede ser +también usado para pasar this como valor.

+

Asignación de métodos

+

Otra cosa que no funciona en JavaScript son los alias en las funciones, es decir, +asignar un método a una variable.

+
var test = someObject.methodTest;
+test();
+

Debido al primer caso, test actúa como una función de llamada; por lo que +this dentro de este no estará referido a someObject.

+

Mientras que la unión de this puede parecer una mala idea en un principio, esto es en +realidad lo que hace trabajar a la herencia de prototipo.

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

Cuando los métodos son llamados desde una instancia de Bar, this se referirá a una +instancia.

+

Closures y referencias

Una de las características más poderosas de JavaScript es la disponibilidad de closures (cerraduras), +esto significa que los ámbitos siempre podrán ser accedidos por ámbitos externos donde +fueron definidos. Dado que sólo el alcance es único en JavaScript en el +ámbito de la función, todas las funciones, por omisión, actúan como closures.

+

Emulando variables privadas

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

En este caso, Counter retorna dos closures. La función increment y la +función get. Ambas funciones mantienen el ámbito de la referencia de
Counter y, por lo tanto, siempre accede a la variable count que fue definido +en el ámbito.

+

¿Por qué las variables privadas trabajan?

+

Dado que no es posible referenciar o asignar ámbitos en JavaScript, no hay +manera de acceder a la variable count desde fuera. Sólo existe una forma para +interactuar con estos vía los dos closures.

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

El código anterior no ha cambiado la variable count en el ámbito de Counter, +desde foo.hack no es definido en ese ámbito. En su lugar se creará - o +se anulará - la variable global count.

+

Closures dentro de bucles

+

Un error frecuente en el uso de closures dentro de bucles, es como si se tratará +de copiar el valor del índice de la variable del bucle.

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);  
+    }, 1000);
+}
+

El código anterior no tendrá como salida los números del 0 al 9, sino +simplementemente se imprimirá el número 10 diez veces.

+

La función anónima hace referencia a i y se llama a +console.log, el bucle for ya ha terminado y finalizo el valor de +i a 10.

+

Con el fin de obtener el comportamiento deseado, es necesario crear una copia +del valor de i.

+

Evitando el problema de referencia

+

Con el fin de copiar el valor de la variable índice del bucle, lo mejor es utilizar +un contenedor anónimo.

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);  
+        }, 1000);
+    })(i);
+}
+

La función anónima externa llamará inmediatamente a i como su primer +argumento y recibirá la copia del valor de i como parámetro de e.

+

La función anónima que se pasa a setTimeout ahora es una referencia a +e, cuyo valor no han sido cambiados por el bucle.

+

No hay otra manera de lograr esto; se debe retornar una función desde +el contenedor anónimo, que tendrá el mismo comportamiento que el código +anterior.

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+

El objeto arguments

Cada ámbito de la función de JavaScript puede acceder a la variable especial arguments. +Está variable contiene una lista de todos los argumentos que se pasan a la función.

+ +

El objeto arguments no es un Array. Si bien cuenta con la semántica +de un array - concretamente la propiedad length - no hereda de +Array.prototype y es de hecho un Objeto.

+

Debido a esto, no es posible usar los métodos estándar de los arrays como push, +pop o slice en arguments. Mientras que la iteración es un simple bucle for que +funciona muy bien, esto se convierte necesariamente en un Array real con el +fin de utilizar los métodos de un Array.

+

Conversión de un Array

+

El siguiente código devuelve un nuevo Array que contiene todos los elementos del +objeto arguments.

+
Array.prototype.slice.call(arguments);
+

Esta conversión es lenta, no es recomendable usarlo en puntos criticos que +afecten el rendimiento del código.

+

Pasar Argumentos

+

El siguiente método es recomendado para pasar argumentos desde una función a +otra.

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // do stuff here
+}
+

Otro truco es utilizar tanto call y apply juntos para crear contenedores rápidos y +consolidados.

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// Crea una versión sin consolidar de "method" 
+// Se toma los parámetros: this, arg1, arg2...argN
+Foo.method = function() {
+
+    // Resultado: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+};
+

Los parámetros formales y argumentos de índices

+

El objeto arguments crea las funciones de getter y setter para sus +propiedades, así como parámetros formales de la función.

+

Como resultado, se ha cambiado el valor formal del parámetro también se cambio el +valor de la propiedad correspondiente del objeto arguments, y al revés.

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2                                                           
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

Mitos y verdades sobre el rendimiento

+

El objeto arguments es siempre creado con las dos únicas excepciones cuando es +el caso en que declarado como un nombre dentro de la función o uno de los +parámetros formales. No importa si se utiliza o no.

+

Ambos getters y setters son siempre creados; por lo tanto, con que casi no se +tiene un impacto en el rendimiento en todo, especialemente no en el código real donde no +es más que un simple acceso a las propiedades del objeto arguments.

+ +

Sin embargo, hay casos en que se reducirá drásticamente el rendimiento en los motores +modernos de JavaScript. Este es el caso del uso de arguments.callee.

+
function foo() {
+    arguments.callee; // realiza algo con la función del objeto
+    arguments.callee.caller; // y llama a la función del objeto
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // Debería ser normalmente entre líneas...
+    }
+}
+

El código anterior, foo no puede estar sujeto a la expansión en línea ya que se +necesita saber acerca de sí mismo y la llamada. Esto no sólo denota los posibles beneficios +de rendimiento que surgen con la expansión en línea, ya que también interrumpe la encapsulación +ya que la función ahora puede ser dependiente de un contexto específico de llamada.

+

Es muy recomendable nunca hacer uso de arguments.callee o de cualquier +de sus propiedades.

+ +

Constructores

Los constructores en JavaScript todavía son diferentes a los de otros lenguajes. +Cualquier llamada que es precedida por la palabra new actua como un constructor.

+

Dentro del constructor - la función llama - el valor de this se refiere a un +Objeto recién creado. El prototipo de este nuevo +objeto se establece en el prototipo de la funcióno que es invocado como el +constructor.

+

Si la función que se llama no tiene una sentencia return explícita, entonces +implícitamente devuelve el valor de this - el nuevo objeto.

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

La llamada de Foo por encima del constructor y establece el prototipo del objeto +recién creado a Foo.prototype.

+

En caso explícito de la sentencia return de la función devuelva el valor especificado +que la declaración, pero sólo si el valor devuelto es un Object.

+
function Bar() {
+    return 2;
+}
+new Bar(); // a new object
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // the returned object
+

Cuando una nueva keyword es omitidad, la función no devuelve un nuevo objeto.

+
function Foo() {
+    this.bla = 1; // se establece en el objeto global
+}
+Foo(); // undefined
+

Auqnue el ejemplo anterior puede parecer que trabaja en algunos casos, debido +a los trabajos de this en JavaScript, que usará el +objeto global como valor de this.

+

Fábricas

+

Con el fin de ser capaz de omitir un nuevo keyword, la función del tiene +explícitamente devolver un valor.

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

Ambos llamadas a Bar devuelven exactamente lo mismo, un reciente objeto creado que +tiene como propiedad llamada el method, esto es un +Closure.

+

También hay que notar que la llamada new Bar() no afecta al prototipo +del objeto devuelto. Mientras que el prototipo se establece en el objeto recién creado, + Bar nunca devuelve un nuevo objeto.

+

En el ejemplo anterior, no hay diferencia funcional entre usar y no usar +el keyword new.

+

Creación de nuevos objetos vía Factorias

+

Una recomendación a menudo es no utilizar new ya que su uso puede +conducir a errores.

+

Con el fin de crear un nuevo objeto, uno bien debe utilizar una fábrica y un +constructor para crear un nuevo objeto dentro de la fábrica.

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

Aunque lo anterior es robuesto frente a la keyword new y, ciertamente hace +que el uso de variables privadas sea fácil, esto viene con +algunas desventajas.

+
    +
  1. Se utiliza más memoria, ya que los objetos creados no comparten los métodos de +un prototipo.
  2. +
  3. Con el fin de heredar de una fábrica se necesita copiar todos los métodos a otro +objeto o poner todo en un prototipo de nuevo objeto.
  4. +
  5. La eliminación de una cadena de prototipo sólo por dejar la keyword new de +alguna manera va en contra del espíritu del lenguaje.
  6. +
+

En conclusión

+

Mientras que se omite el keyword new podría dar a errores, no es ciertamente +una razón para abandonar el uso de prototipos por completo. Al final todo se reduce a +la solución que se adapta mejor a las necesidades de la aplicación, especialmente si es +importante elegir un estilo específico en la creación de objetos +y resistirse.

+

Ámbitos y Namespaces

A pesar que JavaScript tiene una muy buena sintaxis de dos llaves para los bloques, +está no es compatible con el soporte de ámbito de bloques; por lo que todo se deja +al lenguaje con el ámbito de la función.

+
function test() { // un ámbito
+    for(var i = 0; i < 10; i++) { // no es un ámbito
+        // cuenta
+    }
+    console.log(i); // 10
+}
+ +

Tampoco hay distintos namespaces en JavaScript, lo que significa que todo se define +en un namespace global y compartido.

+

Cada vez que una variable es referenciada, JavaScript recorre hacia arriba a través de todos +los ámbitos hasta encontrarlo. En este caso que llegue al ámbito global y todavía no ha +encontrado el nombre solicitado, se generará un error ReferenceError.

+

El terror de las variables globales

+
// script A
+foo = '42';
+
+// script B
+var foo = '42'
+

Estos dos scripts no tienen el mismo efecto. El script A define una variable +llamada foo en el ámbito global y el script B define foo en el +actual ámbito.

+

Una vez más, esto no tiene el mismo efecto para todo, no usar var puede tener +mayor implicación.

+
// ámbito global
+var foo = 42;
+function test() {
+    // ámbito local
+    foo = 21;
+}
+test();
+foo; // 21
+

Dejando de lador la sentencia var dentro de la función test sobre escribiría el +valor de foo. Si bien al principio puede parecer un gran cambio, se tiene +miles de líneas de código en JavaScript y no se usaría var introduciendose en un +horrible y difícil detección de errores.

+
// ámbito global
+var items = [/* some list */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // ámbito de subLoop
+    for(i = 0; i < 10; i++) { // falta la sentencia var
+        // ¡realizar cosas asombrosas!
+    }
+}
+

El bucle externo terminará después de la primera llamada a subLoop, desde subLoop +sobreescribe el valor global de i. Usando var para el segundo bucle for se hace +fácil evitar este error. La sentencia var no debe nunca dejarse a menos que +el efecto deseado es afectado por el ámbito exteriror.

+

Variables locales

+

La única fuente para las variables locales en JavaScript son los parámetros de la +función y variables que fueron declaradas vía la sentencia +var.

+
// ámbito global
+var foo = 1;
+var bar = 2;
+var i = 2;
+
+function test(i) {
+    // ámbito local de la función test
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

Mientras foo y i son variables locales dentro del ámbitor de la función test, +ela asignación de bar sobreescribe la variable global con el mismo nombre.

+

Hoisting

+

La declaración de hoists en JavaScript. Esto significa que tanto la declaración de var y +la función declarada se translada a la parte superior de su ámbito que lo contiene.

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

El código anterior transforma antes de ejecutarse. JavaScript mueve +la declaracione var aspi como las declaraciones de la función a la parte superior a +lo más cercano del ámbito circundante.

+
// declaraciones var movidas aquí
+var bar, someValue; // por omisión 'undefined'
+
+// la función declarada es movida aquí también
+function test(data) {
+    var goo, i, e; // se pierde el ámbito del bloque movido aquí
+    if (false) {
+        goo = 1;
+
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // falla con TypeError desde bar sigue en 'undefined'
+someValue = 42; // las asignaciones no se ven afectadas por hoisting
+bar = function() {};
+
+test();
+

La falta de alcance del bloque no sólo moverá la declaración var fuera de los bucles y +su contenido, sino también hará que los resultados de ciertos constructores if +no sean intuitivas.

+

En el código original la declaración de if si parecía modificar la variable +global goo, mientras actualmente este modifica la variable local - después hoisting +ha sido aplicado.

+

Sin el conocimiento acerca de hoisting, a continuación el código puede parecer +un ReferenceError.

+
// comprueba si SomeImportantThing ha iniciado
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

Pero, por supuesto, lo anterior funciona debido a que la declaración var es movida +a la parte superior del ámbito global.

+
var SomeImportantThing;
+
+// otro código podría iniciar SomeImportantThing aqui, o no
+
+// asegúrese de que está ahí
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

Name Resolution Order

+

All scopes in JavaScript, including the global scope, have the special name +this, defined in them, which refers to the current object.

+

Function scopes also have the name arguments, defined in +them, which contains the arguments that were passed to a function.

+

For example, when trying to access a variable named foo inside the scope of a +function, JavaScript will lookup the name in the following order:

+
    +
  1. In case there is a var foo statement in the current scope, use that.
  2. +
  3. If one of the function parameters is named foo, use that.
  4. +
  5. If the function itself is called foo, use that.
  6. +
  7. Go to the next outer scope, and start with #1 again.
  8. +
+ +

Namespaces

+

A common problem of having only one global namespace is the likeliness of running +into problems where variable names clash. In JavaScript, this problem can +easily be avoided with the help of anonymous wrappers.

+
(function() {
+    // a self contained "namespace"
+
+    window.foo = function() {
+        // an exposed closure
+    };
+
+})(); // execute the function immediately
+

Unnamed functions are considered expressions; so in order to +being callable, they must first be evaluated.

+
( // evaluate the function inside the paranthesis
+function() {}
+) // and return the function object
+() // call the result of the evaluation
+

There are other ways for evaluating and calling the function expression; which, +while different in syntax, do behave the exact same way.

+
// Two other ways
++function(){}();
+(function(){}());
+

In Conclusion

+

It is recommended to always use an anonymous wrapper for encapsulating code in +its own namespace. This does not only protect code against name clashes, but it +also allows for better modularization of programs.

+

Additionally, the use of global variables is considered bad practice. Any +use of them indicates badly written code that is prone to errors and hard to maintain.

+

Arrays

Iteración de un Array y sus propiedades

A pesar que los arrays en JavaScript son objetos, no existe un buena razón para +usarlo en un bucle for para una interación de este. De +hecho, hay un número de buenas razones contra el uso de for in en arrays.

+ +

Dado que el bucle for in enumera todas las propiedades que están en una cadena +de prototipo y la única manera para excluir estas propiedades es el uso de +hasOwnProperty, ya que es veinte veces más +lento que un bucle for normal.

+

Iteración

+

Con el fin de obtener el mejor rendimiento cuando se repite la interación de arrays, +es lo mejor hacer uso del clásico bucle for.

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

Hay una captura adicional en el ejemplo anterior, que es el almacenamiento de la +caché de longitud del array vía l = list.length.

+

Aunque la propiedad length es definida en el mismo array, todavía posee una sobrecarga +para realizar la búsqueda en cada interación del bucle. Y mientras que los últimos +motores de JavaScript pueden aplicar optimizaciones en este caso, no hay manera +de saber si el ćodigo se ejecutará en uno de estos nuevos motores nuevos o no.

+

De hecho, dejando de lado el almacenamiento en caché puede resultar que el bucle +inicie sólo la mitad de rápido que con la longitud de la caché.

+

La propiedad length

+

Mientras que getter de la propiedad length simplemente retorne el número de +elementos son contenidos en un array, el setter puede ser usado para +truncar el array.

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo; // [1, 2, 3]
+

La asignación de un menor número de longitud trunca al array, pero incrementando la +longitud no tiene ningún efecto sobre el array.

+

En conclusión

+

Para obtener el mejor rendimiento es recomendable siempre usar el bucle for +y alamacenar en caché la propiedad length. El uso del bucle for in en un array +es señal de un código mal escrito propenso a errores y un mal desempeño.

+

El constructor Array

Desde el constructor Array es ambiguo en la forma en que ocupa sus párametros, +es recomendable siempre el uso de arrays literales - la notación [] - +cuando se crean nuevos arrays.

+
[1, 2, 3]; // Resultado: [1, 2, 3]
+new Array(1, 2, 3); // Resultado: [1, 2, 3]
+
+[3]; // Resultado: [3]
+new Array(3); // Resultado: []
+new Array('3') // Resultado: ['3']
+

En casos cuando sólo hay un argumento pasado al constructor del Array, +y que el argumento es un Número, el contructor devolverá un array disperso +con la propiedad length establecida al valor del argumento. Esto debe señalarse +que la propiedad length sólo del nuevo array se establecerá de esa manera, +los índices reales de la matriz no se iniciará.

+
var arr = new Array(3);
+arr[1]; // undefined
+1 in arr; // falso, el índice no se ha establecido
+

El comportamiento de poder establecer la longitud de un array inicial sólo es útil +en algunos casos array, como la repetición de una cadena, en la que se evita el uso +del código de bucle for.

+
new Array(count + 1).join(stringToRepeat);
+

En conclusión

+

El uso de un constructor Array debe ser devuelto como sea posible. +Los literales son definitivamente preferidos. Estos son más cortos y tienen una +sintaxis más limpia; por lo tanto, también se incrementa la legibilidad del código.

+

Types

Equality and Comparisons

JavaScript has two different ways of comparing the values of objects for equality.

+

The Equality Operator

+

The equality operator consists of two equal signs: ==

+

JavaScript features weak typing. This means that the equality operator +coerces types in order to compare them.

+
""           ==   "0"           // false
+0            ==   ""            // true
+0            ==   "0"           // true
+false        ==   "false"       // false
+false        ==   "0"           // true
+false        ==   undefined     // false
+false        ==   null          // false
+null         ==   undefined     // true
+" \t\r\n"    ==   0             // true
+

The above table shows the results of the type coercion, and it is the main reason +why the use of == is widely regarded as bad practice. It introduces +hard-to-track-down bugs due to its complicated conversion rules.

+

Additionally, there is also a performance impact when type coercion is in play; +for example, a string has to be converted to a number before it can be compared +to another number.

+

The Strict Equality Operator

+

The strict equality operator consists of three equal signs: ===.

+

It works exactly like the normal equality operator, except that strict equality +operator does not perform type coercion between its operands.

+
""           ===   "0"           // false
+0            ===   ""            // false
+0            ===   "0"           // false
+false        ===   "false"       // false
+false        ===   "0"           // false
+false        ===   undefined     // false
+false        ===   null          // false
+null         ===   undefined     // false
+" \t\r\n"    ===   0             // false
+

The above results are a lot clearer and allow for early breakage of code. This +hardens code to a certain degree and also gives performance improvements in case +the operands are of different types.

+

Comparing Objects

+

While both == and === are stated as equality operators, they behave +differently when at least one of their operands happens to be an Object.

+
{} === {};                   // false
+new String('foo') === 'foo'; // false
+new Number(10) === 10;       // false
+var foo = {};
+foo === foo;                 // true
+

Here, both operators compare for identity and not equality; that is, they +will compare for the same instance of the object, much like is in Python +and pointer comparison in C.

+

In Conclusion

+

It is highly recommended to only use the strict equality operator. In cases +where types need to be coerced, it should be done explicitly +and not left to the language's complicated coercion rules.

+

The typeof Operator

The typeof operator (together with +instanceof) is probably the biggest +design flaw of JavaScript, as it is near of being completely broken.

+

Although instanceof still has its limited uses, typeof really has only one +practical use case, which does not happen to be checking the type of an +object.

+ +

The JavaScript Type Table

+
Value               Class      Type
+-------------------------------------
+"foo"               String     string
+new String("foo")   String     object
+1.2                 Number     number
+new Number(1.2)     Number     object
+true                Boolean    boolean
+new Boolean(true)   Boolean    object
+new Date()          Date       object
+new Error()         Error      object
+[1,2,3]             Array      object
+new Array(1, 2, 3)  Array      object
+new Function("")    Function   function
+/abc/g              RegExp     object (function in Nitro/V8)
+new RegExp("meow")  RegExp     object (function in Nitro/V8)
+{}                  Object     object
+new Object()        Object     object
+

In the above table, Type refers to the value that the typeof operator returns. +As can be clearly seen, this value is anything but consistent.

+

The Class refers to the value of the internal [[Class]] property of an object.

+ +

In order to retrieve the value of [[Class]], one has to make use of the +toString method of Object.prototype.

+

The Class of an Object

+

The specification gives exactly one way of accessing the [[Class]] value, +with the use of Object.prototype.toString.

+
function is(type, obj) {
+    var clas = Object.prototype.toString.call(obj).slice(8, -1);
+    return obj !== undefined && obj !== null && clas === type;
+}
+
+is('String', 'test'); // true
+is('String', new String('test')); // true
+

In the above example, Object.prototype.toString gets called with the value of +this being set to the object whose [[Class]] value should be +retrieved.

+ +

Testing for Undefined Variables

+
typeof foo !== 'undefined'
+

The above will check whether foo was actually declared or not; just +referencing it would result in a ReferenceError. This is the only thing +typeof is actually useful for.

+

In Conclusion

+

In order to check the type of an object, it is highly recommended to use +Object.prototype.toString because this is the only reliable way of doing so. +As shown in the above type table, some return values of typeof are not defined +in the specification; thus, they can differ across various implementations.

+

Unless checking whether a variable is defined, typeof should be avoided at +all costs.

+

The instanceof Operator

The instanceof operator compares the constructors of its two operands. It is +only useful when comparing custom made objects. Used on built-in types, it is +nearly as useless as the typeof operator.

+

Comparing Custom Objects

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // true
+new Bar() instanceof Foo; // true
+
+// This just sets Bar.prototype to the function object Foo
+// But not to an actual instance of Foo
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // false
+

Using instanceof with Native Types

+
new String('foo') instanceof String; // true
+new String('foo') instanceof Object; // true
+
+'foo' instanceof String; // false
+'foo' instanceof Object; // false
+

One important thing to note here is that instanceof does not work on objects +that originate from different JavaScript contexts (e.g. different documents +in a web browser), since their constructors will not be the exact same object.

+

In Conclusion

+

The instanceof operator should only be used when dealing with custom made +objects that originate from the same JavaScript context. Just like the +typeof operator, every other use of it should be avoided.

+

Type Casting

JavaScript is a weakly typed language, so it will apply type coercion +wherever possible.

+
// These are true
+new Number(10) == 10; // Number.toString() is converted
+                      // back to a number
+
+10 == '10';           // Strings gets converted to Number
+10 == '+10 ';         // More string madness
+10 == '010';          // And more 
+isNaN(null) == false; // null converts to 0
+                      // which of course is not NaN
+
+// These are false
+10 == 010;
+10 == '-10';
+ +

In order to avoid the above, use of the strict equal operator +is highly recommended. Although this avoids a lot of common pitfalls, there +are still many further issues that arise from JavaScript's weak typing system.

+

Constructors of Built-In Types

+

The constructors of the built in types like Number and String behave +differently when being used with the new keyword and without it.

+
new Number(10) === 10;     // False, Object and Number
+Number(10) === 10;         // True, Number and Number
+new Number(10) + 0 === 10; // True, due to implicit conversion
+

Using a built-in type like Number as a constructor will create a new Number +object, but leaving out the new keyword will make the Number function behave +like a converter.

+

In addition, having literals or non-object values in there will result in even +more type coercion.

+

The best option is to cast to one of the three possible types explicitly.

+

Casting to a String

+
'' + 10 === '10'; // true
+

By prepending an empty string, a value can easily be casted to a string.

+

Casting to a Number

+
+'10' === 10; // true
+

Using the unary plus operator, it is possible to cast to a number.

+

Casting to a Boolean

+

By using the not operator twice, a value can be converted a boolean.

+
!!'foo';   // true
+!!'';      // false
+!!'0';     // true
+!!'1';     // true
+!!'-1'     // true
+!!{};      // true
+!!true;    // true
+

Núcleo

¿Por qué no usar eval?

La función eval ejecuta un string como código JavaScript en el ámbito local.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

Pero eval sólo ejecutará en ámbito local cuando es llamado directamente y +el nombre de la función llamada es eval.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

El uso de eval debe evitarse a toda costa. El 99.9% de su "uso" puede +lograrse sin su uso..

+

eval disfrazado

+

Las funciones de tiempo de espera setTimeout y setInterval pueden +tomar un string como primer argumento. En este caso, el string siempre se ejecutará en +el ámbito global ya que eval no ha sido llamado directamente.

+

Problemas de seguridad

+

eval es también un problema de seguridad ya que ejecuta cualquier código enviado, +y nunca debe usarse con strings que no se conozcan o tengan un origen no confiable.

+

En conclusión

+

eval nunca debe ser usado, cualquier código que haga uso del mismo debe ser cuestionado +en su funcionamiento, rendimiento y seguridad. En caso de que se necesite trabajar con +eval, el diseño ha de ser cuestionado y no debe utilizarse en primer lugar, se +debe usar un mejor diseño, que no requiera el uso de eval.

+

undefined y null

JavaScript tiene dos valores distintos para nothing, el más útil de estos dos +es undefined.

+

El valor undefined

+

undefined es un tipo de dato con exactamente el mismo valor: undefined.

+

El lenguaje también define una variable global que tiene el valor de undefined, +Esta variable es también llamada undefined. Sin embargo, esta variable no es una +constante, ni es una palabra reservada del lenguaje. Esto significa que el valor +puede ser sobreescrito fácilmente.

+ +

Algunos ejemplos cuando el valor retorna undefined:

+
    +
  • Acceso a la variable global (sin modificar) undefined.
  • +
  • Retorna implícitamente las funciones que no posean la sentencia return.
  • +
  • Sentencia return que no retorna nada de forma explicíta.
  • +
  • Búsquedas de propiedades inexistentes.
  • +
  • Párametros de la función que no tienen ningún valor explicíto pasado.
  • +
  • Cualquier valor que se estable en undefined.
  • +
+

Manejar los cambios en el valor deChanges undefined

+

Dado que la variable undefined sólo tiene una copia del value de
undefined, assigna un nuevo valor que no cambie el valor del +tipo undefined.

+

Aún con el fin de comparar con el valor de undefined es necesario +recuperar el valor de undefined primero.

+

Con el fin de proteger una posible sobreescritura en la variable undefined, +una técnica común es agregar un párametro adicional a un +wrapper anónimo, que consiga ningún párametro que se le pase.

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // undefined en el ámbito local
+    // ahora hace referencia al valor
+
+})('Hello World', 42);
+

Otra forma de lograrlo un mismo efecto es declarar dentro un +wrapper.

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

La única diferencia, es que está versión es de 4 bytes más que utiliza, y en +caso se comprima y no hay otra declaración de 'var' dentro del +wrapper anónimo.

+

Uso de null

+

Mientras que undefined en el contexto del lenguaje JavaScript es muy usado +en el sentido tradicional como null, el actual null (ambos literal y de un tipo) +es más o menos que otro tipo de datos.

+

Es utilizado en algunos detalles internos de JavaScript (como declarar al final de un +cadena de prototipo estableciendo Foo.prototype = null), pero en casi todos los +casos, puede ser reemplazado por undefined.

+

Automatic Semicolon Insertion

Although JavaScript has C style syntax, it does not enforce the use of +semicolons in the source code, so it is possible to omit them.

+

JavaScript is not a semicolon-less language. In fact, it needs the +semicolons in order to understand the source code. Therefore, the JavaScript +parser automatically inserts them whenever it encounters a parse +error due to a missing semicolon.

+
var foo = function() {
+} // parse error, semicolon expected
+test()
+

Insertion happens, and the parser tries again.

+
var foo = function() {
+}; // no error, parser continues
+test()
+

The automatic insertion of semicolon is considered to be one of biggest +design flaws in the language because it can change the behavior of code.

+

How it Works

+

The code below has no semicolons in it, so it is up to the parser to decide where +to insert them.

+
(function(window, undefined) {
+    function test(options) {
+        log('testing!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+
+})(window)
+

Below is the result of the parser's "guessing" game.

+
(function(window, undefined) {
+    function test(options) {
+
+        // Not inserted, lines got merged
+        log('testing!')(options.list || []).forEach(function(i) {
+
+        }); // <- inserted
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        ); // <- inserted
+
+        return; // <- inserted, breaks the return statement
+        { // treated as a block
+
+            // a label and a single expression statement
+            foo: function() {} 
+        }; // <- inserted
+    }
+    window.test = test; // <- inserted
+
+// The lines got merged again
+})(window)(function(window) {
+    window.someLibrary = {}; // <- inserted
+
+})(window); //<- inserted
+ +

The parser drastically changed the behavior of the code above. In certain cases, +it does the wrong thing.

+

Leading Parenthesis

+

In case of a leading parenthesis, the parser will not insert a semicolon.

+
log('testing!')
+(options.list || []).forEach(function(i) {})
+

This code gets transformed into one line.

+
log('testing!')(options.list || []).forEach(function(i) {})
+

Chances are very high that log does not return a function; therefore, +the above will yield a TypeError stating that undefined is not a function.

+

In Conclusion

+

It is highly recommended to never omit semicolons; it is also advocated to +keep braces on the same line with their corresponding statements and to never omit +them for one single-line if / else statements. Both of these measures will +not only improve the consistency of the code, but they will also prevent the +JavaScript parser from changing its behavior.

+

Otros

setTimeout and setInterval

Since JavaScript is asynchronous, it is possible to schedule the execution of a +function by using the setTimeout and setInterval functions.

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // returns a Number > 0
+

When setTimeout gets called, it will return the ID of the timeout and schedule +foo to run in approximately one thousand milliseconds in the future. +foo will then get executed exactly once.

+

Depending on the timer resolution of the JavaScript engine that is running the +code, as well as the fact that JavaScript is single threaded and other code that +gets executed might block the thread, it is by no means a safe bet that one +will get the exact delay that was specified in the setTimeout call.

+

The function that was passed as the first parameter will get called by the +global object, which means that this inside the called function +refers to that very object.

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // this refers to the global object
+        console.log(this.value); // will log undefined
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

Stacking Calls with setInterval

+

While setTimeout only runs the function once, setInterval - as the name +suggests - will execute the function every X milliseconds, but its use is +discouraged.

+

When code that is being executed blocks the timeout call, setInterval will +still issue more calls to the specified function. This can, especially with small +intervals, result in function calls stacking up.

+
function foo(){
+    // something that blocks for 1 second
+}
+setInterval(foo, 100);
+

In the above code, foo will get called once and will then block for one second.

+

While foo blocks the code, setInterval will still schedule further calls to +it. Now, when foo has finished, there will already be ten further calls to +it waiting for execution.

+

Dealing with Possible Blocking Code

+

The easiest solution, as well as most controllable solution, is to use setTimeout within +the function itself.

+
function foo(){
+    // something that blocks for 1 second
+    setTimeout(foo, 100);
+}
+foo();
+

Not only does this encapsulate the setTimeout call, but it also prevents the +stacking of calls and it gives additional control. foo itself can now decide +whether it wants to run again or not.

+

Manually Clearing Timeouts

+

Clearing timeouts and intervals works by passing the respective ID to +clearTimeout or clearInterval, depending which set function was used in +the first place.

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

Clearing all timeouts

+

Because there is no built-in method for clearing all timeouts and/or intervals, +it is necessary to use brute force in order to achieve this functionality.

+
// clear "all" timeouts
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

There might still be timeouts that are unaffected by this arbitrary number; +therefore, is is instead recommended to keep track of all the timeout IDs, so +they can be cleared specifically.

+

Hidden use of eval

+

setTimeout and setInterval can also take a string as their first parameter. +This feature should never be used because it internally makes use of eval.

+ +
function foo() {
+    // will get called
+}
+
+function bar() {
+    function foo() {
+        // never gets called
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

Since eval is not getting called directly in this case, the string +passed to setTimeout will get executed in the global scope; thus, it will +not use the local variable foo from the scope of bar.

+

It is further recommended to not use a string for passing arguments to the +function that will get called by either of the timeout functions.

+
function foo(a, b, c) {}
+
+// NEVER use this
+setTimeout('foo(1,2, 3)', 1000)
+
+// Instead use an anonymous function
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

In Conclusion

+

Never should a string be used as the parameter of setTimeout or +setInterval. It is a clear sign of really bad code, when arguments need +to be supplied to the function that gets called. An anonymous function should +be passed that then takes care of the actual call.

+

Furthermore, the use of setInterval should be avoided because its scheduler is not +blocked by executing JavaScript.

+
\ No newline at end of file diff --git a/site/favicon.ico b/favicon.ico similarity index 100% rename from site/favicon.ico rename to favicon.ico diff --git a/fi/index.html b/fi/index.html new file mode 100644 index 0000000..a2bb8a2 --- /dev/null +++ b/fi/index.html @@ -0,0 +1,1019 @@ +JavaScript-puutarha

Johdanto

Oliot

Olioiden käyttö ja ominaisuudet

Kaikki muuttujat, kahta poikkeusta lukuunottamatta, käyttäytyvät JavaScriptissä oliomaisesti. Nämä poikkeukset ovat null sekä undefined.

+
false.toString(); // epätosi
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

Yleisesti luullaan ettei numeroliteraaleja voida käyttää olioina. Tämä johtuu viasta JavaScriptin parserissa. Se yrittää parsia numeron pistenotaatiota liukulukuliteraalina.

+
2.toString(); // palauttaa SyntaxError-virheen
+

Tämä voidaan välttää esimerkiksi seuraavasti.

+
2..toString(); // toinen piste tunnistuu oikein
+2 .toString(); // huomaa pisteen vasemmalla puolen oleva väli
+(2).toString(); // 2 arvioidaan ensi
+

Oliot tietotyyppinä

+

JavaScriptin olioita voidaan käyttää myös hajautustauluna, koska ne muodostavat pääasiassa avaimien ja niihin liittyvien arvojen välisen mappauksen.

+

Olioliteraalinotaatiota - {} - käyttäen voidaan luoda tyhjä olio. Tämä olio perii Object.prototype-olion eikä sille ole määritelty omia ominaisuuksia.

+
var foo = {}; // uusi, tyhjä olio
+
+// uusi, tyhjä olio, joka sisältää ominaisuuden 'test' arvolla 12
+var bar = {test: 12}; 
+

Pääsy ominaisuuksiin

+

Olion ominaisuuksiin voidaan päästä käsiksi kahta eri tapaa käyttäen. Siihen voidaan käyttää joko piste- tai hakasulkunotaatiota.

+
var foo = {name: 'kitten'}
+foo.name; // kitten
+foo['name']; // kitten
+
+var get = 'name';
+foo[get]; // kitten
+
+foo.1234; // SyntaxError
+foo['1234']; // toimii
+

Kumpikin notaatio toimii samalla tavoin. Ainut ero liittyy siihen, että hakasulkunotaation avulla ominaisuuksien arvoja voidaan asettaa dynaamisesti. Se sallii myös muuten hankalien, virheeseen johtavien nimien käyttämisen.

+

Ominaisuuksien poistaminen

+

Ainut tapa poistaa olion ominaisuus on käyttää delete-operaattoria. Ominaisuuden asettaminen joko arvoon undefined tai null poistaa vain siihen liittyneen arvon muttei itse avainta.

+
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

Yllä oleva koodi tulostaa sekä both undefined että foo null. Ainoastaan baz on poistettu. Täten sitä ei myöskään näy tulosteessa.

+

Avainnotaatio

+
var test = {
+    'case': 'Olen avainsana, joten minun tulee olla merkkijono',
+    delete: 'Myös minä olen avainsana' // palauttaa SyntaxError-virheen
+};
+

Olioiden ominaisuuksia voidaan notatoida käyttäen joko pelkkiä merkkejä tai merkkijonoja. Toisesta JavaScriptin suunnitteluvirheestä johtuen yllä oleva koodi palauttaa SyntaxError-virheen ECMAScript 5:ttä edeltävissä versioissa.

+

Tämä virhe johtuu siitä, että delete on avainsana. Täten se tulee notatoida merkkijonona. Tällöin myös vanhemmat JavaScript-tulkit ymmärtävät sen oikein.

+

Prototyyppi

JavaScript ei sisällä klassista perintämallia. Sen sijaan se käyttää prototyyppeihin pohjautuvaa ratkaisua.

+

Usein tätä pidetään JavaScriptin eräänä suurimmista heikkouksista. Itse asiassa prototyyppipohjainen perintämalli on voimakkaampi kuin klassinen malli. Sen avulla voidaan mallintaa klassinen malli melko helposti. Toisin päin mallintaminen on huomattavasti vaikeampaa.

+

JavaScript on käytännössä ainut laajasti käytetty kieli, joka tarjoaa tuen prototyyppipohjaiselle perinnälle. Tästä johtuen mallien väliseen eroon tottuminen voi viedä jonkin akaa.

+

Ensimmäinen suuri ero liittyy siihen, kuinka perintä toimii. JavaScriptissä se pohjautuu erityisiin prototyyppiketjuihin.

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// Aseta Barin prototyypin uuteen Foo-olioon
+Bar.prototype = new Foo();
+Bar.prototype.foo = 'Terve maailma';
+
+// Huolehdi siitä, että Bar on todellinen konstruktori
+Bar.prototype.constructor = Bar;
+
+var test = new Bar() // luo uusi bar
+
+// Prototyyppiketju
+test [Bar-olio]
+    Bar.prototype [Foo-olio] 
+        { foo: 'Terve maailma' }
+        Foo.prototype
+            { method: ... }
+            Object.prototype
+                { toString: ... /* jne. */ }
+

Yllä olio test perii sekä Bar.prototype- että Foo.prototype-olion. Tällöin se pääsee käsiksi Foo:ssa määriteltyy funktioon method. Se pääsee käsiksi myös ominaisuuteen value, jonka luotu Foo-olio sisältää prototyypissään. On tärkeää huomata, että new Bar() ei luo uutta Foo-oliota vaan käyttää uudelleen sen prototyyppiin asetettua. Tässä tapauksessa kaikki Bar-oliot jakavat siis saman value-ominaisuuden.

+ +

Ominaisuushaut

+

Kun olion ominaisuuksien arvoa haetaan, JavaScript käy prototyyppiketjua läpi ylöspäin, kunnes se löytää ominaisuuden nimeä vastaavan arvon.

+

Jos se saavuttaa ketjun huipun - Object.prototype-olion - eikä ole vieläkään löytänyt haettua ominaisuutta, se palauttaa undefined arvon sen sijaan.

+

Prototyyppi-ominaisuus

+

Vaikka Prototyyppi-ominaisuutta käytetään prototyyppiketjujen rakentamiseen, voidaan siihen asettaa mikä tahansa arvo. Mikäli arvo on primitiivi, se yksinkertaisesti jätetään huomiotta.

+
function Foo() {}
+Foo.prototype = 1; // ei vaikutusta
+

Kuten esimerkissä yllä, prototyyppiin on mahdollista asettaa olioita. Tällä tavoin prototyyppiketjuja voidaan koostaa dynaamisesti.

+

Suorituskyky

+

Prototyyppiketjussa korkealla olevien ominaisuuksien hakeminen voi hidastaa koodin kriittisiä osia. Tämän lisäksi olemattomien ominaisuuksien hakeminen käy koko ketjun läpi.

+

Ominaisuuksia iteroidessa prototyyppiketjun jokainen ominaisuus käydään läpi.

+

Natiivien prototyyppien laajentaminen

+

JavaScript mahdollistaa Object.prototype-olion sekä muiden natiivityyppien laajentamisen.

+

Tätä tekniikkaa kutsutaan nimellä apinapätsäämiseksi. Se rikkoo kapseloinnin. Vaikka yleisesti käytetyt alustat, kuten Prototype, käyttävätkin sitä, ei ole olemassa yhtään hyvää syytä, minkä takia natiivityyppejä tulisi laajentaa epästandardilla* toiminnallisuudella.

+

Ainut hyvä syy on uudempien JavaScript-tulkkien sisältämien ominaisuuksien siirtäminen vanhemmille alustoille. Eräs esimerkki tästä on Array.forEach.

+

Yhteenveto

+

Ennen kuin kirjoitat monimutkaista prototyyppiperintää hyödyntävää koodia, on olennaista, että ymmärrät täysin kuinka se toimii. Ota huomioon myös prototyyppiketjujen pituus ja riko niitä tarpeen mukaan välttääksesi suorituskykyongelmia. Huomioi myös, että natiiveja prototyyppejä ei tule laajentaa milloinkaan ellei kyse ole vain yhteensopivuudesta uudempien JavaScript-ominaisuuksien kanssa.

+

hasOwnProperty

Jotta voimme tarkistaa onko olion ominaisuus määritelty siinä itsessään, tulee käyttää erityistä Object.prototype-oliosta periytyvää hasOwnProperty-metodia. Tällä tavoin vältämme prototyyppiketjun sisältämät ominaisuudet.

+ +

hasOwnProperty on ainut JavaScriptin sisältämä metodi, joka käsittelee ominaisuuksia eikä käy prototyyppiketjun sisältöä läpi.

+
// Object.prototypen myrkyttäminen
+Object.prototype.bar = 1; 
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // tosi
+
+foo.hasOwnProperty('bar'); // epätosi
+foo.hasOwnProperty('goo'); // tosi
+

Ainoastaan hasOwnProperty palauttaa oikean ja odotetun tuloksen. Sen tietäminen on olennaista minkä tahansa olion ominaisuuksia iteroidessa. Tämä on ainut tapa löytää olion itsensä ominaisuudet prototyyppiketjusta riippumatta.

+

hasOwnProperty ominaisuutena

+

JavaScript ei suojele hasOwnProperty-metodin nimeä. Täten on mahdollista, että olio voi sisältää samannimisen ominaisuuden. Jotta voimme saada oikeita tuloksia, tulee sen sijaan käyttää ulkoista hasOwnProperty-metodia.

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Olkoon vaikka lohikäärmeitä'
+};
+
+foo.hasOwnProperty('bar'); // palauttaa aina epätoden
+
+// Käytä toisen olion hasOwnProperty-metodia ja kutsu sitä asettamalla
+// 'this' foohon
+({}).hasOwnProperty.call(foo, 'bar'); // tosi
+

Yhteenveto

+

Mikäli pitää selvittää kuuluuko ominaisuus olioon vai ei, ainoastaan hasOwnProperty voi kertoa sen. Tämän lisäksi on suositeltavaa käyttää hasOwnProperty-metodia osana jokaista for in-luuppia. Tällä tavoin voidaan välttää natiivien prototyyppien laajentamiseen liittyviä ongelmia.

+

for in-luuppi

Aivan kuten in-operaattori, myös for in-luuppi käy olion prototyyppiketjun läpi iteroidessaan sen ominaisuuksia.

+ +
// Object.prototypen myrkyttäminen
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // tulostaa sekä bar että moo
+}
+

Koska for in-luupin käytöstapaa ei voida muokata suoraan, tulee ei-halutut ominaisuudet karsia itse luupin sisällä. Tämä on mahdollista käyttäen Object.prototype-olion hasOwnProperty-metodia.

+ +

hasOwnProperty-metodin käyttäminen karsimiseen

+
// foo kuten yllä
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

Tämä versio on ainut oikea. Se tulostaa ainoastaan moo, koska se käyttää hasOwnProperty-metodia oikein. Kun se jätetään pois, on koodi altis virheille tapauksissa, joissa prototyyppejä, kuten Object.prototype, on laajennettu.

+

Prototype on eräs yleisesti käytetty ohjelmointialusta, joka tekee näin. Kun kyseistä alustaa käytetään, for in-luupit, jotka eivät käytä hasOwnProperty-metodia, menevät varmasti rikki.

+

Yhteenveto

+

On suositeltavaa käyttää aina hasOwnProperty-metodia. Ei ole kannattavaa tehdä ajoympäristöön tai prototyyppeihin liittyviä oletuksia.

+

Funktiot

Funktiomääreet ja lausekkeet

JavaScriptissä funktiot ovat ensimmäisen luokan olioita. Tämä tarkoittaa sitä, että niitä voidaan välittää kuten muitakin arvoja. Usein tätä käytetään takaisinkutsuissa käyttämällä nimettömiä, mahdollisesti asynkronisia funktioita.

+

function-määre

+
function foo() {}
+

Yllä oleva funktio hilataan ennen ohjelman suorituksen alkua. Se näkyy kaikkialle näkyvyysalueessaan, jossa se on määritelty. Tämä on totta jopa silloin, jos sitä kutsutaan ennen määrittelyään.

+
foo(); // Toimii, koska foo on luotu ennen kuin koodi suoritetaan
+function foo() {}
+

function-lauseke

+
var foo = function() {};
+

Tämä esimerkki asettaa nimeämättömän ja nimettömän funktion muuttujan foo arvoksi.

+
foo; // 'undefined'
+foo(); // tämä palauttaa TypeError-virheen
+var foo = function() {};
+

var on määre. Tästä johtuen se hilaa muuttujanimen foo ennen kuin itse koodia ryhdytään suorittamaan.

+

Sijoituslauseet suoritetaan vasta kun niihin saavutaan. Tästä johtuen foo saa arvokseen undefined ennen kuin varsinaista sijoitusta päästään suorittamaan.

+

Nimetty funktiolauseke

+

Nimettyjen funktioiden sijoitus tarjoaa toisen erikoistapauksen.

+
var foo = function bar() {
+    bar(); // Toimii
+}
+bar(); // ReferenceError
+

Tässä tapauksessa bar ei ole saatavilla ulommalla näkyvyysalueessa. Tämä johtuu siitä, että se on sidottu foo:n sisälle. Tämä johtuu siitä, kuinka näkyvyysalueet ja niihin kuuluvat jäsenet tulkitaan. Funktion nimi on aina saatavilla sen paikallisessa näkyvyysalueessa itsessään.

+

Kuinka this toimii

JavaScripting this toimii eri tavoin kuin useimmissa kielissä. Tarkalleen ottaen on olemassa viisi eri tapaa, joiden mukaan sen arvo voi määrittyä.

+

Globaali näkyvyysalue

+
this;
+

Kun this-muuttujaa käytetään globaalissa näkyvyysalueessa, viittaa se globaaliin olioon.

+

Funktiokutsu

+
foo();
+

Tässä tapauksessa this viittaa jälleen globaaliin olioon.

+ +

Metodikutsu

+
test.foo(); 
+

Tässä esimerkissä this viittaa test-olioon.

+

Konstruktorikutsu

+
new foo(); 
+

Funktiokutsu, jota edeltää new-avainsana toimii konstruktorina. Funktion sisällä this viittaa juuri luotuun Object-olioon.

+

this-arvon asettaminen

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // taulukko laajenee alla olevaksi
+foo.call(bar, 1, 2, 3); // tuloksena a = 1, b = 2, c = 3
+

Function.prototype-olion call- ja apply-metodeita käytettäessä this-ominaisuuden arvo määrittyy ensimmäisen annetun argumentin perusteella.

+

Seurauksena foo-funktion sisältämä this asettuu bar-olioon toisin kuin perustapauksessa.

+ +

Yleisiä ongelmakohtia

+

Useimmat näistä tapauksista ovat järkeviä. Ensimmäistä niistä tosin voidaan pitää suunnitteluvirheenä, jolle ei ole mitään järkevää käyttöä ikinä.

+
Foo.method = function() {
+    function test() {
+        // this asettuu globaaliin olioon
+    }
+    test();
+}
+

Yleisesti luullaan, että test-funktion sisältämä this viittaa tässä tapauksessa Foo-olioon. Todellisuudessa se ei kuitenkaan tee näin.

+

Jotta Foo-olioon voidaan päästä käsiksi test-funktion sisällä, tulee metodin sisälle luoda paikallinen muuttuja, joka viittaa Foo-olioon.

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // Käytä thatia thissin sijasta
+    }
+    test();
+}
+

that on normaali nimi, jota käytetään yleisesti viittaamaan ulompaan this-muuttujaan. Sulkeumia käytettäessä this-arvoa voidaan myös välittää edelleen.

+

Metodien sijoittaminen

+

JavaScriptissä funktioita ei voida nimetä uudelleen eli siis sijoittaa edelleen.

+
var test = someObject.methodTest;
+test();
+

Ensimmäisestä tapauksesta johtuen test toimii kuten normaali funktiokutsu; tällöin sen sisältämä this ei enää osoita someObject-olioon.

+

Vaikka this-arvon myöhäinen sidonta saattaa vaikuttaa huonolta idealta, se mahdollistaa prototyyppeihin pohjautuvan perinnän.

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

Kun method-metodia kutsutaan Bar-oliossa, sen this viittaa juurikin tuohon olioon.

+

Sulkeumat ja viitteet

Sulkeumat ovat eräs JavaScriptin voimakkaimmista ominaisuuksista. Näkyvyysalueilla on siis aina pääsy ulompaan näkyvyysalueeseensa. Koska JavaScriptissä ainut tapa määritellä näkyvyyttä pohjautuu funktionäkyvyyteen, kaikki funktiot käyttäytyvät oletuksena sulkeumina.

+

Paikallisten muuttujien emulointi

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

Tässä tapauksessa Counter palauttaa kaksi sulkeumaa. Funktion increment lisäksi palautetaan myös funktio get. Kumpikin funktio viittaa Counter-näkyvyysalueeseen ja pääsee siten käsiksi count-muuttujan arvoon.

+

Miksi paikalliset muuttujat toimivat

+

JavaScriptissä ei voida viitata näkyvyysalueisiin. Tästä seuraa ettei count-muuttujan arvoon voida päästä käsiksi funktion ulkopuolelta. Ainoastaan nämä kaksi sulkeumaa mahdollistavat sen.

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

Yllä oleva koodi ei muuta muuttujan count arvoa Counter-näkyvyysalueessa. Tämä johtuu siitä, että foo.hack-ominaisuutta ei ole määritelty kyseisessä näkyvyysalueessa. Sen sijaan se luo - tai ylikirjoittaa - globaalin muuttujan count.

+

Sulkeumat luupeissa

+

Usein sulkeumia käytetään väärin luuppien sisällä indeksimuuttujien arvon kopiointiin.

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);  
+    }, 1000);
+}
+

Yllä oleva koodi ei tulosta numeroita nollasta yhdeksään. Sen sijaan se tulostaa numeron 10 kymmenen kertaa.

+

Nimetön funktio saa viitteen i-muuttujaan console.log-kutsuhetkellä. Tällöin luuppi on jo suoritettu ja i:n arvoksi on asetettu 10.

+

Päästäksemme haluttuun lopputulokseen on tarpeen luoda kopio i:n arvosta.

+

Viiteongelman välttäminen

+

Voimme välttää ongelman käyttämällä nimetöntä käärettä.

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);  
+        }, 1000);
+    })(i);
+}
+

Nimetöntä ulkofunktiota kutsutaan heti käyttäen i:tä se ensimmäisenä argumenttina. Tällöin se saa kopion i:n arvosta parametrina e.

+

Nimetön funktio, jolle annetaan setTimeout sisältää nyt viitteen e:hen, jonka arvoa luuppi ei muuta.

+

Samaan lopputulokseen voidaan päästä myös palauttamalla funktio nimettömästä kääreestä. Tällöin se käyttäytyy samoin kuten yllä.

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+

arguments-olio

Jokainen JavaScriptin näkyvyysalue pääsee käsiksi erikoismuuttujaan nimeltään arguments. Tämä muuttuja sisältää listan kaikista funktiolle annetuista argumenteista.

+ +

arguments-olio ei ole Array. Sen semantiikka, erityisesti length-ominaisuus, muistuttaa taulukkoa. Tästä huolimatta se ei peri Array.prototype:stä ja on itse asiassa Object.

+

Tästä johtuen arguments-olioon ei voida soveltaa normaaleja taulukkometodeja, kuten push, pop tai slice. Vaikka iterointi onnistuukin for-luuppeja käyttäen, tulee se muuttaa aidoksi Array-olioksi ennen kuin siihen voidaan soveltaa näitä metodeja.

+

Array-olioksi muuttaminen

+

Alla oleva koodi palauttaa uuden Array-olion, joka sisältää arguments-olion kaikki jäsenet.

+
Array.prototype.slice.call(arguments);
+

Tämä muutos on luonteeltaan hidas eikä sitä suositella käytettävän suorituskykyä vaativissa osissa koodia.

+

Argumenttien antaminen

+

Funktiosta toiselle voidaan antaa argumentteja seuraavasti.

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // tee jotain
+}
+

Toinen keino on käyttää sekä call- että apply-funktioita yhdessä ja luoda nopeita, sitomattomia kääreitä.

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// Luo "metodin" sitomaton versio 
+// Se ottaa seuraavat parametrit: this, arg1, arg2...argN
+Foo.method = function() {
+
+    // Tulos: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+};
+

Muodolliset parametrit ja argumenttien indeksit

+

arguments-olio luo sekä getter- että setter-funktiot sekä sen ominaisuuksille että myös funktion muodollisille parametreille.

+

Tästä seuraa, että muodollisen parametrin arvon muuttaminen muuttaa myös arguments-olion vastaavan ominaisuuden arvoa ja toisin päin.

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

Suorituskykyyn liittyviä myyttejä ja totuuksia

+

arguments-olio luodaan aina paitsi jos se on jo julistettu nimenä funktiossa tai sen muodollisena parametrina. Tämä siitä huolimatta käytetäänkö sitä vai ei.

+

Sekä getter- ja setter-funktiot luodaan aina. Tästä seuraa, että niiden käytöllä ei ole juurikaan merkitystä suorituskyvyn kannalta.

+ +

On kuitenkin eräs tapaus, jossa suorituskyky kärsii. Tämä liittyy arguments.callee-ominaisuuden käyttöön.

+
function foo() {
+    arguments.callee; // tee jotain tällä funktio-oliolla
+    arguments.callee.caller; // ja kutsuvalla funktio-oliolla
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // normaalisti tämä olisi inline-optimoitu
+    }
+}
+

Yllä olevassa koodissa foo-kutsua ei voida käsitellä avoimesti, koska sen tulee tietää sekä itsestään että kutsujasta. Sen lisäksi, että se haittaa suorituskykyä, rikkoo se myös kapseloinnin. Tässä tapauksessa funktio voi olla riippuvainen tietystä kutsuympäristöstä.

+

On erittäin suositeltavaa ettei arguments.callee-ominaisuutta tai sen ominaisuuksia käytetä ikinä.

+ +

Konstruktorit

JavaScriptin konstruktorit eroavat monista muista kielistä selvästi. Jokainen funktiokutsu, joka sisältää avainsanan new toimii konstruktorina.

+

Konstruktorin - kutsutun funktion - this-muuttujan arvo viittaa luotuun Object-olioon. Tämän uuden olion prototyyppi asetetaan osoittamaan konstruktorin kutsuman funktio-olion prototyyppiin.

+

Mikäli kutsuttu funktio ei sisällä selvää return-lausetta, tällöin se palauttaa this-muuttujan arvon eli uuden olion.

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

Yllä Foo:ta kutsutaan konstruktorina. Juuri luodun olion prototyyppi asetetaan osoittamaan ominaisuuteen Foo.prototype.

+

Selvän return-lausekkeen tapauksessa funktio palauttaa ainoastaan määritellyn lausekkeen arvon. Tämä pätee tosin vain jos palautettava arvo on tyypiltään Object.

+
function Bar() {
+    return 2;
+}
+new Bar(); // uusi olio
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // palautettu olio
+

Mikäli new-avainsanaa ei käytetä, funktio ei palauta uutta oliota.

+
function Foo() {
+    this.bla = 1; // asetetaan globaalisti
+}
+Foo(); // undefined
+

Vaikka yllä oleva esimerkki saattaa näyttää toimivan joissain tapauksissa, viittaa this globaalin olion this-ominaisuuteen.

+

Tehtaat

+

Mikäli new-avainsanan käyttöä halutaan välttää, voidaan konstruktori pakottaa palauttamaan arvo.

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

Tässä tapauksessa molemmat Bar-funktion kutsut käyttäytyvät samoin. Kumpikin kutsu palauttaa olion, joka sisältää method-ominaisuuden. Kyseinen ominaisuus on sulkeuma.

+

On myös tärkeää huomata, että kutsu new Bar() ei vaikuta palautetun olion prototyyppiin. Vaikka luodun olion prototyyppi onkin asetettu, Bar ei palauta ikinä kyseistä prototyyppioliota.

+

Yllä olevassa esimerkissä new-avainsanan käytöllä tai käyttämällä jättämisellä ei ole toiminnan kannalta mitään merkitystä.

+

Tehtaiden käyttö uusien olioiden luomiseen

+

Usein suositellaan new-avainsanan käytön välttämistä. Tämä johtuu siitä, että sen käyttämättä jättäminen voi johtaa bugeihin.

+

Sen sijaan suositellaan käytettävän tehdasta, jonka sisällä varsinainen olio konstruoidaan.

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

Vaikka yllä oleva esimerkki välttää new-avainsanan käyttöä ja tekee paikallisten muuttujien käytön helpommaksi, sisältää se joitain huonoja puolia.

+
    +
  1. Se käyttää enemmän muistia. Tämä johtuu siitä, että luodut oliot eivät jaa prototyypin metodeja.
  2. +
  3. Perinnän tapauksessa tehtaan tulee kopioida toisen olion kaikki metodit tai vaihtoehtoisesti asettaa kyseinen olio toisen prototyypiksi.
  4. +
  5. Prototyyppiketjun käsitteen unohtaminen on vain välttääksemme new-avainsanan käyttöä on vastoin kielen filosofista perustaa.
  6. +
+

Yhteenveto

+

Vaikka new-avainsanan käyttö voi johtaa bugeihin, prototyyppien käyttöä ei kannata unohtaa kokonaan. Loppujen lopuksi kyse on siitä, kumpi tapa sopii sovelluksen tarpeisiin paremmin. On erityisen tärkeää valita jokin tietty tapa ja pitäytyä sen käytössä.

+

Näkyvyysalueet ja nimiavaruudet

Vaikka JavaScript-käyttääkin aaltosulkeita blokkien ilmaisuun, se ei tue blokkinäkyvyyttä. Tämä tarkoittaa sitä, että kieli tukee ainoastaan *funktionäkyvyyttä.

+
function test() { // näkyvyysalue
+    for(var i = 0; i < 10; i++) { // tämä ei ole näkyvyysalue
+        // count
+    }
+    console.log(i); // 10
+}
+ +

JavaScript ei myöskään sisällä erityistä tukea nimiavaruuksille. Tämä tarkoittaa sitä, että kaikki määritellään oletuksena globaalissa nimiavaruudessa.

+

Joka kerta kun muuttujaan viitataan, JavaScript käy kaikki näkyvyysalueet läpi alhaalta lähtien. Mikäli se saavuttaa globaalin näkyvyystalueen, eikä löydä haettua nimeä, se palauttaa ReferenceError-virheen.

+

Riesa nimeltä globaalit muuttujat

+
// skripti A
+foo = '42';
+
+// skripti B
+var foo = '42'
+

Yllä olevat skriptit käyttäytyvät eri tavoin. Skripti A määrittelee muuttujan nimeltä foo globaalissa näkyvyysalueessa. Skripti B määrittelee foo-muuttujan vallitsevassa näkyvyysalueessa.

+

Tämä ei ole sama asia. var-avainsanan käyttämättä jättäminen voi johtaa vakaviin seurauksiin.

+
// globaali näkyvyysalue
+var foo = 42;
+function test() {
+    // paikallinen näkyvyysalue
+    foo = 21;
+}
+test();
+foo; // 21
+

var-avainsanan pois jättäminen johtaa siihen, että funktio test ylikirjoittaa foo:n arvon. Vaikka tämä ei välttämättä vaikutakaan suurelta asialta, tuhansien rivien tapauksessa var-avainsanan käyttämättömyys voi johtaa vaikeasti löydettäviin bugeihin.

+
// globaali näkyvyysalue
+var items = [/* joku lista */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // aliluupin näkyvyysalue
+    for(i = 0; i < 10; i++) { // hups, var jäi pois
+        // jotain makeaa ja hienoa
+    }
+}
+

Tässä tapauksessa ulomman luupin suoritus lopetetaan ensimmäisen subLoop-kutsun jälkeen. Tämä johtuu siitä, että se ylikirjoittaa i:n globaalin arvon. Mikäli jälkimmäisessä luupissa olisi käytetty var-avainsanaa, olisi ikävyyksiltä vältytty. var-avainsanaa ei siis tule ikinä jättää pois ellei siihen ole hyvää syytä.

+

Paikalliset muuttujat

+

Ainoastaan funktion parametrit ja muuttujat, jotka sisältävät var-määreen ovat paikallisia.

+
// globaali näkyvyysalue
+var foo = 1;
+var bar = 2;
+var i = 2;
+
+function test(i) {
+    // paikallinen näkyvyysalue
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

foo ja i ovatkin test-funktiolle paikallisia. bar sijoitus muuttaa globaalin muuttujan arvoa.

+

Hilaaminen

+

JavaScript hilaa määreitä. Tämä tarkoittaa sitä, että sekä var-lausekkeet että function-määreet siirretään ne sisältävän näkyvyysalueen huipulle.

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

Yllä olevaa koodia muutetaan ennen suoritusta. JavaScript siirtää var-lausekkeet ja function-määreet lähimmän näkyvyysalueen huipulle.

+
// var-lausekkeet siirrettiin tänne
+var bar, someValue; // oletuksena 'undefined'
+
+// myös funktio-määre siirtyi tänne
+function test(data) {
+    var goo, i, e; // ei blokkinäkyvyyttä, siirretään siis tänne
+    if (false) {
+        goo = 1;
+
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // TypeError-virhe, baria ei ole vielä määritelty
+someValue = 42; // hilaus ei koske sijoituksia
+bar = function() {};
+
+test();
+

Sen lisäksi, että puuttuva blokkinäkyvyys siirtää var-lausekkeet luuppien ulkopuolelle, tekee se myös eräistä if-rakenteista vaikeita käsittää.

+

Alkuperäisessä koodissa if-lause näytti muokkaavan globaalia muuttujaa goo. Todellisuudessa se muokkaa paikallista muuttujaa varsinaisen hilauksen jälkeen.

+

Seuraava koodi saattaisi ensi näkemältä aiheuttaa ReferenceError-virheen. Näin ei kuitenkaan tapahdu hilauksen ansiosta.

+
// onko SomeImportantThing alustettu
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

Tämä toimii, koska var-lauseke on hilattu globaalin näkyvyysalueen huipulle.

+
var SomeImportantThing;
+
+// mahdollista alustuskoodia
+
+// onhan se alustettu
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

Nimienerottelujärjestys

+

Kaikki JavaScriptin näkyvyysalueet, globaalin näkyvyysalue mukaanlukien, sisältävät erikoismuuttujan this. this viittaa tämänhetkiseen olioon.

+

Funktioiden näkyvyysalueet sisältävät myös arguments-olion. Se sisältää funktiolle annetut argumentit.

+

Mikäli näkyvyysalueen sisällä pyritään pääsemään käsiksi esimerkiksi foo:n arvoon JavaScript käyttäytyy seuraavasti:

+
    +
  1. Mikäli var foo-lauseke löytyy tämänhetkisestä näkyvyysalueesta, käytä sen arvoa.
  2. +
  3. Mikäli eräs funktion parametreista on foo, käytä sitä.
  4. +
  5. Mikäli funktion nimi itsessään on foo, käytä sitä.
  6. +
  7. Siirry ulompaan näkyvyysalueeseen ja suorita #1 uudelleen.
  8. +
+ +

Nimiavaruudet

+

Globaalin nimiavaruuden ongelmana voidaan pitää nimitörmäyksiä. JavaScriptissä tätä ongelmaa voidaan kiertää käyttämällä nimettömiä kääreitä.

+
(function() {
+    // "nimiavaruus" itsessään
+
+    window.foo = function() {
+        // paljastettu sulkeuma
+    };
+
+})(); // suorita funktio heti
+

Nimettömiä funktioita pidetään lauseina. Jotta niitä voidaan kutsua, tulee ne suorittaa ensin.

+
( // suorita sulkeiden sisältämä funktio
+function() {}
+) // ja palauta funktio-olio
+() // kutsu suorituksen tulosta
+

Samaan lopputulokseen voidaan päästä myös hieman eri syntaksia käyttäen.

+
// Kaksi muuta tapaa
++function(){}();
+(function(){}());
+

Yhteenveto

+

On suositeltavaa käyttää nimettömiä kääreitä nimiavaruuksina. Sen lisäksi, että se suojelee koodia nimitörmäyksiltä, se tarjoaa keinon jaotella ohjelma paremmin.

+

Globaalien muuttujien käyttöä pidetään yleisesti huonona tapana. Mikä tahansa niiden käyttö viittaa huonosti kirjoitettuun, virheille alttiiseen ja hankalasti ylläpidettävään koodiin.

+

Taulukot

Taulukon iterointi ja attribuutit

Vaikka taulukot ovatkin JavaScript-olioita, niiden tapauksessa ei välttämättä kannata käyttää for in loop-luuppia. Pikemminkin tätä tapaa tulee välttää.

+ +

for in-luuppi iteroi kaikki prototyyppiketjun sisältämät ominaisuudet. Tämän vuoksi tulee käyttää erityistä hasOwnProperty-metodia, jonka avulla voidaan taata, että käsitellään oikeita ominaisuuksia. Tästä johtuen iteroint on jo lähtökohtaisesti jopa kaksikymmentä kertaa hitaampaa kuin normaalin for-luupin tapauksessa.

+

Iterointi

+

Taulukkojen tapauksessa paras suorituskyky voidaan saavuttaa käyttämällä klassista for-luuppia.

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

Edelliseen esimerkkiin liittyy yksi mutta. Listan pituus on tallennettu välimuistiin erikseen käyttämällä l = list.length-lauseketta.

+

Vaikka length-ominaisuus määritelläänkin taulukossa itsessään, arvon hakeminen sisältää ylimääräisen operaation. Uudehkot JavaScript-ympäristöt saattavat optimoida tämän tapauksen. Tästä ei kuitenkaan ole mitään takeita.

+

Todellisuudessa välimuistin käytön pois jättäminen voi hidastaa luuppia jopa puolella.

+

length-ominaisuus

+

length-ominaisuuden getteri palauttaa yksinkertaisesti taulukon sisältämien alkioiden määrän. Sen setteriä voidaan käyttää taulukon typistämiseen.

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo; // [1, 2, 3]
+

Pituuden pienemmäksi asettaminen typistää taulukkoa. Sen kasvattaminen ei kuitenkaan vaikuta mitenkään.

+

Yhteenveto

+

Parhaan suorituskyvyn kannalta on parhainta käyttää tavallista for-luuppia ja tallentaa length-ominaisuus välimuistiin. for in-luupin käyttö taulukon tapauksessa on merkki huonosti kirjoitetusta koodista, joka on altis bugeille ja heikolle suorituskyvylle.

+

Array-konstruktori

Array-oletuskonstruktorin käytös ei ole lainkaan yksiselitteistä. Tämän vuoksi suositellaankin, että konstruktorin sijasta käytetään literaalinotaatiota [].

+
[1, 2, 3]; // Tulos: [1, 2, 3]
+new Array(1, 2, 3); // Tulos: [1, 2, 3]
+
+[3]; // Tulos: [3]
+new Array(3); // Tulos: []
+new Array('3') // Tulos: ['3']
+

Mikäli Array-konstruktorille annetaan vain yksi argumentti ja se on tyypiltään Number, konstruktori palauttaa uuden harvan taulukon, jonka length-attribuutti on asetettu annetun numeron mukaisesti. On tärkeää huomata, että ainoastaan length asetetaan tällä tavoin, todellisia taulukon indeksejä ei alusteta.

+
var arr = new Array(3);
+arr[1]; // undefined
+1 in arr; // false, indeksiä ei ole alustettu
+

Tämä on käytännöllistä vain harvoin, kuten merkkijonon toiston tapauksessa. Tällöin voidaan välttää for-luupin käyttämistä.

+
new Array(count + 1).join(stringToRepeat);
+

Yhteenveto

+

Array-konstruktorin käyttöä tulee käyttää niin paljon kuin suinkin mahdollista. Sen sijaan on suositeltavaa käyttää literaalinotaatiota. Literaalit ovat lyhyempiä ja niiden syntaksi on selkeämpi. Tämän lisäksi ne tekevät koodista luettavampaa.

+

Tyypit

Yhtäsuuruus ja vertailut

JavaScript sisältää kaksi erilaista tapaa, joiden avulla olioiden arvoa voidaan verrata toisiinsa.

+

Yhtäsuuruusoperaattori

+

Yhtäsuuruusoperaattori koostuu kahdesta yhtäsuuruusmerkistä: ==

+

JavaScript tyypittyy heikosti. Tämä tarkoittaa sitä, että yhtäsuuruusoperaattori muuttaa tyyppejä verratakseen niitä keskenään.

+
""           ==   "0"           // epätosi
+0            ==   ""            // tosi
+0            ==   "0"           // tosi
+false        ==   "false"       // epätosi
+false        ==   "0"           // tosi
+false        ==   undefined     // epätosi
+false        ==   null          // epätosi
+null         ==   undefined     // tosi
+" \t\r\n"    ==   0             // tosi
+

Yllä oleva taulukko näyttää tyyppimuunnoksen tulokset. Tämä onkin eräs pääsyistä, minkä vuoksi ==-operaattorin käyttöä pidetään huonona asiana. Sen käyttö johtaa hankalasti löydettäviin bugeihin monimutkaisista muunnossäännöistä johtuen.

+

Tämän lisäksi tyyppimuunnos vaikuttaa suorituskykyyn. Esimerkiksi merkkijono tulee muuttaa numeroksi ennen kuin sitä voidaan verrata toiseen numeroon.

+

Tiukka yhtäsuuruusoperaattori

+

Tiukka yhtäsuuruusoperaattori koostuu kolmesta yhtäsuuruusmerkistä: ===

+

Se toimii aivan kuten normaali yhtäsuuruusoperaattori. Se ei tosin tee minkäänlaista tyyppimuunnosta ennen vertailua.

+
""           ===   "0"           // epätosi
+0            ===   ""            // epätosi
+0            ===   "0"           // epätosi
+false        ===   "false"       // epätosi
+false        ===   "0"           // epätosi
+false        ===   undefined     // epätosi
+false        ===   null          // epätosi
+null         ===   undefined     // epätosi
+" \t\r\n"    ===   0             // epätosi
+

Yllä olevat tulokset ovat huomattavasti selkeämpiä ja mahdollistavat koodin menemisen rikki ajoissa. Tämä kovettaa koodia ja tarjoaa myös parempaa suorituskykyä siinä tapauksessa, että operandit ovat erityyppisiä.

+

Olioiden vertailu

+

Vaikka sekä == ja === ovat yhtäsuuruusoperaattoreita, ne toimivat eri tavoin, kun ainakin yksi operandeista sattuu olemaan Object.

+
{} === {};                   // epätosi
+new String('foo') === 'foo'; // epätosi
+new Number(10) === 10;       // epätosi
+var foo = {};
+foo === foo;                 // tosi
+

Tässä tapauksessa molemmat operaattorit vertaavat olion identiteettiä eikä sen arvoa. Tämä tarkoittaa sitä, että vertailu tehdään olion instanssin tasolla aivan, kuten Pythonin is-operaattorin tai C:n osoitinvertailun tapauksessa.

+

Yhteenveto

+

On erittäin suositeltavaa, että ainoastaan tiukkaa yhtäsuuruusoperaattoria käytetään. Mikäli tyyppejä tulee muuttaa, tämä kannattaa tehdä selvästi sen sijaan että luottaisi kielen monimutkaisiin muunnossääntöihin.

+

typeof-operaattori

typeof-operaattori, kuten myös instanceof, on kenties JavaScriptin suurin suunnitteluvirhe. Tämä johtuu siitä, että nämä ominaisuudet ovat liki kokonaan käyttökelvottomia.

+

Vaikka instanceof-operaattorilla onkin tiettyjä rajattuja käyttötarkoituksia, typeof-operaattorille on olemassa vain yksi käytännöllinen käyttötapaus, joka ei tapahdu olion tyyppiä tarkasteltaessa.

+ +

JavaScriptin tyyppitaulukko

+
Arvo                Luokka     Tyyppi
+-------------------------------------
+"foo"               String     string
+new String("foo")   String     object
+1.2                 Number     number
+new Number(1.2)     Number     object
+true                Boolean    boolean
+new Boolean(true)   Boolean    object
+new Date()          Date       object
+new Error()         Error      object
+[1,2,3]             Array      object
+new Array(1, 2, 3)  Array      object
+new Function("")    Function   function
+/abc/g              RegExp     object (Nitro/V8-funktio)
+new RegExp("meow")  RegExp     object (Nitro/V8-funktio)
+{}                  Object     object
+new Object()        Object     object
+

Yllä olevassa taulukossa Tyyppi viittaa arvoon, jonka typeof-operaattori palauttaa. Kuten voidaan havaita, tämä arvo voi olla varsin ristiriitainen.

+

Luokka viittaa olion sisäisen [[Luokka]]-ominaisuuden arvoon.

+ +

Jotta kyseiseen arvoon päästään käsiksi, tulee soveltaa Object.prototype-ominaisuuden toString-metodia.

+

Olion luokka

+

Määritelmä antaa tarkalleen yhden keinon, jonka avulla [[Luokka]] arvoon voidaan päästä käsiksi. Tämä on mahdollista Object.prototype.toString-metodia käyttäen.

+
function is(type, obj) {
+    var clas = Object.prototype.toString.call(obj).slice(8, -1);
+    return obj !== undefined && obj !== null && clas === type;
+}
+
+is('String', 'test'); // tosi
+is('String', new String('test')); // tosi
+

Yllä olevassa esimerkissä Object.prototype.toString-metodia kutsutaan arvolla this, jonka arvo on asetettu olion [[Luokka]] arvoon.

+ +

Määrittelemättömien muuttujien testaaminen

+
typeof foo !== 'undefined'
+

Yllä oleva testi kertoo onko foo määritelty. Pelkästään siihen viittaaminen palauttaisi ReferenceError-virheen. Tämä on ainut asia, johon typeof-operaattoria kannattaa käyttää.

+

Yhteenveto

+

Ainut tapa, jonka avulla olion tyyppi voidaan tarkistaa luotettavasti, on Object.prototype.toString-metodin käyttö, kuten yllä. Kuten yllä oleva tyyppitaulu näyttää, osa typeof-operaattorin palautusarvoista on huonosti määritelty. Tästä johtuen ne voivat erota toteutuksesta riippuen.

+

Muuttujan määrittelemättömyyden testaaminen on ainut tapaus, jossa typeof-operaattoria kannattaa käyttää. Muutoin sen käyttöä kannattaa välttää hinnalla milla hyvänsä.

+

instanceof-operaattori

instanceof-operaattori vertaa kahden operandinsa konstruktoreita keskenään. Se on hyödyllinen ainoastaan, kun vertaillaan itsetehtyjä olioita. Natiivien tyyppien tapauksessa se on lähes yhtä hyödytön kuin typeof-operaattori.

+

Itsetehtyjen olioiden vertailu

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // tosi
+new Bar() instanceof Foo; // tosi
+
+// Tämä asettaa vain Bar.prototype-ominaisuudeksi
+// funktio-olion Foo
+// Se ei kuitenkaan ole Foon todellinen instanssi
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // epätosi
+

instanceof ja natiivit tyypit

+
new String('foo') instanceof String; // tosi
+new String('foo') instanceof Object; // tosi
+
+'foo' instanceof String; // epätosi
+'foo' instanceof Object; // epätosi
+

On tärkeää huomata, että instanceof ei toimi olioilla, jotka tulevat muista JavaScript-konteksteista (esim. selaimen eri dokumenteista). Tässä tapauksessa niiden konstruktorit viittaavat eri olioon.

+

Yhteenveto

+

instanceof-operaattoria tulee käyttää ainoastaan, mikäli käsitellään itsetehtyjä olioita saman JavaScript-kontekstin sisällä. Kuten typeof-operaattorikin, myös muita sen käyttöjä tulee välttää.

+

Tyyppimuunnokset

JavaScript on tyypitetty heikosti. Tämä tarkoittaa sitä, että se pyrkii pakottamaan tyyppejä aina kun se on mahdollista.

+
// Nämä ovat totta
+new Number(10) == 10; // Number.toString() muutetaan
+                      // takaisin numeroksi
+
+10 == '10';           // Merkkijonot muutetaan Number-tyyppiin
+10 == '+10 ';         // Lisää merkkijonohauskuutta
+10 == '010';          // Ja lisää
+isNaN(null) == false; // null muuttuu nollaksi,
+                      // joka ei ole NaN
+
+// Nämä ovat epätosia
+10 == 010;
+10 == '-10';
+ +

Yllä havaittu käytös voidaan välttää käyttämällä tiukkaa vertailuoperaattoria. Sen käyttöä suositellaan lämpimästi. Vaikka se välttääkin useita yleisiä ongelma, sisältää se omat ongelmansa, jotka johtavat juurensa JavaScriptin heikkoon tyypitykseen.

+

Natiivien tyyppien konstruktorit

+

Natiivien tyyppien, kuten Number tai String, konstruktorit käyttäytyvät eri tavoin new-avainsanan kanssa ja ilman.

+
new Number(10) === 10;     // Epätosi, Object ja Number
+Number(10) === 10;         // Tosi, Number ja Number
+new Number(10) + 0 === 10; // Tosi, johtuu tyyppimuunnoksesta
+

Number-tyypin kaltaisen natiivityypin käyttäminen luo uuden Number-olion. new-avainsanan pois jättäminen tekee Number-funktiosta pikemminkin muuntimen.

+

Tämän lisäksi literaalit tai ei-oliomaiset arvot johtavat edelleen uusiin tyyppimuunnoksiin.

+

Paras tapa suorittaa tyyppimuunnoksia on tehdä niitä selvästi.

+

Muunnos merkkijonoksi

+
'' + 10 === '10'; // tosi
+

Arvo voidaan muuttaa merkkijonoksi helposti lisäämällä sen eteen tyhjä merkkijono.

+

Muunnos numeroksi

+
+'10' === 10; // tosi
+

Unaarinen plus-operaattori mahdollistaa numeroksi muuttamisen.

+

Muunnos totuusarvoksi

+

Arvo voidaan muuttaa totuusarvoksi käyttämällä not-operaattoria kahdesti.

+
!!'foo';   // tosi
+!!'';      // epätosi
+!!'0';     // tosi
+!!'1';     // tosi
+!!'-1'     // tosi
+!!{};      // tosi
+!!true;    // tosi
+

Ydin

Miksi eval-funktiota tulee välttää

eval suorittaa JavaScript-koodia sisältävän merkkijonon paikallisessa näkyvyysalueessa.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

eval suoritetaan paikallisessa näkyvyysalueessa ainoastaan kun sitä kutsutaan suorasti ja kutsutun funktion nimi on todellisuudessa eval.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

eval-funktion käyttöä tulee välttää ehdottomasti. 99.9% sen "käyttötapauksista" voidaan toteuttaa ilman sitä.

+

Piilotettu eval

+

Aikakatkaisufunktiot setTimeout and setInterval voivat kumpikin ottaa merkkijonon ensimmäisenä argumenttinaan. Kyseinen merkkijono suoritetaan aina globaalissa näkyvyysalueessa, koska tuolloin eval-funktiota kutsutaan epäsuorasti.

+

Turvallisuusongelmat

+

eval on myös turvallisuusongelma. Se suorittaa minkä tahansa sille annetun koodin. Tämän vuoksi sitä ei tule ikinä käyttää tuntemattomasta tai epäluotttavasta lähteestä tulevien merkkijonojen kanssa.

+

Yhteenveto

+

eval-funktiota ei pitäisi käyttää koskaan. Mikä tahansa sitä käyttävä koodi on kyseenalaista sekä suorituskyvyn että turvallisuuden suhteen. Mikäli jokin tarvitsee eval-funktiota toimiakseen, tulee sen suunnittelutapa kyseenalaistaa. Tässä tapauksessa on parempi suunnitella toisin ja välttää eval-funktion käyttöä.

+

undefined ja null

JavaScript sisältää kaksi erillistä arvoa ei millekään. Näistä hyödyllisempti on undefined.

+

undefined ja sen arvo

+

undefined on tyyppi, jolla on vain yksi arvo: undefined.

+

Kieli määrittelee myös globaalin muuttujan, jonka arvo on undefined. Myös tätä arvoa kutsutaan nimellä undefined. Tämä muuttuja ei kuitenkaan ole vakio eikä kielen avainsana. Tämä tarkoittaa siis sitä, että sen arvo voidaan ylikirjoittaa.

+ +

Seuraavat tapaukset palauttavat undefined-arvon:

+
    +
  • Globaalin (muokkaamattoman) muuttujan undefined arvon haku.
  • +
  • Puuttuvista return-lauseista seuraavat epäsuorat palautusarvot.
  • +
  • return-lauseet, jotka eivät palauta selvästi mitään.
  • +
  • Olemattomien ominaisuuksien haut.
  • +
  • Funktioparametrit, joiden arvoa ei ole asetettu.
  • +
  • Mikä tahansa, joka on asetettu arvoon undefined.
  • +
+

Arvon undefined muutosten hallinta

+

Koska globaali muuttuja undefined sisältää ainoastaan todellisen undefined-tyypin arvon kopion, ei sen asettamienn uudelleen muuta tyypin undefined arvoa.

+

Kuitenkin, jotta undefined-tyypin arvoa voidaan verrata, tulee sen arvo voida hakea jotenkin ensin.

+

Tätä varten käytetään yleisesti seuraavaa tekniikkaa. Ajatuksena on antaa itse arvo käyttäen nimetöntä käärettä.

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // paikallisen näkyvyysalueen undefined 
+    // voi viitata jälleen todelliseen arvoon
+
+})('Hello World', 42);
+

Samaan lopputuloksen voidaan päästä myös käyttämällä esittelyä kääreen sisällä.

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

Tässä tapauksessa ainut ero on se, että pakattu versio vie 4 tavua enemmän tilaa 'var'-lauseen vuoksi.

+

null ja sen käyttötapaukset

+

Vaikka undefined-arvoa käytetäänkin usein perinteisen null-arvon sijasta, todellinen null (sekä literaali että tyyppi) on enemmän tai vähemmän vain tietotyyppi.

+

Sitä käytetään joissain JavaScriptin sisäisissä toiminnoissa, kuten prototyyppiketjun pään toteamisessa (Foo.prototype = null). Useimmissa tapauksissa se voidaan korvata undefined-arvoa käyttäen.

+

Automaattiset puolipisteet

Vaikka JavaScript käyttääkin C:n tapaista syntaksia, se ei pakota käyttämään puolipisteitä. Niiden käyttöä voidaan halutessa välttää.

+

Tästä huolimatta JavaScript ei kuitenkaan ole puolipisteetön kieli. Se tarvitsee niitä ymmärtääkseen lähdekoodia. Tämän vuoksi JavaScript-parseri lisää niitä tarpeen mukaan automaattisesti.

+
var foo = function() {
+} // parsimisvirhe, lisätään puolipiste
+test()
+

Lisäys tapahtuu ja parseri yrittää uudelleen.

+
var foo = function() {
+}; // ei virhettä, parsiminen jatkuu
+test()
+

Automaattista puolipisteiden lisäämistä pidetään eräänä JavaScriptin suurimmista suunnitteluvirheistä. Tämä johtuu siitä, että se voi muuttaa tapaa, jolla koodi käyttäytyy.

+

Kuinka se toimii

+

Alla oleva koodi ei sisällä puolipisteitä. Täten niiden lisääminen jää parserin tehtäväksi.

+
(function(window, undefined) {
+    function test(options) {
+        log('testing!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+
+})(window)
+

Alla parserin arvaus.

+
(function(window, undefined) {
+    function test(options) {
+
+        // Not inserted, lines got merged
+        log('testing!')(options.list || []).forEach(function(i) {
+
+        }); // <- lisätty
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        ); // <- lisätty
+
+        return; // <- lisätty, rikkoo return-lauseen
+        { // kohdellaan lohkona
+
+            // nimike ja yhden lausekkeen lause
+            foo: function() {} 
+        }; // <- lisätty
+    }
+    window.test = test; // <- lisätty
+
+// Rivit yhdistettiin jälleen
+})(window)(function(window) {
+    window.someLibrary = {}; // <- lisätty
+
+})(window); //<- lisätty
+ +

Yllä olevassa tapauksessa parseri muutti huomattavasti koodin käytöstä. Joissain tapauksissa se tekee kokonaan väärän asian.

+

Johtavat sulkeet

+

Parseri ei lisää puolipistettä johtavien sulkeiden tapauksessa.

+
log('testing!')
+(options.list || []).forEach(function(i) {})
+

Koodi muuttuu seuraavaksi.

+
log('testing!')(options.list || []).forEach(function(i) {})
+

On hyvin mahdollista, että log ei palauta funktiota. Tästä johtuen yllä oleva palauttanee TypeError-virheen, joka toteaa että undefined ei ole funktio.

+

Yhteenveto

+

On suositeltavaa ettei puolipisteitä jätetä pois milloinkaan. Tämän lisäksi sulut kannattaa pitää niitä vastaavien lausekkeiden kanssa samalla rivillään. if ja else-lauseiden tapauksessa sulkuja kannattaa käyttää aina. Sen lisäksi että edellä mainitut suositukset tekevät koodista johdonmukaisempaa, estävät ne myös JavaScript-parseria muuttamasta sen käytöstapaa.

+

Muuta

setTimeout ja setInterval

Koska JavaScript on luonteeltaan asynkroninen, voidaan funktioiden suoritusta ajastaa käyttäen setTimeout sekä setInterval-funktioita.

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // palauttaa Numeron > 0
+

Kun setTimeout-funktiota kutsutaan, se palauttaa aikakatkaisun tunnisteen ja ajastaa foo-funktion suoritettavaksi suunnilleen tuhannen millisekunnin päästä. foo suoritetaan tarkalleen kerran.

+

Käytössä olevan JavaScript-tulkin ajastimen tarkkuudesta, JavaScriptin yksisäikeisyydestä sekä muusta koodista riippuen ei ole lainkaan taattua, että viive on tarkalleen sama kuin määritelty.

+

Ensimmäisenä annettu funktio suoritetaan globaalisti. Tämä tarkoittaa sitä, että sen this on asetettu osoittamaan globaaliin olioon.

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // this viittaa globaaliin olioon
+        console.log(this.value); // tulostaa undefined
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

Kutsujen pinoaminen setInterval-funktion avulla

+

setTimeout suoritetaan vain kerran. setInterval sen sijaan, kuten nimestä voi päätellä, suoritetaan aina X millisekunnin välein. Sen käyttöä ei kuitenkaan suositella.

+

Mikäli suoritettava koodi blokkaa katkaisufunktion kutsun, setInterval lisää kutsuja pinoon. Tämä voi olla ongelmallista erityisesti, mikäli käytetään pieniä intervalliarvoja.

+
function foo(){
+    // jotain joka blokkaa sekunnin ajaksi
+}
+setInterval(foo, 1000);
+

Yllä olevassa koodissa foo-funktiota kutsutaan, jonka jälleen se blokkaa sekunnin ajan.

+

Tämän ajan aikana setInterval kasvattaa kutsupinon sisältöä. Kun foo on valmis, kutsupinoon on ilmestynyt jo kymmenen uutta kutsua suoritettavaksi.

+

Mahdollisesti blokkaavan koodin kanssa pärjääminen

+

Helpoin ja joustavin tapa on käyttää setTimeout-funktiota funktiossa itsessään.

+
function foo(){
+    // jotain joka blokkaa sekunnin ajaksi
+    setTimeout(foo, 1000);
+}
+foo();
+

Sen lisäksi että tämä ratkaisu kapseloi setTimeout-kutsun, se myös estää kutsujen pinoutumisen ja tarjoaa joustavuutta. foo voi päättää halutaanko se suorittaa uudelleen vai ei.

+

Katkaisujen poistaminen käsin

+

Katkaisuja ja intervalleja voidaan poistaa antamalla sopiva tunniste joko clearTimeout- tai clearInterval-funktiolle. Se kumpaa käytetään riippuu käytetystä set-funktiosta.

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

Kaikkien katkaisujen poistaminen

+

JavaScript ei sisällä erityistä funktiota kaikkien katkaisujen ja/tai intervallien poistamiseen. Sen sijaan tämä voidaan toteuttaa raakaa voimaa käyttäen.

+
// poista "kaikki" katkaisut
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

On mahdollista, että jopa tämän jälkeen on olemassa katkaisuja, jotka ovat käynnissä. Onkin siis suositeltavaa tallentaa katkaisujen tunnisteet jotenkin. Tällä tavoin ne voidaan poistaa käsin.

+

Piilotettu eval

+

setTimeout ja setInterval voivat ottaa myös merkkijonon ensimmäisenä parametrinaan. Tätä ominaisuutta ei tule käyttää ikinä, koska se käyttää sisäisesti eval-funktiota.

+ +
function foo() {
+    // kutsutaan
+}
+
+function bar() {
+    function foo() {
+        // ei kutsuta ikinä
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

Koska eval-funktiota ei kutsuta suoraan, setTimeout-funktiolle annettu merkkijono suoritetaan globaalissa näkyvyysalueessa. Tässä tapauksessa se ei siis käytä paikallista bar-funktion näkyvyysalueessa olevaa foo-funktiota.

+

Tämän lisäksi on suositeltavaa olla käyttämättä merkkijonoja parametrien antamiseen.

+
function foo(a, b, c) {}
+
+// Älä käytä tätä IKINÄ
+setTimeout('foo(1,2, 3)', 1000)
+
+// Käytä nimetöntä funktiota sen sijaan
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

Yhteenveto

+

Merkkijonoa ei tule antaa setTimeout- tai setInterval-funktiolle koskaan. Tämä on selvä merkki erittäin huonosta koodista erityisesti mikäli sitä käytetään parametrien välittämiseen. Sen sijaan kannattaa käyttää nimetöntä funktiota, joka huolehtii varsinaisesta kutsusta.

+

Tämän lisäksi setInterval-funktion käyttöä tulee välttää. Tämä johtuu siitä, että sen JavaScript ei blokkaa sen vuorottajaa.

+
\ No newline at end of file diff --git a/hu/index.html b/hu/index.html new file mode 100644 index 0000000..78cb7df --- /dev/null +++ b/hu/index.html @@ -0,0 +1,1434 @@ +JavaScript Garden

Bevezető

Objektumok

Objektumok és mezők használata

A JavaSciprtben minden objektumként működik, a null és az undefined kivételével.

+
false.toString(); // 'hamis'
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

Gyakori tévhitként terjed, hogy a JavaScriptben a számok nem használhatóak objektumként. +Ez csak látszólag igaz, mivel a JavaScript a pont utáni részt úgy próbálja értelmezni, +mintha lebegőpontos számot látna. Így hibát kaphatunk.

+
2.toString(); // SyntaxErrort vált ki
+

Azonban számos kifejezés létezik megoldásként, amelyekkel megkerülhető ez a probléma.

+
2..toString(); // így a második pont már az objektumra utal
+2 .toString(); // fontos a space-t észrevenni itt a pont előtt
+(2).toString(); // a 2 értékelődik ki hamarabb
+

Objektumok mint adattípusok

+

Az objektumok JavaScriptben Hash táblaként is használhatóak, mivel természetszerűleg kulcs-érték párokat tartalmaznak.

+

Az objektum literál leírásával - {} jelöléssel - lehet létrehozni egy új objektumot. Ez az új objektum az Object.prototype-ból származik és nincsenek saját mezői definiálva.

+
var foo = {}; // egy új, üres objektum
+
+// egy új objektum egy 'test' nevű mezővel, aminek 12 az értéke
+var bar = {test: 12}; 
+

Mezők elérése

+

Egy objektum mezői kétféle módon érhetőek el, vagy az 'objektum.mezőnév' jelöléssel, +(Ford.: amit "dot notationként" emlegetünk) vagy a szögletes zárójelek kirakásával.

+
var foo = {name: 'macska'}
+foo.name; // macska
+foo['name']; // macska
+
+var get = 'name';
+foo[get]; // macska
+
+foo.1234; // SyntaxError
+foo['1234']; // működik
+

A két jelölés majdnem egyenértékűen használható, kivéve, hogy a szögletes zárójelekkel dinamkusan állíthatunk be mezőket és olyan neveket is választhatunk, amik amúgy szintaxis hibához vezetnének (Fordító: mivel a neveket stringbe kell rakni, megadhatunk a JS által "lefoglalt" kulcsszavakat is mezőnévként, habár ennek használata erősen kerülendő).

+

Mezők törlése

+

Egyetlen módon lehet mezőt törölni egy objektumból ez pedig a delete operátor +használata; a mező értékének undefined-ra vagy null-ra való állítása csak +magára az értékre van kihatással, de a kulcs ugyanúgy megmarad az objektumban.

+
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

A fenti ciklus a bar undefined és a foo null eredményeket fogja kiírni - +egyedül a baz mező került törlésre, és emiatt hiányzik is az outputról.

+

Kulcsok jelölése

+
var test = {
+    'case': 'Kulcsszó vagyok, ezért stringként kell leírnod',
+    delete: 'Én is az vagyok' // SyntaxError
+};
+

Az objektumok mezőnevei mind stringként, mind egyszerű szövegként (Ford.: aposztrófok nélkül) +leírhatóak. A JavaScript értelmező hibája miatt, a fenti kód azonban SyntaxErrort eredményez ECMAScript 5 előtti verzió esetén.

+

Ez a hiba onnan ered, hogy a delete egy kulcsszó, viszont érdemes string literálként +leírni hogy helyesen megértsék a régebbi JavaScript motorok is.

+

A Prototípus

A JavaScript nem a klasszikus öröklődést használja, hanem egy ún. prototípusos +származtatást használ.

+

Míg ezt gyakran a JavaScript legnagyobb hibái között tartják számon, valójában +ez a származtatási modell jóval kifejezőbb mint klasszikus barátja. +Ezt jelzi, hogy például sokkal könnyebb megépíteni a klasszikus modellt, alapul véve +a prototípusos modellt, míg a fordított irány kivitelezése igencsak nehézkes lenne.

+

A JavaScript az egyetlen széles körben elterjedt nyelv, amely ezt a származtatást +használja, így mindenképp időt kell szánni a két modell közti különbség megértésére.

+

Az első feltűnő különbség, hogy ez a fajta származtatás prototípus láncokat +használ.

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// Beállítjuk a Bar prototípusát a Foo egy új példányára
+Bar.prototype = new Foo(); // !
+Bar.prototype.foo = 'Hello World';
+
+// Beállítjuk a Bar konstruktorát
+Bar.prototype.constructor = Bar;
+
+var test = new Bar(); // új Bar példány létrehozása
+
+// A kapott prototípus lánc
+test [instance of Bar]
+    Bar.prototype [instance of Foo]
+        { foo: 'Hello World' }
+        Foo.prototype
+            { method: ... }
+            Object.prototype
+                { toString: ... /* stb. */ }
+

A fenti kódban a test objektum mind a Bar.prototype és Foo.prototype +prototípusokból származik, így lesz hozzáférése a method nevű függvényhez amely +a Foo prototípusában lett definiálva. A value mezőhöz szintén lesz hozzáférése, +amely akkor jött létre, amikor (szám szerint) egy új Foo példányt hoztunk létre. +Érdemes észrevenni hogy a new Bar() kifejezés nem hoz létre egy új Foo példányt +minden alkalommal, azonban újrahasználja azt az egyetlen (//!) inicilalizált Foo pédlányunkat. Így az összes Bar példány egy és ugyanazt a value mezőt (és +értéket) fogja használni.

+ +

Mezők keresése

+

Amikor olyan utasítást adunk ki, amellyel egy objektum mezőjét keressük, a +JavaScript felfele bejárja az egész prototípus láncot, amíg meg nem találja +a kért mezőt.

+

Hogyha eléri a lánc legtetejét - nevezetesen az Object.prototype-t és még +ekkor sem találja a kért mezőt, akkor az undefined-dal fog +visszatérni.

+

A Prototype mező

+

Alapjáraton, a JavaScript a prototype nevű mezőt használja a prototípus láncok +kialakításához, de ettől függetlenül ez is ugyanolyan mező mint a többi, és +bármilyen értéket belehet neki állítani. Viszont a primitív típusokat egyszerűen +figyelmen kívül fogja hagyni a feldolgozó.

+
function Foo() {}
+Foo.prototype = 1; // nincs hatása
+

Az objektumok megadása, mint azt a fentebbi példában láthattuk, hatással van a prototype +mezőkre és ezeknek az átállításával bele lehet szólni a prototípus láncok kialakításába.

+

Teljesítmény

+

Értelemszerűen, minnél nagyobb a prototípus lánc, annál tovább tart egy-egy mező +felkeresése, és ez rossz hatással lehet a kód teljesítményére. Emellett, ha egy +olyan mezőt próbálunk elérni amely nincs az adott objektum példányban, az mindig +a teljes lánc bejárását fogja eredményezni.

+

Vigyázat! Akkor is bejárjuk a teljes láncot, amikor egy objektum mezőin próbálunk iterálni.

+

Natív prototípusok bővítése

+

Egy gyakran elkövetett hiba, hogy az Object.prototype prototípust vagy egy másik előre +definiált prototípust próbálunk kiegészíteni új kóddal.

+

Ezt monkey patching-nek is hívják, és aktívan kerülendő, mivel megtöri +az egységbe zárás elvét. Habár ezt a technikát olyan népszerű framework-ök +is használják mint a Prototype, ettől függetlenül ne hagyjuk magunkat csőbe húzni; +nincs ésszerű indok arra, hogy összezavarjuk a beépített típusokat, további +nem standard saját funkcionalitással.

+

Az egyetlen ésszerű használati indok a natív prototípusokba nyúlásra az lehet, +hogy megpróbáljuk szimulálni az új JavaScript motorok szolgáltatásait régebbi társaikon, például az Array.forEach implementálásával.

+

Zárásként

+

Nagyon fontos megérteni a prototípusos származtatási modellt, mielőtt olyan +kódot próbálnánk írni, amely megpróbálja kihasználni a sajátosságait. Nagyon +oda kell figyelni a prototípuslánc hosszára - osszuk fel több kis láncra ha +szükséges - hogy elkerüljük a teljesítmény problémákat. Továbbá, a natív +prototípusokat soha ne egészítsük ki, egészen addig amíg nem akarunk +JavaScript motorok közötti kompatibilitási problémákat áthidalni.

+

hasOwnProperty

Hogy megtudjuk nézni egy adott objektum saját mezőit - azokat a mezőket amelyek +az objektumon közvetlenül vannak definiálva, és nem valahol a +prototípus láncon -, a hasOwnProperty függvényt használata +ajánlott, amelyet az összes objektum amúgy is örököl az Object.prototype-ból.

+ +

A hasOwnProperty függvény az egyetlen olyan dolog amelyik anélkül tudja ellenőrizni +az objektum mezőit, hogy megpróbálná bejárni a prototípus láncot.

+
// Az Object.prototype beszennyezése
+Object.prototype.bar = 1;
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // igaz
+
+foo.hasOwnProperty('bar'); // hamis
+foo.hasOwnProperty('goo'); // igaz
+

Hogy megértsük a fontosságát, egyedül a hasOwnProperty tudja hozni a korrekt +és elvárt eredményeket mezőellenőrzés szempontjából. Egyszerűen nincs más +módja annak, hogy kizárjuk a szűrésünkből azokat a mezőket amelyek nem az objektumon, +hanem valahol feljebb, a prototípus láncon lettek definiálva.

+

A hasOwnProperty mint mező

+

A JavaScript persze nem védi magát a hasOwnProperty nevet, így egy jókedvű +programozóban mindig megvan a lehetőség, hogy így nevezze el a saját függvényét. +Ennek kikerülése érdekében ajánlott mindig a hasOwnProperty-re kívülről hivatkozni +(Értsd: A hackelt -saját hasOwnPropertyvel ellátott- objektum kontextusán kívüli objektum hasOwnPropertyjét hívjuk meg).

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Mordor itt kezdődik'
+};
+
+foo.hasOwnProperty('bar'); // mindig hamissal tér vissza
+
+// Használhatjuk egy másik objektum hasOwnPropertyjét, 
+// hogy meghívjuk a foo-n.
+({}).hasOwnProperty.call(foo, 'bar'); // ez már igaz
+
+// Szintén jó megoldás lehet közvetlenül az 
+// Object prototypejából hívni ezt a függvényt.
+Object.prototype.hasOwnProperty.call(foo, 'bar'); // ez is igaz
+

Konklúzió

+

A hasOwnProperty használata az egyetlen megbízható módszer annak eldöntésére, +hogy egy mező közvetlenül az objektumon lett-e létrehozva. Melegen ajánlott a +hasOwnProperty-t minden for in ciklusban használni. +Használatával ugyanis elkerülhetjük a kontár módon kiegészített natív prototípusokból +fakadó esetleges hibákat, amire példát az imént láttunk.

+

A for in ciklus

Csak úgy mint a jó öreg in operátor, a for in is bejárja az egész +prototípus láncot, amikor egy objektum mezőin próbálnánk iterálni.

+ +
// Mérgezzük Object.prototypeot!
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // mind a moo és bar is kiírásra kerül
+}
+

Mivel -hála égnek- magának a for in ciklusnak a működését nem lehet befolyásolni, +így más módszert kell találnunk ahhoz hogy száműzzük a váratlan mezőket a ciklus magból. +(Értsd: Azokat amelyek a prototípus láncon csücsülnek csak). Ezt pedig az Object.prototype-ban +lakó hasOwnProperty függvény használatával érhetjük el.

+ +

Szűrés használata a hasOwnProperty-vel

+
// még mindig a fenti foo-nál tartunk
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

Ez az egyetlen helyes útja annak hogy az objektum saját mezőin iteráljunk csak végig. +Mivel a hasOwnProperty-t használjuk, így csak a várt moo-t fogja kiírni. Tehén jó +kódunk van! Hogyha a hasOwnProperty-t kihagynánk, a kódunk ki lenne téve nem várt +hibáknak, amik pl. abból fakadnak hogy valaki ocsmányul kiterjesztette az +Object.prototype-t.

+

Például, ha a Prototype frameworköt használjuk, és nem ilyen stílusban írjuk a +ciklusainkat, a hibák szinte garantáltak, ugyanis ők saját szájízükre kiterjesztik az +Object.prototype-t.

+

Konklúzió

+

A hasOwnProperty használata erősen javasolt. Soha ne éljünk pozitív +feltételezésekkel a futó kódot illetően, főleg olyan döntésekben nem érdemes +orosz rulettezni, mint hogy kiterjeszti-e valaki a natív prototípusokat vagy nem. +Mert általában igen.

+

Függvények

Függvény deklarációk és kifejezések

A függvények JavaScriptben egyben objektumok is. Ez azt jelenti, hogy +ugyanúgy lehet őket passzolgatni mint bármelyik más értékeket. Ezt a featuret +gyakran használják arra, hogy egy névtelen (callback) függvényt átadjunk +egy másik -aszinkron- függvény paramétereként.

+

A függvény deklaráció

+
function foo() {}
+

Ez a függvény felkerül a scope tetejére (hoisting), mielőtt a kód végrehajtása megtörténne. Így abban a scopeban ahol definiálták, mindenhol elérhető, +még abban a trükkös esetben is, hogyha a kód azon pontján hívjuk ezt a függvényt, mielőtt +definiáltuk volna (látszólag).

+
foo(); // Így is működik, mivel a foo fgv. létrejön mielőtt meghívnánk.
+function foo() {}
+

A függvény kifejezés (expression)

+
var foo = function() {};
+

A fentebbi példában egy névtelen függvényt adunk értékül a foo változónak.

+
foo; // 'undefined'
+foo(); // TypeError hiba
+var foo = function() {};
+

Habár ebben a példában a var deklaráció futás előtt a kód tetejére kúszik, +ettől függetlenül a foo mint függvény meghívásakor hibát fogunk kapni.

+

Ugyanis a deklaráció felkúszott, azonban az értékadás csak futásidőben fog megtörténni, +addig is a foo változó értéke undefined marad. Az undefinedet pedig hiába hívjuk függvényként, TypeErrort kapunk végeredményül.

+

Névvel ellátott függvény kifejezés

+

Egy másik érdekes eset, amikor névvel ellátott függvényeket adunk értékül változóknak.

+
var foo = function bar() {
+    bar(); // Működik
+}
+bar(); // ReferenceError
+

Ebben a példában a bart önmagában nem lehet elérni egy külső scopeból (utolsó sor), +mivel egyből értékül adtuk a foo változónak. Ennek ellenére a baron belül elérhető +a bar név. A tanulság az, hogy a függvény önmagát mindig eléri a saját scopeján belül, és ez a JavaScriptben található névfeloldásnak köszönhető.

+

A this mágikus működése

A this kicsit másképp működik a JavaScriptben mint ahogy azt megszokhattuk +más nyelvekben. Ugyanis pontosan ötféle módja lehet annak hogy a this +éppen mire utal a nyelvben.

+

A Globális hatókör

+
this;
+

Amikor globális hatókörben van használva a this, akkor pontosan a globális objektumra utal.

+

Függvény híváskor

+
foo();
+

Itt, a this megint a globális objektumra fog utalni.

+ +

Eljárás hívásakor

+
test.foo(); 
+

Ebben a példában a this a test objektumra fog hivatkozni.

+

Konstuktor hívásakor

+
new foo(); 
+

Ha a függvény hívását a new kulcsszóval előzzük meg, akkor a függvény konstruktorként fog viselkedni. A függvényen belül, a this +az újonnan létrehozott Objektumra fog hivatkozni.

+

A this explicit beállítása

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // ugyanaz mint egy sorral lejjebb
+foo.call(bar, 1, 2, 3); // argumentumok: a = 1, b = 2, c = 3
+

A Function.prototype-ban levő call vagy apply használatakor aztán elszabadul a pokol :). +Ezekben az esetekben ugyanis a this a foo hívásakor egzaktan be lesz állítva az apply/call +első argumentumára.

+

Ennek eredményképp az előzőekben említett Eljárás hívásakor rész nem érvényes, +a foo fentebbi meghívásakor a this értéke a bar objektumra lesz beállítva.

+ +

Gyakori buktatók

+

Míg a fent megtalálható eseteknek van gyakorlatban vett értelme, az első +a nyelv rossz designjára utal, ugyanis ennek soha nem lesz semmilyen +praktikus felhasználási módja.

+
Foo.method = function() {
+    function test() {
+        // A this itt a globális ojjektum.
+    }
+    test();
+}
+

Gyakori hiba, hogy úgy gondolják a fenti példában az emberek, hogy a this a test függvényen +belül az őt körülvevő Foo-ra fog mutatni, pedig nem.

+

Megoldásképp, hogy a Foo-hoz hozzáférhessük a test-en belül, szükségszerű egy változót +lokálisan elhelyezni a method-on belül, ami már valóban a kívánt this-re (Foo-ra) mutat.

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // Használjuk a that-et a this helyett
+    }
+    test();
+}
+

A that tuladjonképpen egy mezei változónév (nem kulcsszó), de sokszor használják arra, +hogy egy másik this-re hivatkozzanak vele. A colsureökkel kombinálva +ez a módszer arra is használható hogy this-eket passzolgassunk a vakvilágban és mégtovább.

+

Eljárások értékül adása

+

Egy másik koncepció ami nem fog a JavaScriptben működni, az az alias függvények létrehozása, ami tulajdonképpen egy függvény másik névhez való kötését jelentené.

+
var test = someObject.methodTest;
+test();
+

Az első eset miatt a test egy sima függvényhívásként működik, azonban a this értéke +a függvényen belül a továbbiakban nem a someObject lesz.

+

Elsőre a this alábbi módon való utánkötése (late binding) nem tűnik jó ötletnek. +Azonban ez az, amitől a prototípusos öröklődés is működni tud, +ami a nyelv egyik fő erőssége.

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

Amikor a method meghívódik a Bar példányaként, a this pontosan a Bar +megfelelő példányára fog mutatni.

+

Closure-ök és referenciák

A JavaScript nyelv egyik legerőteljesebb tulajdonsága a closure-ök használatában rejlik. +Ezek használatával a hatókörök egymásba ágyazhatóak, és egy belső hatókör mindig hozzáfér +az őt körülvevő, külső hatókör változóihoz. Miután JavaScriptben egyetlen dologgal lehet +hatóköröket kifejezni, és ez a funkció (bizony az if, try/catch és hasonló blokkok nem jelentenek új hatókört, mint pl. a Javaban), az összes funkció closure-ként szerepel.

+

Privát változók emulálása

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

Ebben a példában a Counter két closure-rel tér vissza: az increment és +a get funkcióval. Mind a két funkció referenciát tárol a Counter hatókörre, +és így mindketten hozzáférnek a count változóhoz, ami ebben a hatókörben lett +definiálva.

+

Miért működnek a privát változók?

+

Mivel a JavaScriptben egyszerűen nem lehet hatókörre referálni, vagy hatókört +értékül adni, így ezért szintén lehetetlen elérni az iménti count változót a külvilág számára. +Egyetlen mód van a megszólítására, ezt pedig láttuk a fentebbi két closure-ön belül.

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

A fentebbi kód nem fogja megváltoztatni a Counter hatókör count változóját, +mivel a foo.hack mező nem abban a hatókörben lett létrehozva. Ehelyett, okosan, +létre fogja hozni, vagy felül fogja írni a globális count változót (window.count).

+

Closure-ök használata ciklusokban

+

Az egyik leggyakoribb hiba amit el lehet követni, az a closure-ök ciklusokban való használata. +Annak is azon speciális esete amikor a ciklus indexváltozóját szeretnénk lemásolni a closure-ön belül.

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);  
+    }, 1000);
+}
+

A fenti kódrészlet marhára nem a számokat fogja kiírni 0-tól 9-ig, de inkább +a 10-et fogja tízszer kiírni.

+

Ugyanis a belső névtelen függvény egy referenciát fog tárolni a külső i változóra, és +akkor, amikor végül a console.log sor lefut, a for loop már végzett az egész ciklussal, +így az i értéke 10-re lesz beállítva.

+

Ahhoz, hogy a várt működést kapjuk (tehát a számokat 0-tól 9-ig), szükségszerű az i változó +értékét lemásolni.

+

A referencia probléma elkerülése

+

Az előző problémára megoldást úgy lehet jól adni, hogy az utasításoknak megfelelően +lemásoljuk a ciklusváltozót, úgy hogy a jelenlegi ciklusmagöt körbevesszük egy névtelen +függvénnyel.

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);  
+        }, 1000);
+    })(i);
+}
+

A külső (wrapper) névtelen függvény így azonnal meghívódik az i ciklusváltozóval, mint paraméterrel, +és így mindig egy másolatot fog kapni az i változó értékéről, amit ő e néven emészt tovább.

+

Így a setTimeoutban lévő névtelen fgv. mindig az e nevű referenciára fog mutatni, aminek az értéke így már nem változik meg a ciklus futása során.

+

Egy másik lehetséges út a megoldáshoz az, hogy egy wrapper függvényt visszatérítünk a setTimeoutból, aminek ugyanaz lesz a hatása, mint a fentebbi példának.

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+

Az arguments objektum

Minden függvényhatókörben hozzáférhető az arguments nevű speciális változó, +amely azon argumentumok listáját tartalmazza, amelyekkel a függvényt meghívták.

+ +

Lehet hogy úgy néz ki, de az arguments objektum nem egy tömb. Látszólag hasonlít rá, +mivel van például egy length nevű mezője, de igazából nem az Array.prototype-ból "származik", +hanem tisztán az Object-ből.

+

Itt jön a trükk lényege, hogy ennek köszönhetően nem használhatóak rajta a standard +tömb műveletek mint például a push, pop vagy a slice. Míg a sima for ciklusos iterálás +működik itt is, ahhoz hogy az előbb említett műveleteket is tudjuk rajta használni, át kell +konvertálni egy valódi Array objektummá.

+

Tömbbé konvertálás

+

Ez a kódrészlet egy új Array objektummá varázsolja az emlegetett arguments szamarat.

+
Array.prototype.slice.call(arguments);
+

De, ez a konverzió meglehetősen lassú így egyáltalán nem ajánlott teljesítmény kirtikus +alkalmazások írásakor.

+

Argumentumok kezelése

+

A következő módszer ajánlott arra az esetre hogyha az egyik függvény paramétereit egy-az-egyben +át szeretnénk adni egy másik függvény számára.

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // sok okos kód ide
+}
+

Egy másik trükk arra hogy teljesen független wrapper függvényeket gyártsunk, a call +és apply együttes használata.

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// Elkészíti a "method" (this) független verzióját
+// Ezeket kapja paraméterül: this, arg1, arg2...argN
+Foo.method = function() {
+
+    // Eredmény: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+};
+

Paraméterek és argumentum indexek

+

A háttérben az arguments objektum minden egyes indexére (elemére) egy getter és egy setter +függvényt is kap, csak úgy ahogy a függvény paramétereit is felül tudjuk írni, illetve eltudjuk érni.

+

Ennek eredményeképp, az arguments objektumon véghezvitt változtatások szinkronban +változtatják a függvény névvel ellátott paramétereit is.

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

Teljesítmény mítoszok és trükkök

+

Ahogy már azt korábban körvonalaztuk, az arguments objektum csak akkor nem jön létre, +hogyha a függvényhatókörön belül definiálunk egy változót ezzel a névvel, vagy a függvényünk +egyik paraméterének ezt a nevet választjuk.

+

Azonban a getterek és setterek mindig létrejönnek, de ez ne zavarjon meg minket, mert +semmiféle befolyása nincs a teljesítményre, pláne olyan kódban ahol sokkal több mindennel +is foglalkozunk mint az arguments objetkumhoz való hozzáférés.

+ +

Habár, egyetlen eset van, amelynek komoly hatása lehet a kód teljesítményére a modern +JavaScript motorokban. Ez pedig az arguments.callee használata.

+
function foo() {
+    arguments.callee; // csináljunk valamit ezzel a függvény objektummal
+    arguments.callee.caller; // és ennek a hívójával..
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // Így viszont nem lehet behelyettesíteni ide...
+    }
+}
+

A fenti kódban a foo helyére nem lehet egyszerűen behelyettesíteni a függvény törzsét, +mivel a függvény törzsének fogalma kell legyen mind magáról, mind az ő hívójáról. Ez nem csak +hogy azt akadályozza meg, hogy a behelyettesítéssel nyerjünk egy kis többlet performanciát, +de az egységbe zárás elvét is erősen keresztbevágja, hiszen a függvény így erősen támaszkodni +fog a hívó környezetére (kontextusára).

+

Emiatt is, az arguments.callee, vagy bármely mezőjének használata erősen kerülendő.

+ +

Konstruktorok

Csak úgy mint minden más, a konstruktorok működése szintén különbözik +a megszokottól. Itt minden függvényhívás amelyet a new kulcsszó előz meg, +konstruktor hívásnak számít.

+

A this értéke a konstruktoron - hívott függvényen - belül az újonnan létrehozott objektumra +mutat. Az új objektum prototípusa a konstruktor függvény prototípusával fog megegyezni.

+

Ha a konstruktor függvényben nincs return utasítás, akkor automatikusan a this értékével tér vissza - a létrehozott objektummal.

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

A fenti kódban a Foo függvényt mint konstruktort hívjuk meg, ami a test változóban +egy új objektumot fog eredményezni. Ennek az objektumnak a prototípusa a Foo prototípusa lesz.

+

Trükkös ugyan, de ha mégis van return utasítás az éppen konstruált függvényben, akkor +a függvény hívása az annak megfelelő értékkel fog visszatérni, de csak akkor, ha a +visszatérített érték Objektum típusú.

+
function Bar() {
+    return 2;
+}
+new Bar(); // ez egy új üres objektum lesz: {}, a 2 helyett
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // ez a returnben található objektumot fogja eredményezni
+

Hogyha kihagyjuk a new kulcsszó használatát, a függvény nem egy új objektummal fog visszatérni.

+
function Foo() {
+    this.bla = 1; // ez a globális objektumon állítja be a bla értékét 1-re
+}
+Foo(); // undefined
+

A this JavaScript beli működésének köszönhetően, mégha le is +fut az előbbi kód, akkor a this helyére a globális objektumot képzeljük.

+

Gyárak (Factory-k)

+

Ahhoz, hogy teljesen eltudjuk hagyni a new kulcsszó használatát, a konstruktor +függvény explicit értékkel kell visszatérjen.

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

Mindkét Bar-ra történő hívásmód ugyanazt fogja eredményezni. Kapunk általuk +egy újonnan létrehozott objektumot, amelynek lesz egy method nevű mezője, +ami egyébiránt egy Closure.

+

Azt is érdekes itt megjegyezni, hogy a new Bar() hívás nem befolyásolja a +visszatérített objektum prototípusát. Mivel a prototípus csak az újonnan +létrehozott objektumon létezik, amit a Bar nem térít vissza (mivel egy explicit +értéket ad vissza).

+

A fenti példában nincs funkcionális különbség aközött hogy kiírjuk-e a new +varázsszót avagy nem.

+

Új objektumok létrehozása gyárakon keresztül

+

Gyakran bevett módszer egy projetkben, hogy a new varázsszó használatát +teljesen elhagyjuk, mert a kiírásának elfelejtése bugokhoz vezetne.

+

Ennek érdekében egy új objektum létrehozásához inkább egy gyárat kell +implementálni, és annak a belsejében létrehozni az új objektumot.

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

A fenti kód ugyan ellenálló a hiányzó new kulcsszó hibáját illetően és +megfelelően használ privát változókat, érdemes +megemlíteni a dolgok kontra részét is.

+
    +
  1. Több memóriát használ, mivel az így létrehozott objektumok nem +osztják meg a prototípusukat egymás között.
  2. +
  3. A származtatás macerás, mivel a gyár kénytelen ilyenkor lemásolni +az összes származtatandó metódust egy másik objektumról, vagy ezt az objektumot +be kell állítsa a létrehozott új objektum prototípusának.
  4. +
  5. Az a megközelítés miszerint egy kifelejtett new kulcsszó miatt eldobjuk +az objektum teljes prototípusát, ellenkezik a nyelv szellemiségével.
  6. +
+

Összefoglaló

+

A new varázsszó kihagyása ugyan bugokhoz vezethet, de ez nem megfelelő indok +arra hogy ezért eldobjuk a prototípusok használatát. Végeredményben mindig +az fog dönteni a különböző stílusok megválasztása között, hogy mire van +szüksége éppen az aktuális programunknak. Egy dolog azért elengedhetetlenül +fontos, ez pedig hogy megválasszuk melyik stílust fogjuk használni objektumok +létrehozásra, és ezt konzisztensen használjuk a teljes megoldáson keresztül.

+

Névterek és hatókörök

Habár látszólag a kapcsos zárójelek jelentik a blokkok határait JavaScriptben, +fontos megjegyezni hogy nincsen blokk szintű hatókör, csakis függvény hatókörök +léteznek.

+
function test() { // ez egy hatókör
+    for(var i = 0; i < 10; i++) { // ez meg nem
+        // utasítások...
+    }
+    console.log(i); // 10
+}
+ +

A nyelvben nincsenek beépített névterek, ami azt jelenti hogy minden, egyetlen +globálisan megosztott névtérben kerül deklarálásra.

+

Akárhányszor egy változóra hivatkozunk, a JavaScript elkezdi felfele utazva +megkeresni hatókörökön, amíg csak meg nem találja. Hogyha elérjük +a globális hatókört és még mindig nem találjuk a keresett változót, akkor egy +ReferenceError hibával gazdagodik a futásidőnk.

+

A globális változók csapása

+
// A script
+foo = '42';
+
+// B script
+var foo = '42'
+

Érdemes észrevenni, hogy a fenti két scriptnek nem ugyanaz a hatása. Az A script +egy foo nevű változót vezet be a globális hatókörben, a B script pedig egy foo +nevű változót deklarál az ő hatókörében.

+

Mégegyszer tehát, ez a kettő nem ugyanazt jelenti: a var elhagyásának jópár +beláthatatlan következménye is lehet.

+
// globális hatókör
+var foo = 42;
+function test() {
+    // lokális hatókör
+    foo = 21;
+}
+test();
+foo; // 21
+

Itt, a var elhagyása azt eredményezi, hogy a test függvény mindig felülírja +a globális hatókörben definiált foo változó értékét. Habár ez elsőre nem tűnik +nagy dolognak, ha a varokat több száz sornyi JavaScript kódból hagyjuk el, az +olyan hibákhoz vezethet, amit még az anyósunknak se kívánnánk.

+
// globális hatókör
+var items = [/* random lista */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // a subLoop hatóköre
+    for(i = 0; i < 10; i++) { // hiányzik a var
+        // elképesztő dolgokat művelünk itt
+    }
+}
+

Ennél a kódnál a külső ciklus az első subLoop hívás után megáll, mivel a subLoop +felülírja az i változó globális értékét. Hogyha a második for ciklusban használtuk +volna var-t azzal könnyen elkerülhettük volna ezt a hibát. Sose hagyjuk el a var utasítást, ha csak nem direkt az a kívánt hatás, hogy befolyásoljuk a +külső hatókört.

+

Lokális változók

+

Kétféleképp (és nem több módon) lehet lokális változókat JavaScriptben leírni; ez vagy a függvény paraméter vagy a var utasítás.

+
// globális hatókör
+var foo = 1;
+var bar = 2;
+var i = 2;
+
+function test(i) {
+    // a test függvény lokális hatóköre
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

Itt a foo és i lokális változók a test hatókörén belül, viszont a baros +értékadás felül fogja írni a hasonló nevű globális változót.

+

Hoisting

+

A JS hoistolja (megemeli) a deklarációkat. Ez azt jelenti hogy minden var +utasítás és függvény deklaráció az őt körülvevő hatókör tetejére kerül.

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

A fenti kód átalakul egy másik formára mielőtt lefutna. A JavaScript felmozgatja +a var utasításokat és a függvény deklarációkat, az őket körülvevő legközelebbi +hatókör tetejébe.

+
// a var utasítások felkerülnek ide
+var bar, someValue; // alapból mindegyik 'undefined' értékű lesz
+
+// a függvény deklaráció is felkerül ide
+function test(data) {
+    var goo, i, e; // mivel nincs blokk hatókör, ezek is felkerülnek
+    if (false) {
+        goo = 1;
+
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // Ez TypeErrorral elszáll, mivel a bar még 'undefined'
+someValue = 42; // az értékadásokat nem piszkálja a hoisting
+bar = function() {};
+
+test();
+

A hiányzó blokk hatókör ténye nem csak azt eredményezi, hogy a var utasítások +kikerülnek a ciklusmagokból, hanem az if utasítások kimenetele is megjósolhatatlan +lesz.

+

Habár úgy látszik az eredeti kódban, hogy az if utasítás a goo globális +változót módosítja, a hoisting után látjuk hogy valójában a lokális változóra +lesz befolyással. Trükkös.

+

A hoisting tudása nélkül valaki azt hihetné, hogy az alábbi kód egy ReferenceError +-t fog eredményezni.

+
// nézzük meg hogy a SomeImportantThing inicializálva lett-e
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

Persze ez működik, annak köszönhetően hogy a var utasítás a globális hatókör +tetejére lett mozgatva.

+
var SomeImportantThing;
+
+// más kódok még inicializálhatják a SomeImportantThing változót itt...
+
+// ellenőrizzük hogy létezik-e
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

Névfeloldási sorrend

+

JavaScriptben az összes hatókörnek -beleértve a globálisat is- megvan a maga +this változója, amelyik mindig az aktuális objektumra utal.

+

A függvény hatókörökben van még egy speciális arguments +változó is mindig definiálva, amely a függvénynek átadott argumentumokat +tartalmazza.

+

Hogy hozzunk egy példát, amikor valaki a foo nevű változót próbálja elérni egy +függvény hatókörön belül, a JavaScript az alábbi sorrendben fogja keresni az adott +változó nevet.

+
    +
  1. Abban az esetben ha találunk var foo utasítást, használjuk azt.
  2. +
  3. Hogyha bármelyik függvény paraméter neve foo, használjuk azt.
  4. +
  5. Hogyha magának a függvénynek a neve `foo, használjuk azt.
  6. +
  7. Menjünk a külső hatókörre, és kezdjük újra #1-től.
  8. +
+ +

Névterek

+

Hogyha egyetlen globális névterünk van, akkor egy gyakori probléma lehet az, +hogy névütközésekbe futunk. A JavaScriptben szerencsére ez a gond könnyen +elkerülhető a névtelen wrapper függvények használatával.

+
(function() {
+    // egy 'öntartalmazó' névtér
+
+    window.foo = function() {
+        // egy exportált closure
+    };
+
+})(); // a függvényt azonnal végre is hajtjuk
+

A névtelen függvények kifejezésekként vannak értelmezve; így +ahhoz hogy meghívhatóak legyenek, először ki kell értékelni őket.

+
( // a függvény kiértékelése a zárójeleken belül
+function() {}
+) // a függvény objektum visszatérítése
+() // az eredmény meghívása
+

Persze más kifejezések is használhatóak arra hogy kiértékeljük és meghívjuk +a függvény kifejezést, amelyek habár szintaxisukban eltérnek, ugyanazt eredményezik.

+
// Még több stílus anonymus függvények azonnali hívásához...
+!function(){}()
++function(){}()
+(function(){}());
+// és a lista folytatódik...
+

Összegzésül

+

Az anonym wrapper függvények használata erősen ajánlott a kód egységbezárása +érdekében, saját névtér alkotásához. Ez nem csak hogy megvédi a kódunkat a +névütközésektől, de jobb modularizációhoz is vezet.

+

Emelett a globális változók használata nem ajánlott. Bármilyen fajta +használata rosszul megírt kódról árulkodik, amelyik könnyen eltörik és nehezen +karbantartható.

+

Tömbök

Tömb iteráció és tulajdonságok

Habár a tömbök a JavaScriptben objektumok, nincsen jó ok arra, hogy a for in ciklussal járjuk be őket. +Valójában sokkal több jó ok van arra, hogy miért ne így tegyünk.

+ +

Mivel a for in ciklus a prototípus láncon levő összes tulajdonságon végigmegy, +és mivel az egyetlen út ennek megkerülésére a hasOwnProperty használata, így majdnem hússzor +lassabb mint egy sima for ciklus.

+

Iteráció

+

Annak érdekébern hogy a legjobb teljesítményt érjük el a tömbökön való iteráció során, +a legjobb hogyha a klasszikus for ciklust használjuk.

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

Még egy érdekesség van a fenti példában, ami a tömb hosszának cachelését végzi +a l = list.length kifejezés használatával.

+

Habár a length tulajdonság mindig magán a tömbön van definiálva, még mindig +lehet egy kis teljesítmény kiesés amiatt hogy minden iterációban újra meg kell +keresni ezt a tulajdonságot. Persze a legújabb JavaScript motorok talán +használnak erre optimalizációt, de nem lehet biztosan megmondani hogy ahol a kódunk +futni fog, az egy ilyen motor-e vagy sem.

+

Valójában, a cachelés kihagyása azt eredményezheti, hogy a ciklusunk csak +fele olyan gyors lesz mintha a cachelős megoldást választottuk volna.

+

A length mező

+

Míg a length mező getter függvénye egyszerűen csak visszaadja a tömbben +levő elemek számát, addig a setter függvény használható arra (is), hogy +megcsonkítsuk a tömbünket.

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo.push(4);
+foo; // [1, 2, 3, undefined, undefined, undefined, 4]
+

Egy rövidebb hossz alkalmazása csonkítja a tömböt. A nagyobb hossz megadása +értelemszerűen növeli.

+

Összegzésül

+

A megfelelő teljesítmény érdekében, a for ciklus használata és a length cachelése +ajánlott. A for in ciklus használata a tömbökön a rosszul megírt kód jele, amely +tele lehet hibákkal, és teljesítményben sem jeleskedik.

+

Az Array konstruktor

Mivel az Array konstruktora kétértelműen bánik a paraméterekkel, melegen +ajánlott mindig a tömb literált - [] jelölés - használni új tömbök létrehozásakor.

+
[1, 2, 3]; // Eredmény: [1, 2, 3]
+new Array(1, 2, 3); // Eredmény: [1, 2, 3]
+
+[3]; // Eredmény: [3]
+new Array(3); // Eredmény: []
+new Array('3') // Eredmény: ['3']
+

Abban az esetben, hogyha ez a konstruktor csak egy szám paramétert kap, akkor +visszatérési értékül egy olyan tömböt fog létrehozni amelynek a length mezője +akkorára van beállítva, ahogy azt megadtuk az argumentumban. Megjegyzendő hogy +csak a length tulajdonság lesz ekkor beállítva; az egyes indexek külön-külön +nem lesznek inicializálva.

+
var arr = new Array(3);
+arr[1]; // undefined
+1 in arr; // hamis, nincs ilyen index
+

A tömb hosszának közvetlen állítása amúgy is csak elég kevés esetben +használható értelmesen, mint például alább, hogyha el akarjuk kerülni a +for ciklus használatát egy string ismétlésekor.

+
new Array(count + 1).join(ismetlendoString);
+

Összegzésül

+

Az Array konstruktor közvetlen használata erősen kerülendő. A literálok használata +elfogadott inkább, mivel rövidebbek, tisztább a szintaxisuk és olvashatóbb kódot +eredményeznek.

+

Típusok

Egyenlőség vizsgálat

A JavaScriptben két különböző megoldás létezik az objektumok egyenlőségének +vizsgálatára

+

Az egyenlőség operátor

+

Az egyenlőség vizsgálatot végző (egyik) operátort így jelöljük: ==

+

A JavaScript egy gyengén típusos nyelv. Ez azt jelenti hogy az egyenlőség +operátor típuskényszerítést alkalmaz ahhoz, hogy össze tudjon hasonlítani +két értéket.

+
""           ==   "0"           // hamis
+0            ==   ""            // igaz
+0            ==   "0"           // igaz
+false        ==   "false"       // hamis
+false        ==   "0"           // igaz
+false        ==   undefined     // hamis
+false        ==   null          // hamis
+null         ==   undefined     // igaz
+" \t\r\n"    ==   0             // igaz
+

A fenti táblázat szépen mutatja hogy mi a típuskényszerítés eredménye, és egyben +azt is, hogy miért rossz szokás a == használata. Szokás szerint, ez megint +olyan fícsör ami nehezen követhető kódhoz vezethet a komplikált konverziós +szabályai miatt.

+

Pláne, hogy a kényszerítés teljesítmény problémákhoz is vezet; ugyanis, mielőtt +egy stringet egy számhoz hasonlítanánk azelőtt a karakterláncot át kell konvertálni +a megfelelő típusra.

+

A szigorú(bb) egyenlőség operátor

+

Ez az operátor már három egyenlőségjelből áll: ===.

+

Ugyanúgy működik mint az előbbi, kivéve hogy ez a változat nem alkalmaz +típuskényszerítést az operandusai között.

+
""           ===   "0"           // hamis
+0            ===   ""            // hamis
+0            ===   "0"           // hamis
+false        ===   "false"       // hamis
+false        ===   "0"           // hamis
+false        ===   undefined     // hamis
+false        ===   null          // hamis
+null         ===   undefined     // hamis
+" \t\r\n"    ===   0             // hamis
+

A felső eredmények sokkal egyértelműbbek és ennek köszönhetően sokkal hamarabb +eltörik a kód egy-egy ellenőrzésen. Ettől sokkal hibatűrőbb lesz +a produktumunk, és ráadásul teljesítménybeli gondjaink sem lesznek.

+

Objektumok összehasonlítása

+

Habár mind a ==-t és a ===-t is egyenlőség operátornak hívjuk, eltérően +viselkednek hogyha legalább az egyik operandusuk egy objektum.

+
{} === {};                   // hamis
+new String('foo') === 'foo'; // hamis
+new Number(10) === 10;       // hamis
+var foo = {};
+foo === foo;                 // igaz
+

Ebben az esetben mindkét operátor identitást és nem egyenlőséget +ellenőriz; tehát azt fogják ellenőrizni hogy az operandus két oldalán +ugyanaz az objektum referencia áll-e, mint az is operátor Pythonban +vagy a pointerek összehasonlítása C-ben. (A ford.: Tehát nem azt, hogy a +két oldalon álló objektumnak például ugyanazok-e a mezői, hanem azt hogy ugyanazon +a memóriacímen található-e a két operandus).

+

Összegzésül

+

Azt érdemes tehát megjegyezni, hogy a szigorú egyenlőség vizsgálatot érdemes +mindig használni. Amikor szeretnék típuskényszerítést alkalmazni, akkor azt +inkább tegyük meg direkt módon, és ne a nyelv komplikált +automatikus szabályaira bízzuk magunkat.

+

A typeof vizsgálat

A typeof operátor (az instanceof-al karöltve) +lehetőség szerint a JavaScript nyelv egyik legnagyobb buktatója, mivel majdnem +teljesen rosszul működik.

+

Habár az instanceof-nak korlátozottan még lehet értelme, a typeof operátor +tényleg csak egyetlen praktikus use case-zel rendelkezik és ez nem az hogy egy +objektum típusvizsgálatát elvégezzük.

+ +

Az instanceof operátor

Az instanceof operátor a két operandusának konstruktorait hasonlítja össze. +Csak akkor bizonyul hasznosnak, amikor saját készítésű objektumokon alkalmazzuk. +Beépített típusokon ugyanolyan hasztalan alkalmazni mint a typeof operátort.

+

Saját objektumok összehasonlítása

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // igaz
+new Bar() instanceof Foo; // igaz
+
+// Ez csak a Bar.prototypeot beállítja a Foo fv. objektumra,
+// de nem egy kimondott Foo példányra
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // hamis
+

Az instanceof reakciója natív típusokra

+
new String('foo') instanceof String; // igaz
+new String('foo') instanceof Object; // igaz
+
+'foo' instanceof String; // hamis
+'foo' instanceof Object; // hamis
+

Érdemes itt megjegyezni hogy az instanceof nem működik olyan objektumokon, +amelyek különböző JavaScript kontextusokból származnak (pl. különböző dokumentumok +a böngészőn belül), mivel a konstruktoruk nem pontosan ugyanaz az objektum lesz.

+

Összegzésül

+

Az instanceof-ot tehát csak megegyező JS kontextusból származó, saját készítésű objektumoknál használjuk. Minden más felhasználása kerülendő, csak úgy mint a typeof operátor esetén.

+

Típus kasztolás

Előre kössük le, hogy a JavaScript egy gyengén típusos nyelv, így ahol +csak tud, ott típus kényszerítést használ.

+
// Ezek igazak
+new Number(10) == 10; // A Number.toString() számmá lesz
+                      // visszaalakítva
+
+10 == '10';           // A Stringek visszaalakulnak számmá
+10 == '+10 ';         // Mégtöbb string varázslat
+10 == '010';          // és mégtöbb
+isNaN(null) == false; // a null varázslatosan 0-vá alakul
+                      // ami persze nem NaN
+
+// Ezek hamisak
+10 == 010;
+10 == '-10';
+ +

Hogy elkerüljük a fenti varázslatokat, a szigorú egyenlőség ellenőrzés melegen ajánlott. Habár ezzel elkerüljük +a problémák farkasrészét, még mindig tartogat a JS gyengén típusos rendszere +meglepetéseket.

+

Natív típusok konstruktorai

+

A jó hír az, hogy a natív típusok mint a Number és a String különféle +módon viselkednek hogyha a new kulcsszóval avagy anélkül vannak inicializálva.

+
new Number(10) === 10;     // Hamis, Objektum vs. Szám
+Number(10) === 10;         // Igaz, Szám vs. szám
+new Number(10) + 0 === 10; // Igaz, az implicit konverziónak hála
+

Ha egy natív típust mint a Number konstruktorként kezelünk, akkor egy új +Number objektumot kapunk. De ha kihagyjuk a new kulcsszót akkor a Number +egy egyszerű konverter függvényként fog viselkedni.

+

Ráadásul a literálok passzolgatásakor még több típuskonverzió üti fel a fejét.

+

A legjobb megoldás hogyha a három típus valamelyikére expliciten kasztolunk.

+

Stringre kasztolás

+
'' + 10 === '10'; // igaz
+

Egy üres string hozzáfűzésével könnyen tudunk egy értéket stringgé kasztolni.

+

Számra kaszt

+
+'10' === 10; // igaz
+

Az unáris plusz operátor használatával lehetséges egy értéket számra alakítani.

+

Booleanre kasztolás

+

A nem operátor kétszeri alkalmazásával tudunk booleanné kasztolni.

+
!!'foo';   // igaz
+!!'';      // hamis
+!!'0';     // igaz
+!!'1';     // igaz
+!!'-1'     // igaz
+!!{};      // igaz
+!!true;    // igaz
+

Lényeg

Miért Ne Használjuk az eval-t

Az eval (evil) funkció egy stringbe ágyazott JavaScript kódot futtat a +lokális scopeon belül.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

Viszont az eval csak akkor viselkedik így, hogyha expliciten hívjuk meg +és a meghívott funkció neve valóban eval.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

Az eval használata kerülendő. A "felhasználása" az esetek 99.9%-ban +mellőzhető.

+

Az eval ezer arca

+

A setTimeout és setInterval nevű timeout függvények is +tudnak úgy működni, hogy első paraméterükként egy stringbe ágyazott kódot várnak. +Ez a string mindig a globális hatókörben lesz végrehajtva, mivel az evalt +így nem direktben hívjuk meg.

+

Biztonsági problémák

+

Az eval azért is veszélyes, mert bármilyen JS kódot végrehajt, amit odaadunk +neki. Éppen ezért sose használjuk olyan kódok végrehajtására amiknek az eredete +nem megbízható/ismeretlen.

+

Összegzésül

+

Soha ne használjunk evalt. Bármilyen kód működése, teljesítménye, ill. biztonsága +megkérdőjelezhető amely használja ezt a nyelvi elemet. Semmilyen megoldás +használata nem ajánlott amely első sorban evalra épül. Ekkor egy jobb +megoldás szükségeltetik, amely nem függ az evaltól.

+

Az undefined és a null

A JavaScript két értéket is tartogat a semmi kifejezésére, ezek a null és az +undefined és ezek közül az utóbbi a hasznosabb.

+

Az undefined

+

Ha az előbbi bevezetőtől nem zavarodtál volna össze; az +undefined egy típus amelynek pontosan egy értéke van, az undefined.

+

A nyelvben szintén van egy undefined nevű globális változó amelynek az értékét +hogy-hogy nem undefined-nak hívják. Viszont ez a változó nem konstans vagy +kulcsszó a nyelvben. Ez azt jeletni hogy az értéke könnyedén felülírható.

+ +

Itt van pár példa, hogy mikor is találkozhatunk az undefined értékkel:

+
    +
  • Az undefined globális változó elérésekor
  • +
  • Egy deklarált, de nem inicializált változó elérésekor.
  • +
  • Egy függvény hívásakor ez a visszatérési érték, return utasítás híján.
  • +
  • Egy olyan return utasítás lefutásakor, amely nem térít vissza értéket.
  • +
  • Nem létező mezők lekérésekor.
  • +
  • Olyan függvény paraméterek elérésekor amelyeknek a hívó oldalon nem kaptak értéket.
  • +
  • Bármikor amikor az undefined érték van valaminek beállítva.
  • +
  • Bármelyik void(kifejezés) utasítás futtatásakor.
  • +
+

undefined megőrzési trükkök

+

Mivel az undefined nevű globális változó csak egy másolatot tárol az +undefined elnevezésű értékből, az értékének megváltoztatása nem írja +felül az eredeti undefined típus értékét.

+

Ezért, ha valamilyen értékkel össze szeretnénk hasonlítani az undefined értéket, +nem árt hogyha először magát az undefined-ot el tudjuk érni.

+

Egy gyakori technika annak érdekében hogy megvédjük a kódunkat az +undefined lehetséges felüldefiniálásaitól, hogy egy névtelen (wrapper) függvénybe +csomagoljuk az egész kódunkat, amelynek lesz egy direkt üres paramétere.

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // az undefined ebben a hatókörben 
+    // megint valóban az `undefined` értékre referáll.
+
+})('Hello World', 42);
+

Egy másik módja ennek, hogy használunk egy "üres" deklarációt a wrapper függvényen +belül.

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

Az egyetlen különbség ebben a változatban, hogyha minifikáljuk ezt a kódot, +és nem definiálunk további változókat ezen a részen belül, akkor ezzel a +változattal extra 4 byte "veszteséget" szenvedünk el.

+

Mikor használjunk nullt

+

Miközben az undefined a natív JavaScript megvalósításokban inkább a (más +nyelvekben levő) tradícionális null helyett használandó, azalatt maga a null +inkább csak egy különböző adattípusnak számít, mindenféle különös jelentés nélkül.

+

Egy pár belső JavaScriptes megoldásban ugyan használják (ahol pl. a prototípus lánc végét a Foo.prototype = null beállítással jelölik), de a legtöbb esetben ez +felcserélhető az undefined-al.

+

(A ford.: A null annak az esetnek a jelölésére hasznos, amikor +egy referencia típusú változót deklarálunk, de még nem adunk neki értéket. Pl. a +var ezObjektumLesz = null kifejezés ezt jelöli. Tehát a null leginkább +kezdeti értékként állja meg a helyét, minden másra ott az undefined)

+

Automatic Semicolon Insertion

Bár a JavaScriptnek látszólag C-s szintaxisa van, mégsem kötelező benne +kirakni a pontosvesszőket, így (helyenként) kihagyhatóak a forrásból. +(A ford.: hiszen interpretált nyelv lévén nincsenek fordítási hibák, így +nyelvi elemek meglétét sem tudja erőltetni a nyelv)

+

Itt jön a csel, hogy ennek ellenére a JavaScript csak pontosvesszőkkel +értelmezi megfelelően a beírt kódot. Következésképp, a JS automatikusan +illeszti be a pontosvesszőket (megpróbálja kitalálni a gondolataink) +azokra a helyekre, ahol amúgy emiatt értelmezési hibába futna.

+
var foo = function() {
+} // értelmezési hiba, pontosvessző kéne
+test()
+

Az automatikus beillesztés megtörténik, ezután így értelmeződik a kód

+
var foo = function() {
+}; // nincs hiba, mindenki örül
+test()
+

Az automatikus beillesztés (ASI) a JavaScript (egyik) legnagyobb design +hibája, mivel igen... meg tudja változtatni a kód értelmezését

+

Hogyan Működik

+

Az alábi kódban nincsen pontosvessző, így az értelmező (parser) feladata kitalálni, +hogy hova is illessze be őket.

+
(function(window, undefined) {
+    function test(options) {
+        log('testing!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'hosszú string az argumentumban',
+            'még még még még még hossszabbbbbbb'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+
+})(window)
+

Alább mutatjuk a "kitalálós" játék eredményét.

+
(function(window, undefined) {
+    function test(options) {
+
+        // Nincs beillesztés, a sorok össze lettek vonva
+        log('testing!')(options.list || []).forEach(function(i) {
+
+        }); // <- beillesztés
+
+        options.value.test(
+            'hosszú string az argumentumban',
+            'még még még még még hossszabbbbbbb'
+        ); // <- beillesztés
+
+        return; // <- beillesztés, eltörik a return kifejezésünk
+        { // blokként értelemződik
+
+            // név: kifejezés formátumban értelmeződik
+            foo: function() {} 
+        }; // <- beillesztés
+    }
+    window.test = test; // <- beillesztés
+
+// Ezeket a sorokat összeilleszti
+})(window)(function(window) {
+    window.someLibrary = {}; // <- beillesztés
+
+})(window); //<- beillesztés
+ +

Az értelmező drasztikusan megváltoztatta a fenti kódot. A legtöbb esetben a +beillesztő rosszul tippel.

+

(A ford.: Semmilyen nyelvben sem jó, hogyha hagyjuk hogy a gép találja ki mit +szerettünk volna írni. Néma gyereknek az anyja sem érti a kódját ugye)

+

Kezdő Zárójelek

+

Az értelmező nem rak be új pontosvesszőt, hogyha a sor eleje (nyitó) zárójellel kezdődik.

+
log('testing!')
+(options.list || []).forEach(function(i) {})
+

Ez a kód egy sorként értelmeződik

+
log('testing!')(options.list || []).forEach(function(i) {})
+

Az esélyek arra elég magasak, hogy a log nem egy függvényt fog visszatéríteni; így a fenti kód egy TypeError típusú hibát fog dobni +undefined is not a function üzenettel.

+

Összefoglalásképp

+

Szükségszerűen soha ne hagyjuk ki a pontoszvesszőket. Nem árt a kapcsos +zárójeleket is ugyanazon a soron tartani, mint amelyiken az utasítást elkezdtük, +így nem ajánlott az egysoros if / else kifejezések kedvéért elhagyni +őket. Ezek a szempontok nem csak a kódot (és annak olvashatóságát) tartják +konzisztensen, de megelőzik azt is hogy a JavaScript értelmező valamit rosszul +"találjon ki".

+

A delete Operátor

Röviden, lehetetlen globális változókat, függvényeket és olyan dolgokat törölni +JavaScriptben amelyeknek a DontDelete attribútuma be van állítva.

+

Globális kód és Függvény kód

+

Amikor egy változó/függvény, globális vagy +függvény hatókörben van definiálva, +akkor az vagy az Aktivációs (Activation) vagy a Globális (Global) objektum egyik mezőjeként +jön létre. Az ilyen mezőknek van egy halom attribútuma, amelyek közül az egyik +a DontDelete. A változó és függvény deklarációk a globális vagy függvény kódon +belül mindig DontDelete tulajdonságú mezőket hoznak létre, így nem lehet őket +törölni.

+
// globális változó
+var a = 1; // A DontDelete be lett állítva
+delete a; // hamis
+a; // 1
+
+// függvény:
+function f() {} // A DontDelete be lett állítva
+delete f; // hamis
+typeof f; // "function"
+
+// új értékadással sem megy
+f = 1;
+delete f; // hamis
+f; // 1
+

Explicit mezők

+

Az expliciten beállított mezőket persze normálisan lehet törölni.

+
// expliciten beállított mező
+var obj = {x: 1};
+obj.y = 2;
+delete obj.x; // igaz
+delete obj.y; // igaz
+obj.x; // undefined
+obj.y; // undefined
+

A fenti példábna az obj.x és obj.y törölhető, mivel nincs DontDelete +attribútuma egyik mezőnek sem. Ezért működik az alábbi példa is.

+
// működik, kivéve IE-ben
+var GLOBAL_OBJECT = this;
+GLOBAL_OBJECT.a = 1;
+a === GLOBAL_OBJECT.a; // igaz - egy globális változó
+delete GLOBAL_OBJECT.a; // igaz
+GLOBAL_OBJECT.a; // undefined
+

Itt egy trükköt használunk az a törlésére. A this itt +a Globális objektumra mutat, és expliciten bezetjük rajta az a változót, mint +egy mezőjét, így törölni is tudjuk.

+

Mint az szokás, a fenti kód egy kicsit bugos IE-ben (legalábbis 6-8-ig).

+

Függvény argumentumok és beépített dolgaik

+

A függvény argumentumok, az arguments objektum +és a beépített mezők szintén DontDelete tulajdonságúak.

+
// függvény argumentumok és mezők
+(function (x) {
+
+  delete arguments; // hamis
+  typeof arguments; // "object"
+
+  delete x; // hamis
+  x; // 1
+
+  function f(){}
+  delete f.length; // hamis
+  typeof f.length; // "number"
+
+})(1);
+

Vendég (host) objektumok

+

A delete operátor működése megjósolhatatlan a vendég objektumokra. A specifikáció +szerint ezek az objektumok szükség szerint bármilyen viselkedést implementálhatnak.

+

(A ford.: Vendég objektumok azok az objektumok, amelyek nincsenek konkrétan +meghatározva az ES aktuális verziójú specifikációjában, pl. a window)

+

Összegzésképp

+

A delete működése helyenként megjósolhatatlan, így biztonsággal csak olyan +objektumok mezőin használhatjuk amelyeket expliciten mi állítottunk be.

+

Egyéb

A varázslatos setTimeout és setInterval

Mivel a JavaScript aszinkron, a setTimeout és setInterval használatával +lehetséges késleltetni a kódok lefutási idejét.

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // Egy számmal (> 0) tér vissza
+

Amikor a setTimeout függvényt meghívjuk, válaszul egy timeout ID-t kapunk +valamint be lesz ütemezve a foo függvényhívás, hogy körülbelül 1000 miliszekundum múlva fusson le a jövőben. A foo egyszer lesz végrehajtva.

+

Az aktuális JavaScript motor időzítésétől függően, és annak figyelembe vételével +hogy a JavaScript mindig egyszálú, tehát a megelőző kódok blokkolhatják a szálat, +soha nem lehet biztonságosan meghatározni hogy valóban a kért időzítéssel +fog lefutni a kód amit megadtunk a setTimeoutban. Erre semmilyen biztosíték nincs.

+

Az első helyen bepasszolt függvény a globális objektum által lesz meghívva, ami +azt jelenti hogy a this a függvényen belül a globális objektumra +utal.

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // a this egy globális objektumra utal, nem a Foo-ra
+        console.log(this.value); // undefined-ot logol ki
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

Híváshalmozás a setIntervalal

+

Míg a setTimeout csak egyszer futtatja le a megadott függvényt, a setInterval

+
    +
  • ahogy a neve is mutatja - minden X miliszekundumban végrehajtja a +neki átadott kódot, használata pedig erősen kerülendő.
  • +
+

Nagy hátulütője, hogy még akkor is ütemezi az újabb és újabb +hívásokat, hogyha az aktuálisan futattot kód a megadott időintervallumon +felül blokkolja a további kód futtatást. Ez, hogyha megfelelően rövid +intervallumokat állítunk be, felhalmozza a függvényhívásokat a call stacken.

+
function foo(){
+    // kód ami 1 másodpercig feltartja a futtatást
+}
+setInterval(foo, 100);
+

A fenti kódban amikor a foo meghívódik, 1 másodpercig feltartja a további futtatást.

+

A setInterval persze ütemezni fogja a jövőbeli foo hívásokat továbbra is, amíg +blokkolódik a futtatás. Így tíz további hívás fog várakozni, miután a foo +futtatása először végzett.

+

Hogyan Bánjunk El a Blokkolással

+

A legkönnyebb és kontrollálhatóbb megoldásnak az bizonyul, hogyha a setTimeout +függvényt a rögtön a foo-n belül használjuk.

+
function foo(){
+    // 1 másodpercig blokkoló kód
+    setTimeout(foo, 100);
+}
+foo();
+

Ez nem csak egységbe zárja a setTimeout hívást, de meggátolja a felesleges hívások +felhalmozását, és több irányítást ad a kezünkbe. A foo így magától eltudja +dönteni, hogy akarja-e újra futtatni önmagát vagy sem.

+

Timeout Tisztogatás Kézzel

+

A clearTimeout vagy clearInterval hívással tudjuk a timeoutjainkat +megszüntetni, természetesen attól függ hogy melyiket használjuk, +hogy melyik set függvénnyel indítottuk útjára a timeoutunkat.

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

Az Összes Timeout Megszüntetése

+

Mivel nincsen beépített megoldás az összes timeout és/vagy interval +hívás törlésére, ezért bruteforce módszerekhez kell folyamodjunk.

+
// az "összes" timeout kitörlése
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

Persze ez csak véletlenszerű lövöldözés, semmi sem garantálja hogy a fenti +módszerrel nem marad timeout a rendszerben (A ford.: például az ezredik timeout vagy +afelett). Szóval egy másik módszer ennek megoldására, hogy feltételezzük hogy +minden setTimeout hívással az azonosítók száma egyel növekszik.

+
// az "összes" timeout kiírtása
+var legnagyobbTimeoutId = window.setTimeout(function(){}, 1),
+i;
+for(i = 1; i <= legnagyobbTimeoutId; i++) {
+    clearTimeout(i);
+}
+

Habár ez a megoldás minden böngészőben megy (egyenlőre), ez az azonosítókról született mondás nincs specifikációban rögzítve, és ennek megfelelően változhat. +Az ajánlott módszer továbbra is az, hogy kövessük nyomon az összes timeout azonosítót amit generáltunk, és így ki is tudjuk őket rendesen törölni.

+

eval A Színfalak Mögött

+

Habár a setTimeout és a setInterval (kód) stringet is tud első paramétereként +fogdani, ezt a fajta formáját használni kimondottan tilos, mivel a függöny +mögött ő is csak evalt használ.

+ +
function foo() {
+    // meg lesz hívva
+}
+
+function bar() {
+    function foo() {
+        // soha nem hívódik meg
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

Mivel az evalt nem direkt módon hívjuk meg a fenti esetben, +a setTimeoutnak passzolt string a globális hatókörben fog lefutni; így +a lokális foo függvényt sosem használjuk a bar hatóköréből.

+

Továbbá nem ajánlott argumentumokat átadni annak a függvénynek amelyik +a timeout függvények által meg lesz hívva a későbbiekben.

+
function foo(a, b, c) {}
+
+// SOHA ne használd így!
+setTimeout('foo(1, 2, 3)', 1000)
+
+// Ehelyett csomagoljuk névtelen függvénybe
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

Összegzésképp

+

Soha ne használjunk stringeket a setTimeout vagy setInterval első +paramétereiként. Ha argumentumokat kell átadni a meghívandó függvénynek, az +egyértelműen rossz kódra utal. Ebben az esetben a függvényhívás +lebonyolításához egy anoním függvény használata ajánlott.

+

Továbbá, mivel az ütemező kódja nem blokkolódik a JavaScript futás által, a +setInterval használata úgy általában kerülendő.

+
\ No newline at end of file diff --git a/site/image/sidebar-icon.png b/image/sidebar-icon.png similarity index 100% rename from site/image/sidebar-icon.png rename to image/sidebar-icon.png diff --git a/index.html b/index.html new file mode 100644 index 0000000..a90a6a5 --- /dev/null +++ b/index.html @@ -0,0 +1,1499 @@ +JavaScript Garden

Intro

Intro

JavaScript Garden is a growing collection of documentation about the most +quirky parts of the JavaScript programming language. It gives advice to +avoid common mistakes and subtle bugs, as well as performance issues and bad +practices, that non-expert JavaScript programmers may encounter on their +endeavours into the depths of the language.

+

JavaScript Garden does not aim to teach you JavaScript. Former knowledge +of the language is strongly recommended in order to understand the topics covered +in this guide. In order to learn the basics of the language, please head over to +the excellent guide on the Mozilla Developer Network.

+

The Authors

+

This guide is the work of two lovely Stack Overflow users, Ivo Wetzel +(Writing) and Zhang Yi Jiang (Design).

+

It's currently maintained by Tim Ruffles.

+

Contributors

+ +

Hosting

+

JavaScript Garden is hosted on GitHub, but Cramer Development supports us +with a mirror at JavaScriptGarden.info.

+

License

+

JavaScript Garden is published under the MIT license and hosted on +GitHub. If you find errors or typos please file an issue or a pull +request on the repository. You can also find us in the JavaScript room on +Stack Overflow chat.

+

Objects

Object Usage and Properties

Everything in JavaScript acts like an object, with the only two exceptions being +null and undefined.

+
false.toString(); // 'false'
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

A common misconception is that number literals cannot be used as +objects. That is because a flaw in JavaScript's parser tries to parse the dot +notation on a number as a floating point literal.

+
2.toString(); // raises SyntaxError
+

There are a couple of workarounds that can be used to make number literals act +as objects too.

+
2..toString(); // the second point is correctly recognized
+2 .toString(); // note the space left to the dot
+(2).toString(); // 2 is evaluated first
+

Objects as a Data Type

+

Objects in JavaScript can also be used as Hashmaps; they mainly consist +of named properties mapping to values.

+

Using an object literal - {} notation - it is possible to create a +plain object. This new object inherits from Object.prototype and +does not have own properties defined.

+
var foo = {}; // a new empty object
+
+// a new object with a 'test' property with value 12
+var bar = {test: 12}; 
+

Accessing Properties

+

The properties of an object can be accessed in two ways, via either the dot +notation or the square bracket notation.

+
var foo = {name: 'kitten'}
+foo.name; // kitten
+foo['name']; // kitten
+
+var get = 'name';
+foo[get]; // kitten
+
+foo.1234; // SyntaxError
+foo['1234']; // works
+

The notations work almost identically, with the only difference being that the +square bracket notation allows for dynamic setting of properties and +the use of property names that would otherwise lead to a syntax error.

+

Deleting Properties

+

The only way to remove a property from an object is to use the delete +operator; setting the property to undefined or null only removes the +value associated with the property, but not the key.

+
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

The above outputs both bar undefined and foo null - only baz was +removed and is therefore missing from the output.

+

Notation of Keys

+
var test = {
+    'case': 'I am a keyword, so I must be notated as a string',
+    delete: 'I am a keyword, so me too' // raises SyntaxError
+};
+

Object properties can be both notated as plain characters and as strings. Due to +another mis-design in JavaScript's parser, the above will throw +a SyntaxError prior to ECMAScript 5.

+

This error arises from the fact that delete is a keyword; therefore, it must be +notated as a string literal to ensure that it will be correctly interpreted by +older JavaScript engines.

+

The Prototype

JavaScript does not feature a classical inheritance model; instead, it uses a +prototypal one.

+

While this is often considered to be one of JavaScript's weaknesses, the +prototypal inheritance model is in fact more powerful than the classic model. +It is, for example, fairly trivial to build a classic model on top of a +prototypal model, while the other way around is a far more difficult task.

+

JavaScript is the only widely used language that features prototypal +inheritance, so it can take time to adjust to the differences between the two +models.

+

The first major difference is that inheritance in JavaScript uses prototype +chains.

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// Set Bar's prototype to a new instance of Foo
+Bar.prototype = new Foo();
+Bar.prototype.foo = 'Hello World';
+
+// Make sure to list Bar as the actual constructor
+Bar.prototype.constructor = Bar;
+
+var test = new Bar(); // create a new bar instance
+
+// The resulting prototype chain
+test [instance of Bar]
+    Bar.prototype [instance of Foo]
+        { foo: 'Hello World' }
+        Foo.prototype
+            { method: ... }
+            Object.prototype
+                { toString: ... /* etc. */ }
+

In the code above, the object test will inherit from both Bar.prototype and +Foo.prototype; hence, it will have access to the function method that was +defined on Foo. It will also have access to the property value of the +one Foo instance that is its prototype. It is important to note that new +Bar() does not create a new Foo instance, but reuses the one assigned to +its prototype; thus, all Bar instances will share the same value property.

+ +

Property Lookup

+

When accessing the properties of an object, JavaScript will traverse the +prototype chain upwards until it finds a property with the requested name.

+

If it reaches the top of the chain - namely Object.prototype - and still +hasn't found the specified property, it will return the value +undefined instead.

+

The Prototype Property

+

While the prototype property is used by the language to build the prototype +chains, it is still possible to assign any given value to it. However, +primitives will simply get ignored when assigned as a prototype.

+
function Foo() {}
+Foo.prototype = 1; // no effect
+

Assigning objects, as shown in the example above, will work, and allows for dynamic +creation of prototype chains.

+

Performance

+

The lookup time for properties that are high up on the prototype chain can have +a negative impact on performance, and this may be significant in code where +performance is critical. Additionally, trying to access non-existent properties +will always traverse the full prototype chain.

+

Also, when iterating over the properties of an object +every property that is on the prototype chain will be enumerated.

+

Extension of Native Prototypes

+

One mis-feature that is often used is to extend Object.prototype or one of the +other built in prototypes.

+

This technique is called monkey patching and breaks encapsulation. While +used by popular frameworks such as Prototype, there is still no good +reason for cluttering built-in types with additional non-standard functionality.

+

The only good reason for extending a built-in prototype is to backport +the features of newer JavaScript engines; for example, +Array.forEach.

+

In Conclusion

+

It is essential to understand the prototypal inheritance model before +writing complex code that makes use of it. Also, be aware of the length of the +prototype chains in your code and break them up if necessary to avoid possible +performance problems. Further, the native prototypes should never be +extended unless it is for the sake of compatibility with newer JavaScript +features.

+

hasOwnProperty

To check whether an object has a property defined on itself and not somewhere +on its prototype chain, it is necessary to use the +hasOwnProperty method which all objects inherit from Object.prototype.

+ +

hasOwnProperty is the only thing in JavaScript which deals with properties and +does not traverse the prototype chain.

+
// Poisoning Object.prototype
+Object.prototype.bar = 1;
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // true
+
+foo.hasOwnProperty('bar'); // false
+foo.hasOwnProperty('goo'); // true
+

Only hasOwnProperty will give the correct and expected result; this is +essential when iterating over the properties of any object. There is no other +way to exclude properties that are not defined on the object itself, but +somewhere on its prototype chain.

+

hasOwnProperty as a Property

+

JavaScript does not protect the property name hasOwnProperty; thus, if the +possibility exists that an object might have a property with this name, it is +necessary to use an external hasOwnProperty to get correct results.

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Here be dragons'
+};
+
+foo.hasOwnProperty('bar'); // always returns false
+
+// Use another Object's hasOwnProperty and call it with 'this' set to foo
+({}).hasOwnProperty.call(foo, 'bar'); // true
+
+// It's also possible to use hasOwnProperty from the Object
+// prototype for this purpose
+Object.prototype.hasOwnProperty.call(foo, 'bar'); // true
+

In Conclusion

+

Using hasOwnProperty is the only reliable method to check for the +existence of a property on an object. It is recommended that hasOwnProperty +is used in every for in loop to avoid errors from +extended native prototypes.

+

The for in Loop

Just like the in operator, the for in loop traverses the prototype +chain when iterating over the properties of an object.

+ +
// Poisoning Object.prototype
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // prints both bar and moo
+}
+

Since it is not possible to change the behavior of the for in loop itself, it +is necessary to filter out the unwanted properties inside the loop body; +this is done using the hasOwnProperty method of +Object.prototype.

+ +

Using hasOwnProperty for Filtering

+
// still the foo from above
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

This version is the only correct one to use. Due to the use of hasOwnProperty, it +will only print out moo. When hasOwnProperty is left out, the code is +prone to errors in cases where the native prototypes - e.g. Object.prototype - +have been extended.

+

One widely used framework that extends Object.prototype is Prototype. +When this framework is included, for in loops that do not use +hasOwnProperty are guaranteed to break.

+

In Conclusion

+

It is recommended to always use hasOwnProperty. Assumptions should never +be made about the environment the code is running in, or whether the native +prototypes have been extended or not.

+

Functions

Function Declarations and Expressions

Functions in JavaScript are first class objects. That means they can be +passed around like any other value. One common use of this feature is to pass +an anonymous function as a callback to another, possibly an asynchronous function.

+

The function Declaration

+
function foo() {}
+

The above function gets hoisted before the execution of the +program starts; thus, it is available everywhere in the scope it was +defined, even if called before the actual definition in the source.

+
foo(); // Works because foo was created before this code runs
+function foo() {}
+

The function Expression

+
var foo = function() {};
+

This example assigns the unnamed and anonymous function to the variable foo.

+
foo; // 'undefined'
+foo(); // this raises a TypeError
+var foo = function() {};
+

Due to the fact that var is a declaration that hoists the variable name foo +before the actual execution of the code starts, foo is already declared when +the script gets executed.

+

But since assignments only happen at runtime, the value of foo will default +to undefined before the corresponding code is executed.

+

Named Function Expression

+

Another special case is the assignment of named functions.

+
var foo = function bar() {
+    bar(); // Works
+}
+bar(); // ReferenceError
+

Here, bar is not available in the outer scope, since the function only gets +assigned to foo; however, inside of bar, it is available. This is due to +how name resolution in JavaScript works, the name of the +function is always made available in the local scope of the function itself.

+

How this Works

JavaScript has a different concept of what the special name this refers to +than most other programming languages. There are exactly five different +ways in which the value of this can be bound in the language.

+

The Global Scope

+
this;
+

When using this in global scope, it will simply refer to the global object.

+

Calling a Function

+
foo();
+

Here, this will again refer to the global object.

+ +

Calling a Method

+
test.foo(); 
+

In this example, this will refer to test.

+

Calling a Constructor

+
new foo(); 
+

A function call that is preceded by the new keyword acts as +a constructor. Inside the function, this will refer +to a newly created Object.

+

Explicit Setting of this

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // array will expand to the below
+foo.call(bar, 1, 2, 3); // results in a = 1, b = 2, c = 3
+

When using the call or apply methods of Function.prototype, the value of +this inside the called function gets explicitly set to the first argument +of the corresponding function call.

+

As a result, in the above example the method case does not apply, and this +inside of foo will be set to bar.

+ +

Common Pitfalls

+

While most of these cases make sense, the first can be considered another +mis-design of the language because it never has any practical use.

+
Foo.method = function() {
+    function test() {
+        // this is set to the global object
+    }
+    test();
+}
+

A common misconception is that this inside of test refers to Foo; while in +fact, it does not.

+

In order to gain access to Foo from within test, it is necessary to create a +local variable inside of method that refers to Foo.

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // Use that instead of this here
+    }
+    test();
+}
+

that is just a normal variable name, but it is commonly used for the reference to an +outer this. In combination with closures, it can also +be used to pass this values around.

+

Assigning Methods

+

Another thing that does not work in JavaScript is function aliasing, which is +assigning a method to a variable.

+
var test = someObject.methodTest;
+test();
+

Due to the first case, test now acts like a plain function call; therefore, +this inside it will no longer refer to someObject.

+

While the late binding of this might seem like a bad idea at first, in +fact, it is what makes prototypal inheritance work.

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

When method gets called on an instance of Bar, this will now refer to that +very instance.

+

Closures and References

One of JavaScript's most powerful features is the availability of closures. +With closures, scopes always keep access to the outer scope, in which they +were defined. Since the only scoping that JavaScript has is +function scope, all functions, by default, act as closures.

+

Emulating private variables

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

Here, Counter returns two closures: the function increment as well as +the function get. Both of these functions keep a reference to the scope of +Counter and, therefore, always keep access to the count variable that was +defined in that scope.

+

Why Private Variables Work

+

Since it is not possible to reference or assign scopes in JavaScript, there is +no way of accessing the variable count from the outside. The only way to +interact with it is via the two closures.

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

The above code will not change the variable count in the scope of Counter, +since foo.hack was not defined in that scope. It will instead create - or +override - the global variable count.

+

Closures Inside Loops

+

One often made mistake is to use closures inside of loops, as if they were +copying the value of the loop's index variable.

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);  
+    }, 1000);
+}
+

The above will not output the numbers 0 through 9, but will simply print +the number 10 ten times.

+

The anonymous function keeps a reference to i. At the time +console.log gets called, the for loop has already finished, and the value of +i has been set to 10.

+

In order to get the desired behavior, it is necessary to create a copy of +the value of i.

+

Avoiding the Reference Problem

+

In order to copy the value of the loop's index variable, it is best to use an +anonymous wrapper.

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);  
+        }, 1000);
+    })(i);
+}
+

The anonymous outer function gets called immediately with i as its first +argument and will receive a copy of the value of i as its parameter e.

+

The anonymous function that gets passed to setTimeout now has a reference to +e, whose value does not get changed by the loop.

+

There is another possible way of achieving this, which is to return a function +from the anonymous wrapper that will then have the same behavior as the code +above.

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+

There's yet another way to accomplish this by using .bind, which can bind +a this context and arguments to function. It behaves identially to the code +above

+
for(var i = 0; i < 10; i++) {
+    setTimeout(console.log.bind(console, i), 1000);
+}
+

The arguments Object

Every function scope in JavaScript can access the special variable arguments. +This variable holds a list of all the arguments that were passed to the function.

+ +

The arguments object is not an Array. While it has some of the +semantics of an array - namely the length property - it does not inherit from +Array.prototype and is in fact an Object.

+

Due to this, it is not possible to use standard array methods like push, +pop or slice on arguments. While iteration with a plain for loop works +just fine, it is necessary to convert it to a real Array in order to use the +standard Array methods on it.

+

Converting to an Array

+

The code below will return a new Array containing all the elements of the +arguments object.

+
Array.prototype.slice.call(arguments);
+

Because this conversion is slow, it is not recommended to use it in +performance-critical sections of code.

+

Passing Arguments

+

The following is the recommended way of passing arguments from one function to +another.

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // do stuff here
+}
+

Another trick is to use both call and apply together to create fast, unbound +wrappers.

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// Create an unbound version of "method" 
+// It takes the parameters: this, arg1, arg2...argN
+Foo.method = function() {
+
+    // Result: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+};
+

Formal Parameters and Arguments Indices

+

The arguments object creates getter and setter functions for both its +properties, as well as the function's formal parameters.

+

As a result, changing the value of a formal parameter will also change the value +of the corresponding property on the arguments object, and the other way around.

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

Performance Myths and Truths

+

The only time the arguments object is not created is where it is declared as +a name inside of a function or one of its formal parameters. It does not matter +whether it is used or not.

+

Both getters and setters are always created; thus, using it has nearly +no performance impact at all, especially not in real world code where there is +more than a simple access to the arguments object's properties.

+ +

However, there is one case which will drastically reduce the performance in +modern JavaScript engines. That case is the use of arguments.callee.

+
function foo() {
+    arguments.callee; // do something with this function object
+    arguments.callee.caller; // and the calling function object
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // Would normally be inlined...
+    }
+}
+

In the above code, foo can no longer be a subject to inlining since it +needs to know about both itself and its caller. This not only defeats possible +performance gains that would arise from inlining, but it also breaks encapsulation +because the function may now be dependent on a specific calling context.

+

Making use of arguments.callee or any of its properties is highly discouraged.

+ +

Constructors

Constructors in JavaScript are yet again different from many other languages. Any +function call that is preceded by the new keyword acts as a constructor.

+

Inside the constructor - the called function - the value of this refers to a +newly created object. The prototype of this new +object is set to the prototype of the function object that was invoked as the +constructor.

+

If the function that was called has no explicit return statement, then it +implicitly returns the value of this - the new object.

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

The above calls Foo as constructor and sets the prototype of the newly +created object to Foo.prototype.

+

In case of an explicit return statement, the function returns the value +specified by that statement, but only if the return value is an Object.

+
function Bar() {
+    return 2;
+}
+new Bar(); // a new object
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // the returned object
+

When the new keyword is omitted, the function will not return a new object.

+
function Foo() {
+    this.bla = 1; // gets set on the global object
+}
+Foo(); // undefined
+

While the above example might still appear to work in some cases, due to the +workings of this in JavaScript, it will use the +global object as the value of this.

+

Factories

+

In order to be able to omit the new keyword, the constructor function has to +explicitly return a value.

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

Both calls to Bar return the same thing, a newly create object that +has a property called method, which is a +Closure.

+

It should also be noted that the call new Bar() does not affect the +prototype of the returned object. While the prototype will be set on the newly +created object, Bar never returns that new object.

+

In the above example, there is no functional difference between using and +not using the new keyword.

+

Creating New Objects via Factories

+

It is often recommended to not use new because forgetting its use may +lead to bugs.

+

In order to create a new object, one should rather use a factory and construct a +new object inside of that factory.

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

While the above is robust against a missing new keyword and certainly makes +the use of private variables easier, it comes with some +downsides.

+
    +
  1. It uses more memory since the created objects do not share the methods +on a prototype.
  2. +
  3. In order to inherit, the factory needs to copy all the methods from another +object or put that object on the prototype of the new object.
  4. +
  5. Dropping the prototype chain just because of a left out new keyword +is contrary to the spirit of the language.
  6. +
+

In Conclusion

+

While omitting the new keyword might lead to bugs, it is certainly not a +reason to drop the use of prototypes altogether. In the end it comes down to +which solution is better suited for the needs of the application. It is +especially important to choose a specific style of object creation and use it +consistently.

+

Scopes and Namespaces

Although JavaScript deals fine with the syntax of two matching curly +braces for blocks, it does not support block scope; hence, all that is left +in the language is function scope.

+
function test() { // a scope
+    for(var i = 0; i < 10; i++) { // not a scope
+        // count
+    }
+    console.log(i); // 10
+}
+ +

There are also no distinct namespaces in JavaScript, which means that everything +gets defined in one globally shared namespace.

+

Each time a variable is referenced, JavaScript will traverse upwards through all +the scopes until it finds it. In the case that it reaches the global scope and +still has not found the requested name, it will raise a ReferenceError.

+

The Bane of Global Variables

+
// script A
+foo = '42';
+
+// script B
+var foo = '42'
+

The above two scripts do not have the same effect. Script A defines a +variable called foo in the global scope, and script B defines a foo in the +current scope.

+

Again, that is not at all the same effect: not using var can have major +implications.

+
// global scope
+var foo = 42;
+function test() {
+    // local scope
+    foo = 21;
+}
+test();
+foo; // 21
+

Leaving out the var statement inside the function test will override the +value of foo. While this might not seem like a big deal at first, having +thousands of lines of JavaScript and not using var will introduce horrible, +hard-to-track-down bugs.

+
// global scope
+var items = [/* some list */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // scope of subLoop
+    for(i = 0; i < 10; i++) { // missing var statement
+        // do amazing stuff!
+    }
+}
+

The outer loop will terminate after the first call to subLoop, since subLoop +overwrites the global value of i. Using a var for the second for loop would +have easily avoided this error. The var statement should never be left out +unless the desired effect is to affect the outer scope.

+

Local Variables

+

The only source for local variables in JavaScript are +function parameters and variables declared via the +var statement.

+
// global scope
+var foo = 1;
+var bar = 2;
+var i = 2;
+
+function test(i) {
+    // local scope of the function test
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

While foo and i are local variables inside the scope of the function test, +the assignment of bar will override the global variable with the same name.

+

Hoisting

+

JavaScript hoists declarations. This means that both var statements and +function declarations will be moved to the top of their enclosing scope.

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

The above code gets transformed before execution starts. JavaScript moves +the var statements, as well as function declarations, to the top of the +nearest surrounding scope.

+
// var statements got moved here
+var bar, someValue; // default to 'undefined'
+
+// the function declaration got moved up too
+function test(data) {
+    var goo, i, e; // missing block scope moves these here
+    if (false) {
+        goo = 1;
+
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // fails with a TypeError since bar is still 'undefined'
+someValue = 42; // assignments are not affected by hoisting
+bar = function() {};
+
+test();
+

Missing block scoping will not only move var statements out of loops and +their bodies, it will also make the results of certain if constructs +non-intuitive.

+

In the original code, although the if statement seemed to modify the global +variable goo, it actually modifies the local variable - after hoisting +has been applied.

+

Without knowledge of hoisting, one might suspect the code below would raise a +ReferenceError.

+
// check whether SomeImportantThing has been initialized
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

But of course, this works due to the fact that the var statement is being +moved to the top of the global scope.

+
var SomeImportantThing;
+
+// other code might initialize SomeImportantThing here, or not
+
+// make sure it's there
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

Name Resolution Order

+

All scopes in JavaScript, including the global scope, have the special name +this, defined in them, which refers to the current object.

+

Function scopes also have the name arguments, defined in +them, which contains the arguments that were passed to the function.

+

For example, when trying to access a variable named foo inside the scope of a +function, JavaScript will look up the name in the following order:

+
    +
  1. In case there is a var foo statement in the current scope, use that.
  2. +
  3. If one of the function parameters is named foo, use that.
  4. +
  5. If the function itself is called foo, use that.
  6. +
  7. Go to the next outer scope, and start with #1 again.
  8. +
+ +

Namespaces

+

A common problem associated with having only one global namespace is the +likelihood of running into problems where variable names clash. In JavaScript, +this problem can easily be avoided with the help of anonymous wrappers.

+
(function() {
+    // a self contained "namespace"
+
+    window.foo = function() {
+        // an exposed closure
+    };
+
+})(); // execute the function immediately
+

Unnamed functions are considered expressions; so in order to +be callable, they must first be evaluated.

+
( // evaluate the function inside the parentheses
+function() {}
+) // and return the function object
+() // call the result of the evaluation
+

There are other ways to evaluate and directly call the function expression +which, while different in syntax, behave the same way.

+
// A few other styles for directly invoking the 
+!function(){}()
++function(){}()
+(function(){}());
+// and so on...
+

In Conclusion

+

It is recommended to always use an anonymous wrapper to encapsulate code in +its own namespace. This does not only protect code against name clashes, but it +also allows for better modularization of programs.

+

Additionally, the use of global variables is considered bad practice. Any +use of them indicates badly written code that is prone to errors and hard to maintain.

+

Arrays

Array Iteration and Properties

Although arrays in JavaScript are objects, there are no good reasons to use +the for in loop. In fact, there +are a number of good reasons against the use of for in on arrays.

+ +

Because the for in loop enumerates all the properties that are on the prototype +chain and because the only way to exclude those properties is to use +hasOwnProperty, it is already up to twenty times +slower than a normal for loop.

+

Iteration

+

In order to achieve the best performance when iterating over arrays, it is best +to use the classic for loop.

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

There is one extra catch in the above example, which is the caching of the +length of the array via l = list.length.

+

Although the length property is defined on the array itself, there is still an +overhead for doing the lookup on each iteration of the loop. And while recent +JavaScript engines may apply optimization in this case, there is no way of +telling whether the code will run on one of these newer engines or not.

+

In fact, leaving out the caching may result in the loop being only half as +fast as with the cached length.

+

The length Property

+

While the getter of the length property simply returns the number of +elements that are contained in the array, the setter can be used to +truncate the array.

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo.push(4);
+foo; // [1, 2, 3, undefined, undefined, undefined, 4]
+

Assigning a smaller length truncates the array. Increasing it creates a sparse array.

+

In Conclusion

+

For the best performance, it is recommended to always use the plain for loop +and cache the length property. The use of for in on an array is a sign of +badly written code that is prone to bugs and bad performance.

+

The Array Constructor

Since the Array constructor is ambiguous in how it deals with its parameters, +it is highly recommended to use the array literal - [] notation - +when creating new arrays.

+
[1, 2, 3]; // Result: [1, 2, 3]
+new Array(1, 2, 3); // Result: [1, 2, 3]
+
+[3]; // Result: [3]
+new Array(3); // Result: []
+new Array('3') // Result: ['3']
+

In cases when there is only one argument passed to the Array constructor +and when that argument is a Number, the constructor will return a new sparse +array with the length property set to the value of the argument. It should be +noted that only the length property of the new array will be set this way; +the actual indexes of the array will not be initialized.

+
var arr = new Array(3);
+arr[1]; // undefined
+1 in arr; // false, the index was not set
+

Being able to set the length of the array in advance is only useful in a few +cases, like repeating a string, in which it avoids the use of a loop.

+
new Array(count + 1).join(stringToRepeat);
+

In Conclusion

+

Literals are preferred to the Array constructor. They are shorter, have a clearer syntax, and increase code +readability.

+

Types

Equality and Comparisons

JavaScript has two different ways of comparing the values of objects for equality.

+

The Equality Operator

+

The equality operator consists of two equal signs: ==

+

JavaScript features weak typing. This means that the equality operator +coerces types in order to compare them.

+
""           ==   "0"           // false
+0            ==   ""            // true
+0            ==   "0"           // true
+false        ==   "false"       // false
+false        ==   "0"           // true
+false        ==   undefined     // false
+false        ==   null          // false
+null         ==   undefined     // true
+" \t\r\n"    ==   0             // true
+

The above table shows the results of the type coercion, and it is the main reason +why the use of == is widely regarded as bad practice. It introduces +hard-to-track-down bugs due to its complicated conversion rules.

+

Additionally, there is also a performance impact when type coercion is in play; +for example, a string has to be converted to a number before it can be compared +to another number.

+

The Strict Equality Operator

+

The strict equality operator consists of three equal signs: ===.

+

It works like the normal equality operator, except that strict equality +operator does not perform type coercion between its operands.

+
""           ===   "0"           // false
+0            ===   ""            // false
+0            ===   "0"           // false
+false        ===   "false"       // false
+false        ===   "0"           // false
+false        ===   undefined     // false
+false        ===   null          // false
+null         ===   undefined     // false
+" \t\r\n"    ===   0             // false
+

The above results are a lot clearer and allow for early breakage of code. This +hardens code to a certain degree and also gives performance improvements in case +the operands are of different types.

+

Comparing Objects

+

While both == and === are called equality operators, they behave +differently when at least one of their operands is an Object.

+
{} === {};                   // false
+new String('foo') === 'foo'; // false
+new Number(10) === 10;       // false
+var foo = {};
+foo === foo;                 // true
+

Here, both operators compare for identity and not equality; that is, they +will compare for the same instance of the object, much like is in Python +and pointer comparison in C.

+

In Conclusion

+

It is highly recommended to only use the strict equality operator. In cases +where types need to be coerced, it should be done explicitly +and not left to the language's complicated coercion rules.

+

The typeof Operator

The typeof operator (together with +instanceof) is probably the biggest +design flaw of JavaScript, as it is almost completely broken.

+

Although instanceof still has limited uses, typeof really has only one +practical use case, which does not happen to be checking the type of an +object.

+ +

The JavaScript Type Table

+
Value               Class      Type
+-------------------------------------
+"foo"               String     string
+new String("foo")   String     object
+1.2                 Number     number
+new Number(1.2)     Number     object
+true                Boolean    boolean
+new Boolean(true)   Boolean    object
+new Date()          Date       object
+new Error()         Error      object
+[1,2,3]             Array      object
+new Array(1, 2, 3)  Array      object
+new Function("")    Function   function
+/abc/g              RegExp     object (function in Nitro/V8)
+new RegExp("meow")  RegExp     object (function in Nitro/V8)
+{}                  Object     object
+new Object()        Object     object
+

In the above table, Type refers to the value that the typeof operator returns. +As can be clearly seen, this value is anything but consistent.

+

The Class refers to the value of the internal [[Class]] property of an object.

+ +

In order to retrieve the value of [[Class]], one has to make use of the +toString method of Object.prototype.

+

The Class of an Object

+

The specification gives exactly one way of accessing the [[Class]] value, +with the use of Object.prototype.toString.

+
function is(type, obj) {
+    var clas = Object.prototype.toString.call(obj).slice(8, -1);
+    return obj !== undefined && obj !== null && clas === type;
+}
+
+is('String', 'test'); // true
+is('String', new String('test')); // true
+

In the above example, Object.prototype.toString gets called with the value of +this being set to the object whose [[Class]] value should be +retrieved.

+ +

Testing for Undefined Variables

+
typeof foo !== 'undefined'
+

The above will check whether foo was actually declared or not; just +referencing it would result in a ReferenceError. This is the only thing +typeof is actually useful for.

+

In Conclusion

+

In order to check the type of an object, it is highly recommended to use +Object.prototype.toString because this is the only reliable way of doing so. +As shown in the above type table, some return values of typeof are not defined +in the specification; thus, they can differ between implementations.

+

Unless checking whether a variable is defined, typeof should be avoided.

+

The instanceof Operator

The instanceof operator compares the constructors of its two operands. It is +only useful when comparing custom made objects. Used on built-in types, it is +nearly as useless as the typeof operator.

+

Comparing Custom Objects

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // true
+new Bar() instanceof Foo; // true
+
+// This just sets Bar.prototype to the function object Foo,
+// but not to an actual instance of Foo
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // false
+

Using instanceof with Native Types

+
new String('foo') instanceof String; // true
+new String('foo') instanceof Object; // true
+
+'foo' instanceof String; // false
+'foo' instanceof Object; // false
+

One important thing to note here is that instanceof does not work on objects +that originate from different JavaScript contexts (e.g. different documents +in a web browser), since their constructors will not be the exact same object.

+

In Conclusion

+

The instanceof operator should only be used when dealing with custom made +objects that originate from the same JavaScript context. Just like the +typeof operator, every other use of it should be avoided.

+

Type Casting

JavaScript is a weakly typed language, so it will apply type coercion +wherever possible.

+
// These are true
+new Number(10) == 10; // Number.toString() is converted
+                      // back to a number
+
+10 == '10';           // Strings gets converted to Number
+10 == '+10 ';         // More string madness
+10 == '010';          // And more 
+isNaN(null) == false; // null converts to 0
+                      // which of course is not NaN
+
+// These are false
+10 == 010;
+10 == '-10';
+ +

To avoid the issues above, use of the strict equal operator +is highly recommended. Although this avoids a lot of common pitfalls, there +are still many further issues that arise from JavaScript's weak typing system.

+

Constructors of Built-In Types

+

The constructors of the built in types like Number and String behave +differently when being used with the new keyword and without it.

+
new Number(10) === 10;     // False, Object and Number
+Number(10) === 10;         // True, Number and Number
+new Number(10) + 0 === 10; // True, due to implicit conversion
+

Using a built-in type like Number as a constructor will create a new Number +object, but leaving out the new keyword will make the Number function behave +like a converter.

+

In addition, passing literals or non-object values will result in even more +type coercion.

+

The best option is to cast to one of the three possible types explicitly.

+

Casting to a String

+
'' + 10 === '10'; // true
+

By prepending an empty string, a value can easily be cast to a string.

+

Casting to a Number

+
+'10' === 10; // true
+

Using the unary plus operator, it is possible to cast to a number.

+

Casting to a Boolean

+

By using the not operator twice, a value can be converted a boolean.

+
!!'foo';   // true
+!!'';      // false
+!!'0';     // true
+!!'1';     // true
+!!'-1'     // true
+!!{};      // true
+!!true;    // true
+

Core

Why Not to Use eval

The eval function will execute a string of JavaScript code in the local scope.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

However, eval only executes in the local scope when it is being called +directly and when the name of the called function is actually eval.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

The use of eval should be avoided. 99.9% of its "uses" can be achieved +without it.

+

eval in Disguise

+

The timeout functions setTimeout and setInterval can both +take a string as their first argument. This string will always get executed +in the global scope since eval is not being called directly in that case.

+

Security Issues

+

eval also is a security problem, because it executes any code given to it. +It should never be used with strings of unknown or untrusted origins.

+

In Conclusion

+

eval should never be used. Any code that makes use of it should be questioned +in its workings, performance and security. If something requires eval in +order to work, it should not be used in the first place. A better design +should be used, that does not require the use of eval.

+

undefined and null

JavaScript has two distinct values for nothing, null and undefined, with +the latter being more useful.

+

The Value undefined

+

undefined is a type with exactly one value: undefined.

+

The language also defines a global variable that has the value of undefined; +this variable is also called undefined. However, this variable is neither a constant +nor a keyword of the language. This means that its value can be easily +overwritten.

+ +

Here are some examples of when the value undefined is returned:

+
    +
  • Accessing the (unmodified) global variable undefined.
  • +
  • Accessing a declared but not yet initialized variable.
  • +
  • Implicit returns of functions due to missing return statements.
  • +
  • return statements that do not explicitly return anything.
  • +
  • Lookups of non-existent properties.
  • +
  • Function parameters that do not have any explicit value passed.
  • +
  • Anything that has been set to the value of undefined.
  • +
  • Any expression in the form of void(expression)
  • +
+

Handling Changes to the Value of undefined

+

Since the global variable undefined only holds a copy of the actual value of +undefined, assigning a new value to it does not change the value of the +type undefined.

+

Still, in order to compare something against the value of undefined, it is +necessary to retrieve the value of undefined first.

+

To protect code against a possible overwritten undefined variable, a common +technique used is to add an additional parameter to an anonymous +wrapper that gets no argument passed to it.

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // undefined in the local scope does 
+    // now again refer to the value `undefined`
+
+})('Hello World', 42);
+

Another way to achieve the same effect would be to use a declaration inside the +wrapper.

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

The only difference here is that this version results in 4 more bytes being +used in case it is minified, and there is no other var statement inside the +anonymous wrapper.

+

Uses of null

+

While undefined in the context of the JavaScript language is mostly used in +the sense of a traditional null, the actual null (both a literal and a type) +is more or less just another data type.

+

It is used in some JavaScript internals (like declaring the end of the +prototype chain by setting Foo.prototype = null), but in almost all cases, it +can be replaced by undefined.

+

Automatic Semicolon Insertion

Although JavaScript has C style syntax, it does not enforce the use of +semicolons in the source code, so it is possible to omit them.

+

JavaScript is not a semicolon-less language. In fact, it needs the +semicolons in order to understand the source code. Therefore, the JavaScript +parser automatically inserts them whenever it encounters a parse +error due to a missing semicolon.

+
var foo = function() {
+} // parse error, semicolon expected
+test()
+

Insertion happens, and the parser tries again.

+
var foo = function() {
+}; // no error, parser continues
+test()
+

The automatic insertion of semicolon is considered to be one of biggest +design flaws in the language because it can change the behavior of code.

+

How it Works

+

The code below has no semicolons in it, so it is up to the parser to decide where +to insert them.

+
(function(window, undefined) {
+    function test(options) {
+        log('testing!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+
+})(window)
+

Below is the result of the parser's "guessing" game.

+
(function(window, undefined) {
+    function test(options) {
+
+        // Not inserted, lines got merged
+        log('testing!')(options.list || []).forEach(function(i) {
+
+        }); // <- inserted
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        ); // <- inserted
+
+        return; // <- inserted, breaks the return statement
+        { // treated as a block
+
+            // a label and a single expression statement
+            foo: function() {} 
+        }; // <- inserted
+    }
+    window.test = test; // <- inserted
+
+// The lines got merged again
+})(window)(function(window) {
+    window.someLibrary = {}; // <- inserted
+
+})(window); //<- inserted
+ +

The parser drastically changed the behavior of the code above. In certain cases, +it does the wrong thing.

+

Leading Parenthesis

+

In case of a leading parenthesis, the parser will not insert a semicolon.

+
log('testing!')
+(options.list || []).forEach(function(i) {})
+

This code gets transformed into one line.

+
log('testing!')(options.list || []).forEach(function(i) {})
+

Chances are very high that log does not return a function; therefore, +the above will yield a TypeError stating that undefined is not a function.

+

In Conclusion

+

It is highly recommended to never omit semicolons. It is also recommended +that braces be kept on the same line as their corresponding statements and to +never omit them for single-line if / else statements. These measures will +not only improve the consistency of the code, but they will also prevent the +JavaScript parser from changing code behavior.

+

The delete Operator

In short, it's impossible to delete global variables, functions and some other +stuff in JavaScript which have a DontDelete attribute set.

+

Global code and Function code

+

When a variable or a function is defined in a global or a function +scope it is a property of either the Activation object or +the Global object. Such properties have a set of attributes, one of which is +DontDelete. Variable and function declarations in global and function code +always create properties with DontDelete, and therefore cannot be deleted.

+
// global variable:
+var a = 1; // DontDelete is set
+delete a; // false
+a; // 1
+
+// normal function:
+function f() {} // DontDelete is set
+delete f; // false
+typeof f; // "function"
+
+// reassigning doesn't help:
+f = 1;
+delete f; // false
+f; // 1
+

Explicit properties

+

Explicitly set properties can be deleted normally.

+
// explicitly set property:
+var obj = {x: 1};
+obj.y = 2;
+delete obj.x; // true
+delete obj.y; // true
+obj.x; // undefined
+obj.y; // undefined
+

In the example above, obj.x and obj.y can be deleted because they have no +DontDelete atribute. That's why the example below works too.

+
// this works fine, except for IE:
+var GLOBAL_OBJECT = this;
+GLOBAL_OBJECT.a = 1;
+a === GLOBAL_OBJECT.a; // true - just a global var
+delete GLOBAL_OBJECT.a; // true
+GLOBAL_OBJECT.a; // undefined
+

Here we use a trick to delete a. this here refers +to the Global object and we explicitly declare variable a as its property +which allows us to delete it.

+

IE (at least 6-8) has some bugs, so the code above doesn't work.

+

Function arguments and built-ins

+

Functions' normal arguments, arguments objects +and built-in properties also have DontDelete set.

+
// function arguments and properties:
+(function (x) {
+
+  delete arguments; // false
+  typeof arguments; // "object"
+
+  delete x; // false
+  x; // 1
+
+  function f(){}
+  delete f.length; // false
+  typeof f.length; // "number"
+
+})(1);
+

Host objects

+

The behaviour of delete operator can be unpredictable for hosted objects. Due +to the specification, host objects are allowed to implement any kind of behavior.

+

In conclusion

+

The delete operator often has unexpected behaviour and can only be safely +used to delete explicitly set properties on normal objects.

+

Other

setTimeout and setInterval

Since JavaScript is asynchronous, it is possible to schedule the execution of a +function using the setTimeout and setInterval functions.

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // returns a Number > 0
+

When setTimeout is called, it returns the ID of the timeout and schedule +foo to run approximately one thousand milliseconds in the future. +foo will then be executed once.

+

Depending on the timer resolution of the JavaScript engine running the code, as +well as the fact that JavaScript is single threaded and other code that gets +executed might block the thread, it is by no means a safe bet that one will +get the exact delay specified in the setTimeout call.

+

The function that was passed as the first parameter will get called by the +global object, which means that this inside the called function +refers to the global object.

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // this refers to the global object
+        console.log(this.value); // will log undefined
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

Stacking Calls with setInterval

+

While setTimeout only runs the function once, setInterval - as the name +suggests - will execute the function every X milliseconds, but its use is +discouraged.

+

When code that is being executed blocks the timeout call, setInterval will +still issue more calls to the specified function. This can, especially with small +intervals, result in function calls stacking up.

+
function foo(){
+    // something that blocks for 1 second
+}
+setInterval(foo, 1000);
+

In the above code, foo will get called once and will then block for one second.

+

While foo blocks the code, setInterval will still schedule further calls to +it. Now, when foo has finished, there will already be ten further calls to +it waiting for execution.

+

Dealing with Possible Blocking Code

+

The easiest solution, as well as most controllable solution, is to use setTimeout within +the function itself.

+
function foo(){
+    // something that blocks for 1 second
+    setTimeout(foo, 1000);
+}
+foo();
+

Not only does this encapsulate the setTimeout call, but it also prevents the +stacking of calls and gives additional control. foo itself can now decide +whether it wants to run again or not.

+

Manually Clearing Timeouts

+

Clearing timeouts and intervals works by passing the respective ID to +clearTimeout or clearInterval, depending on which set function was used +in the first place.

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

Clearing All Timeouts

+

As there is no built-in method for clearing all timeouts and/or intervals, +it is necessary to use brute force in order to achieve this functionality.

+
// clear "all" timeouts
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

But there might still be timeouts that are unaffected by this arbitrary number. +Another way of doing this is to consider that the ID given to a timeout is +incremented by one every time you call setTimeout.

+
// clear "all" timeouts
+var biggestTimeoutId = window.setTimeout(function(){}, 1),
+i;
+for(i = 1; i <= biggestTimeoutId; i++) {
+    clearTimeout(i);
+}
+

Even though this works on all major browsers today, it isn't specified that +the IDs should be ordered that way and it may change. Therefore, it is instead +recommended to keep track of all the timeout IDs, so they can be cleared +specifically.

+

Hidden Use of eval

+

setTimeout and setInterval can also take a string as their first parameter. +This feature should never be used because it internally makes use of eval.

+ +
function foo() {
+    // will get called
+}
+
+function bar() {
+    function foo() {
+        // never gets called
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

Since eval is not getting called directly in this case, the string +passed to setTimeout will be executed in the global scope; thus, it will +not use the local variable foo from the scope of bar.

+

It is further recommended to not use a string to pass arguments to the +function that will get called by either of the timeout functions.

+
function foo(a, b, c) {}
+
+// NEVER use this
+setTimeout('foo(1, 2, 3)', 1000)
+
+// Instead use an anonymous function
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

In Conclusion

+

A string should never be used as the parameter of setTimeout or +setInterval. It is a clear sign of really bad code, when arguments need +to be supplied to the function that gets called. An anonymous function should +be passed that then takes care of the actual call.

+

Furthermore, the use of setInterval should be avoided because its scheduler is not +blocked by executing JavaScript.

+
\ No newline at end of file diff --git a/it/index.html b/it/index.html new file mode 100644 index 0000000..bb36de9 --- /dev/null +++ b/it/index.html @@ -0,0 +1,1587 @@ +JavaScript Garden

Introduzione

Introduzione

JavaScript Garden è una collezione in continua crescita di documentazione +relativa alle parti più peculiari del linguaggio di programmazione JavaScript. +Il suo intento è quello di mostrare come evitare i più comuni errori, i +problemi legati alla performance e le cattive abitudini che i programmatori +JavaScript non esperti possono incontrare lungo il loro cammino di +approfondimento del linguaggio.

+

L'obiettivo di JavaScript Garden non è quello di insegnarti JavaScript. +Una conoscenza pregressa del linguaggio è fortemenete consigliata, in modo da +capire gli argomenti trattati da questa guida. Per poter imparare le basi del +linguaggio, ti suggeriamo di leggere l'eccellente guida su Mozilla +Developer Network.

+

Gli autori

+

Questa guida è il risultato del lavoro di due utenti di Stack Overflow, +Ivo Wetzel (stesura) e Zhang Yi Jiang (progettazione).

+

È attualmente mantenuto da Tim Ruffles.

+

Collaboratori

+ +

Hosting

+

JavaScript Garden è ospitato su GitHub, ma Cramer Development ci supporta +con un mirror su JavaScriptGarden.info.

+

Licenza

+

JavaScript Garden è pubblicato sotto la licenza MIT ed ospitato su +GitHub. Se trovi inesattezze o errori di battitura, ti prego di +segnalare il problema o fare un pull request sul nostro repository. +Puoi anche trovarci nella stanza JavaScript della chat di Stack +Overflow.

+

Oggetti

Utilizzo di oggetti e proprietà

Tutto in JavaScript funziona come un oggetto, con la sola eccezione di +null e undefined.

+
false.toString(); // 'false'
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

Un'idea comunemente errata è che i numeri letterali non possano essere +usati come oggetti. Questo a causa di una scorretta gestione da parte del +parser di JavaScript, che tenta di analizzare la dot notation di un +numero come se fosse un letterale in virgola mobile.

+
2.toString(); // solleva SyntaxError
+

Esistono un paio di soluzioni che possono essere usate per far sì che i +numeri letterali vengano considerati come oggetti.

+
2..toString(); // il secondo punto viene correttamente riconosciuto
+2 .toString(); // notate lo spazio tra il numero e il punto
+(2).toString(); // viene prima valutato 2
+

Oggetti come un tipo di dato

+

Gli oggetti in JavaScript possono anche essere usati come tabelle hash e +consistono principalmente di proprietà con un nome che mappano dei valori.

+

Usando un oggetto letterale (notazione {}) è possibile creare un +semplice oggetto. Questo nuovo oggetto eredita da +Object.prototype e non ha proprietà definite.

+
var foo = {}; // un nuovo oggetto vuoto
+
+// un nuovo oggetto con una proprietà `test` con valore 12
+var bar = {test: 12};
+

Accedere alle proprietà

+

È possibile accedere alle proprietà di un oggetto in due modi. +Usando il punto oppure attraverso l'uso delle parentesi quadre.

+
var foo = {name: 'kitten'}
+foo.name; // kitten
+foo['name']; // kitten
+
+var get = 'name';
+foo[get]; // kitten
+
+foo.1234; // SyntaxError
+foo['1234']; // funziona
+

Le due notazioni funzionano quasi in modo identico, con la sola differenza +che usando le parentesi quadre è possibile impostare dinamicamente le +proprietà ed il loro nome identificatore, cosa che altrimenti genererebbe +un errore di sintassi.

+

Cancellazione delle proprietà

+

Il solo modo per rimuovere una proprietà da un oggetto è quello di usare +l'operatore delete. Impostando la proprietà a undefined o null, infatti, +si rimuove solo il valore associato alla proprietà, ma non la chiave.

+
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

Il codice qui sopra, stamperà sia bar undefined che foo null. Soltanto +baz è stato rimosso, e quindi non compare nell'output.

+

Notazione delle chiavi

+
var test = {
+    'case': 'Parola chiave, scrivimi come stringa',
+    // solleva SyntaxError
+    delete: 'Parola chiave, anche io devo essere una stringa'
+};
+

Le proprietà di un oggetto possono essere scritte sia come normali caratteri +che come stringhe. A causa di un altro errore di progettazione del parser di +JavaScript, il codice appena visto genererà un SyntaxError in ECMAScript +precedente alla versione 5.

+

Questo errore nasce dal fatto che delete è una parola chiave, quindi, +deve essere scritta come una stringa letterale per assicurarsi che venga +correttamente interpretata dai vecchi motori JavaScript.

+

Il prototipo

JavaScript non segue il classico modello di ereditarietà ma, piuttosto, +utilizza quello prototipale.

+

Anche se ciò viene considerato come uno dei punti più deboli del JavaScript, +il modello ad ereditarietà prototipale è difatto più potente di quello +classico. Ad esempio, è piuttosto semplice creare un modello classico +sulle basi di quello prototipale, mentre l'operazione inversa è piuttosto +complessa.

+

JavaScript è il solo linguaggio ampiamente utilizzato che sfrutta l'ereditarietà +prototipale, quindi è possibile prendersi il proprio tempo per adeguarsi alle +differenze esistenti tra i due modelli.

+

La prima grande differenza è che l'ereditarietà in JavaScript utilizza le +catene di prototipi.

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// Imposta il prototipo di Bar ad una nuova istanza di Foo
+Bar.prototype = new Foo();
+Bar.prototype.foo = 'Hello World';
+
+// Si assicura di elencare Bar come l'attuale costruttore
+Bar.prototype.constructor = Bar;
+
+var test = new Bar(); // crea una nuova istanza di bar
+
+// La catena di prototipi finale
+test [istanza di Bar]
+    Bar.prototype [istanza di Foo]
+        { foo: 'Hello World' }
+        Foo.prototype
+            { method: ... }
+            Object.prototype
+                { toString: ... /* ecc. */ }
+

Nel codice qui sopra, l'oggetto test erediterà sia da Bar.prototype che da +Foo.prototype, e quindi avrà accesso alla funzione method che era stata +definita in Foo. Avrà anche accesso alla proprietà value dell'unica +istanza di Foo, cioè il suo prototipo. È importante notare come +new Bar() non crei una nuova istanza di Foo, ma piuttosto riutilizzi +quella assegnata al suo prototipo. Perciò, tutte le istanze di Bar +condivideranno la stessa proprietà value.

+ +

Tabella delle proprietà

+

Quando si accede alle proprietà di un oggetto, JavaScript risale la +catena di prototipi fino a che non incontra una proprietà con il nome +richiesto.

+

Se raggiunge la cima della catena (cioè Object.prototype) senza aver +trovato le specifica proprietà, ritorna il valore undefined.

+

La proprietà Prototype

+

Anche se la proprietà prototype viene usata dal linguaggio per creare la +catena di prototipi, è comunque sempre possibile assegnarvi un qualsiasi +dato valore. Nonostante cio, i dati primitivi verranno semplicemente ignorati +quando assegnati ad un prototipo.

+
function Foo() {}
+Foo.prototype = 1; // nessun effetto
+

L'assegnazione di oggetti, come mostrato nell'esempio precedente, funzionerà, +e permette la creazione dinamica di catene di prototipi.

+

Performance

+

Il tempo di ricerca per proprietà presenti in alto (all'inizio) della catena +di prototipi, può avere un impatto negativo sulla performance, e questo deve +essere tenuto bene in considerazione in codice dove la performance è un fattore +critico. Inoltre, il tentativo di accedere a proprietà inesistenti obbligherà +comunque ad attraversare tutta la catena di prototipi.

+

Oltre a ciò, iterando tra le proprietà di un oggetto, +ogni proprietà presente nella catena di prototipi verrà enumerata.

+

Estensione di prototipi nativi

+

Una caratteristica che viene spesso abusata, è quella di estendere +Object.prototype o uno degli altri prototipi interni al linguaggio.

+

Questa tecnica viene detta monkey patching e vìola il principio di +incapsulamento. Anche se usata da popolari framework come Prototype, +non c'è una valida ragione per pasticciare, aggiungendo ai tipi interni del +linguaggio funzionalità non standard.

+

La sola buona ragione per estendere un prototipo interno è quella di +effettuare il backport di funzionalità presenti nei motori JavaScript +più recenti, come ad esempio Array.forEach.

+

In conclusione

+

È essenziale capire il modello di ereditarietà prototipale prima +di scrivere codice complesso che ne faccia uso. Bisogna, inoltre, tenere +sotto controllo la lunghezza della catena di prototipi nel proprio codice, +e suddividerla in più catene se necessario, per evitare possibili problemi di +performance. Inoltre, i prototipi nativi non dovrebbero mai essere +estesi a meno che non sia per garantire compatibilità con le funzionalità +più recenti di JavaScript.

+

hasOwnProperty

Per verificare se un oggetto ha (possiede) una proprietà definita dentro +se stesso piuttosto che in qualche parte della sua +catena di prototipi, è necessario usare il metodo +hasOwnProperty che tutti gli oggetti ereditano da Object.prototype.

+ +

hasOwnProperty è la sola cosa in JavaScript che si occupa delle proprietà +senza attraversare la catena di prototipi.

+
// Modifichiamo Object.prototype
+Object.prototype.bar = 1;
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // true
+
+foo.hasOwnProperty('bar'); // false
+foo.hasOwnProperty('goo'); // true
+

Solo hasOwnProperty darà il risultato atteso e corretto. Questo è essenziale +quando si itera tra le proprietà di un qualsiasi oggetto. Non c'è altro +modo per escludere proprietà che non sono definite all'interno dell'oggetto +stesso, ma da qualche altra parte nella sua catena di prototipi.

+

hasOwnProperty come proprietà

+

JavaScript non protegge il nome di proprietà hasOwnProperty. Quindi, se +esiste la possibilità che un oggetto possa avere una proprietà con questo +nome, è necessario usare un hasOwnProperty esterno per ottenere il +risultato corretto.

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Here be dragons'
+};
+
+foo.hasOwnProperty('bar'); // ritorna sempre false
+
+// Usa un altro hasOwnProperty di Object e lo richiama con 'this' impostato a foo
+({}).hasOwnProperty.call(foo, 'bar'); // true
+
+// E' anche possibile usare hasOwnProperty dal prototipo di
+// Object per questo scopo
+Object.prototype.hasOwnProperty.call(foo, 'bar'); // true
+

In conclusione

+

Usare hasOwnProperty è l'unico metodo affidabile per verificare +l'esistenza di una proprietà in un oggetto. È raccomandabile usare +hasOwnProperty in ogni ciclo for in per +evitare errori con i prototipi nativi estesi.

+

Il ciclo for in

Come per l'operatore in, il ciclo for in attraversa la catena di +prototipi quando itera tra le proprietà di un oggetto.

+ +
// Modifichiamo Object.prototype
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // stampa sia bar che moo
+}
+

Dato che non è possibile modificare il comportamento del ciclo for in, +è necessario filtrare le proprietà indesiderate all'interno del ciclo stesso. +Questo può essere fatto usando il metodo hasOwnProperty +di Object.prototype.

+ +

Usare hasOwnProperty per il filtraggio

+
// questo è il foo dell'esempio precedente
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

Questa è la sola versione corretta da usare. Proprio a causa dell'utilizzo di +hasOwnProperty, soltanto moo verrà stampato; mentre omettendone l'uso, +il codice sarà soggetto ad errori nei casi dove i prototipi nativi (ad esempio +Object.prototype) sono stati estesi.

+

Un framework ampiamente usato che estende Object.prototype è Prototype. +Quando questo framework viene incluso, è sicuro che i cicli for in che non +utilizzano hasOwnProperty non funzioneranno.

+

In conclusione

+

Si raccomanda di usare sempre hasOwnProperty. Non si dovrebbe mai dare +per scontato l'ambiente in cui il codice sta girando, o se i prototipi +nativi sono stati estesi o meno.

+

Funzioni

Dichiarazioni ed espressioni di funzione

Le funzioni in JavaScript sono oggetti di prima classe. Ciò significa che +possono essere usate come ogni altro valore. Un uso comune di questa +caratteristica è quello di passare una funzione anonima come funzione di +callback ad un'altra, possibilmente asincrona, funzione.

+

La dichiarazione di function

+
function foo() {}
+

La funzione qui sopra viene elevata (hoisted) prima +che inizi l'esecuzione del programma. Questo vuol dire che essa è disponibile +da un qualsasi punto dello scope in cui è stata definita, anche se +richiamata prima dell'effettiva definizione nel sorgente.

+
foo(); // funziona perché foo è stata creata prima di eseguire il codice
+function foo() {}
+

L'espressione function

+
var foo = function() {};
+

Questo esempio assegna la funzione anonima alla variabile foo.

+
foo; // 'undefined'
+foo(); // questo solleva un TypeError
+var foo = function() {};
+

Dato che var è una dichiarazione che eleva il nome di variabile foo +prima che l'esecuzione del codice inizi, foo è già dichiarata quando lo +script viene eseguito.

+

Ma, dal momento che le assegnazioni avvengono solo a runtime, il valore di +foo sarà undefined per default, prima che il relativo +codice sia eseguito.

+

Espressione di funzione con nome

+

Un altro caso speciale è l'assegnazione di funzioni con nome.

+
var foo = function bar() {
+    bar(); // funziona
+}
+bar(); // ReferenceError
+

Qui, bar non è disponibile nello scope più esterno, dal momento che la +funzione viene assegnata solo a foo, mentre è disponibile all'interno di +bar. Ciò è dato dal modo in cui funziona la risoluzione dei nomi +in JavaScript: il nome della funzione è sempre reso disponibile nello scope +locale della funzione stessa.

+

Come funziona this

JavaScript ha una concezione differente di ciò a cui il nome speciale this +fa normalmente riferimento nella maggior parte degli altri linguaggi di +programmazione. Ci sono esattamente cinque differenti modi nei quali +il valore di this può essere associato nel linguaggio.

+

Lo scope globale

+
this;
+

Usando this nello scope globale, esso farà semplicemente riferimento +all'oggetto globale.

+

Richiamando una funzione

+
foo();
+

Qui, this farà ancora riferimento all'oggetto globale.

+ +

Richiamando un metodo

+
test.foo();
+

In questo esempio, this farà riferimento a test.

+

Richiamando un costruttore

+
new foo();
+

Una chiamata di funzione che viene preceduta dalla parola chiave new +agisce come un costruttore. Dentro la funzione, +this farà riferimento all'Object appena creato.

+

Impostazione esplicita di this

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // l'array verrà espanso come mostrato sotto
+foo.call(bar, 1, 2, 3); // risulterà in a = 1, b = 2, c = 3
+

Quando si usano i metodi call o apply di Function.prototype, il valore di +this all'interno della funzione chiamata viene esplicitamente impostato +al primo argomento della corrispondente chiamata di funzione.

+

Come risultato, nell'esempio sopra, il caso del metodo non viene applicato, +e this all'interno di foo sarà impostato a bar.

+ +

Insidie comuni

+

Mentre molti di questi casi hanno senso, il primo può essere considerato +un altro errore di progettazione del linguaggio perché non ha mai un +uso pratico.

+
Foo.method = function() {
+    function test() {
+        // this viene impostato all'oggetto globale
+    }
+    test();
+}
+

Una comune credenza è che this all'interno di test faccia riferimento a +Foo mentre, invece, non è così.

+

Per poter ottenere l'accesso a Foo dall'interno di test, è necessario creare +una variabile locale all'interno di method che faccia riferimento a Foo.

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // Qui viene usato that invece di this
+    }
+    test();
+}
+

that è solo un normale nome di variabile, ma viene comunemente usato come +riferimento ad un this più esterno. Abbinato alle closures +può anche essere usato per passare il valore di this.

+

Metodi di asseganzione

+

Un'altra cosa che non funziona in JavaScript è la creazione di un alias ad +una funzione, cioè l'assegnazione di un metodo ad una variabile.

+
var test = someObject.methodTest;
+test();
+

A causa della prima dichiarazione, test ora agisce da semplice chiamata a +funzione e quindi, this all'interno di essa non farà più riferimento a +someObject.

+

Mentre l'assegnazione tardiva di this potrebbe sembrare una cattiva idea +in un primo momento, alla prova dei fatti è ciò che fa funzionare +l'ereditarietà prototipale.

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

Quando method viene chiamato da un'istanza di Bar, this farà riferimento +a quell'istanza.

+

Closures e riferimenti

Una delle caratteristiche più potenti di JavaScript è la disponibilità delle +closure. Con le closure, gli scope hanno sempre accesso allo scope +più esterno nel quale sono state definite. Dal momento che il solo scope che +JavaScript ha è lo scope di funzione, tutte le funzioni, +per default, agiscono da closure.

+

Emulare variabili private

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

Qui, Counter ritorna due closure: la funzione increment e get. +Entrambe mantengono un riferimento allo scope di Counter e, quindi, +hanno sempre accesso alla variabile count definita in quello scope.

+

Perché le variabili private funzionano

+

Dato che non è possibile fare riferimento o assegnare scope in JavaScript, +non c'è modo per accedere alla variabile count dall'esterno. Il solo +modo per interagire con essa è tramite le due closure.

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

Il codice sopra non modificherà la variabile count nello scope di Counter, +dato che foo.hack non è stato definito in quello scope. Invece, creerà +(o meglio, sostituirà) la variabile globale count.

+

Closure nei cicli

+

Un errore che spesso viene fatto è quello di usare le closure all'interno dei +cicli, come se stessero copiando il valore della variabile dell'indice del ciclo.

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);
+    }, 1000);
+}
+

Questo esempio non stamperà i numeri da 0 a 9, ma semplicemente il +numero 10 dieci volte.

+

La funzione anonima mantiene un riferimento ad i, ma al momento in cui +console.log viene richiamata, il ciclo for è già terminato, ed il valore +di i è stato impostato a 10.

+

Per ottenere l'effetto desiderato, è necessario creare una copia del valore +di i.

+

Evitare il problema del riferimento

+

Per copiare il valore della variabile indice del ciclo, è meglio usare un +contenitore anonimo.

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);
+        }, 1000);
+    })(i);
+}
+

La funzione anonima più esterna viene chiamata immediatamente con i come +suo primo argomento e riceverà una copia del valore di i come suo +parametro e.

+

La funzione anonima che viene passata a setTimeout ora ha un riferimento a +e, il cui valore non viene modificato dal ciclo.

+

C'è anche un altro possibile modo per ottenere il medesimo risultato, e cioè +ritornare una funzione dal contenitore anonimo che avrà quindi lo stesso +comportamento del codice visto precedentemente.

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+

C'è un ulteriore modo per ottenere ciò, usando .bind, che può assegnare un +contesto this e degli argomenti ad una funzione. Esso funziona allo stesso +modo degli esempi precedenti

+
for(var i = 0; i < 10; i++) {
+    setTimeout(console.log.bind(console, i), 1000);
+}
+

L'oggetto arguments

Ogni scope di funzione in JavaScript può accedere alla speciale variabile +arguments. Questa variabile mantiene un elenco di tutti gli argomenti +che sono stati passati alla funzione.

+ +

L'oggetto arguments non è un Array. Sebbene abbia in parte la +semantica di un array (nello specifico la proprietà length), esso non +eredita da Array.prototype ed è a tutti gli effetti un Object.

+

Proprio per questo motivo, non è possibile usare su arguments i metodi +standard degli array come push, pop, slice. E mentre l'iterazione con +un semplice ciclo for funzionerà senza problemi, sarà necessario convertire +l'oggetto in un vero Array per poter usare i metodi standard di Array con +esso.

+

Conversione ad array

+

Il codice seguente ritornerà un nuovo Array contenenente tutti gli elementi +dell'oggetto arguments.

+
Array.prototype.slice.call(arguments);
+

Dato che questa conversione è lenta, non è raccomandato usarla in sezioni +di codice in cui la performance è un fattore critico.

+

Passaggio di argomenti

+

Quello che segue è il metodo raccomandato per passare argomenti da una funzione +ad un'altra.

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // codice da eseguire
+}
+

Un altro trucco è quello di usare call e apply insieme per creare veloci +contenitori senza vincoli.

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// Crea una versione senza vincoli di "method"
+// Richiede i parametri: this, arg1, arg2...argN
+Foo.method = function() {
+
+    // Risultato: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+};
+

Parametri formali e indici degli argomenti

+

L'oggetto arguments crea funzioni getter e setter sia per le sue +proprietà che per i parametri formali della funzione.

+

Come risultato, la modifica del valore di un parametro formale modificherà +anche il valore della corrispondente proprietà nell'oggetto arguments, e +vice versa.

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

Miti e verità sulla performance

+

Il solo caso in cui l'oggetto arguments non viene creato, è quando esso +viene dichiarato come un nome all'interno di una funzione o uno dei suoi +parametri formali. Non importa che venga usato o meno.

+

Sia i getter che i setter vengono sempre creati. Perciò, il loro +utilizzo non ha praticamente alcun impatto sulle prestazioni, specialmente +nel mondo reale dove nel codice c'è più di un semplice accesso alle proprietà +dell'oggetto arguments.

+ +

Ad ogni modo, c'è un caso che ridurrà drasticamente la performance nei motori +JavaScript moderni. È il caso dell'utilizzo di arguments.callee.

+
function foo() {
+    arguments.callee; // fa qualcosa con questo oggetto funzione
+    arguments.callee.caller; // e l'oggetto funzione chiamante
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // normalmente sarebbe sostituito con il suo codice...
+    }
+}
+

Nel codice qui sopra, foo non può più essere soggetto ad inlining +dal momento che necessita di conoscere sia se stesso che il suo chiamante. +Questo non solo annulla possibili guadagni prestazionali ottenibili con +l'inlining, ma spezza anche il principio di incapsulazione perché la funzione +ora potrebbe essere dipendente da uno specifico contesto di esecuzione.

+

L'utilizzo di arguments.callee o di qualsiasi altra delle sue proprietà +è altamente sconsigliato.

+ +

Costruttori

I costruttori in JavaScript sono differenti da quelli di molti altri linguaggi. +Qualsiasi chiamata a funzione preceduta dalla parola chiave new agisce come +un costruttore.

+

Dentro al costruttore (la funzione chiamata) il valore di this fa riferimento +al nuovo oggetto creato. Il prototype di questo nuovo +oggetto viene impostato al prototype dell'oggetto funzione che è stato invocato +come costruttore.

+

Se la funzione che è stata chiamata non ha un'istruzione return esplicita, +allora essa ritorna implicitamente il valore di this (il nuovo oggetto).

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

Questo esempio chiama Foo come costruttore ed imposta il prototype del +nuovo oggetto creato a Foo.prototype.

+

In caso di istruzione return esplicita, la funzione ritorna il valore +specificato da quell'istruzione, ma solo se il valore di ritorno è un +Object.

+
function Bar() {
+    return 2;
+}
+new Bar(); // un nuovo oggetto
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // l'oggetto ritornato
+

Quando la parola chiave new viene omessa, la funzione non ritornerà un +nuovo oggetto.

+
function Foo() {
+    this.bla = 1; // imposta la proprietà dell'oggetto globale
+}
+Foo(); // undefined
+

Mentre l'esempio precedente potrebbe sembrare essere funzionante in alcuni +casi, a causa del modo in cui lavora this in JavaScript, +esso userà l'oggetto globale come valore di this.

+

Factory (Fabbriche di oggetti)

+

Per poter omettere la parola chiave new, la funzione costruttore deve +esplicitamente ritornare un valore.

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

Entrambe le chiamate a Bar ritornano lo stesso risultato, un nuovo oggetto +creato con una proprietà chiamata method, che è una Closure.

+

Bisogna anche notare che la chiamata new Bar() non influisce sul prototipo +dell'oggetto ritornato. Mentre il prototipo sarà impostato con il nuovo oggetto +creato, Bar non ritornerà mai quel nuovo oggetto.

+

Nell'esempio sopra, non c'è differenza funzionale nell'usare o meno la parola +chiave new.

+

Creare nuovi oggetti tramite factory

+

Viene spesso raccomandato di non usare new perché una sua dimenticanza +può portare a bug potenzialmente insidiosi da risolvere.

+

Per poter creare un nuovo oggetto, si dovrebbe invece usare una factory e +costruire un nuovo oggetto all'interno di quella factory.

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

Sebbene questo esempio sia a prova di omissione della parola chiave new e +renda sicuramente più semplice l'utilizzo delle variabili private, +esso ha alcuni aspetti negativi.

+
    +
  1. Usa più memoria dal momento che gli oggetti creati non condividono +i metodi di un prototipo.
  2. +
  3. Per poter ereditare, la factory deve copiare tutti i metodi da un altro +oggetto oppure mettere quell'oggetto nel proptotipo del nuovo oggetto.
  4. +
  5. Perdere la catena di prototipi solo perché si vuole tralasciare la +parola chiave new è contrario allo spirito del linguaggio.
  6. +
+

In conclusione

+

Sebbene l'omissione della parola chiave new possa portare all'introduzione di +bug, non è certo un motivo per privarsi completamente dell'uso dei prototipi. +Alla fine si tratta di decidere quale sia la soluzione più adatta per +l'applicazione. È specialmente importante scegliere uno specifico stile +di creazione degli oggetti ed usarlo in maniera consistente.

+

Scope e spazi di nome (namespace)

Sebbene JavaScript non abbia problemi con la sintassi delle parentesi +graffe per la definizione di blocchi, esso non supporta lo scope +per blocco, quindi, tutto ciò che il linguaggio ci mette a disposizione +è lo scope di funzione.

+
function test() { // questo è uno scope
+    for(var i = 0; i < 10; i++) { // questo non è uno scope
+        // conta
+    }
+    console.log(i); // 10
+}
+ +

Anche gli spazi di nome (namespace) non sono gestiti in JavaScript, e ciò +significa che ogni cosa viene definita in un namespace globalmente condiviso.

+

Ogni volta che ci si riferisce ad una variabile, JavaScript risale attraverso +tutti gli scope fino a che non la trova e, nel caso esso raggiunga lo scope +globale senza aver trovato il nome richiesto, solleva un ReferenceError.

+

Il problema delle variabili globali

+
// script A
+foo = '42';
+
+// script B
+var foo = '42'
+

Questi due script non hanno lo stesso effetto. Lo script A definisce una +variabile chiamata foo nello scope globale, mentre lo script B definisce +una foo nello scope attuale.

+

Ancora una volta. Questo esempio non sortisce lo stesso effetto: il +non utilizzo di var può avere importanti conseguenze.

+
// scope globale
+var foo = 42;
+function test() {
+    // scope locale
+    foo = 21;
+}
+test();
+foo; // 21
+

L'omissione dell'istruzione var all'interno della funzione test sostituirà +il valore di foo. Sebbene questo possa non sembrare un grosso problema in +un primo momento, ritrovarsi con migliaia di linee di JavaScript senza +utilizzare var introdurrà orribili bug molto difficili da individuare.

+
// scope globale
+var items = [/* un elenco */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // scope di subLoop
+    for(i = 0; i < 10; i++) { // istruzione var omessa
+        // fai qualcosa di eccezionale!
+    }
+}
+

Il ciclo esterno terminerà dopo la prima chiamata a subLoop, dato che subLoop +sovrascriverà il valore globale di i. L'utilizzo di una var per il secondo ciclo +for avrebbe facilmente evitato questo errore. L'istruzione var non dovrebbe +mai essere omessa a meno che l'effetto desiderato non sia proprio quello +di influenzare lo scope esterno.

+

Variabili locali

+

In JavaScript le sole sorgenti per le variabili locali sono i parametri +funzione e le variabili dichiarate tramite l'istruzione +var.

+
// scope globale
+var foo = 1;
+var bar = 2;
+var i = 2;
+
+function test(i) {
+    // scope locale della funzione test
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

Mentre foo e i sono variabili locali all'interno dello scope della funzione +test, l'assegnazione di bar sostituirà la variabile globale con lo stesso +nome.

+

Elevamento (hoisting)

+

JavaScript eleva le dichiarazioni. Questo significa che le istruzioni var +e le dichiarazioni function verranno spostate in cima agli scope che le +racchiudono.

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

Il codice qui sopra, viene trasformato prima che inizi l'esecuzione. JavaScript +sposta sia le istruzioni var che le dichiarazioni function in cima al più +vicino scope che le racchiude.

+
// le istruzioni var vengono spostate qui
+var bar, someValue; // di default a 'undefined'
+
+// la dichiarazione function viene spostate qui
+function test(data) {
+    var goo, i, e; // il blocco scope mancante sposta qui queste istruzioni
+    if (false) {
+        goo = 1;
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // fallisce con un TypeError dato che bar è ancora 'undefined'
+someValue = 42; // le assegnazioni non vengono influenzate dall'elevazione
+bar = function() {};
+
+test();
+

L'omissione del blocco di scope non solo muoverà le istruzioni var fuori dal +corpo dei cicli, ma renderà anche i risultati di certi costrutti if poco +intuitivi.

+

Nel codice originale, sebbene l'istruzione if sembrasse modificare la +variabile globale goo, effettivamente essa va a modificare la variabile locale +(dopo che l'elevazione è stata eseguita).

+

Senza la conoscenza dell'elevazione, uno potrebbe pensare che il codice +qui sotto sollevi un ReferenceError.

+
// verifica se SomeImportantThing è stato inizializzato
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

Ma ovviamente tutto funziona grazie al fatto che l'istruzione var è stata +spostata all'inzio dello scope globale.

+
var SomeImportantThing;
+
+// qui altro codice potrebbe o meno inizializzare SomeImportantThing
+
+// ci assicuriamo che ci sia
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

Ordine di risoluzione dei nomi

+

Tutti gli scope in JavaScript, scope globale incluso, hanno lo speciale +nome this definito in essi, che fa riferimento +all'oggetto attuale.

+

Gli scope di funzione hanno anche il nome arguments +definito in essi, che contiene gli argomenti passati alla funzione.

+

Per esempio, cercando di accedere ad una variabile di nome foo all'interno +dello scope di una funzione, JavaScript effettuerà una ricerca del nome nel +seguente ordine:

+
    +
  1. Nel caso ci sia un'istruzione var foo nello scope attuale, usa quella.
  2. +
  3. Se uno dei parametri funzione si chiama foo, usa quello.
  4. +
  5. Se la funzione stessa si chiama foo, usa quella.
  6. +
  7. Vai al successivo scope esterno e ricomincia dal numero 1.
  8. +
+ +

Spazi di nome (Namespace)

+

Un comune problema associato al fatto di avere un solo spazio nomi globale, +è che facilmente si incappa in problemi dove i nomi di variabile si +sovrappongono. In JavaScript queso problema può essere facilmente evitato +con l'aiuto dei contenitori anonimi.

+
(function() {
+    // "namespace" auto contenuto
+
+    window.foo = function() {
+        // una closure esposta
+    };
+
+})(); // esecue immediatamente la funzione
+

Le funzioni anonime sono considerate espressioni, quindi +per poter essere richiamabili, esse devono prima essere valutate.

+
( // valuta la funzione dentro le parentesi
+function() {}
+) // e ritorna l'oggetto funzione
+() // richiama il risultato della valutazione
+

Ci sono altri modi per valutare e chiamare direttamente l'espressione funzione +i quali, sebbene differenti nella sintassi, hanno tutti il medesimo effetto.

+
// Alcuni modi per invocare direttamente la
+!function(){}()
++function(){}()
+(function(){}());
+// e così via...
+

In conclusione

+

Si raccomanda sempre di usare un contenitore anonimo per incapsulare il +codice nel suo proprio namespace. Questo non solo protegge il codice da +eventuali conflitti con i nomi, ma permette anche una migliore modularizzazione +dei programmi.

+

Inoltre, l'uso delle variabili globali è considerato una cattiva pratica. +Qualsiasi loro uso indica codice scritto male che è suscettibile ad errori +e difficile da mantenere.

+

Array

Iterazione e proprietà degli Array

Sebbene gli array in JavaScript siano oggetti, non ci sono valide ragioni +per usare il ciclo for in. Infatti, ci sono varie +buone ragioni per evitare l'utilizzo di for in con gli array.

+ +

Dato che il ciclo for in enumera tutte le proprietà che sono presenti nella +catena di prototipi, e dal momento che il solo modo per escludere queste +proprietà è quello di usare hasOwnProperty, +esso è già venti volte più lento di un normale ciclo for.

+

Iterazione

+

Per poter ottenere la miglior performance durante l'iterazione degli array, +è meglio usare il classico ciclo for.

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

In questo esempio c'è un ulteriore particolare da notare, che è il caching +della lunghezza dell'array tramite l = list.length.

+

Sebbene la proprietà length sia definita nell'array stesso, c'è ancora un +sovraccarico di lavoro dato dal fatto che deve essere ricercata ad ogni +iterazione del ciclo. E mentre i motori JavaScript recenti potrebbero +applicare delle ottimizzazioni in questo caso, non c'è modo di dire se il +codice verrà eseguito su uno di questi nuovi motori oppure no.

+

Infatti, l'omissione della parte di caching può risultare in un ciclo eseguito +soltanto alla metà della velocità con cui potrebbe essere eseguito facendo +il caching della lunghezza.

+

La proprietà length

+

Mentre il getter della proprietà length ritorna semplicemente il numero di +elementi che sono contenuti nell'array, il setter può essere usato per +troncare l'array.

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo.push(4);
+foo; // [1, 2, 3, undefined, undefined, undefined, 4]
+

Assegnando una lunghezza più piccola si tronca l'array. Incrementandola si +crea un array frammentato.

+

In conclusione

+

Per la miglior performance, si raccomanda di usare sempre il ciclo for +classico e fare il caching della proprietà length. L'uso di for in su di +un array è segno di un codice scritto male che è suscettibile a bug e pessima +performance.

+

Il costruttore Array

Dato che il costruttore Array è ambiguo riguardo a come esso gestisca i suoi +parametri, si consiglia calorosamente di usare l'array letterale (notazione []) +quando si creano array.

+
[1, 2, 3]; // Risultato: [1, 2, 3]
+new Array(1, 2, 3); // Risultato: [1, 2, 3]
+
+[3]; // Risultato: [3]
+new Array(3); // Risultato: []
+new Array('3') // Risultato: ['3']
+

Nei casi in cui c'è solo un argomento passato al costruttore Array e quando +l'argomento è un Number, il costruttore ritornerà un nuovo array frammentato +con la proprietà length impostata al valore dell'argomento. Si noti +che in questo modo solo la proprietà length del nuovo array verrà impostata, +mentre gli indici dell'array non verranno inizializzati.

+
var arr = new Array(3);
+arr[1]; // undefined
+1 in arr; // false, l'indice non è stato impostato
+

Essere in grado di impostare la lunghezza dell'array in anticipo è utile soltanto +in poche situazioni, come ad esempio la ripetizione di una stringa, nel cui caso +si eviterebbe l'uso di un ciclo.

+
new Array(count + 1).join(stringToRepeat);
+

In conclusione

+

I letterali sono da preferirsi al costruttore Array. Sono più concisi, hanno una +sintassi più chiara ed incrementano la leggibilità del codice.

+

Tipi di dati

Uguaglianza e comparazioni

JavaScript usa due differenti metodi per comparare l'uguaglianza dei +valori degli oggetti.

+

L'operatore di uguaglianza

+

L'operatore di uguaglianza consiste di due segni di uguaglianza: ==.

+

JavaScript supporta la tipizzazione debole. Questo significa che +l'operatore di uguaglianza converte i tipi in modo da poterli +confrontare.

+
""           ==   "0"           // false
+0            ==   ""            // true
+0            ==   "0"           // true
+false        ==   "false"       // false
+false        ==   "0"           // true
+false        ==   undefined     // false
+false        ==   null          // false
+null         ==   undefined     // true
+" \t\r\n"    ==   0             // true
+

Questa tabella mostra i risultati della conversione di tipo, ed è il +principale motivo per cui l'uso di == è ampiamente considerato una +cattiva pratica. Esso introduce bug difficili da rilevare a causa delle +complesse regole di conversione.

+

Inoltre, c'è anche un impatto sulla performance quando entra in gioco la +conversione di tipo. Ad esempio, una stringa deve essere convertita in un +numero prima di poter essere confrontata con un altro numero.

+

L'operatore di uguaglianza stretta

+

L'operatore di uguaglianza stretta consiste di tre segni di uguaglianza: ===.

+

Funziona come il normale operatore di uguaglianza, con l'eccezione di +non eseguire la conversione di tipo tra gli operandi.

+
""           ===   "0"           // false
+0            ===   ""            // false
+0            ===   "0"           // false
+false        ===   "false"       // false
+false        ===   "0"           // false
+false        ===   undefined     // false
+false        ===   null          // false
+null         ===   undefined     // false
+" \t\r\n"    ===   0             // false
+

I risultati qui sono più chiari e permettono di identificare subito un problema +con il codice. Questo rende il codice più solido di un certo grado e fornisce anche +migliorie alla performance nel caso di operandi di tipo differente.

+

Comparazione di oggetti

+

Nonostante == e === vengano definiti operatori di uguaglianza, essi +funzionano differentemente quando almeno uno degli operandi è un Object.

+
{} === {};                   // false
+new String('foo') === 'foo'; // false
+new Number(10) === 10;       // false
+var foo = {};
+foo === foo;                 // true
+

Qui, entrambe gli operatori confrontano per identità e non per +uguaglianza. Essi confrontano, cioè, che sia la stessa istanza dell'oggetto, +in modo molto simile a is in Python e la comparazione di puntatori in C.

+

In conclusione

+

Si raccomanda calorosamente di usare solo l'operatore di uguaglianza stretta. +Nei casi dove è necessario che i tipi vengano convertiti, questa operazione +dovrebbe essere fatta esplicitamente piuttosto che essere +lasciata alle complesse regole di conversione del linguaggio.

+

L'operatore typeof

L'operatore typeof (assieme a instanceof) è +probabilmente il più grande difetto di progettazione di JavaScript, +dato che è quasi completamente inusabile.

+

Sebbene instanceof abbia ancora limitati casi d'uso, typeof ha realmente +un solo caso d'uso, che non è quello di verificare il tipo di un oggetto.

+ +

Tabella dei tipi di JavaScript

+
Valore              Classe     Tipo
+-------------------------------------
+"foo"               String     string
+new String("foo")   String     object
+1.2                 Number     number
+new Number(1.2)     Number     object
+true                Boolean    boolean
+new Boolean(true)   Boolean    object
+new Date()          Date       object
+new Error()         Error      object
+[1,2,3]             Array      object
+new Array(1, 2, 3)  Array      object
+new Function("")    Function   function
+/abc/g              RegExp     object (function in Nitro/V8)
+new RegExp("meow")  RegExp     object (function in Nitro/V8)
+{}                  Object     object
+new Object()        Object     object
+

In questa tabella, Tipo fa riferimento al valore ritornato dall'operatore typeof. +Come si può chiaramente vedere, questo valore è tutto fuorchè affidabile.

+

Classe si riferisce al valore della proprietà interna [[Class]] di un oggetto.

+ +

Per ottenere il valore di [[Class]], bisogna usare il metodo toString di +Object.prototype.

+

La classe di un oggetto

+

Le specifiche forniscono esattamente un modo per accedere al valore di +[[Class]], con l'uso di Object.prototype.toString.

+
function is(type, obj) {
+    var clas = Object.prototype.toString.call(obj).slice(8, -1);
+    return obj !== undefined && obj !== null && clas === type;
+}
+
+is('String', 'test'); // true
+is('String', new String('test')); // true
+

Nel esempio qui sopra, Object.prototype.toString viene chiamato con il valore +di this impostato all'oggetto di cui si vuole ottenere il +valore di [[Class]].

+ +

Testare variabili non definite

+
typeof foo !== 'undefined'
+

Questo esempio verificherà se foo è stata attualmente dichiarata oppure no. +Un semplice referenziamento ad essa risulterebbe in un ReferenceError. +Questo è l'unico caso in cui typeof è utile a qualcosa.

+

In conclusione

+

Per verificare il tipo di un oggetto, è altamente raccomandato l'utilizzo di +Object.prototype.toString, dato che questo è il solo modo affidabile per +fare ciò. Come mostrato nella tabella precedente, alcuni valori di ritorno +di typeof non sono definiti nelle specifiche, e ciò dimostra come essi +potrebbero differire tra implementazioni differenti.

+

A meno che non si debba verificare se una variabile è definta, typeof +dovrebbe essere evitato.

+

L'operatore instanceof

L'operatore instanceof confronta i costruttori dei suoi due operandi. +È utile soltanto per la comparazione di oggetti realizzati dal +programmatore. Se usato sui tipi interni del linguaggio, esso è +praticamente inutile alla stregua dell'operatore typeof.

+

Confronto di oggetti personalizzati

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // true
+new Bar() instanceof Foo; // true
+
+// Questo imposta Bar.prototype all'oggetto funzione Foo,
+// ma non ad un'istanza di Foo
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // false
+

Uso di instanceof con i tipi nativi

+
new String('foo') instanceof String; // true
+new String('foo') instanceof Object; // true
+
+'foo' instanceof String; // false
+'foo' instanceof Object; // false
+

Un'importante cosa da notare qui è che instanceof non funziona con oggetti +originati da differenti contesti JavaScript (ad esempio, differenti +documenti in un browser web), dato che i loro costruttori non saranno +esattamente lo stesso oggetto.

+

In conclusione

+

L'operatore instanceof dovrebbe essere usto solo quando si ha a che fare +con oggetti personalizzati creati dal programmatore, che provengono dallo +stesso contesto JavaScript. Proprio come per l'operatore typeof, +ogni altro tipo di utilizzo dovrebbe essere evitato.

+

Conversione di tipo (Type Casting)

JavaScript è un linguaggio debolmente tipizzato, perciò esso applicherà +una conversione di tipo ovunque sia possibile.

+
// Queste sono vere
+new Number(10) == 10; // Number.toString() viene convertito
+                      // nuovamente in un numero
+
+10 == '10';           // String viene convertita in Number
+10 == '+10 ';         // Stringa più assurda
+10 == '010';          // a ancora di più
+isNaN(null) == false; // null viene convertito in 0
+                      // che ovviamente non è NaN
+
+// Queste sono false
+10 == 010;
+10 == '-10';
+ +

Per evitare i problemi appena visti, l'uso +dell'operatore di uguaglianza stretta è altamente +raccomandato. Sebbene questo eviti molti dei comuni problemi, ci sono ancora +molti ulteriori problemi che possono essere generati dal sistema debolemente +tipizzato di JavaScript.

+

Costruttori di tipi interni

+

I costruttori dei tipi interni del linguaggio, come Number e String, +funzionano in modo differente a seconda che venga usata o meno la +parola chiave new.

+
new Number(10) === 10;     // False, Object e Number
+Number(10) === 10;         // True, Number e Number
+new Number(10) + 0 === 10; // True, a causa della conversione implicita
+

L'uso di un tipo di dato interno come Number come costruttore, creerà un +nuovo oggetto Number, ma l'omissione della parola chiave new farà sì +che la funzione Number agisca da convertitore.

+

Inoltre, il passaggio di valori letterali o non oggetto risulterà in un'ancora +maggiore conversione di tipo.

+

La miglior opzione è quella di fare esplicitamente la conversione ad uno +dei tre possibili tipi.

+

Convertire in una stringa

+
'' + 10 === '10'; // true
+

Anteponendo una stringa vuota, un valore può facilmente essere convertito in +una stringa.

+

Convertire in un numero

+
+'10' === 10; // true
+

Usando l'operatore unario di addizione, è possibile convertire in un numero.

+

Convertire in un booleano

+

Usando due volte l'operatore not, un valore può essere convertito in un +booleano.

+
!!'foo';   // true
+!!'';      // false
+!!'0';     // true
+!!'1';     // true
+!!'-1'     // true
+!!{};      // true
+!!true;    // true
+

Base

Perché non usare eval

La funzione eval eseguirà una stringa di codice JavaScript nello scope locale.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

Comunque, eval esegue solo nello scope locale quando viene chiamata +direttamente e quando il nome della funzione chiamata è eval.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

L'uso di eval dovrebbe essere evitato. Il 99.9% dei suoi "utilizzi" può +essere ottenuto senza di essa.

+

eval sotto mentite spoglie

+

Le funzioni di timeout setTimeout e setInterval possono +entrambe accettare una stringa come loro primo argomento. Questa stringa verrà +sempre eseguita nello scope globale dato che eval non viene chiamato +direttamente in questo caso.

+

Problemi di sicurezza

+

eval è anche un problema di sicurezza, perché essa esegue qualsiasi +codice le viene passato. Non si dovrebbe mai usare con stringhe di origine +sconosciuta o inaffidabile.

+

In conclusione

+

eval non dovrebbe mai essere usata. Qualsiasi codice che ne faccia uso dovrebbe +essere messo in discussione sotto l'aspetto della funzionalità, della performance +e della sicurezza. Se qualcosa richiede eval per poter funzionare, allora non +dovrebbe essere usato in primo luogo, ma si dovrebbe prevedere una +miglior progettazione che non richieda l'uso di eval.

+

undefined e null

JavaScript usa due valori distinti per il nulla, null e undefined, e +quest'ultimo è il più utile.

+

Il valore undefined

+

undefined è un tipo con esattamente un valore: undefined.

+

Il linguaggio definisce anche una variabile globale che ha il valore di undefined. +Questa variabile è anche chiamata undefined. Comunque, questa variabile non è +né una costante né una parola chiave del linguaggio. Ciò significa che il suo valore +può facilmente essere sovrascritto.

+ +

Ecco alcuni esempi di quando il valore undefined viene ritornato:

+
    +
  • Accedendo la variabile globale (non modificata) undefined.
  • +
  • Accedendo una variabile dichiarata ma non ancora inizializzata.
  • +
  • Ritorno implicito da funzioni che non hanno l'istruzione return.
  • +
  • Istruzioni return che non ritornano esplicitamente alcun valore.
  • +
  • Ricerca di proprietà inesistenti.
  • +
  • Parametri funzione a cui non viene esplicitamente passato alcun valore.
  • +
  • Qualsiasi cosa a cui sia stato assegnato il valore undefined.
  • +
  • Qualsiasi espressione nella forma di void(espressione).
  • +
+

Gestire le modifiche al valore di undefined

+

Dato che la variabile globale undefined mantiene solo una copia dell'attuale +valore di undefined, assegnandole un nuovo valore non cambia il valore del +tipo undefined.

+

Inoltre, per confrontare qualcosa con il valore di undefined, è necessario +ottenere prima il valore di undefined.

+

Per proteggere il codice da possibili sovrascritture della variabile undefined, +viene usata una comune tecnica che prevede l'aggiunta di un ulteriore parametro +ad un contenitore anonimo al quale non viene passato alcun +argomento.

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // ora undefined nello scope locale
+    // fa nuovamente riferimento al valore `undefined`
+
+})('Hello World', 42);
+

Un altro modo per ottenere lo stesso effetto sarebbe quello di usare una +dichiarazione all'interno del contenitore.

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

La sola differenza è che questa versione si traduce in 4 byte in più quando +minificata, e non c'è nessun'altra istruzione var al'interno del contenitore +anonimo.

+

Utilizzi di null

+

Mentre undefined nel contesto del linguaggio JavaScript viene principalmente +usato come un tradizionale null, l'attuale null (sia letterale che tipo di +dati) è più o meno solo un altro tipo di dato.

+

Viene usato in alcune funzioni interne al JavaScript (come la dichiarazione +del termine della catena di prototipi, impostando Foo.prototype = null), ma +nella maggior parte dei casi, può essere rimpiazzato da undefined.

+

Inserimento automatico dei punti-e-virgola

Sebbene JavaScript utilizzi lo stile di sintassi del C, esso non +obbliga l'uso dei punti-e-virgola nel codice sorgente, perciò è possibile +ometterli.

+

Detto questo, JavaScript non è un linguaggio che fa a meno dei punti-e-virgola. +Infatti, esso necessita di punti-e-virgola per poter comprendere il codice +sorgente. Quindi, il parser del JavaScript li inserisce automaticamente +ogni volta che incontra un errore di analisi dato dalla mancanza di un +punto-e-virgola.

+
var foo = function() {
+} // errore di analisi, atteso punto-e-virgola
+test()
+

Quindi avviene l'inserimento, ed il parser prova nuovamente.

+
var foo = function() {
+}; // nessun errore, il parser continua
+test()
+

L'inserimento automatico dei punti-e-virgola è considerato essere uno dei +più grandi errori di progettazione del linguaggio, perché può +modificare il comportamento del codice.

+

Come funziona

+

Il codice qui sotto non ha punti-e-virgola, quindi sta al parser decidere dove +inserirli.

+
(function(window, undefined) {
+    function test(options) {
+        log('testing!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+
+})(window)
+

Di seguito il risultato del gioco da "indovino" del parser.

+
(function(window, undefined) {
+    function test(options) {
+
+        // Non inserito, linee unite
+        log('testing!')(options.list || []).forEach(function(i) {
+
+        }); // <- inserito
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        ); // <- inserito
+
+        return; // <- inserito, invalida l'istruzione return
+        { // trattato come un blocco
+
+            // un'etichetta e una singola espressione
+            foo: function() {}
+        }; // <- inserito
+    }
+    window.test = test; // <- inserito
+
+// Le linee vengono unite nuovamente
+})(window)(function(window) {
+    window.someLibrary = {}; // <- inserito
+
+})(window); //<- inserito
+ +

Il parser ha drasticamente modificato il comportamento del codice. In alcuni casi, +questo porta ad eseguire cose sbagliate.

+

Parentesi ad inizio riga

+

Nel caso di parentesi ad inizio riga, il parser non inserirà un punto-e-virgola.

+
log('testing!')
+(options.list || []).forEach(function(i) {})
+

Questo codice viene trasformato in una sola linea.

+
log('testing!')(options.list || []).forEach(function(i) {})
+

Le possibilità che log non ritorni una funzione sono veramente alte, +perciò il codice qui sopra porterà ad un TypeError dichiarando che +undefined is not a function (undefined non è una funzione).

+

In conclusione

+

È fortemente raccomandato di non omettere mai i punti-e-virgola. +Si raccomanda anche di mantenere le parentesi sulla stessa linea della +corrispondente istruzione, e di non ometterle mai in istruzioni if / else +a linea singola. Queste misure precauzionali non solo miglioreranno la +consistenza del codice, ma preverranno anche che il parser JavaScript +modifichi il comportamento del codice in modo inaspettato.

+

L'operatore delete

In breve, è impossibile eliminare variabili globali, funzioni e qualche +altra cosa in JavaScript che ha l'attributo DontDelete impostato.

+

Codice globale e codice funzione

+

Quando una variabile o una funzione viene definita in un scope globale o +funzione, essa è una proprietà dell'oggetto Activation +o dell'oggetto Global. Queste proprietà hanno un set di attributi, tra i quali +DontDelete. Dichiarazioni di variabile o funzione nel codice globale o +funzione, creano sempre proprietà con DontDelete, e quindi non possono essere +eliminate.

+
// variabile globale:
+var a = 1; // DontDelete è impostato
+delete a; // false
+a; // 1
+
+// funzione normale:
+function f() {} // DontDelete è impostato
+delete f; // false
+typeof f; // "function"
+
+// la riassegnazione non aiuta:
+f = 1;
+delete f; // false
+f; // 1
+

Proprietà esplicite

+

Proprietà esplicitamente impostate possono essere eliminate normalmente.

+
// proprietà impostata esplicitamente:
+var obj = {x: 1};
+obj.y = 2;
+delete obj.x; // true
+delete obj.y; // true
+obj.x; // undefined
+obj.y; // undefined
+

Nel codice qui sopra, obj.x e obj.y possono essere eliminate perché +non hanno l'attributo DontDelete. Ecco perché anche l'esempio seguente +funziona.

+
// questo funziona, tranne che per IE:
+var GLOBAL_OBJECT = this;
+GLOBAL_OBJECT.a = 1;
+a === GLOBAL_OBJECT.a; // true - solo una variabile globale
+delete GLOBAL_OBJECT.a; // true
+GLOBAL_OBJECT.a; // undefined
+

Qui usiamo un trucco per eliminare a. this qui fa +riferimento all'oggetto Global e noi dichiariamo esplicitamente la +variabile a come sua proprietà, il che ci permette di eliminarla.

+

IE (almeno 6-8) ha alcuni bug, quindi il codice precedente non funziona.

+

Argomenti funzione e proprietà interne

+

Anche i normali argomenti delle funzioni, gli +oggetti arguments e le proprietà interne hanno +DontDelete impostato.

+
// argomenti funzione e proprietà:
+(function (x) {
+
+  delete arguments; // false
+  typeof arguments; // "object"
+
+  delete x; // false
+  x; // 1
+
+  function f(){}
+  delete f.length; // false
+  typeof f.length; // "number"
+
+})(1);
+

Oggetti non nativi (host)

+

Il comportamento dell'operatore delete può essere inaspettato con gli oggetti +non nativi. A causa delle specifiche, agli oggetti non nativi è permesso di +implementare qualsiasi tipo di funzionalità.

+

In conclusione

+

L'operatore delete spesso ha un comportamento inaspettato e può solo essere +usato con sicurezza per eliminare proprietà esplicitamente impostate in oggetti +normali.

+

Varie

setTimeout e setInterval

Dato che JavaScript è asincrono, è possibile programmare l'esecuzione di una +funzione usando le funzioni setTimeout e setInterval.

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // ritorna un Number > 0
+

Quando chiamato, setTimeout ritorna l'ID del timeout e programma foo per +essere eseguito approssimativamente un migliaio di millisecondi nel futuro. +foo verrà quindi eseguito una volta.

+

Dipendendo dalla risoluzione del timer del motore JavaScript che esegue il codice, +come anche dal fatto che JavaScript è single threaded e quindi altro codice +potrebbe essere eseguito bloccando il thread, non è mai sicuro scommettere +che una funzione verrà eseguita esattamente al ritardo specifiato nella chiamata +a setTimeout.

+

La funzione che è stata passata come primo parametro verrà chiamata dall'oggetto globale, +e ciò significa che this all'interno della funzione chiamata +farà riferimento all'oggetto globale.

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // this fa riferimento all'oggetto globale
+        console.log(this.value); // stamperà undefined
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

Sovrapposizione di chiamate con setInterval

+

Mentre setTimeout esegue solo una volta la funzione, setInterval (come il +nome suggerisce) eseguirà la funzione ogni X millisecondi, ma il suo +utilizzo è sconsigliato.

+

Quando il codice che viene eseguito blocca la chiamata timeout, setInterval +eseguirà ancora più chiamate alla specifica funzione. Questo può, specialmente +con intervalli molto brevi, tradursi in chiamate a funzione che si sovrappongono.

+
function foo(){
+    // qualcosa che blocca per 1 secondo
+}
+setInterval(foo, 1000);
+

Nel codice precedente, foo verrà chiamato una volta e quindi bloccherà per +un secondo.

+

Mentre foo blocca il codice, setInterval continuerà a programmare ulteriori +chiamate ad essa. Ora, quando foo ha finito, ci saranno già dieci ulteriori +chiamate ad essa in attesa per essere eseguite.

+

Gestione di potenziale codice bloccante

+

La soluzione più semplice, come anche la più controllabile, è quella di usare +setTimeout all'interno di se stessa.

+
function foo(){
+    // qualcosa che blocca per 1 secondo
+    setTimeout(foo, 1000);
+}
+foo();
+

Non solo questo incapsula la chiamata a setTimeout, ma previene anche la +sovrapposizione delle chiamate e da un controllo addizionale. foo stessa +può ora decidere se vuole continuare ad essere eseguita oppure no.

+

Pulizia manuale dei timeout

+

La pulizia di timeout ed intervalli funziona passando il rispettivo ID a +clearTimeout o clearInterval, in base a quale set di funzioni è stato +usato precedentemente.

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

Pulizia di tutti i timeout

+

Dato che non c'è un metodo interno per la pulizia di tutti i timeout e/o +intervalli, è necessario usare la forza bruta per poter raggiungere questo +scopo.

+
// pulisce "tutti" i timeout
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

Ma ci potrebbero ancora essere timeout che non vengono toccati da questo +numero arbitrario. Un altro modo per ottenere ciò, è considerare che l'ID +dato ad un timeout viene incrementato di uno ogni volta che si chiama +setTimeout.

+
// pulisce "tutti" i timeout
+var biggestTimeoutId = window.setTimeout(function(){}, 1),
+i;
+for(i = 1; i <= biggestTimeoutId; i++) {
+    clearTimeout(i);
+}
+

Sebbene questo funzioni con la maggior parte dei browser odierni, non è +specificato che gli ID debbano essere ordinati in quel modo e ciò potrebbe +anche cambiare in futuro. Perciò, si raccomanda di tener traccia di tutti +gli ID dei timeout, così che possano essere puliti in modo specifico.

+

Uso nascosto di eval

+

setTimeout e setInterval possono anche accettare una stringa come loro +primo parametro. Questa caratteristica non dovrebbe essere mai usata +perché internamente fa uso di eval.

+ +
function foo() {
+    // verrà chiamata
+}
+
+function bar() {
+    function foo() {
+        // non verrà mai chiamata
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

Dal momento che eval non viene chiamata direttamente in questo +caso, la stringa passata a setTimeout verrà eseguita nello scope globale. +Quindi, non verrà usata la variabile locale foo dallo scope di bar.

+

Si raccomanda inoltre di non usare una stringa per passare argomenti alla +funzione che verrà chiamata da una delle funzioni di timeout.

+
function foo(a, b, c) {}
+
+// non usare MAI questo
+setTimeout('foo(1, 2, 3)', 1000)
+
+// Usare invece una funzione anonima
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

In conclusione

+

Una stringa non dovrebbe mai essere usata come parametro di setTimeout o +setInterval. È un chiaro segno di codice veramente pessimo, quando +gli argomenti necessitano di essere passati alla funzione che deve essere +chiamata. Dovrebbe invece essere passata una funzione anonima che si incarichi +di gestire l'effettiva chiamata.

+

Inoltre, l'uso di setInterval dovrebbe essere evitato perché il suo schedulatore +non viene bloccato dall'esecuzione di JavaScript.

+
\ No newline at end of file diff --git a/ja/index.html b/ja/index.html new file mode 100644 index 0000000..927a51a --- /dev/null +++ b/ja/index.html @@ -0,0 +1,1127 @@ +JavaScript Garden

前書き

前書き

JavaScript Garden はJavaScriptというプログラム言語の一番奇妙な部分についてのドキュメント集です。 +このドキュメントはJavaScriptという言語に慣れていないプログラマーがこの言語について深く知ろうとする際に遭遇する、良くある間違い・小さなバグ・パフォーマンスの問題・悪い習慣などを避ける為のアドバイスを与えます。

+

JavaScript GardenはJavaScriptを教える事を目的にしていません。このガイドの項目を理解する為には、この言語に対する前提知識がある事を推奨します。この言語の基礎部分についてはMozilla Developer Networkのガイド がオススメです。

+

著者

+

このガイドは愛すべきStack Overflowの2人のユーザーIvo Wetzel +(執筆)とZhang Yi Jiang (デザイン)によって作られました。

+

貢献者

+ +

ホスティング

+

JavaScript GardenはGitHubでホスティングされていますが、Cramer DevelopmentJavaScriptGarden.infoというミラーサイトを作ってくれています。

+

ライセンス

+

JavaScript GardenはMIT licenseの下で公開されており、GitHubでホスティングされています。もしもエラーやtypoを見つけたらfile an issueに登録するかリポジトリにプルリクエストを送ってください。 +またStack OverflowチャットのJavaScript roomに私達はいます。

+

オブジェクト

オブジェクトの使用法とプロパティ

JavaScriptの全ての要素は2つの例外を除いて、オブジェクトのように振る舞います。 +その2つとはnullundefinedです。

+
false.toString(); // 'false'
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

良くありがちな誤解として、数値リテラルがオブジェクトとして使用できないというものがあります。この理由としては、JavaScriptパーサーが浮動小数点のドットをドット記法として解釈しようとしてしまうからです。

+
2.toString(); // シンタックスエラーが発生する
+

数値リテラルをオブジェクトとして使用する為の回避策がいくつかあります。

+
2..toString(); // 2つ目のドットが正しく解釈される
+2 .toString(); // ドットの左隣のスペースがポイント
+(2).toString(); // 2が一番最初に評価される
+

オブジェクトはデータタイプ

+

JavaScriptのオブジェクトはハッシュマップとしても使用されます。これは名前付きのプロパティと値として構成されています。

+

オブジェクトリテラル({}記法)を使用すると、オブジェクトそのものを作る事ができます。この方法で作られたオブジェクトはObject.prototypeから継承され、own propertiesが何も設定されてない状態になります。

+
var foo = {}; // 新しい空のオブジェクト
+
+// 12という値の'test'というプロパティを持った新しいオブジェクト
+var bar = {test: 12}; 
+

プロパティへのアクセス

+

オブジェクトのプロパティには2通りのアクセス方法があります。1つはドット記法によるアクセス、もう1つはブラケット記法です。

+
var foo = {name: 'kitten'}
+foo.name; // kitten
+foo['name']; // kitten
+
+var get = 'name';
+foo[get]; // kitten
+
+foo.1234; // シンタックスエラー
+foo['1234']; // 動作する
+

どちらの記法も働きとしての違いは無いですが、唯一の違いとしてブラケット記法は通常のプロパティ名と同様に動的にプロパティを設定する事ができます。これ以外で動的にプロパティを設定しようとするとシンタックスエラーになります。

+

プロパティの削除

+

実際にオブジェクトからプロパティを削除する唯一の方法はdelete演算子を使う事です。プロパティにundefinednullをセットしても、プロパティ自身ではなく、キーに設定されたを削除するだけです。

+
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

上記の例では、bazは完全に削除されて出力がされていませんが、それ以外の2つbar undefinedfoo nullはどちらも出力されてしまっています。

+

キーの記法

+
var test = {
+    'case': 'I am a keyword so I must be notated as a string',
+    delete: 'I am a keyword too so me' // シンタックスエラーが起こる
+};
+

オブジェクトのプロパティは普通の文字か文字列として記述する事が出来ます。JavaScriptパーサーの設計ミスが原因ですが、ECMAScript5以前では上記のコードはシンタックスエラーを表示するでしょう。

+

このエラーはdelete予約語になっているのが原因なので、古いJavaScriptエンジンに正しく解釈させる為には文字リテラルを使って記述する事を推奨します。

+

プロトタイプ

JavaScriptはクラスベース継承モデルは実装されておらず、この代わりにプロトタイプを用いています。

+

プロトタイプモデルを使っている事が、JavaScriptの弱点の一つになっていると良く考えられがちですが、プロトタイプ継承モデルはクラスベース継承モデルよりパワフルだというのは事実です。この事はちょっとしたものでもクラスベースの継承で実装しようとすると、プロトタイプベースの継承よりも作業が難しくなるという事でも分かります。

+

JavaScriptはプロトタイプベースが採用されている唯一の広範に使用されている基本的なプログラミング言語という現実があるので、プロトタイプベースとクラスベースの違いを時々調整しないとなりません。

+

最初の大きな違いはJavaScriptの継承はプロトタイプチェーンと呼ばれるもので実行されているという事です。

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// BarのプロトタイプをFooの新しいインスタンスとしてセットする
+Bar.prototype = new Foo();
+Bar.prototype.foo = 'Hello World';
+
+// Barを実際のコンストラクタとして確実にする為に代入する
+Bar.prototype.constructor = Bar;
+
+var test = new Bar() // 新しくbarインスタンスを作成
+
+// プロトタイプチェーンの結果
+test [instance of Bar]
+    Bar.prototype [instance of Foo] 
+        { foo: 'Hello World' }
+        Foo.prototype
+            { method: ... }
+            Object.prototype
+                { toString: ... /* その他 */ }
+

上記ではtestBar.prototypeFoo.prototypeの2つのオブジェクトより継承されます。その為Fooの中で設定されたmethod関数にアクセスできるようになります。また、Fooのプロトタイプとしてのインスタンスそれ自体valueプロパティにもアクセスが可能です。new Bar()Fooのインスタンスを新しく作りませんが、プロトタイプに割り合てられたFooインスタンスを再利用している事は注目に値します。従って全てのBarインスタンスは同じvalueプロパティを共有します。

+ +

プロパティ探索

+

オブジェクトのプロパティにアクセスする時には、JavaScriptはプロトタイプチェーンを要求された名前を見つけるまで遡って探索します。

+

チェーンの先頭(すなわちObject.prototype)に到達した際に、まだ指定されたプロパティが見つからなければ、代わりにundefinedという値を返します。

+

プロトタイププロパティ

+

プロトタイププロパティはJavaScriptの中でプロトタイプチェーンを構築する為に使われていますが、任意の値を代入する事も可能になっています。しかし、プロトタイプとしてプリミティブが代入された場合は単に無視されるだけです。

+
function Foo() {}
+Foo.prototype = 1; // 効果無し
+

オブジェクトの代入は上記の例のように動作し、動的にプロトタイプチェーンを作る事ができます。

+

パフォーマンス

+

プロトタイプチェーンの上位にあるプロパティを探索する時間はコードの実行パフォーマンスに重大な悪影響を与えます。特に存在しないプロパティにアクセスしようとすると、プロトタイプチェーンの全てのプロパティを探索してしまいます。

+

また、オブジェクトのプロパティに対して反復処理をすると、プロトタイプチェーン上の全てのプロパティを列挙してしまいます。

+

既存のプロトタイプの拡張

+

元々組み込まれてるプロトタイプやObject.prototypeを拡張するのは、良くありがちなイケていない実装方法になります。

+

このテクニックはmonkey patchingと呼ばれるものでカプセル化を壊してしまいます。このテクニックはPrototypeのようなフレームワークにより広まりましたが、非標準の機能を持っている組み込み型のオブジェクトの乱立という点でも推奨されません。

+

唯一組み込みのプロトタイプを拡張しても良い理由としては、JavaScriptエンジンに将来実装されるであろう機能の移植だけです。 +例えばArray.forEachなどが、それに当たります。

+

終わりに

+

ここまでがプロトタイプベース継承モデルを使って複雑なコードを書く前に必ず理解すべき事です。また、プロパティチェーンの長さを観察して、もしパフォーマンスに悪影響を及ぼすのを防ぐ為ならば、これを分割をしなければなりません。さらに組み込みのプロトタイプは新しいJavaScriptの機能と互換性が無い限りは絶対に拡張してはいけません。

+

hasOwnProperty

オブジェクトは自分自身自分以外のどちらで定義されたプロパティかをprototype chainのどこかでチェックしなくてはなりません。これはObject.prototypeから継承される全てのオブジェクトのhasOwnPropertyメソッドを使う必要があります。

+ +

hasOwnPropertyはJavaScriptで唯一プロトタイプチェーン内を遡らずにプロパティを扱う事が出来ます。

+
// Object.prototype汚染
+Object.prototype.bar = 1; 
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // true
+
+foo.hasOwnProperty('bar'); // false
+foo.hasOwnProperty('goo'); // true
+

hasOwnPropertyだけが、正しく期待した結果を出すでしょう。これはあらゆるオブジェクトのプロパティの繰り返し処理をする時必須の事です。オブジェクト自身に定義されておらず、プロトタイプチェーンのどこかには定義されているというプロパティを除外する手段が他にありません

+

プロパティとしてのhasOwnProperty

+

JavaScriptはプロパティ名としてhasOwnPropertyを保護していません。;従って、この名前のプロパティを持ったオブジェクトが存在する事がありえます。正しい結果を得る為には外部hasOwnPropertyを使う必要があります。

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Here be dragons'
+};
+
+foo.hasOwnProperty('bar'); // 常にfalseを返す
+
+// 他のオブジェクトのhasOwnPropertyを使い、fooの'this'にセットして呼び出す
+({}).hasOwnProperty.call(foo, 'bar'); // true
+

終わりに

+

オブジェクトのプロパティの存在判定をする時は、hasOwnProperty唯一のメソッドになります。 +また、全てfor in ループ内でhasOwnPropertyを使う事を推奨します。 +そうする事により組み込みのprototypesの拡張が原因のエラーを避ける事が出来ます。

+

for inループ

inオペレーターは単に、for inループの中でオブジェクトのプロパティをプロトタイプチェーンの中で繰り返し遡る為にあるものです。

+ +
// Object.prototype汚染
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // barとmooが両方とも表示される
+}
+

for inループそれ自体の動作を変更する事は不可能ですが、ループ内にある要らないプロパティをフィルタリングする必要があります。そんな時はObject.prototypehasOwnPropertyメソッドを使うと解決します。

+ +

hasOwnPropertyをフィルタリングに使用する

+
// 継承されているfoo
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

このループの唯一正しい使い方がこの方法です。hasOwnPropertyを使用しているので、 +mooのみが表示されるようになります。hasOwnPropertyが省略されている場合は、このコードは +組み込みのプロトタイプが存在する場合に(特にObject.prototypeが拡張されている場合)エラーを発生しやすくなります。

+

一般に広く使用されているJavaScriptフレームワークとしてPrototypeが挙げられます。このフレームワークには、 +for in 内でhasOwnPropertyが使用されプロトタプチェーン内を頭まで遡るのを中断する事が保証されています。

+

終わりに

+

常にhasOwnPropertyを使用する事を推奨します。コードの実行環境や、組み込みのプロトタイプが拡張されているかどうかを仮定して書くようなコードを絶対書いてはいけません。

+

関数

関数の宣言と式

関数はJavaScriptの第一級オブジェクトです。この事は、その他の値と同じように渡す事が出来るという事です。この機能で良く使われる一つとして匿名関数を他のオブジェクトにコールバックとして渡すというものがあり、これで非同期での実装が可能になります。

+

関数宣言

+
function foo() {}
+

上記の関数はプログラムの開始時の前に評価されるように巻き上げられます。従って定義されたスコープ内のどこでも使用する事が可能になります。ソース内での実際の定義が呼ばれる前でもです。

+
foo(); // このコードが動作する前にfooが作られているので、ちゃんと動作する
+function foo() {}
+

関数

+
var foo = function() {};
+

この例では、fooという変数に無名で匿名の関数が割り当てられています。

+
foo; // 'undefined'
+foo(); // これはTypeErrorが起こる
+var foo = function() {};
+

varは宣言である為に、変数名fooがコードが開始される実際の評価時より前のタイミングにまで巻き上げられています。fooは既にスクリプトが評価される時には定義されているのです。

+

しかし、コードの実行時にのみこの割り当てがされるため、fooという変数は対応するコードが実行される前にデフォルト値であるundefinedが代入されるのです。

+

名前付き関数式

+

他に特殊なケースとして、名前付き関数があります。

+
var foo = function bar() {
+    bar(); // 動作する
+}
+bar(); // ReferenceError
+

この場合のbarfooに対して関数を割り当てるだけなので、外部スコープでは使用できません。しかし、barは内部では使用できます。これはJavaScriptの名前解決の方法によるもので、関数名はいつも関数自身のローカルスコープ内で有効になっています。

+

thisはどのように動作するのか

JavaScriptのthisと名付けられた特殊なキーワードは他のプログラム言語と違うコンセプトを持っています。JavaScriptのthisは正確に5個の別々の使い道が存在しています。

+

グローバルスコープとして

+
this;
+

thisをグローバルスコープ内で使用すると、単純にグローバルオブジェクトを参照するようになります。

+

関数呼び出しとして

+
foo();
+

このthisは、再度グローバルオブジェクトを参照しています。

+ +

メソッド呼び出しとして

+
test.foo(); 
+

この例ではthistestを参照します。

+

コンストラクター呼び出し

+
new foo(); 
+

newキーワードが付いた関数呼び出しはコンストラクターとして機能します。関数内部ではthis新規に作成されたObjectを参照します。

+

thisの明示的な設定

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // 配列は下記で展開される
+foo.call(bar, 1, 2, 3); // 結果はa = 1, b = 2, c = 3
+

Function.prototypecallapplyメソッドを使用した時には、呼び出された関数の内部でのthisの値は、対応する関数呼び出しの最初の引数に明示的に設定されます。

+

結果として、上記の例ではメソッドケースが適用されずfooの内部のthisbarに設定されます。

+ +

良くある落し穴

+

これらのケースのほとんどは理にかなったものですが、最初のケースは実際に利用されることが絶対にないので、間違った言語設計だとみなせるでしょう。

+
Foo.method = function() {
+    function test() {
+        // このファンクションはグローバルオブジェクトに設定される
+    }
+    test();
+}
+

良くある誤解としてtestの中のthisFooを参照しているというものがありますが、そのような事実は一切ありません。

+

testの中のFooにアクセスする為には、Fooを参照するmethodのローカル変数を作る必要があります。

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // ここでthisの代わりに使用する
+    }
+    test();
+}
+

thatは通常の変数名ですが、外部のthisの参照の為に良く使われます。クロージャと組み合わせる事でthisの値を渡す事ができるようになります。

+

メソッドの割り当て

+

JavaScriptを使用する上で、もう一つ動かないものが関数のエイリアスです。これは変数へメソッドを割り当てする事です。

+
var test = someObject.methodTest;
+test();
+

最初のケースのtestは通常の関数呼び出しになる為に、この中のthisは、もはやsomeobjectを参照できなくなってしまいます。

+

thisの遅延バインディングは最初見た時にはダメなアイデアに見えますが、プロトタイプ継承により、きちんと動作します。

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

methodBarのインスタンスにより呼び出された時に、thisはまさにそのインスタンスを参照するようになります。

+

クロージャと参照

JavaScriptの一番パワフルな特徴の一つとしてクロージャが使える事が挙げられます。これはスコープが自身の定義されている外側のスコープにいつでもアクセスできるという事です。JavaScriptの唯一のスコープは関数スコープですが、全ての関数は標準でクロージャとして振る舞います。

+

プライベート変数をエミュレートする

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

ここでCounter2つのクロージャを返します。関数incrementと同じく関数getです。これら両方の関数はCounterのスコープを参照し続けます。その為、そのスコープ内に定義されているcount変数に対していつもアクセスできるようになっています。

+

なぜプライベート変数が動作するのか?

+

JavaScriptでは、スコープ自体を参照・代入する事が出来無い為に、外部から変数countにアクセスする手段がありません。唯一の手段は、2つのクロージャを介してアクセスする方法だけです。

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

上記のコードはCounterのスコープ中にある変数countの値を変更する事はありませんfoo.hackそのスコープで定義されていないからです。これは(Counter内の変数countの変更)の代わりにグローバル変数countの作成 -または上書き- する事になります。

+

ループ中のクロージャ

+

一つ良くある間違いとして、ループのインデックス変数をコピーしようとしてか、ループの中でクロージャを使用してしまうというものがあります。

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);
+    }, 1000);
+}
+

上記の例では0から9の数値が出力される事はありません。もっと簡単に10という数字が10回出力されるだけです。

+

匿名関数はiへの参照を維持しており、同時にforループは既にiの値に10をセットし終ったconsole.logが呼ばれてしまいます。

+

期待した動作をする為には、iの値のコピーを作る必要があります。

+

参照問題を回避するには

+

ループのインデックス変数をコピーする為には、匿名ラッパーを使うのがベストです。

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);  
+        }, 1000);
+    })(i);
+}
+

外部の匿名関数はiを即座に第一引数として呼び出し、引数eiのコピーとして受け取ります。

+

eを参照しているsetTimeoutを受け取った匿名関数はループによって値が変わる事がありません。

+

他にこのような事を実現する方法があります。それは匿名ラッパーから関数を返してあげる事です。これは上記のコードと同じ振る舞いをします。

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+

オブジェクトのarguments

JavaScriptの全ての関数スコープはargumentsと呼ばれる特別な変数にアクセスできます。この変数は関数が受け取った全ての引数を保持する変数です。

+ +

argumentsオブジェクトはArrayではありません。これは配列と同じような -lengthプロパティと名付けられています- 文法を持っていますが、Array.prototypeを継承している訳では無いので、実際Objectになります。

+

この為、argumentspushpopsliceといった通常の配列メソッドは使用する事が出来ません。プレーンなforループのような繰り返しでは上手く動作しますが、通常のArrayメソッドを使いたい場合は本当のArrayに変換しなければなりません。

+

配列への変換

+

下のコードはargumentsオブジェクトの全ての要素を含んだ新しいArrayを返します。

+
Array.prototype.slice.call(arguments);
+

この変換は遅いです。コードのパフォーマンスに関わる重要な部分での使用は推奨しません

+

引き数の受け渡し

+

下記の例はある関数から別の関数に引数を引き渡す際に推奨される方法です。

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // do stuff here
+}
+

他のテクニックとして、高速で非結合のラッパーとしてcallapply両方を一緒に使用するという物があります。

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// "メソッド"の非結合バージョンを作成する
+// このメソッドはthis, arg1, arg2...argNのパラメーターを持っている
+Foo.method = function() {
+
+    // 結果: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+};
+

仮パラメーターと引数のインデックス

+

argumentsオブジェクトはゲッターセッター機能を自身のプロパティと同様に関数の仮パラメーターとして作成します。

+

結果として、仮パラメーターを変更するとargumentsの対応する値も変更されますし、逆もしかりです。

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

パフォーマンスの神話と真実

+

argumentsオブジェクトは、関数の内部の名前宣言と仮パラメーターという2つの例外を常に持ちながら生成されます。これは、使用されているかどうかは関係がありません。

+

ゲッターセッターは両方とも常に生成されます。その為これを使用してもパフォーマンスに影響は全くといって言い程ありません。argumentsオブジェクトのパラメーターに単純にアクセスしているような、実際のコードであれば尚更です。

+ +

しかし、一つだけモダンJavaScriptエンジンにおいて劇的にパフォーマンスが低下するケースがあります。そのケースとはarguments.calleeを使用した場合です。

+
function foo() {
+    arguments.callee; // この関数オブジェクトで何かする
+    arguments.callee.caller; // そして関数オブジェクトを呼び出す
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // 通常はインライン展開する
+    }
+}
+

上記のコードでは、fooは自身と自身の呼び出し元の両方を知らないとインライン展開の対象になる事が出来ません。この事は、インライン展開によるパフォーマンスの向上の機会を失くす事になり、また、特定のコンテクストの呼び出しに依存する関数のせいで、カプセル化が解除されてしまいます。

+

この為にarguments.calleeを使用または、そのプロパティを決して使用しない事を強く推奨します。

+ +

コンストラクタ

JavaScriptのコンストラクタは色々ある他のプログラム言語とは一味違います。newキーワードが付いているどんな関数呼び出しも、コンストラクタとして機能します。

+

コンストラクタ内部では -呼び出された関数の事です- thisの値は新規に生成されたObjectを参照しています。この新規のオブジェクトのprototypeは、コンストラクタとして起動した関数オブジェクトのprototypeに設定されています。

+

もし呼び出された関数が、returnステートメントを明示していない場合は、暗黙の了解でthisの値を -新規のオブジェクトとして- 返します。

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

上記は、Fooをコンストラクタとして呼び出し、新規に生成されたオブジェクトのprototypeFoo.prototypeに設定しています。

+

明示的にreturnステートメントがある場合、関数は返り値がObjectである場合に限りステートメントで明示した値を返します。

+
function Bar() {
+    return 2;
+}
+new Bar(); // 新しいオブジェクト
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // 返ってきたオブジェクト
+

newキーワードが省略されている場合は、関数は新しいオブジェクトを返す事はありません

+
function Foo() {
+    this.bla = 1; // グローバルオブジェクトに設定される
+}
+Foo(); // undefinedが返る
+

JavaScriptのthisの働きのせいで、上記の例ではいくつかのケースでは動作するように見える場合がありますが、それはグローバルオブジェクトthisの値として使用されるからです。

+

ファクトリー

+

newキーワードを省略するためには、コンストラクタ関数が明示的に値を返す必要があります。

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

Barで呼び出されたものは両方とも全く同じものものになります。これには、methodと呼ばれるプロパティを持ったオブジェクトが新しく生成されますが、これはクロージャです。

+

また、注意する点として呼び出されたnew Bar()は返ってきたオブジェクトのプロトタイプに影響しません。プロトタイプは新しく生成されたオブジェクトにセットされはしますが、Barは絶対にその新しいオブジェクトを返さないのです。

+

上記の例では、newキーワードの使用の有無は機能的に違いがありません。

+

ファクトリーとして新しくオブジェクトを作成する

+

多くの場合に推奨される事として、newの付け忘れによるバグを引き起こしやすいので、newを使用しないようにするという事があります。

+

新しいオブジェクトを作成するためにファクトリーを使用して、そのファクトリー内部に新しいオブジェクトを作成すべきだという事です。

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

上記の例ではnewキーワードが無いため堅牢になりますし、確実にプライベート変数を使用するのが簡単になりますが、いくつかの欠点があります。

+
    +
  1. 作られたオブジェクトがプロトタイプ上のメソッドを共有しないために、よりメモリーを消費してしまいます。
  2. +
  3. ファクトリーを継承するために、他のオブジェクトの全てのメソッドをコピーする必要があるか、新しいオブジェクトのプロトタイプ上にそのオブジェクトを設置する必要があります。
  4. +
  5. newキーワードが無いという理由だけで、プロトタイプチェーンから外れてしまうのは、どことなく言語の精神に反します。
  6. +
+

終わりに

+

newキーワードが省略される事によりバグの可能性がもたらされますが、それによりプロトタイプを全く使わないという確かな理由にはなりません。最終的には、アプリケーションの必要性により、どちらの解決法がより良いかが決まってきます。特に大切なのは、オブジェクトの作成に特定のスタイルを選ぶ事、またそのスタイルに固執する事です。

+

スコープと名前空間

JavaScriptはブロックに2つのペアの中括弧を使うのが素晴しいですが、これはブロックスコープをサポートしていません。その為、この言語に残されているのは関数スコープだけです。

+
function test() { // スコープ
+    for(var i = 0; i < 10; i++) { // スコープではない
+        // 数える
+    }
+    console.log(i); // 10
+}
+ +

JavaScriptはまた明確な名前空間を持ちません。この事は全て一つのグローバルで共有された名前空間で定義されるという事です。

+

変数が参照されるまでの間、JavaScriptはスコープ全てを遡って参照を探索します。グローバルスコープまで遡っても要求した名前が無いとReferenceErrorが発生します。

+

グローバル変数の致命傷

+
// スクリプト A
+foo = '42';
+
+// スクリプト B
+var foo = '42'
+

上記の2つのスクリプトは同じ効果を持っていません。スクリプト Aはfooと呼ばれる変数を、グローバルスコープに定義しており、スクリプト Bはfoo現在のスコープで定義ています。

+

繰り返しますが、この2つのスクリプトは同じ影響を全く持っていないスクリプトになります。varを使用しない事は重大な意味を持ちます。

+
// グローバルスコープ
+var foo = 42;
+function test() {
+    // ローカルスコープ
+    foo = 21;
+}
+test();
+foo; // 21
+

test関数の中のvarステートメントを省略するとfooの値をオーバーライドします。最初の内は大した事ではないように思いますが、JavaScriptが何千行規模になると、varを使っていない事でバグの追跡が酷く困難になります。

+
// グローバルスコープ
+var items = [/* 何かのリスト */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // サブループのスコープ
+    for(i = 0; i < 10; i++) { // varステートメントが無くなった
+        // 素敵な実装を!
+    }
+}
+

外側のループはsubloopが最初に呼ばれた後に終了します。なぜなら、subloopがグローバル変数iの値で上書きされているからです。2番目のforループにvarを使用する事によって簡単にこのエラーを回避する事ができます。目的とする効果を外側のスコープに与えようとしない限り、絶対varステートメントは省略してはいけません。

+

ローカル変数

+

JavaScriptのローカル変数の為の唯一の作成方法はfunctionパラメーターとvarステートメントによって宣言された変数になります。

+
// グローバルスコープ
+var foo = 1;
+var bar = 2;
+var i = 2;
+
+function test(i) {
+    // 関数testのローカル変数
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

fooiは、関数testのスコープ内のローカル変数ですが、barの代入は同じ名前でグローバル変数で上書きしてしまいます。

+

巻き上げ

+

JavaScriptは宣言を巻き上げます。これはvarステートメントとfunction宣言が、それらを含むスコープの一番先頭に移動するという事を意味します。

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

上記のコードは、実行を開始する前に変換されてしまいます。JavaScriptはvarステートメントと同じように、直近で囲んでいるfunction宣言を先頭に移動させます。

+
// varステートメントはここに移動する
+var bar, someValue; // 'undefined'がデフォルト
+
+// function宣言もここに移動する
+function test(data) {
+    var goo, i, e; // 無くなったブロックスコープはこちらに移動する
+    if (false) {
+        goo = 1;
+
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // barが'undefined'のままなので、Typeerrorで呼び出し失敗
+someValue = 42; // 割り当てすると巻き上げの影響を受けない
+bar = function() {};
+
+test();
+

ブロックスコープの欠落はvarステートメントをループやボディの外に移動するだけでなく、ifの構成を直感的ではないものにしてしまいます。

+

元のコードの中のifステートメントはグローバル変数であるgooも変更しているように見えますが、実際には -巻き上げが適用された後に- ローカル変数を変更しています。

+

巻き上げについての知識がないと、下に挙げたコードはReferenceErrorになるように見えます。

+
// SomeImportantThingが初期化されているかチェックする
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

しかし、勿論上記の動きはvarステートメントがグローバルスコープの上に移動しているという事実に基づいています。

+
var SomeImportantThing;
+
+// 他のコードがSomeImportantThingをここで初期化するかもしれないし、しないかもしれない
+
+// SomeImportantThingがある事を確認してください
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

名前解決の順序

+

JavaScriptのグローバルスコープを含む、全てのスコープは、現在のオブジェクトを参照している特殊な名前thisを持っています。

+

関数スコープはまた、argumentsという名前も持っています。それは関数スコープの中で定義され、関数に渡された引数を含んでいます。

+

例として、関数の中でfooと命名された変数にアクセスしようとする場合を考えましょう。JavaScriptは以下の順番で、その名前を探索しようとします。

+
    +
  1. var fooステートメントが現在のスコープで使われている場合
  2. +
  3. fooという名前の関数パラメーターが存在するかどうか
  4. +
  5. 関数それ自体がfooとして呼ばれているかどうか
  6. +
  7. 一つ外のスコープに行き、再度#1から始める
  8. +
+ +

名前空間

+

一つしかグローバルの名前空間を持たない事による良くある問題は変数名の衝突による問題の起きる可能性です。JavaScriptでは、この問題を匿名関数ラッパーの助けで簡単に回避できます。

+
(function() {
+    // "名前空間"に自分を含む
+
+    window.foo = function() {
+        // 露出したクロージャ
+    };
+
+})(); // 即座に関数を実行する
+

無名関数はexpressionsとみなされ、呼び出し可能になり最初に評価されます。

+
( // カッコ内の関数が評価される
+function() {}
+) // 関数オブジェクトが返される
+() // 評価の結果が呼び出される
+

関数式を評価し、呼び出す別の方法として構文は違いますが、同様の動作をするのが下記です。

+
// 2つの別の方法
++function(){}();
+(function(){}());
+

終わりに

+

自身の名前空間にカプセル化する為に常に匿名関数ラッパーを使用する事を推奨します。これは、コードを名前衝突から守る為だけでなく、プログラムのより良いモジュール化の為でもあります。

+

さらに、グローバル変数の使用は悪い習慣と考えられています。一回でもグローバル変数を使用するとエラーが発生しやすく、メンテナンスがしにくいコードになってしまいます。

+

配列

配列の繰り返しとプロパティ

JavaScriptの配列もまたオブジェクトですが、for in ループを配列の繰り返し処理で使用することの良い理由は1つもありません。実際、配列にfor inを使用しない為の正当な理由はたくさんあります。

+ +

for inループはプロトタイプチェーン上の全てのプロパティを列挙するため、hasOwnPropertyをそれらのプロパティの存在判定に使います。この為、通常のforループよりも20倍遅くなります。

+

繰り返し

+

配列の要素を繰り返すとのに、最高のパフォーマンスを出したければ昔ながらのforループを使うのが一番です。

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

上記の例では1つ追加の仕掛けがありますが、それはl = list.lengthによって配列の長さをキャッシュする部分です。

+

lengthプロパティは配列自身に定義されてはいますが、ループ中の繰り返しで毎回これを参照してしまうと、やはりオーバーヘッドが存在してしまいます。最近のJavaScriptエンジンはこのような場合に最適化するはずですが、コードが新しいエンジンで実行されるかどうか、知る方法はありません。

+

実際には、キャッシュを抜きにするとループの結果はキャッシュされたものに比べてたった半分の速度にしかなりません。

+

lengthプロパティ

+

lengthプロパティのゲッターは単に配列に含まれる要素の数を返すだけにも関わらず、セッターは配列をトランケートする為にも使用できます。

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo; // [1, 2, 3]
+

より小さいlengthを割り当てると配列をトランケートしますが、lengthが大きくなっても配列には何も影響しません。

+

終わりに

+

最高のパフォーマンスの為には、常にforループを使用し、lengthプロパティをキャッシュする事をお勧めします。for inループを配列で使用するのは、バグや最低のパフォーマンスの傾向があるコードを書く前兆になります。

+

Arrayコンストラクター

Arrayコンストラクターはそのパラメーターの扱い方が曖昧なので、新しい配列を作る時には、常に配列リテラル - []記法 - を使用する事を強くお勧めします。

+
[1, 2, 3]; // 結果: [1, 2, 3]
+new Array(1, 2, 3); // 結果: [1, 2, 3]
+
+[3]; // Result: [3]
+new Array(3); // 結果: []
+new Array('3') // 結果: ['3']
+

このケースの場合、Arrayコンストラクターに渡される引数は一つだけですが、その引数はNumberになります。コンストラクターは、引数に値がセットされたlengthプロパティを伴った新しい配列を返します。特筆すべきなのは、新しい配列のlengthプロパティのみが、このようにセットされるという事です。実際の配列のインデックスは初期化されません。

+
var arr = new Array(3);
+arr[1]; // undefined
+1 in arr; // false, インデックスがセットされていない
+

配列の長さが先行してセットされるという振舞いは、いくつかの場合に便利です。例えば、文字の繰り返しや、for loopを使用したコードの回避などの場合です。

+
new Array(count + 1).join(stringToRepeat);
+

終わりに

+

Arrayコンストラクターの使用は出来る限り避けてください。リテラルが当然望ましい形です。それらは、短かく明快な文法をもっている為に、コードの可読性を高めてくれます。

+

等価と比較

JavaScriptはオブジェクトの値の等価の比較方法を2種類持っています。

+

等価演算子

+

等価演算子は2つのイコール記号: ==から成っています。

+

JavaScriptは弱い型付けを特徴としています。これは等価演算子が比較をする際に型付けを強制するという意味です。

+
""           ==   "0"           // false
+0            ==   ""            // true
+0            ==   "0"           // true
+false        ==   "false"       // false
+false        ==   "0"           // true
+false        ==   undefined     // false
+false        ==   null          // false
+null         ==   undefined     // true
+" \t\r\n"    ==   0             // true
+

上記の表では型強制の結果が表示されています。==の使用が一般に悪い習慣とみなされる大きな理由として、変換ルールが複雑な為、バグの追跡が困難になる事が挙げられます。

+

加えて、型強制が行なわれるとパフォーマンスにも影響してしまいます。例えば、文字列は他の数字と比較する前に数値に変換されなければなりません。

+

厳密等価演算子

+

厳密等価演算子は3つのイコール記号:===で成っています。

+

これはオペランドの間で強制的な型変換が実行されない事を除けば、通常の等価演算子と同じように正確に動作します。

+
""           ===   "0"           // false
+0            ===   ""            // false
+0            ===   "0"           // false
+false        ===   "false"       // false
+false        ===   "0"           // false
+false        ===   undefined     // false
+false        ===   null          // false
+null         ===   undefined     // false
+" \t\r\n"    ===   0             // false
+

上記の結果は、より明確でコードの早期破損を可能にします。これはある程度までコードを硬化させて、オペランドが別の型の場合にパフォーマンスが向上します。

+

オブジェクトの比較

+

=====は両方とも等価演算子とされていますが、そのオペランドの少なくとも一つがObjectの場合は、両者は異なる動きをします。

+
{} === {};                   // false
+new String('foo') === 'foo'; // false
+new Number(10) === 10;       // false
+var foo = {};
+foo === foo;                 // true
+

これら2つの演算子は同一性を比較していているのであって、等価を比較しているわけではありません。これは、これらの演算子はPythonのis演算子やCのポインター比較と同じように、同じオブジェクトのインスタンスを比較するという事になります。

+

終わりに

+

厳密等価演算子だけを使用することを特に推奨します。型を強制的に型変換する場合はexplicitlyであるべきで、言語自体の複雑な変換ルールが残っているべきではありません。

+

typeof演算子

typeof演算子(instanceofも同様です)は恐らくJavaScriptの最大の設計ミスです。完全に壊れている存在に近いものです。

+

instanceofはまだ限られた用途で使用できますが、typeofは本当に使用できる実用的なケースはオブジェクトの型を調べるという起こらないケース一つしかありません。

+ +

JavaScript の型テーブル

+
Value               Class      Type
+-------------------------------------
+"foo"               String     string
+new String("foo")   String     object
+1.2                 Number     number
+new Number(1.2)     Number     object
+true                Boolean    boolean
+new Boolean(true)   Boolean    object
+new Date()          Date       object
+new Error()         Error      object
+[1,2,3]             Array      object
+new Array(1, 2, 3)  Array      object
+new Function("")    Function   function
+/abc/g              RegExp     object (Nitro/V8ではfunction)
+new RegExp("meow")  RegExp     object (Nitro/V8ではfunction)
+{}                  Object     object
+new Object()        Object     object
+

上記のテーブルにおいてTypetypeof演算子が返す値を参照しています。はっきりと分かるように、この値はどれでも一貫しています。

+

Classはオブジェクト内部の[[Class]]プロパティの値を参照しています。

+ +

[[Class]]の値を取得する為に、Object.prototypeメソッドのtoStringを使う事があります。

+

オブジェクトのクラス

+

仕様では[[Class]]の値にアクセスするためにはObject.prototype.toStringを使用した厳密な一つの方法が与えられています。

+
function is(type, obj) {
+    var clas = Object.prototype.toString.call(obj).slice(8, -1);
+    return obj !== undefined && obj !== null && clas === type;
+}
+
+is('String', 'test'); // true
+is('String', new String('test')); // true
+

上記の例ではthisの値と共にObject.prototype.toStringが呼び出され[[Class]]の取得されている値がオブジェクトとして設定されます。

+ +

未定義変数のテスト

+
typeof foo !== 'undefined'
+

上記ではfooが実際に宣言されたかどうかをReferenceErrorの結果を参照してチェックします。これはtypeofが唯一実際に役に立つ場合です。

+

終わりに

+

オブジェクトの型をチェックする為には、Object.prototype.toStringを使用する事を強くお勧めします。これが唯一信頼できる方法だからです。上述の型テーブルでも分かるように、typeofの戻り値は仕様で定義されていないものを返します。よって、実装によって別の結果になる事があります。

+

変数が定義されているかチェックしない限りは、typeofどんな事をしても避けるべきです。

+

instanceofオペレーター

instanceofオペレーターは2つのオペランドのコンストラクタを比較します。これはカスタムで作ったオブジェクトを比較する時にのみ有用です。組み込みの型に使用するのはtypeof operatorを使用するのと同じくらい意味がありません。

+

カスタムオブジェクトの比較

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // true
+new Bar() instanceof Foo; // true
+
+// これは単に関数オブジェクトFooにBar.prototypeをセットしただけです。
+// しかし、実際のFooのインスタンスではありません。
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // false
+

ネイティブ型でinstanceofを使用する

+
new String('foo') instanceof String; // true
+new String('foo') instanceof Object; // true
+
+'foo' instanceof String; // false
+'foo' instanceof Object; // false
+

ここで1つ重要な事は、異なるJavaScriptのコンテキスト(例えば、ブラウザの異なるウィンドウ)を元としたオブジェクトでは、コンストラクタが厳密に同じものでは無い為にinstanceofは上手く動作しません。

+

終わりに

+

instanceofオペレーターは同じJavaScriptのコンテキストが起源になっているカスタムメイドのオブジェクトを扱う場合のみ使うべきです。ちょうどtypeofオペレーターのように、その他での使用は避けるべきです。

+

型変換

JavaScriptは弱い型付けの言語なので、可能な限り型強制が適用されます。

+
// これらはtrueです。
+new Number(10) == 10; // Number.toString()が変換される
+                      // numberに戻る
+
+10 == '10';           // StringsがNumberに変換される
+10 == '+10 ';         // バカみたいに文字列を追加
+10 == '010';          // もっともっと
+isNaN(null) == false; // nullが0に変換される
+                      // もちろんNaNではないです
+
+// これらはfalseです
+10 == 010;
+10 == '-10';
+ +

上記の自体を避ける為に、厳密等価演算子を使用する事を強く推奨します。また、これはたくさんある落し穴を避けますが、それでもまだJavaScriptの弱い型付けシステムから発生する色々な課題が残っています。

+

組み込み型のコンストラクタ

+

NumberStringのような組み込み型のコンストラクタは、newキーワードの有無で振る舞いが違ってきます。

+
new Number(10) === 10;     // False, ObjectとNumber
+Number(10) === 10;         // True, NumberとNumber
+new Number(10) + 0 === 10; // True, 暗黙の型変換によります
+

Numberのような組み込み型をコンストラクタとして使うと、新しいNumberオブジェクトが作られますが、newキーワードを除外するとNumber関数がコンバーターのように振る舞います。

+

加えて、リテラルかオブジェトではない値を持っていると、さらに型強制が多くなります。

+

最良のオプションは以下の3つの方法の内、1つで型を明示してキャストする事になります。

+

Stringでキャストする

+
'' + 10 === '10'; // true
+

空の文字列の付加により値を簡単に文字列にキャストできます。

+

Numberでキャストする

+
+'10' === 10; // true
+

単項プラスオペレーターを使うと数字にキャストする事が可能です。

+

Booleanでキャストする

+

notオペレーターを2回使うと、値はブーリアンに変換できます。

+
!!'foo';   // true
+!!'';      // false
+!!'0';     // true
+!!'1';     // true
+!!'-1'     // true
+!!{};      // true
+!!true;    // true
+

コア

なぜ、evalを使ってはいけないのか

eval関数はローカルスコープ中のJavaScriptコードの文字列を実行します。

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

しかし、eval直接ローカルスコープから呼ばれて、かつ呼んだ関数の名前が実際のevalでないと実行しません。

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

evalの使用は全てのコストを払ってでも回避するべきです。その「使用法」の99.9%で、これ無しでも実装できます。

+

偽装されたeval

+

timeout functionsであるsetTimeoutsetIntervalはどちらも最初の引数として文字列を取る事ができます。この文字列はevalがこの場合直接呼ばれていないので、常にグローバルスコープで実行されてしまいます。

+

セキュリティの問題

+

evalはまたセキュリティの問題もあります。なぜなら、どんなコードを与えられても実行してしまうからで、絶対に不明または信頼できない発行元の文字列は使ってはいけません。

+

終わりに

+

evalは絶対に使用しないでください。これを使用しているどんなコードも、その働き、パフォーマンスやセキュリティについて問われてしまいます。evalが必要な場合でも、最初の段階で使用しないでください。より良いデザインを使用するべきで、それにはevalを使う必要性はありません。

+

undefinednull

JavaScriptはnothingを表す2つの別個の値を持っています。これら2つの内でundefinedはより便利な存在です。

+

undefinedの値

+

undefinedはただ1つの値undefinedを持つ型です。

+

この言語はまた、undefinedの値を持つグローバル変数を定義しています。この値もまたundefinedと呼ばれています。しかし、この変数は どちらも 言語のキーワードではなく、定数です。この事はこのは簡単に上書きされてしまうという事になります。

+ +

undefinedが返される時の例をいくつか挙げます。

+
    +
  • (未定義の)グローバル変数undefinedにアクセスした時
  • +
  • return文が無い為に、暗黙のうちに関数が返された時
  • +
  • 何も返されないreturnがある時
  • +
  • 存在しないプロパティを探索する時
  • +
  • 関数のパラメーターで明示的な値が何も無い時
  • +
  • undefinedが設定された全ての値
  • +
+

undefinedの値に変更する処理

+

グローバル変数undefinedのみが実際のundefinedのコピーを保持するので、これに新しい値を代入してもundefined の値が変更される事はありません

+

まだ、undefinedの値に対して何かしらの比較をしないといけない場合は、最初にundefinedの値を取得する必要があります。

+

コードのundefinedの変数の上書きを可能な限りしないよう保護する為には、一般的なテクニックとしてanonymous wrapperの引数にパラメーターを追加するというものがあります。

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // ローカルスコープではundefined。
+    // ここで値に対して参照がされる
+
+})('Hello World', 42);
+

同じ効果を得る事ができる別の方法として、ラッパーの内部での宣言を使うものがあります。

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

これらの唯一の違いは、こちらのバージョンの方が4バイト余計に短縮できるという物です。また、他にvarステートメントは匿名ラッパーの中にはありません。

+

nullの使用

+

JavaScriptというプログラム言語のコンテキストの中では、undefinedは主に伝統的な意味でのnullの意味で使用される事が多いです。実際のnull(リテラルも型も両方)は多かれ少なかれ、単なるデータ型です。

+

それはJavaScriptの内部でいくつか使われています(プロトタイプチェーンの終わりにFoo.prototype = nullという宣言をするようなもの)が、ほとんど全てのケースで、undefinedに置き替える事が可能です。

+

セミコロン自動挿入

JavaScriptはC言語スタイルのシンタックスを持っていますが、これはソースコードの中でセミコロンの使用を強制している事にはならないので、これらを省略する事も可能です。

+

JavaScriptはセミコロン無しの言語ではありません。実際に、ソースコードを理解する為にもセミコロンは必要になります。ですので、JavaScriptのパーサーはセミコロンが無い事によるパースエラーを検出する度に、自動的にセミコロンを挿入します。

+
var foo = function() {
+} // セミコロンが入っている事が期待されるので、パースエラーになる
+test()
+

挿入が起こると、パーサーはもう一度パースします。

+
var foo = function() {
+}; // エラーが無いので、パーサーは次の解析をする
+test()
+

セミコロンの自動挿入は、コードの振る舞いを変えられる為に、言語の最大の欠陥の内の一つと考えられています。

+

どのように動くか

+

以下のコードはセミコロンが無いので、パーサーはどこにセミコロンを挿入するか決めなくてはなりません。

+
(function(window, undefined) {
+    function test(options) {
+        log('testing!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+
+})(window)
+

下記がパーサーの「推測」ゲームの結果になります。

+
(function(window, undefined) {
+    function test(options) {
+
+        // 行がマージされて、挿入されない
+        log('testing!')(options.list || []).forEach(function(i) {
+
+        }); // <- 挿入
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        ); // <- 挿入
+
+        return; // <- inserted, breaks the return statement
+        { // ブロックとして扱われる
+
+            // a label and a single expression statement
+            foo: function() {} 
+        }; // <- 挿入
+    }
+    window.test = test; // <- 挿入
+
+// 再度行がマージされる
+})(window)(function(window) {
+    window.someLibrary = {}; // <- 挿入
+
+})(window); //<- 挿入
+ +

パーサーは上記のコードの振舞いを劇的に変化させます。あるケースにおいては、間違っている事にもなってしまいます。

+

先頭の括弧

+

先頭に括弧がある場合、パーサーはセミコロンを挿入しません

+
log('testing!')
+(options.list || []).forEach(function(i) {})
+

このコードは1つの行に変形します。

+
log('testing!')(options.list || []).forEach(function(i) {})
+

logが関数を返さない確率はとても高いです。しかし、上記ではundefined is not a functionというTypeErrorが繰り返されます。

+

終わりに

+

セミコロンを省略するのは絶対にお勧めしません。括弧を対応する文と同じ行に記述すること、および一行のif / else文に対して括弧を省略しないことが推奨されています。これら両方の処理がコードの整合性を高めてくれるだけでなく、JavaScriptパーサーの振舞いを変えてしまうのを防いでくれるでしょう。

+

delete演算子

端的に言って、JavaScriptの関数やその他の要素はDontDelete属性が設定されているので、グローバル変数を消去する事は不可能です。

+

グローバルコードと関数コード

+

変数や、関数がグローバルまたは関数スコープで定義された時は、そのプロパティは有効なオブジェクトかグローバルオブジェクトになります。このようなプロパティは属性のセットを持っていますが、それらの内の1つがDontDeleteになります。変数や関数がグローバルや関数コードで宣言されると、常にDontDelete属性を作るために、消去できません。

+
// グローバル変数:
+var a = 1; // DontDelete属性が設定される
+delete a; // false
+a; // 1
+
+// 通常関数:
+function f() {} // DontDelete属性が設定される
+delete f; // false
+typeof f; // "function"
+
+// 再代入も役に立たない:
+f = 1;
+delete f; // false
+f; // 1
+

明示的なプロパティ

+

明示的にプロパティを設定することが、通常通りの消去を可能にします。

+
// プロパティを明示的に設定する
+var obj = {x: 1};
+obj.y = 2;
+delete obj.x; // true
+delete obj.y; // true
+obj.x; // undefined
+obj.y; // undefined
+

上記の例の中で、obj.xobj.yはそれぞれDontDelete属性が無い為に消去できます。これが下記の例でも動作する理由です。

+
// IE以外では、これも動作する
+var GLOBAL_OBJECT = this;
+GLOBAL_OBJECT.a = 1;
+a === GLOBAL_OBJECT.a; // true - ただのグローバルのvar
+delete GLOBAL_OBJECT.a; // true
+GLOBAL_OBJECT.a; // undefined
+

ここではa. thisを消す為にグローバルオブジェクトと明示的に宣言したaをそのプロパティとして参照させて、消去する事を許可するトリックを使います。

+

IE(最低でも6-8で)は多少のバグがある為に、上記のコードは動作しません。

+

関数の引数と組み込み引数

+

関数の通常の引数である、arguments objectsと組み込みのプロパティもまた、DontDeleteが設定されています。

+
// 関数の引数とプロパティ:
+(function (x) {
+
+  delete arguments; // false
+  typeof arguments; // "object"
+
+  delete x; // false
+  x; // 1
+
+  function f(){}
+  delete f.length; // false
+  typeof f.length; // "number"
+
+})(1);
+

ホストオブジェクト

+

delete演算子の挙動はホストオブジェクトにとって予測不可能になりかねません。仕様によりホストオブジェクトは、あらゆる挙動の実行が許可されている為です。

+

終わりに

+

delete演算子は、しばしば予期せぬ挙動をします。唯一安全な使用方法は通常のオブジェクトに明示的に設定されたプロパティを扱う場合だけです。

+

その他

setTimeoutsetInterval

JavaScriptは非同期なので、setTimeoutsetInterval関数を使ってある関数の実行のスケジュールを決める事が可能です。

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // Number > 0を返す
+

setTimeoutが呼ばれた時に、タイムアウトのIDを返し、この先おおよそ1000ms以内に実行するfooをスケジュールします。fooは正確に1度だけ実行されます。

+

これは、setTimeout関数の呼び出しで指定した遅延時間を正確に間違いなく得られるという事では決してありません。コードが実行されているJavaScriptエンジンのタイマー分解能によって決まります。この事実はJavaScriptがシングルスレッドなので、他のスレッドでの実行を妨害してしまう事があるかもしれません。

+

第一パラメーターを渡された関数はグローバルオブジェクトによって呼び出されます。これは呼び出された関数の内部でthisがまさにこのオブジェクトを参照しているという事になります。

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // これはグローバルオブジェクトを参照しています
+        console.log(this.value); // undefinedを記録するはずです
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

setIntervalでスタッキングコール

+

setTimeoutは関数を一度だけ実行します。setInterval - 名前が示すように - 毎回Xミリ秒毎に関数を実行しますが、この使用は推奨されていません。

+

コードがタイムアウト呼び出しブロックで実行される時に、setIntervalは指定された関数を呼び出します。これは、特に小さい間隔で、関数の結果をスタックに積む事ができます。

+
function foo(){
+    // 1秒おきにブロックの何かを実行
+}
+setInterval(foo, 1000);
+

上記のコードでは、fooが1回呼び出されて、1秒ブロックされます。

+

fooがコードをブロックしている間、setIntervalは呼び出される予定を確保しています。fooが完了した瞬間に、実行を待っている呼び出しが10回以上存在しているでしょう。

+

ブロッキング可能なコードの取り扱い

+

簡単かつ、一番コントロール可能な解決法として、関数自体の中でsetTimeoutを使うという方法があります。

+
function foo(){
+    // 1秒ブロックする何か
+    setTimeout(foo, 1000);
+}
+foo();
+

このカプセル化はsetTimeoutの呼び出しだけでなく、呼び出しのスタッキングを防止してより詳細なコントロールが出来ます。fooそれ自身が今や、再度実行するかしないかを決める事が出来るのです。

+

手動でタイムアウトをクリアする

+

タイムアウトとインターバルのクリアは、clearTimeoutclearIntervalに個別のIDを渡せば出来ます。最初にset関数を使った場所に依存します。

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

全てのタイムアウトをクリアする

+

全てのタイムアウトや、インターバルをクリアする組み込みメソッドが無い為、機能的にクリアする為には暴力的な手段を使う必要があります。

+
// "全ての"タイムアウトをクリアする
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

ここまでもまだ、任意の数字を与えられた為に影響を受けないタイムアウトがあるかもしれません。そのため、代わりに全てのタイムアウトのIDを追跡する事が推奨されます。それで個別にクリアされます。

+

隠されたevalの使用

+

setTimeoutsetInterval は、第一引数に文字列を取る事が可能です。この仕様は内部でevalを使用する為に、絶対に使うべきではありません。

+ +
function foo() {
+    // この先呼ばれる
+}
+
+function bar() {
+    function foo() {
+        // 絶対に呼ばれない
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

この場合、eval直接呼ばれないので、文字列が渡されたsetTimeoutglobal scopeで実行されます。よって、barのスコープからfooのローカル変数は使われないのです。

+

いずれかのタイムアウト関数によって呼び出される関数に引数を渡すために文字列を使わないという事は、さらに推奨されています。

+
function foo(a, b, c) {}
+
+// 絶対にこのように使わない
+setTimeout('foo(1,2, 3)', 1000)
+
+// 匿名関数を代わりに使用する
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

終りに

+

setTimeoutsetIntervalのパラメーターに文字列を用いてはいけません。引数が関数に呼び出される必要がある場合本当に悪いコードの明確なサインになります。実際の呼び出しには匿名関数を渡すべきです。

+

さらに、setIntervalの使用はスケジューラーがJavaScriptの実行によってブロックされないので、避けるべきでしょう。

+
\ No newline at end of file diff --git a/site/javascript/garden.js b/javascript/garden.js similarity index 100% rename from site/javascript/garden.js rename to javascript/garden.js diff --git a/site/javascript/html5.js b/javascript/html5.js similarity index 100% rename from site/javascript/html5.js rename to javascript/html5.js diff --git a/site/javascript/plugin.js b/javascript/plugin.js similarity index 100% rename from site/javascript/plugin.js rename to javascript/plugin.js diff --git a/site/javascript/prettify.js b/javascript/prettify.js similarity index 100% rename from site/javascript/prettify.js rename to javascript/prettify.js diff --git a/ko/index.html b/ko/index.html new file mode 100644 index 0000000..0e14c09 --- /dev/null +++ b/ko/index.html @@ -0,0 +1,1103 @@ +JavaScript Garden

소개

Intro

JavaScript 언어의 핵심에 대한 내용을 모아 JavaScript Garden을 만들어 었다. 이 글이 초보자가 JavaScript 익히면서 자주 겪는 실수, 미묘한 버그, 성능 이슈, 나쁜 습관들 줄일 수 있도록 도와줄 것이다.

+

JavaScript Garden은 단순히 JavaScript 언어 자체를 설명하려 만들지 않았다. 그래서 이 글에서 설명하는 주제들을 이해하려면 반드시 언어에 대한 기본 지식이 필요하다. 먼저 Mozilla Developer Network에 있는 문서로 JavaScript 언어를 공부하기 바란다.

+

저자들

+

이 글은 Stack Overflow에서 사랑받는 두 사람 Ivo WetzelZhang Yi Jiang의 작품이다. Ivo Wetzel이 글을 썼고 Zhang Yi jiang이 디자인을 맡았다.

+

기여자들

+ +

번역

+ +

호스팅

+

JavaScript Garden은 Github에서 호스팅하고 있고 Cramer DevelopmentJavaScriptGarden.info에서 미러링해주고 있다.

+

저작권

+

JavaScript Garden은 MIT license를 따르고 GitHub에서 호스팅하고 있다. 문제를 발견하면 이슈를 보고하거나 수정해서 Pull Request를 하라. 아니면 Stack Overflow 채팅 사이트의 Javascript room에서 우리를 찾으라.

+

객체

객체와 프로퍼티

JavaScript에서 nullundefined를 제외한 모든 것들은 객체처럼 동작한다.

+
false.toString(); // 'false'
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

숫자 리터럴은 객체처럼 사용되지 못할꺼라는 오해가 있는데 이것은 단지 JavaScript 파서의 문제일 뿐이다. JavaScript 파서는 숫자에 Dot Notation이 들어가면 오류라고 생각한다.

+
2.toString(); // SyntaxError가 난다.
+

하지만, 숫자를 객체처럼 사용할수 있는 꼼수가 몇 가지 있다.

+
2..toString(); // 두 번째 점은 잘 된다.
+2 .toString(); // 왼쪽 공백이 있으면 잘 된다.
+(2).toString(); // 2를 먼저 해석한다.
+

Object 타입

+

JavaScript 객체는 name/value 쌍으로 된 프로퍼티로 구성되기 때문에 Hashmap처럼 사용될 수도 있다.

+

객체 리터럴인 Object Notation으로 객체를 만들면 Object.prototype을 상속받고 프로퍼티를 하나도 가지지 않은 객체가 만들어진다.

+
var foo = {}; // 깨끗한 새 객체를 만든다.
+
+// 값이 12인 'test' 프로퍼티가 있는 객체를 만든다.
+var bar = {test: 12}; 
+

프로퍼티 접근

+

객체의 프로퍼티는 객체이름 다음에 점을 찍어(Dot Notation) 접근하거나 각괄호를 이용해(Square Bracket Notation) 접근할 수 있다.

+
var foo = {name: 'kitten'}
+foo.name; // kitten
+foo['name']; // kitten
+
+var get = 'name';
+foo[get]; // kitten
+
+foo.1234; // SyntaxError
+foo['1234']; // works
+

두 방식 모두 거의 동일하게 동작한다. 다만 차이가 있다면 각괄호 방식은 프로퍼티 이름을 동적으로 할당해서 값에 접근 할수 있지만 점을 이용한 방식은 구문 오류를 발생시킨다.

+

프로퍼티 삭제

+

객체의 프로퍼티를 삭제하려면 delete를 사용해야만 한다. 프로퍼티에 undefinednull을 할당하는 것은 프로퍼티를 삭제하는 것이 아니라 프로퍼티에 할당된 value만 지우고 key는 그대로 두는 것이다.

+
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

위 코드의 출력 결과는 baz만 제거했기 때문에 bar undefinedfoo null은 출력되고 baz와 관련된 것은 출력되지 않는다.

+

Notation of Keys

+
var test = {
+    'case': 'I am a keyword, so I must be notated as a string',
+    delete: 'I am a keyword, so me too' // SyntaxError가 난다.
+};
+

프로퍼티는 따옴표 없는 문자열(plain characters)과 따옴표로 감싼 문자열(strings)을 모두 Key 값으로 사용할 수 있다. 하지만 위와 같은 코드는 JavaScript 파서의 잘못된 설계 때문에 구버전(ECMAScript 5 이전 버전)에서는 SystaxError가 발생할 것이다.

+

위 코드에서 문제가 되는 delete 키워드를 따옴표로 감싸면 구버전의 JavaScript 엔진에서도 제대로 해석될 것이다.

+

Prototype

Javascript는 클래스 스타일의 상속 모델을 사용하지 않고 프로토타입 스타일의 상속 모델을 사용한다.

+

'이 점이 JavaScript의 약점이다.'라고 말하는 사람들도 있지만 실제로는 prototypal inheritance 모델이 훨씬 더 강력하다. 그 이유는 프로토타입 모델에서 클래스 모델을 흉내 내기는 매우 쉽지만, 반대로 클래스 모델에서 프로토타입 모델을 흉내 내기란 매우 어렵기 때문이다.

+

실제로 Prototypal Inheritance 모델을 채용한 언어 중에서 JavaScript만큼 널리 사용된 언어가 없었기 때문에 두 모델의 차이점이 다소 늦게 정리된 감이 있다.

+

먼저 가장 큰 차이점은 프로토타입 체인이라는 것을 이용해 상속을 구현한다는 점이다.

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// Foo의 인스턴스를 만들어 Bar의 prototype에 할당한다.
+Bar.prototype = new Foo();
+Bar.prototype.foo = 'Hello World';
+
+// Bar 함수를 생성자로 만들고
+Bar.prototype.constructor = Bar;
+
+var test = new Bar() // bar 인스턴스를 만든다.
+
+// 결과적으로 만들어진 프로토타입 체인은 다음과 같다. 
+test [instance of Bar]
+    Bar.prototype [instance of Foo] 
+        { foo: 'Hello World' }
+        Foo.prototype
+            { method: ... }
+            Object.prototype
+                { toString: ... /* etc. */ }
+

위 코드에서 test 객체는 Bar.prototypeFoo.prototype을 둘 다 상속받았기 때문에 Foo에 정의한 method 함수에 접근할 수 있다. 그리고 프로토타입 체인에 있는 Foo 인스턴스의 value 프로퍼티도 사용할 수 있다. new Bar()를 해도 Foo 인스턴스는 새로 만들어지지 않고 Bar의 prototype에 있는 것을 재사용한다. 그래서 모든 Bar 인스턴스는 같은 value 프로퍼티를 공유한다.

+ +

프로토타입 탐색

+

객체의 프로퍼티에 접근하려고 하면 JavaScript는 해당 이름의 프로퍼티를 찾을 때까지 프로토타입 체인을 거슬러 올라가면서 탐색하게 된다.

+

프로토타입 체인을 끝까지 탐색했음에도(보통은 Object.prototype임) 불구하고 원하는 프로퍼티를 찾지 못하면 undefined를 반환한다.

+

prototype 프로퍼티

+

prototype 프로퍼티는 프로토타입 체인을 만드는 데 사용하고 어떤 값이든 할당할 수 있지만, primitive 값을 할당되면 무시한다.

+
function Foo() {}
+Foo.prototype = 1; // 무시됨
+

반면에 위 예제처럼 객체를 할당하면 프로토타입 체인이 동적으로 잘 만들어진다.

+

성능

+

프로토타입 체인을 탐색하는 시간이 오래걸릴수록 성능에 부정적인 영향을 줄수있다. 특히 성능이 중요한 코드에서 프로퍼티 탐색시간은 치명적인 문제가 될수있다. 가령, 없는 프로퍼티에 접근하려고 하면 항상 프로토타입 체인 전체를 탐색하게 된다.

+

뿐만아니라 객체를 순회(Iterate)할때도 프로토타입 체인에 있는 모든 프로퍼티를 탐색하게 된다.

+

네이티브 프로토타입의 확장

+

종종 Object.prototype을 이용해 내장 객체를 확장하는 경우가 있는데, 이것도 역시 잘못 설계된 것중에 하나다.

+

위와 같이 확장하는 것을 Monkey Patching라고 부르는데 캡슐화를 망친다. 물론 Prototype같은 유명한 프레임워크들도 이런 확장을 사용하지만, 기본 타입에 표준도 아닌 기능들을 너저분하게 추가하는 이유를 여전히 설명하지 못하고 있다.

+

기본 타입을 확장해야하는 유일한 이유는 Array.forEach같이 새로운 JavaScript 엔진에 추가된 기능을 대비해 미리 만들어 놓는 경우 말고는 없다.

+

결론

+

프로토타입을 이용해 복잡한 코드를 작성하기 전에 반드시 프로토타입 상속 (Prototypal Inheritance) 모델을 완벽하게 이해하고 있어야 한다. 뿐만아니라 프로토타입 체인과 관련된 성능 문제로 고생하지 않으려면 프로토타입 체인이 너무 길지 않도록 항상 주의하고 적당히 끊어줘야 한다. 마지막으로 새로운 JavaScript 기능에 대한 호환성 유지 목적이 아니라면 절대로 네이티브 프로토타입을 확장하지마라.

+

hasOwnProperty

어떤 객체의 프로퍼티가 자기 자신의 프로퍼티인지 아니면 프로토타입 체인에 있는 것인지 확인하려면 hasOwnProperty 메소드를 사용한다. 그리고 이 메소드는 Object.prototype으로 부터 상속받아 모든 객체가 가지고 있다.

+ +

hasOwnProperty메소드는 프로토타입 체인을 탐색하지 않고, 프로퍼티를 다룰수있는 유일한 방법이다.

+
// Object.prototype을 오염시킨다.
+Object.prototype.bar = 1; 
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // true
+
+foo.hasOwnProperty('bar'); // false
+foo.hasOwnProperty('goo'); // true
+

hasOwnProperty 메소드는 어떤 프로퍼티가 자기 자신의 프로퍼티인지 아닌지 정확하게 알려주기 때문에 객체의 프로퍼티를 순회할때 꼭 필요하다. 그리고 프로토타입 체인 어딘가에 정의된 프로퍼티만을 제외하는 방법은 없다.

+

hasOwnProperty 메소드도 프로퍼티다

+

JavaScript는 hasOwnProperty라는 이름으로 프로퍼티를 덮어 쓸수도 있다. 그래서 객체 안에 같은 이름으로 정의된 hasOwnProperty가 있을 경우, 본래 hasOwnProperty의 값을 정확하게 얻고 싶다면 다른 객체의 hasOwnProperty 메소드를 빌려써야 한다.

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Here be dragons'
+};
+
+foo.hasOwnProperty('bar'); // 항상 false를 반환한다.
+
+// 다른 객체의 hasOwnProperty를 사용하여 foo 객체의 프로퍼티 유무를 확인한다.
+({}).hasOwnProperty.call(foo, 'bar'); // true
+
+// Object에 있는 hasOwnProperty를 사용해도 된다.
+Object.prototype.hasOwnProperty.call(obj, 'bar'); // true
+

결론

+

어떤 객체에 원하는 프로퍼티가 있는지 확인하는 가장 확실한 방법은 hasOwnProperty를 사용하는 것이다. for in loop에서 네이티브 객체에서 확장된 프로퍼티를 제외하고 순회하려면 hasOwnProperty와 함께 사용하길 권한다.

+

for in Loop

객체의 프로퍼티를 탐색할때 in 연산자와 마찬가지로 for in 문도 프로토타입 체인까지 탐색한다.

+ +
// Object.prototype을 오염시킨다.
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // bar와 moo 둘 다 출력한다.
+}
+

for in문에 정의된 기본 동작을 바꿀순 없기 때문에 루프 안에서 불필요한 프로퍼티를 필터링 해야한다. 그래서 Object.prototypehasOwnProperty메소드를 이용해 본래 객체의 프로퍼티만 골라낸다.

+ +

hasOwnProperty로 필터링 하기

+
// 위의 예제에 이어서 
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

위와 같이 사용해야 올바른 사용법이다. hasOwnProperty 때문에 오직 moo만 출력된다. hasOwnProperty가 없으면 이 코드는 Object.prototype으로 네이티브 객체가 확장될 때 에러가 발생할 수 있다.

+

따라서 Proptotype 라이브러리처럼 네이티브 객체를 프로토타입으로 확장한 프레임워크를 사용할 경우 for in 문에 hasOwnProperty를 사용하지 않을 경우 문제가 발생할 수 있다.

+

결론

+

hasOwnProperty를 항상 사용하길 권한다. 실제 코드가 동작하는 환경에서는 절대로 네이티브 객체가 프로토타입으로 확장됐다 혹은 확장되지 않았다를 가정하면 안된다.

+

함수

함수 선언과 함수 표현식

JavaScript에서 함수는 First Class Object다. 즉, 함수 자체가 또 다른 함수의 인자될 수 있다는 말이다. 그래서 익명 함수를 비동기 함수의 콜백으로 넘기는 것도 이런 특징을 이용한 일반적인 사용법이다.

+

함수 선언

+
function foo() {}
+

위와 같이 선언한 함수는 프로그램이 실행하기 전에 먼저 호이스트(Hoist) (스코프가 생성)되기 때문에 정의된 스코프(Scope) 안에서는 어디서든 이 함수를 사용할 수 있다. 심지어 함수를 정의하기 전에 호출해도 된다.

+
foo(); // 이 코드가 실행되기 전에 foo가 만들어지므로 잘 동작한다.
+function foo() {}
+

함수 표현식

+
var foo = function() {};
+

위 예제는 foo 변수에 익명 함수를 할당한다.

+
foo; // 'undefined'
+foo(); // TypeError가 난다.
+var foo = function() {};
+

'var'문을 이용해 선언하는 경우, 코드가 실행되기 전에 'foo' 라는 이름의 변수를 스코프의 맨 위로 올리게 된다.(호이스트 된다) 이때 foo 값은 undefiend로 정의된다.

+

하지만 변수에 값을 할당하는 일은 런타임 상황에서 이루어지게 되므로 실제 코드가 실행되는 순간의 foo변수는 기본 값인 undefined이 된다.

+

이름있는 함수 표현식

+

이름있는 함수를 할당할때도 특이한 경우가 있다.

+
var foo = function bar() {
+    bar(); // 이 경우는 동작 하지만, 
+}
+bar(); // 이 경우는 참조에러를 발생시킨다. 
+

foo 함수 스코프 밖에서는 foo 변수 외에는 다른 값이 없기 때문에 bar는 함수 밖에서 사용할 수 없지만 함수 안에서는 사용할 수 있다. 이와 같은 방법으로 자바스크립트에서 어떤 함수의 이름은 항상 그 함수의 지역 스코프 안에서 사용할수있다.

+

this의 동작 원리

다른 프로그래밍 언어에서 this가 가리키는 것과 JavaScript에서 this가 가리키는 것과는 좀 다르다. this가 가리킬 수 있는 객체는 정확히 5종류나 된다.

+

Global Scope에서

+
this;
+

Global Scope에서도 this가 사용될 수 있고 이때에는 Global 객체를 가리킨다.

+

함수를 호출할 때

+
foo();
+

이때에도 thisGlobal 객체를 가리킨다.

+ +

메소드로 호출할 때

+
test.foo(); 
+

이 경우에는 thistest를 가리킨다.

+

생성자를 호출할 때

+
new foo(); 
+

new 키워드로 생성자를 실행시키는 경우에 이 생성자 안에서 this는 새로 만들어진 객체를 가리킨다.

+

this가 가리키는 객체 정해주기.

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // a = 1, b = 2, c = 3으로 넘어간다.
+foo.call(bar, 1, 2, 3); // 이것도... 
+

Function.prototypecall이나 apply 메소드를 호출하면 this가 무엇을 가리킬지 정해줄 수 있다. 호출할 때 첫 번째 인자로 this가 가리켜야 할 객체를 넘겨준다.

+

그래서 foo Function 안에서 this는 위에서 설명했던 객체 중 하나를 가리키는 것이 아니라 bar를 가리킨다.

+ +

대표적인 함정

+

this가 Global 객체를 가리키는 것도 잘못 설계된 부분 중 하나다. 괜찮아 보이지만 실제로는 전혀 사용하지 않는다.

+
Foo.method = function() {
+    function test() {
+        // 여기에서 this는 Global 객체를 가리킨다.
+    }
+    test();
+}
+

test 에서 thisFoo를 가리킬 것으로 생각할 테지만 틀렸다. 실제로는 그렇지 않다.

+

test에서 Foo에 접근하려면 method에 Local 변수를 하나 만들고 Foo를 가리키게 하여야 한다.

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // 여기에서 this 대신에 that을 사용하여 Foo에 접근한다.
+    }
+    test();
+}
+

that은 this에 접근하기 위해 만든 변수다. closures와 함께 this의 값을 넘기는 데 사용할 수 있다.

+

Method할당 하기

+

JavaScript의 또다른 함정은 바로 함수의 별칭을 만들수 없다는 점이다. 별칭을 만들기 위해 메소드를 변수에 넣으면 자바스크립트는 별칭을 만들지 않고 바로 할당해 버린다.

+
var test = someObject.methodTest;
+test();
+

첫번째 코드로 인해 이제 test는 다른 함수와 똑같이 동작한다. 그래서 test 함수 내부의 this도 더이상 someObject를 가리키지 않는다. (역주: test가 methodTest의 별칭이라면 methodTest 함수 내부의 this도 someObject를 똑같이 가리켜야 하지만 test의 this는 더이상 someObject가 아니다.)

+

이렇게 this를 늦게 바인딩해서 나타나는 약점때문에 늦은 바인딩이 나쁜 거라고 생각할수도 있지만, 사실 이런 특징으로 인해 프로토타입 상속(prototypal inheritance)도 가능해진다.

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

Bar 인스턴스에서 method를 호출하면 method에서 this는 바로 그 인스턴스를 가리킨다.

+

클로져(Closure)와 참조(Reference)

클로져는 JavaScript의 특장점 중 하나다. 클로저를 만들면 클로저 스코프 안에서 클로저를 만든 외부 스코프(Scope)에 항상 접근할 있다. JavaScript에서 스코프는 함수 스코프밖에 없기 때문에 기본적으로 모든 함수는 클로저가 될수있다.

+

private 변수 만들기

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

여기서 Counterincrement 클로저와 get 클로저 두 개를 반환한다. 이 두 클로저는 Counter 함수 스코프에 대한 참조를 유지하고 있기 때문에 이 함수 스코프에 있는 count 변수에 계속 접근할 수 있다.

+

Private 변수의 동작 원리

+

JavaScript에서는 스코프(Scope)를 어딘가에 할당해두거나 참조할수 없기 때문에 스코프 밖에서는 count 변수에 직접 접근할 수 없다. 접근할수 있는 유일한 방법은 스코프 안에 정의한 두 클로저를 이용하는 방법밖에 없다.

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

위 코드에서 foo.hack 함수는 Counter 함수 안에서 정의되지 않았기 때문에 이 함수가 실행되더라도 Counter 함수 스코프 안에 있는 count 값은 변하지 않는다. 대신 foo.hack 함수의 countGlobal 스코프에 생성되거나 이미 만들어진 변수를 덮어쓴다.

+

반복문에서 클로저 사용하기

+

사람들이 반복문에서 클로저를 사용할 때 자주 실수를 하는 부분이 있는데 바로 인덱스 변수를 복사할때 발생한다.

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);  
+    }, 1000);
+}
+

이 코드는 0부터 9까지의 수를 출력하지 않고 10만 열 번 출력한다.

+

타이머에 설정된 익명 함수는 변수 i에 대한 참조를 들고 있다가 console.log가 호출되는 시점에 i의 값을 사용한다. console.log가 호출되는 시점에서 for loop는 이미 끝난 상태기 때문에 i 값은 10이 된다.

+

기대한 결과를 얻으려면 i 값을 복사해 두어야 한다.

+

앞의 참조 문제 해결하기

+

반복문의 index 값을 복사하는 가장 좋은 방법은 익명함수로 랩핑Anonymous Wrapper하는 방법이다.

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);  
+        }, 1000);
+    })(i);
+}
+

이 익명 함수에 i를 인자로 넘기면 이 함수의 파라미터 e에 i의 이 복사되어 넘어갈 것이다.

+

그리고 setTimeout는 익명 함수의 파라미터인 e에 대한 참조를 갖게 되고 e값은 복사되어 넘어왔으므로 loop의 상태에 따라 변하지 않는다.

+

또다른 방법으로 랩핑한 익명 함수에서 출력 함수를 반환하는 방법도 있다. 아래 코드는 위 코드와 동일하게 동작한다.

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+

arguments 객체

JavaScript의 모든 함수 스코프에는 arguments라는 특별한 변수가 있다. 이 변수는 함수에 넘겨진 모든 인자에 대한 정보가 담겨 있다.

+ +

arguments 객체는 Array가 아니다. 물론 length 프로퍼티도 있고 여러모로 Array와 비슷하게 생겼지만 Array.prototype을 상속받지는 않았다.

+

그래서 arguments에는 push, pop, slice 같은 표준 메소드가 없다. 일반 for문을 이용해 순회는 할수 있지만, Array의 메소드를 이용하려면 arguments를 Array로 변환해야 한다.

+

Array로 변환하기

+

다음 코드는 arguments에 있는 객체를 새로운 Array에 담아 반환한다.

+
Array.prototype.slice.call(arguments);
+

이 변환 과정은 느리기 때문에 성능이 중요한 부분에 사용하는 것은 별로 바람직하지 못 하다.

+

arguemnts 객체 넘기기

+

어떤 함수에서 다른 함수로 arguments 객체를 넘길 때에는 다음과 같은 방법을 권한다. (역주: foo 함수는 bar 함수 한번 랩핑한 함수다. )

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // 내곡동에 땅이라도 산다.
+}
+

또 다른 방법으로는 함수를 랩핑하지 않고, 풀어서 callapply를 함께 사용하는 방법이 있다. (역주: 프로토타입에 있는 method를 호출하기 전에 Foo 객체 안에 있는 method로 한번더 필터링하는 효과가 있다. )

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// "method"를 풀어 쓴(unbound) 버전
+// 이 Function의 인자: this, arg1, arg2...argN
+Foo.method = function() {
+
+    // 결과: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+};
+

일반 파라미터와 arguments 객체의 인덱스

+

일반 파라미터와 arguments 객체의 프로퍼티는 모두 gettersetter를 가진다.

+

그래서 파라미터나 arguments 객체의 프로퍼티의 값을 바꾸면 둘 다 바뀐다.

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

성능에 대한 오해와 진실.

+

arguments 객체는 항상 만들어지지만 두가지 예외사항이 있다. arguments라는 이름으로 변수를 함수 안에 정의하거나 arguments 객체로 넘겨받는 인자중 하나라도 정식 인자로 받아서 사용하면 arguemnts 객체는 만들어지지 않는다. 하지만 뭐 이런 경우들은 어차피 arguments 객체를 안쓰겠다는 의미니까 상관 없다.

+

그리고 gettersetter는 항상 생성되기 때문에 getter/setter를 사용하는 것은 성능에 별 영향을 끼치지 않는다. 예제처럼 단순한 코드가 아니라 arguments 객체를 다방면으로 활용하는 실제 코드에서도 마찬가지다.

+ +

그러나 예외도 있다. 최신 JavaScript 엔진에서 arguments.callee를 사용하면 성능이 확 떨어진다.

+
function foo() {
+    arguments.callee; // 이 함수를 가리킨다.
+    arguments.callee.caller; // 이 함수를 호출한 부모함수를 가리킨다.
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // 원래 인라인 돼야 하는디...
+    }
+}
+

위 코드에서 'foo' 함수는 자기 자신과 자신을 호출한 함수를 알아야 하기 때문에 더이상 인라인되지 않는다. 이렇게 쓰면 인라인이 주는 성능상 장점을 포기해야 하는데다가 이 함수가 호출되는 상황(calling context)에 의존하게 돼 버려서 캡슐화(Encapsulation)도 해친다. +(역주: 보통 코드가 컴파일 될때 코드를 인라인 시키면서 최적화 하는데, 위와 같이 arguments.callee나 caller를 사용하게 되면 런타임시에 해당 함수가 결정되므로 인라인 최적화를 할수가 없다.)

+

arguments.callee와 arguments.callee의 프로퍼티들은 절대 사용하지 말자!.

+ +

생성자

JavaScript의 생성자는 다른 언어들과 다르게 new 키워드로 호출되는 함수가 생성자가 된다.

+

생성자로 호출된 함수의 this 객체는 새로 생성된 객체를 가리키고, 새로 만든 객체의 prototype에는 생성자의 prototype이 할당된다.

+

그리고 생성자에 명시적인 return 구문이 없으면 this가 가리키는 객체를 반환한다.

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

위 코드는 new 키워드가 실행되는 시점에 Foo를 생성자로 호출하고 Foo.prototype을 새 객체의 prototype에 할당한다.

+

아래 코드와 같이 생성자에 명시적인 return 문이 있는 경우에는 반환하는 값이 객체인 경우에만 그 값을 반환한다.

+
function Bar() {
+    return 2;
+}
+new Bar(); // 새 객체를 만들어 반환
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // 명시한 객체를 반환
+

new 키워드가 없으면 그 함수는 객체를 반환하지 않는다.

+
function Foo() {
+    this.bla = 1; // 전역객체에 할당된다.
+}
+Foo(); // undefined
+

위 예제는 그때그때 다르게 동작한다. 그리고 this 객체의 동작 원리에 따라서 Foo 함수안의 this의 값은 Global 객체를 가리키게된다. +(역주: 결국 new 키워드를 빼고, 코드를 작성할 경우 원치 않은 this 참조 오류가 발생할 수 있다.)

+

팩토리

+

생성자가 객체를 반환하면 new 키워드를 생략할 수 있다.

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

new 키워드의 유무과 관계없이 Bar 생성자의 동작은 동일한다. 즉 클로저가 할당된 method 프로퍼티가 있는 새로운 객체를 만들어 반환한다.

+

new Bar()로 호출되는 생성자는 반환되는 객체의 prototype 프로퍼티에 아무런 영향을 주지 않는다. 객체를 반환하지 않는 생성자로 만들어지는 경우에만 객체의 prototype이 생성자의 것으로 할당된다.

+

그러니까 이 예제에서 new 키워드의 유무는 아무런 차이가 없다. +(역주: 생성자에 객체를 만들어 명시적으로 반환하면 new 키워드에 관계없이 잘 동작하는 생성자를 만들수있다. 즉, new 키워드가 빠졌을때 발생하는 this 참조 오류를 방어해준다.)

+

팩토리로 객체 만들기

+

new 키워드를 빼먹었을 때 버그가 생긴다는 이유로 아예 new를 사용하지 말 것을 권하기도 한다.

+

객체를 만들고 반환해주는 팩토리를 사용하여 new 키워드 문제를 회피할 수 있다.

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

new 키워드가 없어도 잘 동작하고 private 변수를 사용하기도 쉽다. 그렇지만, 단점도 있다.

+
    +
  1. prototype으로 메소드를 공유하지 않으므로 메모리를 좀 더 사용한다.
  2. +
  3. 팩토리를 상속하려면 모든 메소드를 복사하거나 객체의 prototype에 객체를 할당해 주어야 한다.
  4. +
  5. new 키워드를 누락시켜서 prototype chain을 끊어버리는 것은 아무래도 언어의 의도에 어긋난다.
  6. +
+

결론

+

new 키워드가 생략되면 버그가 생길 수 있지만 그렇다고 prototype을 사용하지 않을 이유가 되지 않는다. 애플리케이션에 맞는 방법을 선택하는 것이 나을 거고 어떤 방법이든 *엄격하고 한결같이 지켜야 한다.

+

스코프와 네임스페이스

JavaScript는 '{}' Block이 배배 꼬여 있어도 문법적으로는 잘 처리하지만, Block Scope은 지원하지 않는다. 그래서 JavaScript에서는 항상 함수 스코프를 사용한다.

+
function test() { // Scope
+    for(var i = 0; i < 10; i++) { // Scope이 아님
+        // count
+    }
+    console.log(i); // 10
+}
+ +

그리고 JavaScript에는 Namepspace 개념이 없기 때문에 모든 값이 하나의 전역 스코프에 정의된다.

+

변수를 참조 할 때마다 JavaScript는 해당 변수를 찾을 때까지 상위 방향으로 스코프를 탐색한다. 변수 탐색하다가 전역 스코프에서도 찾지 못하면 ReferenceError를 발생시킨다.

+

전역 변수 문제.

+
// script A
+foo = '42';
+
+// script B
+var foo = '42'
+

이 두 스크립트는 전혀 다르다. Script A는 전역 스코프에 foo라는 변수를 정의하는 것이고 Script B는 스코프에 변수 foo를 정의하는 것이다.

+

다시 말하지만, 이 둘은 전혀 다르고 var가 없을 때 특별한 의미가 있다.

+
// Global Scope
+var foo = 42;
+function test() {
+    // local Scope
+    foo = 21;
+}
+test();
+foo; // 21
+

test 함수 안에 있는 'foo' 변수에 var 구문을 빼버리면 Global Scope의 foo의 값을 바꿔버린다. '뭐 이게 뭐가 문제야'라고 생각될 수 있지만 수천 줄인 JavaScript 코드에서 var를 빼먹어서 생긴 버그를 해결하는 것은 정말 어렵다.

+
// Global Scope
+var items = [/* some list */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // Scope of subLoop
+    for(i = 0; i < 10; i++) { // var가 없다.
+        // 내가 for문도 해봐서 아는데...
+    }
+}
+

subLoop 함수는 전역 변수 i의 값을 변경해버리기 때문에 외부에 있는 for문은 subLoop을 한번 호출하고 나면 종료된다. 두 번째 for문에 var를 사용하여 i를 정의하면 이 문제는 생기지 않는다. 즉, 의도적으로 외부 스코프의 변수를 사용하는 것이 아니라면 var를 꼭 넣어야 한다.

+

지역 변수

+

JavaScript에서 지역 변수는 함수의 파라미터var로 정의한 변수밖에 없다.

+

// 전역 공간 + var foo = 1; + var bar = 2; + var i = 2;

+
function test(i) {
+    // test 함수의 지역 공간
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

foo 변수와 i 변수는 test함수 스코프에 있는 지역 변수라서 전역 공간에 있는 foo, i 값은 바뀌지 않는다. 하지만 bar는 전역 변수이기 때문에 전역 공간에 있는 bar의 값이 변경된다.

+

호이스팅(Hoisting)

+

JavaScript는 선언문을 모두 호이스트(Hoist)한다. 호이스트란 var 구문이나 function 선언문을 해당 스코프의 맨 위로 옮기는 것을 말한다.

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

코드를 본격적으로 실행하기 전에 JavaScript는 var 구문과 function 선언문을 해당 스코프의 맨위로 옮긴다.

+
// var 구문이 여기로 옮겨짐.
+var bar, someValue; // default to 'undefined'
+
+// function 선언문도 여기로 옮겨짐
+function test(data) {
+    var goo, i, e; // Block Scope은 없으므로 local 변수들은 여기로 옮겨짐
+    if (false) {
+        goo = 1;
+
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // bar()가 아직 'undefined'이기 때문에 TypeError가 남
+someValue = 42; // Hoisting은 할당문은 옮기지 않는다.
+bar = function() {};
+
+test();
+

블록 스코프(Block Scope)는 없으므로 for문과 if문 안에 있는 var 구문들까지도 모두 함수 스코프 앞쪽으로 옮겨진다. 그래서 if Block의 결과는 좀 이상해진다.

+

원래 코드에서 if Block은 전역 변수 goo를 바꾸는 것처럼 보였지만 호이스팅(Hoisting) 후에는 지역 변수를 바꾼다.

+

호이스팅을 모르면 다음과 같은 코드는 ReferenceError를 낼 것으로 생각할 것이다.

+
// SomeImportantThing이 초기화됐는지 검사한다.
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

var 구문은 전역 스코프의 맨위로 옮겨지기 때문에 이 코드는 잘 동작한다.

+
var SomeImportantThing;
+
+// SomeImportantThing을 여기서 초기화하거나 말거나...
+
+// SomeImportantThing는 선언돼 있다.
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

이름 찾는 순서

+

JavaScript의 모든 Scope은 현 객체를 가리키는 this를 가지고 있다. 전역 스코프에도 this가 있다.

+

함수 스코프에는 arguments라는 변수가 하나 더 있다. 이 변수는 함수에 인자로 넘겨진 값들이 담겨 있다.

+

예를 들어 함수 스코프에서 foo라는 변수에 접근할 때 JavaScript는 다음과 같은 순서로 찾는다.

+
    +
  1. 해당 Scope에서 var foo 구문으로 선언된 것을 찾는다.
  2. +
  3. Function 파라미터에서 foo라는 것을 찾는다.
  4. +
  5. 해당 Function 이름이 foo인지 찾는다.
  6. +
  7. 상위 Scope으로 있는지 확인하고 있으면 #1부터 다시 한다.
  8. +
+ +

네임스페이스

+

JavaScript에서는 전역 공간(Namepspace) 하나밖에 없어서 변수 이름이 중복되기 쉽다. 하지만 이름없는 랩퍼(Anonymous Wrappers)를 통해 쉽게 피해갈 수 있다.

+
(function() {
+    // 일종의 네임스페이스라고 할 수 있다.
+
+    window.foo = function() {
+        // 이 클로저는 전역 스코프에 노출된다.
+    };
+
+})(); // 함수를 정의하자마자 실행한다.
+

이름없는 함수는 표현식(expressions)이기 때문에 호출되려면 먼저 평가(Evaluate)돼야 한다.

+
( // 소괄호 안에 있는 것을 먼저 평가한다.
+function() {}
+) // 그리고 함수 객체를 반환한다.
+() // 평가된 결과를 호출한다.
+

함수를 평가하고 바로 호출하는 방법이 몇가지 더 있다. 문법은 다르지만 똑같다.

+
// 함수를 평가하자마자 호출하는 방법들...
+!function(){}();
++function(){}();
+(function(){}());
+// 등등...
+

결론

+

코드를 캡슐화할 때는 항상 이름없는 랩퍼(Anonymous Wrapper)로 네임스페이스를 만들어 사용할 것을 추천한다. 이 래퍼(Wrapper)는 이름이 중복되는 것을 막아 주고 더 쉽게 모듈화할 수 있도록 해준다.

+

그리고 전역 변수를 사용하는 것은 좋지 못한 습관이다. 이유야 어쨌든 에러 나기 쉽고 관리하기도 어렵다.

+

Array

배열 순회와 프로퍼티

JavaScript에서는 배열(Array)도 객체(Object)지만 객체 순회(Iterate)를 할 때 for in을 사용해서 좋을 게 없다. 실제로 배열을 탐색할때 for in문 사용하지 말아야 할 이유가 매우 많다.

+ +

for in은 프로토타입 체인에 있는 프로퍼티를 모두 훑는(enumerate) 데다가 객체 자신의 프로퍼티만 훑으려면 hasOwnProperty를 사용해야 하기 때문에 for보다 20배 느리다.

+

배열 순회

+

배열을 순회 할때는 일반적인 for문을 사용하는 것이 가장 빠르다.

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

이 예제에서 l = list.length로 배열의 length 값을 캐시해야 한다는 것을 꼭 기억해야 한다.

+

매번 반복할때마다 배열에 있는 length 프로퍼티에 접근하는 것은 좀 부담스럽다. 최신 JavaScript 엔진은 이 일을 알아서 처리해주기도 하지만 코드가 늘 새 엔진에서 실행되도록 보장할 방법이 없다.

+

실제로 캐시 하지 않으면 성능이 반으로 줄어든다.

+

length 프로퍼티

+

length 프로퍼티의 getter는 단순히 Array 안에 있는 엘리먼트의 개수를 반환하고 setter는 배열을 할당한 수만큼 잘라 버린다.

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo; // [1, 2, 3]
+

현재 크기보다 더 작은 값을 할당하면 배열을 자르지만, 현재 크기보다 더 큰 값을 할당한다고 해서 배열을 늘리진 않는다.

+

결론

+

최적의 성능을 위해서는 for문을 사용하고 length 프로퍼티 값을 캐시해야 한다. 배열에 for in을 사용하면 성능도 떨어지고 버그 나기도 쉽다.

+

배열 생성자

배열을 만들때 배열 생성자에 파라미터를 넣어 만드는 방법은 헷갈릴수있다. 그래서 항상 각 괄호([]) 노테이션을 이용해 배열을 만들 것을 권한다

+
[1, 2, 3]; // Result: [1, 2, 3]
+new Array(1, 2, 3); // Result: [1, 2, 3]
+
+[3]; // Result: [3]
+new Array(3); // Result: []
+new Array('3') // Result: ['3']
+

배열 생성자에 숫자를 인자로 넣으면 그 숫자 크기 만큼의 빈 배열을 반환한다. 즉 배열의 length는 그 숫자가 된다. 이때 생성자는 단지 length 프로퍼티에 그 숫자를 할당하기만 하고 배열은 실제로 초기화 하지도 않는다.

+
var arr = new Array(3);
+arr[1]; // undefined
+1 in arr; // false, 이 인덱스는 초기화되지 않음.
+

for문을 사용하지 않고 문자열을 더하는 경우에는 length 프로퍼티에 숫자를 할당해주는 기능이 유용할 때도 있다.

+
new Array(count + 1).join(stringToRepeat);
+

결론

+

배열 생성자는 가능하면 사용하지 말고, 각 괄호 ([]) 노테이션이을 사용하자. 후자가 더 간략하고 명확할 뿐만 아니라 보기도 좋다.

+

타입

객체 비교하기

JavaScript에서 객체를 비교하는 방법은 두 가지가 있다.

+

이중 등호 연산자

+

이중 등호 연산자는 ==을 말한다.

+

JavaScript는 Weak Typing을 따르기 때문에 이중 등호를 이용해 비교할 때 두 객체의 자료형을 강제로 변환한다.

+
""           ==   "0"           // false
+0            ==   ""            // true
+0            ==   "0"           // true
+false        ==   "false"       // false
+false        ==   "0"           // true
+false        ==   undefined     // false
+false        ==   null          // false
+null         ==   undefined     // true
+" \t\r\n"    ==   0             // true
+

이 표는 이중 등호를 사용하면 왜 안되는지를 보여준다. 이 복잡한 변환 규칙은 실제로 골치 아픈 버그를 만들어 낸다.

+

게다가 강제로 타입을 변환하게 되면 성능에도 영향을 준다. 예를 들어 문자와 숫자를 비교하려면 반드시 먼저 문자를 숫자로 변환해야 한다.

+

삼중 등호 연산자

+

삼중 등호 연산자는 ===을 말한다.

+

삼중 등호는 강제로 타입을 변환하지 않는다는 사실을 제외하면 이중 등호와 동일하다.

+
""           ===   "0"           // false
+0            ===   ""            // false
+0            ===   "0"           // false
+false        ===   "false"       // false
+false        ===   "0"           // false
+false        ===   undefined     // false
+false        ===   null          // false
+null         ===   undefined     // false
+" \t\r\n"    ===   0             // false
+

위 결과가 훨씬 더 명확하고 문제가 쉽게 드러난다. 삼중 등호를 사용하면 코드를 좀 더 튼튼하게 만들수 있고, 비교하는 두 객체의 타입이 다르면 더 좋은 성능을 얻을 수도 있다.

+

객체 비교하기

+

이중 등호와(==)와 삼중 등호(===)는 둘 다 값을 비교하는 연산이지만 피연산자중에 Object 타입이 하나라도 있으면 다르게 동작한다.

+
{} === {};                   // false
+new String('foo') === 'foo'; // false
+new Number(10) === 10;       // false
+var foo = {};
+foo === foo;                 // true
+

두 연산자 모두 두 객체의 값이 같은지를 비교하지 않고, 두 객체가 같은 객체(identity)인지를 비교한다. C에서 포인터를 비교하거나 Python의 is처럼 같은 인스턴스인지 비교하는 것이다.

+

결론

+

삼중 등호 연산자를 사용할 것을 강력하게 권한다. 비교하기 위해서 타입 변환이 필요하면 언어의 복잡한 변환 규칙에 맡기지 말고 꼭 명시적으로 변환한 후에 비교해야 한다.

+

typeof 연산자

typeof 연산자도 instanceof 연산자와 함께 JavaScript에서 치명적으로 잘못 설계된 부분이다. 이건 정말이지 아무짝에도 쓸모가 없다.

+

instanceof 연산자는 그래도 여전히 쓸만한 데가 좀 있는데 typeof 연산자는 객체의 타입을 검사하는 것 외에는 쓸만한데가 없고, 이마저도 거의 쓸일이 없다.

+ +

JavaScript 타입 표

+
Value               Class      Type
+-------------------------------------
+"foo"               String     string
+new String("foo")   String     object
+1.2                 Number     number
+new Number(1.2)     Number     object
+true                Boolean    boolean
+new Boolean(true)   Boolean    object
+new Date()          Date       object
+new Error()         Error      object
+[1,2,3]             Array      object
+new Array(1, 2, 3)  Array      object
+new Function("")    Function   function
+/abc/g              RegExp     object (function in Nitro/V8)
+new RegExp("meow")  RegExp     object (function in Nitro/V8)
+{}                  Object     object
+new Object()        Object     object
+

위 표에서 Typetypeof가 반환하는 값이다. 위 표에서처럼 일치되는 값이 거의 없다.

+

위 표에서 Class는 객체 내부에 있는 [[Class]] 프로퍼티의 값을 말한다.

+ +

[[Class]] 프로퍼티의 값을 가져다 쓰려면 Object.prototypetoString 메소드를 사용한다.

+

객체의 클래스

+

표준에 의하면 [[Class]] 값을 얻는 방법은 Object.prototype.toString 하나뿐이다.

+
function is(type, obj) {
+    var clas = Object.prototype.toString.call(obj).slice(8, -1);
+    return obj !== undefined && obj !== null && clas === type;
+}
+
+is('String', 'test'); // true
+is('String', new String('test')); // true
+

Object.prototype.toStringthis[[Class]] 값을 가져오는 것이니까 this를 obj로 바꾸어 사용한다.

+ +

변수가 Undefined인지 확인하기

+
typeof foo !== 'undefined'
+

위 코드는 foo가 정의됐는지 아닌지를 확인해준다. 정의되지 않은 변수에 접근하면 ReferenceError 나는데 이것을 방지할 수 있다. typeof가 유용한 건 이때뿐이다.

+

결론

+

객체의 타입을 검사하려면 Object.prototype.toString를 사용해야 한다. 다른 방법은 신뢰할 수 없다. 위 표에서 보여준 것처럼 typeof가 반환하는 값은 표준에 나와 있지 않기 때문에 구현방법도 제각각이다.

+

변수가 정의됐는지 확인할 때를 제외하고 가급적 typeof는 피해야한다.

+

instanceof 연산자

instanceof연산자는 두 피연산자의 생성자를 비교할때 사용하고 직접 만든 객체를 비교할 때 매우 유용하다. 내장 타입에 쓰는 경우에는 typeof처럼 거의 쓸모가 없다.

+

커스텀 객체를 intanceof로 비교하기

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // true
+new Bar() instanceof Foo; // true
+
+// Bar.prototype에 함수 객체인 Foo를 할당하면
+// Bar의 인스턴스는 Foo의 인스턴스가 아니다.
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // false
+

기본 내장 객체 타입을 intanceof로 비교하기

+
new String('foo') instanceof String; // true
+new String('foo') instanceof Object; // true
+
+'foo' instanceof String; // false
+'foo' instanceof Object; // false
+

JavaScript 컨텍스트마다(웹 브라우저의 도큐먼트 같은) 객체의 생성자는 다를 수밖에 없어서 instanceof는 다른 JavaScript 컨텍스트에 있는(웹 브라우저의 다른 도큐먼트에 있는) 객체와는 비교할 수 없다.

+

결론

+

instanceof는 한 JavaScript 컨텍스트 내에서 사용자가 만든 타입의 객체를 비교할 때에만 유용하다. typeof처럼 다른 목적으로는 사용하지 않는 것이 좋다.

+

타입 캐스팅

JavaScript는 Weak Typing 언어이기 때문에 필요할 때마다 알아서 타입을 변환한다.

+
// 다음은 모두 true
+new Number(10) == 10; // Number.toString()이 호출되고 
+                      // 다시 Number로 변환된다.
+
+10 == '10';           // 스트링은 Number로 변환된다.
+10 == '+10 ';         // 이상한 스트링
+10 == '010';          // 엉뚱한 스트링
+isNaN(null) == false; // null은 NaN이 아녀서 0으로 변환된다.
+
+// 다음은 모두 false
+10 == 010;
+10 == '-10';
+ +

위와 같은 문제들은 *반드시 삼중 등호 연산자를 이용해 해결하길 권한다. 물론 삼중 등호로 많은 결점을 보완할 수 있지만, 여전히 weak typing 시스템 때문에 생기는 많은 문제가 남아있다.

+

기본 타입 생성자

+

NumberString 같은 기본 타입들의 생성자는 new 키워드가 있을 때와 없을 때 다르게 동작한다.

+
new Number(10) === 10;     // False, Object와 Number
+Number(10) === 10;         // True, Number와 Number
+new Number(10) + 0 === 10; // True, 타입을 자동으로 변환해주기 때문에 
+

new 키워드와 함께 Number 같은 기본 타입의 생성자를 호출하면 객체를 생성하지만 new 없이 호출하면 형 변환만 시킨다.

+

그리고 객체가 아니라 단순히 값이나 리터럴을 사용하면 타입 변환이 더 많이 일어난다.

+

가능한 정확하게 타입을 변환해주는 것이 최선이다.

+

스트링으로 변환하기

+
'' + 10 === '10'; // true
+

숫자를 빈 스트링과 더하면 쉽게 스트링으로 변환할 수 있다.

+

숫자로 변환하기

+
+'10' === 10; // true
+

+ 연산자만 앞에 붙여주면 스트링을 쉽게 숫자로 변환할 수 있다.

+

Boolean으로 변환하기

+

'!' 연산자를 두 번 사용하면 쉽게 Boolean으로 변환할 수 있다.

+
!!'foo';   // true
+!!'';      // false
+!!'0';     // true
+!!'1';     // true
+!!'-1'     // true
+!!{};      // true
+!!true;    // true
+

핵심

eval을 사용하면 안 될까?

eval 함수는 JavaScript 문자열을 지역 스코프에서 실행한다.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

eval함수는 eval이라는 이름으로 직접 실행할 때에만 지역 스코프에서 실행된다. 그리고 eval이라는 이름에 걸맞게 악명또한 높다.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

어쨌든 eval은 사용하지 말아야 한다. eval을 사용하는 99.9%는 사실 eval 없이도 만들수있다.

+

가짜 eval

+

setTimeoutsetInterval은 첫 번째 인자로 스트링을 입력받을 수 있다. 이 경우에는 eval을 직접 호출하는 것이 아니라서 항상 Global Scope에서 실행된다.

+

보안 이슈

+

eval은 어떤 코드라도 무조건 실행하기 때문에 보안 문제도 있다. 따라서 신뢰하지 못하거나 모르는 코드가 포함되어 있을 경우 절대로 사용해서는 안된다.

+

결론

+

eval은 사용하지 않는 게 좋다. eval을 사용하는 모든 코드는 성능, 보안, 버그 문제를 일으킬 수 있다. 만약 eval이 필요해지면 설계를 변경하여 eval이 필요 없게 만들어야 한다.

+

undefinednull

JavaScript는 nothing을 표현할때 nullundefined 두 가지로 표현할 수 있고 그중 undefined가 더 유용하다.

+

undefined도 변수

+

undefinedundefined라는 값을 가지는 데이터 형식이다.

+

undefined는 상수도 아니고 JavaScript의 키워드도 아니다. 그냥 undefined라는 이름의 Global 변수이고 이 변수에는 undefined라고 할당돼 있다. 그래서 이 Global 변수의 값을 쉽게 바꿀 수 있다.

+ +

undefined 값이 반환될 때:

+
    +
  • global 변수 undefined에 접근할 때.
  • +
  • 선언은 했지만 아직 초기화하지 않은 변수에 접근할 때.
  • +
  • return 구문이 없는 함수는 암묵적으로 undefined를 반환함.
  • +
  • return 구문으로 아무것도 반환하지 않을 때.
  • +
  • 없는 프로퍼티를 찾을 때.
  • +
  • 함수 인자가 생략될 때.
  • +
  • undefined가 할당된 모든 것.
  • +
  • void(expression) 형식으로 된 표현
  • +
+ +

undefined가 바뀔 때를 대비하기

+

global 변수 undefinedundefined라는 객체를 가리키는 것뿐이기 때문에 새로운 값을 할당한다고 해도 undefined의 값 자체가 바뀌는 것이 아니다.

+

그래서 undefined와 비교하려면 먼저 undefined의 값을 찾아와야 한다.

+

undefined 변수가 바뀔 때를 대비해서 undefined라는 변수를 인자로 받는 anonymous wrapper로 감싸고 인자를 넘기지 않는 꼼수를 사용한다.

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // Local Scope에 undefined를 만들어서
+    // 원래 값을 가리키도록 했다.
+
+})('Hello World', 42);
+

wrapper 안에 변수를 새로 정의하는 방법으로도 같은 효과를 볼 수 있다.

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

이 두 방법의 차이는 minified했을 때 4바이트만큼 차이 난다는 것과 한쪽은 wrapper 안에 var 구문이 없다는 것밖에 없다.

+

Null 객체의 용도

+

JavaScript 언어에서는 undefined를 다른 언어의 null 처럼 쓴다. 진짜 null은 그냥 데이터 타입 중 하나일 뿐이지 더도덜도 아니다.

+

JavaScript를 깊숙히 건드리는 것이 아니면 null 대신 undefined를 사용해도 된다(Foo.prototype = null같이 프로토타입 체인을 끊을 때는 null을 사용한다).

+

자동으로 삽입되는 쎄미콜론

JavaScript는 C와 문법이 비슷하지만, 꼭 코드에 쎄미콜론을 사용하도록 강제하지는 않는다. 그래서 생략할 수 있다.

+

사실 JavaScript는 쎄미콜론이 꼭 있어야 하고 없으면 이해하지 못한다. 그래서 JavaScript 파서는 쎄미콜론이 없으면 자동으로 쎄미콜론을 추가한다.

+
var foo = function() {
+} // 쎄미콜론이 없으니 에러 난다.
+test()
+

파서는 쎄미콜론을 삽입하고 다시 시도한다.

+
var foo = function() {
+}; // 에러가 없어짐.
+test()
+

쎄미콜론을 자동으로 삽입한 것이 대표적인 JavaScript 설계 오류다. 쎄미콜론 유무에 따라 전혀 다른 코드가 될 수 있다.

+

어떻게 다를까?

+

코드에 쎄미콜론이 없으면 파서가 어디에 넣을지 결정한다.

+
(function(window, undefined) {
+    function test(options) {
+        log('testing!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+
+})(window)
+

파서는 이 코드에 쎄미콜론을 다음과 같이 삽입한다.

+
(function(window, undefined) {
+    function test(options) {
+
+        // 쎄미콜론을 넣는 것이 아니라 줄을 합친다.
+        log('testing!')(options.list || []).forEach(function(i) {
+
+        }); // <- 여기
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        ); // <- 여기
+
+        return; // <- 여기에 넣어서 그냥 반환시킨다.
+        { // 파서는 단순 블럭이라고 생각하고
+
+            // 단순한 레이블과 함수
+            foo: function() {}
+        }; // <- 여기
+    }
+    window.test = test; // <- 여기
+
+// 이 줄도 합쳐진다.
+})(window)(function(window) {
+    window.someLibrary = {}; // <- 여기
+
+})(window); //<- 여기에 파서는 쎄미콜론을 넣는다.
+ +

파서는 완전히 다른 코드로 만들어 버린다. 이것은 오류다.

+

괄호 해석

+

파서는 괄호에는 쎄미콜론을 넣지 않는다.

+
log('testing!')
+(options.list || []).forEach(function(i) {})
+

그래서 다음과 같이 한줄로 코드를 바꾼다.

+
log('testing!')(options.list || []).forEach(function(i) {})
+

이렇게 한줄로 바뀌면 log 함수가 함수를 반환할 가능성이 거의 없으므로 undefined is not a function이라는 TypeError가 발생한다.

+

결론

+

쎄미콜론은 반드시 사용해야 한다. 그리고 {}도 생략하지 않고 꼭 사용하는 것이 좋다. 한 줄밖에 안 되는 if / else 블럭에서도 꼭 사용해야 한다. 이 두 가지 규칙을 잘 지키면 JavaScript 파서가 잘못 해석하는 일을 미리 방지하고 코드도 튼튼해진다.

+

delete 연산자

간단히 말해서 전역 변수와 전역 함수 그리고 DontDelete 속성을 가진 자바스크립트 객체는 삭제할 수 없다.

+

Global 코드와 Function 코드

+

전역이나 함수 스코프에 정의한 함수나 변수는 모두 Activation 객체나 전역 객체의 프로퍼티다. 이 프로퍼티는 모두 DontDelete 속성을 가진다. 전역이나 함수 코드에 정의한 변수와 함수는 항상 DontDelete 프로퍼티로 만들어지기 때문에 삭제될 수 없다:

+
// Global 변수:
+var a = 1; // DontDelete가 설정된다.
+delete a; // false
+a; // 1
+
+// Function:
+function f() {} // DontDelete가 설정된다.
+delete f; // false
+typeof f; // "function"
+
+// 다시 할당해도 삭제할 수 없다:
+f = 1;
+delete f; // false
+f; // 1
+

명시적인(Explicit) 프로퍼티

+

다음 예제에서 만드는 프로퍼티는 delete할 수 있다. 이런 걸 명시적인(Explicit) 프로퍼티라고 부른다:

+
// Explicit 프로퍼티를 만든다:
+var obj = {x: 1};
+obj.y = 2;
+delete obj.x; // true
+delete obj.y; // true
+obj.x; // undefined
+obj.y; // undefined
+

obj.xobj.yDontDelete 속성이 아니라서 delete할 수 있다. 하지만 다음과 같은 코드도 잘 동작하기 때문에 헷갈린다:

+
// IE를 빼고 잘 동작한다:
+var GLOBAL_OBJECT = this;
+GLOBAL_OBJECT.a = 1;
+a === GLOBAL_OBJECT.a; // true - 진짜 Global 변수인지 확인하는 것
+delete GLOBAL_OBJECT.a; // true
+GLOBAL_OBJECT.a; // undefined
+

this가 전역 객체를 가리키는 것을 이용해서 명시적으로 프로퍼티 a를 선언하면 삭제할 수 있다. 이것은 꼼수다.

+

IE (적어도 6-8)는 버그가 있어서 안 된다.

+

Argument들과 Function의 기본 프로퍼티

+

Function의 arguments 객체와 기본 프로퍼티도 DontDelete 속성이다.

+
// Function의 arguments와 프로퍼티:
+(function (x) {
+
+  delete arguments; // false
+  typeof arguments; // "object"
+
+  delete x; // false
+  x; // 1
+
+  function f(){}
+  delete f.length; // false
+  typeof f.length; // "number"
+
+})(1);
+

Host 객체

+ +

Host 객체를 delete하면 어떻게 될지 알 수 없다. 표준에는 어떻게 Host 객체를 delete해야 하는지 정의하지 않았다.

+

결론

+

delete 연산자는 엉뚱하게 동작할 때가 많다. 명시적으로 정의한 일반 객체의 프로퍼티만 delete하는 것이 안전하다.

+

기타

setTimeoutsetInterval

JavaScript는 setTimeoutsetInterval함수를 이용해 비동기로 함수를 실행시킬수있다.

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // 0보다 큰 수를 반환한다.
+

setTimeout을 호출하면 타이머의 ID를 반환하고 대략 1,000밀리 초 후에 foo를 실행시킨다. foo딱 한 번만 실행한다.

+

JS엔진은 타이머에 설정한 시간(timer resolution)에 따라서 코드를 실행하지만 단일 쓰레드이기 때문에 특정 코드는 실행이 지연 될수도 있다. 따라서 setTimeout으로 코드가 실행돼야 할 시간을 정해줘도 정확하게 그 시간에 실행되지 않을수도 있다..

+

첫 번째 인자로 넘긴 함수는 전역 객체가 실행시킨다. 따라서 인자로 넘겨진 함수 내부의 this전역 객체를 가리키게 된다.

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // this는 전역 객체를 가리키기 때문에 
+        console.log(this.value); // undefined를 출력한다.
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

함수 호출을 쌓는(Stacking) setInterval함수.

+

setTimeout은 딱 한 번 함수를 호출하지만 setInterval은 이름처럼 지정한 시간마다 함수를 실행시켜준다. 하지만 이 함수의 사용은 좀 생각해봐야한다.

+

setInterval은 실행하는 코드가 일정시간 동안 블럭되도 계속해서 함수를 호출하기 때문에 주기가 짧은 경우 함수 호출이 쉽게 쌓여버린다.

+
function foo(){
+    // 1초 동안 블럭함.
+}
+setInterval(foo, 1000);
+

위 코드에서 foo함수는 호출될 때마다 1초씩 실행을 지연시킨다.

+

하지만 foo함수가 블럭되더라도 setInterval함수는 계속해서 함수 호출을 쌓기 때문에 foo함수 호출이 끝나면 10번 이상의 함수 호출이 쌓여서 대기하고 있을수도 있다. +(역주: 따라서 함수 호출이 쌓이게 되면 원래 기대했던 실행 주기를 보장받지 못한다.)

+

블럭되는 코드 해결법

+

앞에 문제를 해결하는 가장 쉽고 일반적인 방법은 setTimeout 함수에서 자기 자신을 다시 호출하는 방법이다.

+
function foo(){
+    // something that blocks for 1 second
+    setTimeout(foo, 1000);
+}
+foo();
+

이 방법은 함수 호출이 쌓이지도 않을 뿐만 아니라 setTimeout 호출을 해당 함수 안에서 관리하기 때문에 foo 함수에서 계속 실행할지 말지도 조절할 수 있다.

+

타이머 없애기

+

clearTimeoutclearInterval 함수로 setTimeout과 setInterval로 등록한 timeout과 interval을 삭제할 수 있다. set 함수들이 반환한 id를 저장했다가 clear 함수를 호출해서 삭제한다.

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

모든 타이머 없애기

+

등록한 timeout과 interval을 한꺼번에 제거하는 내장 함수는 없다. 따라서 좀 무식하지만 직접 구현해야 한다.

+
// "모든" 타이머 지우기
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

위와 같은 방법은 숫자가 미치지 못하는 타이머는 여전히 남아있을수 있다는 단점이 있다. 또 다른 해결 방법은 타이머가 반환하는 값이 항상 전보다 1만큼 큰 수를 반환한다는 점을 착안한 방법이다.

+
// "모든" 타이머 지우기
+var biggestTimeoutId = window.setTimeout(function(){}, 1),
+i;
+for(i = 1; i <= biggestTimeoutId; i++) {
+    clearTimeout(i);
+}
+

이 방법은 모든 주요 브라우저에서 문제없이 잘 동작하지만 ID가 항상 순차적이어야 한다고 표준에 명시된 것이 아니다. 그러므로 timeout ID를 모두 저장했다가 삭제하는 것이 가장 안전하다. 그러면 전부 깨끗하게 제거할 수 있다.

+

보이지 않게 사용되는 eval함수

+

setTimeoutsetInterval의 첫 파라미터로 문자열을 넘길 수 있다. 하지만 내부적으로 eval을 사용하는 것이기 때문에 절대 사용해서는 안된다.

+ +
function foo() {
+    // 이게 호출됨
+}
+
+function bar() {
+    function foo() {
+        // 이것은 절대 호출 안 됨
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

이 경우 eval그냥(directly) 호출되는 것이 아니다. setTimeout에 인자로 넘어간 문자열은 전역 스코프에서 실행되기 때문에 bar함수 영역에 있는 지역 변수 foo가 실행되는 것이 아니라 전역 스코프에 있는 foo가 실행된다.

+

함수에 파라미터를 넘겨야 하면 스트링을 사용하지 말아야 한다.

+
function foo(a, b, c) {}
+
+// 절대 사용하면 안 됨
+setTimeout('foo(1, 2, 3)', 1000)
+
+// 대신 익명 함수를 사용하는 게 좋다.
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

결론

+

setTimeoutsetInterval함수에 문자열 인자를 절대 사용해서는 안된다. 핸들러 함수에 인자를 넘기는 코드도 절대 좋은 코드가 아니다. 익명 함수을 사용해서 호출해야 한다.

+

그리고 setInterval은 해당 핸들러가 블럭되든 말든 상관하지 않기 때문에 되도록이면 쓰지말자.

+
\ No newline at end of file diff --git a/pl/index.html b/pl/index.html new file mode 100644 index 0000000..1afff47 --- /dev/null +++ b/pl/index.html @@ -0,0 +1,1441 @@ +JavaScript Garden

Wstęp

Licencja

JavaScript Garden jest publikowany w ramach licencji MIT i kod źródłowy znajduje +się na serwerze GitHub. Jeśli znajdziesz jakieś błędy lub literówki, zgłoś proszę +problem lub rozwiąż go i zgloś pull request ze swojego repozytorium. +Możesz nas także znaleźć w pokoju JavaScript na chacie Stack Overflow.

+

Obiekty

Wykorzystanie obiektów i ich właściwości

Wszystko w JavaScripcie zachowuje sie jak obiekt, z dwoma wyjątkami +null oraz undefined.

+
false.toString(); // 'false'
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

Popularnym błędem jest traktowanie literałów liczbowych jak obiektu. +Spowodowane jest to specyfiką parsera JavaScript, który interpretuje kropkę +po literale liczbowym jako rozdzielenie części całkowitej od części ułamkowej +liczby.

+
2.toString(); // wyrzuca błąd SyntaxError
+

Istnieje kilka rozwiązań, dzieki którym literał liczbowy będzie zachowywał się +jak obiekt.

+
2..toString(); // druga kropka jest poprawnie rozpoznana
+2 .toString(); // zauważ, że pozostawiona jest spacja przed kropką
+(2).toString(); // 2 zostanie najpierw zewaluowane
+

Obiekty jako typy danych

+

Obiekty w języku JavaScript mogą być używana jako tablice asocjacyjne, +ponieważ obiekty składają się głównie z mapowań pomiędzy nazwanymi właściwościami (kluczami) +a wartościami dla tych atrybutów.

+

Używając literału obiektu - notacji {} - istnieje możliwość stworzenia obiektu prostego. +Ten nowy obiekt bedzie dziedziczył z Object.prototype oraz +nie bedzie posiadał żadnych własnych właściwości.

+
var foo = {}; // nowy, pusty obiekt
+
+// nowy obiekt z właściwością test o wartości 12
+var bar = {test: 12}; 
+

Dostęp do właściwości

+

Właściwości obiektu można uzyskać na dwa sposoby - poprzez notację z kropką +lub z nawiasami kwadratowymi.

+
var foo = {name: 'kitten'}
+foo.name; // kitten
+foo['name']; // kitten
+
+var get = 'name';
+foo[get]; // kitten
+
+foo.1234; // wyrzuca błąd SyntaxError
+foo['1234']; // działa, zwraca undefined
+

Obie notacje są identyczne w swoim działaniu, z tą tylko różnicą, że notacja z nawiasami +kwadratowymi pozwala na dynamiczne dodawanie właściwości i nie prowadzi do wyrzucenia +błędu podczas odczytu nieistniejącej właściwości.

+

Usuwanie właściwości

+

Jedynym sposobem na faktycze usunięcie własności z obiektu jest użycie operatora +delete. Ustawienie własności na undefined lub null usunie tylko wartość +związaną z własnością, ale nie usunie to klucza (nazwy własności) z obiektu.

+
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

Powyższy kod wypisuje dwie linie - bar undefined i foo null. Tylko własność baz +została usunięta i dlatego nie została wypisana.

+

Notacja właściwości

+
var test = {
+    'case': 'jestem słowem kluczowym, więc muszę być w cudzysłowie',
+    delete: 'tak samo jak ja' // wyrzuca błąd SyntaxError
+};
+

Nazwy właściwości obiektu mogą być zarówno zapisane jako tekst (bez cudzysłowów +lub apostrofów) lub jako string (w cudzisłowach lub apostrofach). +Ze względu na kolejne niedociągnięcie w parserze JavaScript, +powyższy kod wyrzuci błąd SyntaxError dla implementacji JavaScript ponizej ECMAScript 5.

+

Ten błąd wynika z faktu, że delete jest słowem kluczowym, dlatego musi zostać +zapisany jako string (z cudzysłowami lub apostrofami), aby zapewnić, że zostanie +to poprawnie zinterpretowane przez starsze silniki języka JavaScript.

+

Prototyp

JavaScript nie posiada klasycznego modelu dziedziczenia. Zamiast tego +dziedziczenie jest realizowane poprzez prototypy.

+

Choć jest to często uważane za jedną ze słabości języka JavaScript, +prototypowy model dziedziczenia, jest w rzeczywistości potężniejszy od klasycznego +modelu. Na przykład stworzenia klasycznego modelu na podstawie modelu prototypowego +jest dość proste, podczas gdy zrobienie odwrotnego przekształcenie to o wiele trudniejsze zadanie.

+

Ze względu na fakt, że w JavaScript jest w zasadzie jedynym powszechnie stosowanym +językiem, któy posiada prototypowy model dziedziczenia, dostosowanie się do różnic pomiędzy +tymi dwoma modelami wymaga trochę czasu.

+

Pierwszą znaczącą różnicą jest to, że dziedziczenie w JavaScript odbywa się za pomocą +tak zwanych łańcuchów prototypów.

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// Ustawienie prototypu Bar na nową instancję Foo
+Bar.prototype = new Foo();
+Bar.prototype.foo = 'Hello World';
+
+// Upewniamy się, że Bar jest ustawiony jako rzeczywisty konstruktor
+Bar.prototype.constructor = Bar;
+
+var test = new Bar() // tworzymy nową instancję Bar
+
+// The resulting prototype chain
+test [instance of Bar]
+    Bar.prototype [instance of Foo] 
+        { foo: 'Hello World' }
+        Foo.prototype
+            { method: ... }
+            Object.prototype
+                { toString: ... /* etc. */ }
+

W powyższym przykładzie obiekt test będzie dziedziczył z obydwu, tj. +Bar.prototyp i Foo.prototyp, stąd będzie miał dostęp do funkcji method, +która była zdefiniowana w Foo. Ponadto obiekt będzie miał dostęp do +właściwości value, która jest jednyną instancją Foo i stała się jego prototypem. +Należy pamiętać, że new Bar nie tworzy nowej instancji Foo, +tylko wykorzystuje instancję, która jest przypisana do własności prototype. +Zatem Wszystkie instancje Bar będą dzieliły tą samą własność value.

+ +

Wyszukiwanie własności

+

Podczas dostępu do właściwości obiektu JavaScript przejdzie w górę łańcucha +prototypów, dopóki nie znajdzie właściwości bez nazwy.

+

Gdy przeszukiwanie dotrze do końca (szczytu) łańcucha, mianowicie Object.prototype +i nadal nie znajdzie określonej właściwości, to zwróci wartość +undefined.

+

Właściwość prototype

+

Podczas gdy właściwość prototype jest używana przez język do budowania łańcucha +prototypów, istnieje możliwość przypisania do niej dowolnej wartości. Jednakże +prymitywne typy będą po prostu ignorowanie, jeżeli zostaną ustawione jako prototype.

+
function Foo() {}
+Foo.prototype = 1; // nie ma wpływu
+

Przypisywanie obiektów, jak pokazano w powyższym przykładzie, zadziała i pozwala +na dynamiczne tworzenie łańcuchów prototypów.

+

Wydajność

+

Czas wyszukiwania właściwości, które są na końcu łańcucha prototypów może mieć +negatywny wpływ na wydajność krytycznych części kodu. Dodatkowo, próba dostępu +do nieistniejącej właściwości zawsze spowoduje przeszukanie całego łańcucha prototypów.

+

Również podczas iteracji po właściwościach obiektu +każda właściwość, która znajduje się w łańcuchu prototypów (niezależnie +na jakim znajduje się poziomie) zostanie wyliczona.

+

Rozszerzanie natywnych prototypów

+

Rozszerzanie Object.prototype lub innego prototypu wbudowanych typów jest jednym z +najczęściej nadużywanej częsci języka JavaScript.

+

Technika ta nazywana jest monkey patching i łamie zasady enkapsulacji. +Mimo to jest szeroko rozpowszechniona w frameworkach takich jak Prototype. +Nie ma jednak dobrego powodu, aby zaśmiecać wbudowane typy poprzez wzbogacanie ich o +niestandardowe funkcjonalności.

+

Jedynym dobrym powodem do rozszerzania wbudowanych prototypów jest portowanie
funkcjonalności znajdujących sie w nowszych silnikach JavaScript, np. Array.forEach

+

Wnioski

+

Zanim przystąpi się do pisania skomplikowanego kodu korzystającego z dziedziczania,
należy całkowicie zrozumieć prototypowy model dziedziczenia. Ponadto trzeba uważać +na długość łańcucha prototypów i w razie potrzeby zmniejszać ilość dziedziczeń, +aby uniknąć problemów z wydajnością. Natywne prototypy nigdy nie powinny być +rozszerzane, chyba że ze względu na wprowadzanie kompatybilności z nowszymi silnikami +JavaScript.

+

hasOwnProperty

W celu sprawdzenia, czy dana właściwość została zdefiniowana w tym obiekcie, a nie +w łańcuchu prototypów, niezbędne jest skorzystanie z metody +hasOwnProperty, której wszystkie obiekty dziedziczą z Object.prototype.

+ +

hasOwnProperty jest jedyną metodą w języku JavaScript, która operuje na właściwościach +i nie przegląda całego łańcucha prototypów.

+
// Zatrucie Object.prototype
+Object.prototype.bar = 1; 
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // true
+
+foo.hasOwnProperty('bar'); // false
+foo.hasOwnProperty('goo'); // true
+

Tylko hasOwnProperty da prawidłowy i oczekiwany rezultat. Jest to istotne podczas +iteracji po właściwościach obiektu. Nie ma innego sposobu na ominięcie +właściwości, która nie została zdefiniowana przez ten konkretny obiekt, +ale gdzieś indziej w łańcuchu prototypów.

+

hasOwnProperty jako właściwość

+

JavaScript nie chroni właściwości o nazwie hasOwnProperty, zatem istnieje +możliwość, że obiekt będzie posiadać tak nazwaną właściwość. Konieczne jest użycie +zewnętrznego hasOwnProperty, aby otrzymać poprawne rezultaty.

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Here be dragons'
+};
+
+foo.hasOwnProperty('bar'); // zawsze zwraca false
+
+// Została użyta metoda innego obiektu i wywołana z konkekstem 
+// `this` ustawionym na foo
+({}).hasOwnProperty.call(foo, 'bar'); // true
+

Wnioski

+

Jedyną metodą służącą do sprawdzenia istnienia jakiejś właściwości w konkretnym +obiekcie jest metoda hasOwnProperty. Zaleca się korzystać z hasOwnProperty w +każdej pętli for in. Pozwoli to uniknąć błędów pochodzących +z rozszerzonych natywnych prototypów.

+

Pętla for in

Podobnie jak operator in, pętla for in przeszukuje łańcuch prototypów +podczas iteracji po właściwościach obiektu.

+ +
// Zatrucie Object.prototype
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // wyświetla obie właściwości: bar i moo
+}
+

Ponieważ zmiana zachowania pętli for in nie jest możliwa, niezbędne +jest odfiltrowanie niechcianych właściwości wewnątrz ciała pętli, korzystając +z metody hasOwnProperty z Object.prototype.

+ +

Filtrowania przy użyciu hasOwnProperty

+
// foo z przykładu powyżej
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

To jest jedyna poprawna wersja, której należy używać. Ze względu na użycie +hasOwnProperty zostanie wypisane jedynie moo. Gdy opuścimy hasOwnProperty, +kod będzie podatny na błędy, gdy natywne prototypy (np. Object.prototype) +zostaną rozszerzone.

+

Prototype jest jednym z popularniejszych frameworków, które dokonują +takiego rozszerzenia. Używanie tego frameworku oraz nie stosowanie w pętli for in +metody hasOwnProperty gwarantuje błędy w wykonaniu.

+

Wnioski

+

Zaleca się, aby zawsze używać metody hasOwnProperty. Nigdy nie powinno się dokonywać +żadnych założeń na temat środowiska, w którym kod będzie wykonywany ani tego, czy +natywne prototypy zostały rozszerzone, czy nie.

+

Funkcje

Deklaracje funkcji i wyrażenia funkcyjne

Funcje w języku JavaScript są typami pierwszoklasowymi, co oznacza, że mogą +być przekazywane jak każda inna wartość. Jednym z typowych zastosowań tej cechy +jest przekazywanie anonimowej funkcji jako callback do innej, prawdopodobnie +asynchronicznej funkcji.

+

Deklaracja funkcji

+
function foo() {}
+

Powyższa funkcja zostaje wyniesiona zanim program wystartuje. Dzięki temu +jest dostępna wszędzie w ramach zasięgu, w którym została zadeklarowana, +nawet, jeżeli ta funkcja została wywołana przed faktyczną definicją w kodzie źródłowym.

+
foo(); // Działa ponieważ definicja funkcji została wyniesiona 
+       // na początek zasięgu przed uruchomieniem kodu
+function foo() {}
+

Wyrażenie funkcyjne

+
var foo = function() {};
+

Ten przykład przypisuje nienazwaną i anonimową funkcję do zmiennej foo.

+
foo; // 'undefined'
+foo(); // wyrzuca błąd TypeError
+var foo = function() {};
+

Ze względu na fakt, że deklaracja var wynosi zmienną foo na początek zasięgu +zanim kod faktycznie zostanie uruchomiony, foo będzie zdefiniowane kiedy skrypt +będzie wykonywany.

+

Ale ponieważ przypisania robione są dopiero podczas wykonania, wartość foo będzie +ustawiona na domyślną wartość undefined zanim powyższy kod +zostanie uruchomiony.

+

Nazwane wyrażenia funkcyjne

+

Kolejnym specjalnym przypadkiem jest przypisanie nazwanej funkcji.

+
var foo = function bar() {
+    bar(); // Działa
+}
+bar(); // wyrzuca ReferenceError
+

W zewnętrznym zakresie bar nie będzie dostępna, ponieważ funkcja zostaje +przypisana do foo, jednakże w wewnętrznym zakresie bar będzie dostępna. +Jest to spowodowane tym, jak działa rozwiązywanie nazw +w języku JavaScript. Nazwa funkcji jest zawsze dostępna w lokalnym +zakresie tej funkcji.

+

Jak działa this

JavaScript posiada inną koncepcję odnośnie tego na co wskazuje słowo kluczowe +this, niż większość innych języków programowania. Istnieje dokładnie +pięć różnych sytuacji, w których wartość this jest przypisana w języku JavaScript.

+

Zasięg globalny

+
this;
+

Używanie this w globalnym zasięgu, zwróci po prostu referencję do obiektu global.

+

Wywołanie funkcji

+
foo();
+

Tutaj this również będzie wkazywało na obiekt global

+ +

Wywoływanie metody

+
test.foo(); 
+

W tym przypadku this będzie wskazywało na test.

+

Wywołanie konstruktora

+
new foo(); 
+

Wywołanie funkcji, które jest poprzedzone słowem kluczowym new, zachowuje się +jak konstruktor. Wewnątrz funkcji this będzie +wskazywało na nowo utworzony obiekt.

+

Jawne ustawienie this

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // tablica zostanie zamieniona w to co poniżej
+foo.call(bar, 1, 2, 3); // rezultat a = 1, b = 2, c = 3
+

Używając metod call lub apply z prototypu Function.prototype, wartość this +wewnątrz wołanej funkcji zostanie jawnie ustawiona na pierwszy argument przekazany +podczas wywołania tych metod.

+

Zatem w powyższym przykładzie przypadek Wywoływanie metody nie będzie miał +miejsca i this wewnątrz foo będzie wskazywać na bar.

+ +

Częste pułapki

+

Mimo iż Większość z tych przypadków ma sens, to pierwszy przypadek powinien być +traktorany jako błąd podczas projektowania języka i nigdy nie wykorzystywany +w praktyce.

+
Foo.method = function() {
+    function test() {
+        // wewnątrz tej funkcji this wskazuje na obiekt global
+    }
+    test();
+}
+

Powszechnym błędem jest myślenie, że this wewnątrz test wskazuje na Foo, +podczas gdy w rzeczywistości tak nie jest.

+

Aby uzyskać dostęp do Foo wewnątrz test, niezbędne jest stworzenie wewnątrz +metody lokalnej zmiennej, która będzie wskazywała na Foo.

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // Należy używać that zamiast this wewnątrz tej funkcji
+    }
+    test();
+}
+

that jest zwykłą zmienną, ale jest to powszechnie stosowana konwencja otrzymywania
wartości zewnętrznego this. W połączeniu z domknięciami(closures), +jest to sposób na przekazywanie wartości this wokół.

+

Metody przypisywania

+

Kolejną rzeczą, która nie działa w języku JavaScript, jest nadawanie aliasów +funkcjom, co oznacza przypisanie metody do zmiennej.

+
var test = someObject.methodTest;
+test();
+

Podobnie jak w pierwszym przypadku test zachowuje się jak wywołanie zwykłej +funkcji, a zatem wewnątrz funkcji this już nie będzie wskazywało someObject.

+

Podczas gdy późne wiązanie this może się na początku wydawać złym pomysłem, +to w rzeczywistości jest to rzecz, która sprawia, że +dziedziczenie prototypowe działa.

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

Kiedy metoda method zostanie wywołana na instancji Bar, this będzie +wskazywało właśnie tę instancję.

+

Domknięcia i referencje

Jedną z najpotężniejszych funkcjonalności języka JavaScript są domknięcia. +Oznacza to że zasięg zawsze posiada dostęp do zewnętrznego zasięgu, w którym +został zdefiniowany. Ponieważ zasięg w JavaScript można definiować tylko poprzez +funkcję, wszystkie funkcje domyślnie zachowują się jak domknięcia.

+

Emulowanie prywatnych zmiennych

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

Tutaj Counter zwraca dwa domknięcia: funkcję increment oraz funkcję get. +Obie te funkcje trzymają referencję do zasięgu Counter, a co za tym idzie +zawsze posiadają dostęp do zmiennej count tak, jakby ta zmienna była zdefiniowana +w zasięgu tych funkcji.

+

Dlaczego zmienne prywatne działają?

+

Ponieważ nie ma możliwości wskazania lub przypisania zasięgu w JavaScript, +nie istnieje sposób, aby uzyskać dostęp do zmiennej count z zewnątrz. +Wykorzystanie tych dwóch domknięć jest jedynym sposobem na interakcję z tą zmienną.

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

Powyższy kod nie zmieni wartości zmiennej count wewnątrz zasięgu Counter, +ponieważ foo.hack nie została zadeklarowana wewnątrz tego konkretnego zasięgu. +Zamiast tego funkcja utworzy lub nadpisze globalną zmienną count.

+

Domknięcia wewnątrz pętli

+

Jednym z częstrzych błędów jest wykorzystywanie domknięć wewnątrz pętli, +aby wartość zmiennej po której odbywa się iteracja była kopiowana do +wewnętrznej funkcji.

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);  
+    }, 1000);
+}
+

Powyższy kod nie wypisze numerów od 0 do 9, ale wypisze +dziesięć razy liczbę 10.

+

Anonimowa funkcja trzyma wskaźnik do zmiennej i i podczas uruchomienia +console.log, pętla for już zakończyła działanie i wartość zmiennej i +została ustawiona na 10.

+

Aby otrzymać zamierzony efekt, niezbędne jest skopiowanie wartości +zmiennej i.

+

Unikanie problemu z referencją

+

Aby skopiować wartość zmiennej, po której iterujemy w pętli, należy skorzystać +z anonimowego wrappera.

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);  
+        }, 1000);
+    })(i);
+}
+

Zewnętrzna anonimowa funkcja zostanie wywołana od razu z parametrem i +jako pierwszym argumentem oraz otrzyma kopię wartości zmiennej i jako +zmienną e.

+

Anonimowa funkcja która zostaje przekazana do setTimeout teraz posiada +referencję do zmiennej e, która nie zostanie zmieniona przez pętle for.

+

Istnieje jeszcze jeden sposób na osiągnięcie tego samego efektu. Należy zwrócic +fukcję z anonimowego wrappera, wówczas kod będzie zachowywał się jak ten +wcześniejszy.

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+

Obiekt arguments

Każdy zasięg funkcyjny w języku JavaScript ma dostęp do specjalnej zmiennej arguments. +Ta zmienna trzyma listę wszystkich argumentów przekazanych do funkcji.

+ +

Obiekt arguments nie jest typu Array. Mimo że posiada pewne cechy +semantyki tablic - właściwość length - to w rzeczywistości nie dziedziczy +on z Array.prototype, tylko z Object.

+

Ze względu na to, na obiekcie arguments nie można używać standardowych dla tablic metod, +takich jak push, pop czy slice. Mimo że iteracja przy pomocy +pętli for działa dobrze, to aby skorzystać ze standardowych metod tablicowych +należy skonwertować arguments do prawdziwego obiekt Array.

+

Konwersja do tablicy

+

Poniższy kod zwróci nowy obiekt Array zawierający wszystkie elementy +obiektu arguments.

+
Array.prototype.slice.call(arguments);
+

Jednakże konwersja ta jest wolna i nie jest zalecana w sekcjach, +które mają duży wpływ na wydajność.

+

Przekazywanie argumentów

+

Zalecany sposób przekazywania argumentów z jednej funkcji do następnej +wyglada następująco:

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // do stuff here
+}
+

Kolejną sztuczką jest użycie razem call i apply w celu stworzenia +szybkich i nieograniczonych wrapperów.

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// Stworzenie nieograniczoną wersję metody "method" 
+// która przyjmuje parametry: this, arg1, arg2...argN
+Foo.method = function() {
+
+    // Rezultat: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+};
+

Parametry formalne i indeksy argumentów

+

Obiekt arguments tworzy funkcje getter i setter nie tylko dla swoich +właściwości, ale również dla parametrów formalnych funkcji.

+

W rezultacie zmiana wartości parametru formalnego zmieni również wartość +odpowiadającemu mu wpisowi w obiekcie arguments. Zachodzi to również w drugą stronę.

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2                                                           
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

Mity i prawdy o wydajności

+

Obiekt arguments jest tworzony zawsze, z wyjątkiem dwóch przypadków, gdy +zmienna o takiej nazwie jest zdefiniowana wewnątrz funkcji lub jeden z parametrów +formalnych funkcji ma taką nazwę. Nie ma znaczenia czy obiekt arguments jest +używany czy nie.

+

Zarówno gettery jak i settery są zawsze tworzone, zatem używanie ich nie ma +praktycznie żadnego wpływu na wydajność. Zwłaszcza w rzeczywistym kodzie, który +wykorzystuje coś więcej niż tylko prosty dostęp do właściwości obiektu arguments.

+ +

Jednakże, istnieje jeden przypadek w którym wydajność drastycznie spada w +nowoczesnych silnikach JavaScript. Ten przypadek to wykorzystanie +arguments.callee.

+
function foo() {
+    arguments.callee; // operowanie na obiekcie funkcji
+    arguments.callee.caller; // i obiekcie funkcji wywołującej
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // Normalnie zostałaby wykorzystana metoda inline
+    }
+}
+

W powyższym przykładzie foo nie może zostać wykorzystana metoda inline +ponieważ potrzebne są nie tylko informacje na własny temat ale również +na temat funkcji wywołującej. Takie użycie nie tylko uniemożliwia +inlining i korzyści z niego wynikające, ale też łamie zasady enkapsulacji, +ponieważ ta funkcja jest zależna od kontekstu w jakim została wywołana.

+

Mocno zalecane jest aby nigdy nie korzystać z arguments.callee +i żadnej jej własności.

+ +

Konstruktory

Konstruktory w JavaScript również wyglądają inaczej niż innych językach. Każde +wywołanie funkcji, które jest poprzedone słowem kluczowym new, zachowuje się +jak konstruktor.

+

Wewnątrz konstruktora - wywoływanej fukcji - wartość this wskazuje na +nowo utworzony obiekt Object. Prototyp prototype tego +nowego obiektu będzie wskazywał na prototyp prototype obiektu fukcji, +która została wywołana jako konstruktor.

+

Jeżeli wywołana funkcja nie posiada jawnej deklaracji return, wówczas +fukcja domyślnie zwraca wartość this - nowy obiekt.

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

Powyżej wywołana została funkcja Foo jako konstruktor oraz ustawia +nowo utworzonemu obiektowi właściwość prototype na Foo.prototype.

+

W tym przypadku jawna deklaracja return w funkcji zwraca wartość +ustawioną w deklaracji, ale tylko jeżeli zwracaną wartością jest +obiekt Object.

+
function Bar() {
+    return 2;
+}
+new Bar(); // nowy obiekt
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // zwrócony obiekt
+

Jeżeli słowo kluczowe new zostanie pominięte, funkcja nie zwróci nowego +obiektu.

+
function Foo() {
+    this.bla = 1; // zostanie ustawiona w obiekcie global
+}
+Foo(); // undefined
+

Mimo że powyższy kod może zadziałać w pewnych przypadkach, w związku +z działaniem this w języku JavaScript, to jako +wartość this zostanie wykorzystany obiekt global.

+

Fabryki

+

Aby móc ominąć słowo kluczowe new, konstruktor musi jawnie zwracać wartość.

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

Oba wywołania Bar zwrócą tę samą rzecz, nowo utworzony obiekt, który posiada +właściwość nazwaną method i dla którego Bar jest Domknięciem.

+

Należy również pamiętać, że wywołanie new Bar() nie ma wpływu na +prototyp zwróconego obiektu (prototypem będzie object.prototype a nie Bar.prototype). +Kiedy prototyp zostanie przypisany do nowo utworzonego obiektu, Bar nidgy +nie zwróci tego nowego obiektu Bar, tylko literał obiektu, który jest po +słowie kluczowym return.

+

W powyższym przykładzie nie ma żadnej różnicy w działaniu pomiędzy użyciem +i nieużyciem słowa kluczowego new.

+

Tworzenie nowych obiektów korzystając z fabryk

+

Często zaleca się nie korzystać z operatora new, ponieważ zapominanie +o jego stosowaniu może prowadzić do błędów.

+

W celu stworzenia nowego obiektu, powinno się używać fabryki i konstruować +nowy obiekt wewnątrz tej fabryki.

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

Mimo że powyższy kod jest odporny na brak słowa kluczowego new i ułatwia +korzystanie ze zmiennych prywatnych, to posiada +pewne wady.

+
    +
  1. Zużywa więcej pamięci, ponieważ tworzony obiekt nie współdzieli metod +poprzez prototyp.
  2. +
  3. Aby móc dziedziczyć fabryka musi skopiować wszystkie metody z dziedziczonego +obiektu lub przypisać ten obiekt, z którego się dziedziczy, jako prototyp +do nowo utworzonego obiektu.
  4. +
  5. Porzucenie łańcucha prototypów tylko ze względu na opuszczone słowo kluczowe +new jest sprzeczne z duchem języka.
  6. +
+

Wnioski

+

Pominięcie słowa kluczowego new może prowadzić do błędów, ale na pewno nie +powinno to być powodem odrzucenia używania prototypów w ogóle. Sprowadza się to +do wyboru rozwiązania, które bardziej pasuje do potrzeb aplikacji. Szczególnie +ważne jest, aby wybrać określony styl tworzenia obiektów i trzymać się go.

+

Zasięg zmiennych i przestrzenie nazw

Mimo że JavaScript radzi sobie dobrze ze składnią opisującą dwa pasujące +nawiasy klamrowe jako blok, to jednak nie wspiera zasięgu blokowego. +Jedynym zasięgiem jaki istnieje w JavaScript jest zasięg funkcyjny.

+
function test() { // definiuje zasięg (scope)
+    for(var i = 0; i < 10; i++) { // nie definiuje zasięgu (scope)
+        // count
+    }
+    console.log(i); // 10
+}
+ +

W JavaScripcie nie ma również przestrzeni nazw, co oznacza, że wszystko jest +definiowane w jednej globalnie współdzielonej przestrzeni nazw.

+

Z każdym odwołaniem do zmiennej, JavaScript przeszukuje w górę wszystkie zasięgi +dopóki nie znajdzie tej zmiennej. W przypadku, gdy przeszukiwanie dotrze do globalnego +zasięgu i nadal nie znajdzie żądanej nazwy, to wyrzuca błąd ReferenceError.

+

Zmora globalnych zmiennych

+
// script A
+foo = '42';
+
+// script B
+var foo = '42'
+

Powyższe dwa skrypty nie dają tego samego efektu. Skrypt A definiuje zmienną +nazwaną foo w globalnym zasięgu, natomiast skrypt B definiuje foo +w aktualnym zasięgu.

+

Jeszcze raz, to wcale nie daje tego samego efektu. Nie użycie var może mieć +poważne konsekwencje.

+
// globalny zasięg
+var foo = 42;
+function test() {
+    // lokalny zasięg
+    foo = 21;
+}
+test();
+foo; // 21
+

Pominięcie słowa var w deklaracji wewnątrz funkcji test nadpisze wartość +zmiennej globalnej foo. Mimo że nie wygląda to na początku na duży problem, +posiadanie wielu tysięcy linii kodu w JavaScript i nie korzystanie z var +wprowadzi straszne i trudne do wyśledzenia błędy.

+
// globalny zasięg 
+var items = [/* jakaś lista */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // scope of subLoop
+    for(i = 0; i < 10; i++) { // brakuje słowa var w deklaracji
+        // do amazing stuff!
+    }
+}
+

Zewnętrzna pętla zakończy działanie po pierwszym wywołaniu subLoop, ponieważ +subLoop nadpisuje wartość globalnej zmiennej i. Użycie var w drugiej pętli +for pozwoliłoby łatwo uniknąć problemu. Słowo kluczowe var nie powinno być +nigdy pominięte w deklaracji, chyba że pożądanym skutkiem jest wpłynięcie na +zewnętrzny zasięg.

+

Lokalne zmienne

+

Jedynym źródłem zmiennych lokalnych w JavaScripcie są parametry funkcji +oraz zmienne zadeklarowane poprzez deklaracje var wewnątrz funkcji.

+
// globalny zasięg
+var foo = 1;
+var bar = 2;
+var i = 2;
+
+function test(i) {
+    // lokalny zasięg fukcji test
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

Zmienne foo oraz i są lokalnymi zmiennymi wewnątrz zasiegu funkcji test, +natomiast przypisanie wartości do bar nadpisze zmienną globalną o tej samej nazwie.

+

"Hoisting" - wywindowanie, podnoszenie

+

JavaScript winduje deklaracje. Oznacza to, że zarówno deklaracja ze słowem +kluczowym var jak i deklaracje funkcji function zostaną przeniesione na +początek otaczającego zasięgu.

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

Powyższy kod zostanie przekształcony przed rozpoczęciem wykonania. JavaScript +przeniesie deklarację zmiennej var oraz deklarację funkcji function na szczyt +najbliższego zasięgu.

+
// deklaracje var zostaną przeniesione tutaj
+var bar, someValue; // ustawione domyślnie na 'undefined'
+
+// deklaracje funkcji zostaną również przeniesione na górę
+function test(data) {
+    var goo, i, e; // brak blokowego zasięgu spowoduje przeniesienie tutaj
+    if (false) {
+        goo = 1;
+
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // powoduje błąd TypeError ponieważ bar jest nadal 'undefined'
+someValue = 42; // przypisania nie zostają zmienione przez 'hoisting'
+bar = function() {};
+
+test();
+

Brak blokowego zasięgu nie tylko przeniesie deklaracje var poza ciało pętli, +ale również spowoduje, że niektóre porównania if staną się nieintuicyjne.

+

W oryginalnym kodzie instrukcja warunkowa if zdaje się modyfikować zmienną +globalną goo, podczas gdy faktycznie modyfikuje ona zmienną lokalną - po tym +jak zostało zastosowane windowanie (hoisting).

+

Bez wiedzy na temat podnoszenia (hoistingu), poniższy kod może sprawiać wrażenie, +że zobaczymy błąd ReferenceError.

+
// sprawdz czy SomeImportantThing zostało zainicjalizowane
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

Oczywiście powyższy kod działa ze względu na fakt, że deklaracja var zostanie +przeniesiona na początek globalnego zasięgu.

+
var SomeImportantThing;
+
+// inny kod który może ale nie musi zainicjalizować SomeImportantThing
+
+// upewnienie sie, że SomeImportantThing zostało zainicjalizowane
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

Kolejność rozwiązywania nazw

+

Wszystkie zasięgi w JavaScripcie, włączając globalny zasięg, posiadają +zdefiniowaną wewnątrz specjalną nazwę this, która wskazuje +na aktualny obiekt.

+

Zasięg funkcyjny posiada również zdefiniowaną wewnętrznie nazwę +arguments, która zawiera listę argumentów przekazaną do +funkcji.

+

Na przykład, kiedy próbujemy odczytać zmienną foo wewnątrz zasięgu funkcji, +JavaScript będzie szukać nazwy w określonej kolejności:

+
    +
  1. Jeżeli wewnątrz aktualnego zasięgu znajduje się deklaracja var foo skorzystaj z niej.
  2. +
  3. Jeżeli jeden z parametrów fukcji został nazwany foo użyj go.
  4. +
  5. Jeżeli fukcja została nazwana foo skorzystaj z tego.
  6. +
  7. Przejdz do zewnętrznego zasięgu i przejdz do kroku #1.
  8. +
+ +

Przestrzenie nazw

+

Powszechnym problemem posiadania tylko jednej globalnej przestrzeni nazw jest +prawdopodobieństwo wystąpienia kolizji nazw. W JavaScripcie, można łatwo uniknąć +tego problemu korzystając z anonimowych wrapperów.

+
(function() {
+    // autonomiczna "przestrzeń nazw"
+
+    window.foo = function() {
+        // wyeksponowane domkniecie (closure)
+    };
+
+})(); // natychmiastowe wykonanie funkcji
+

Anonimowe funkcje są rozpoznane jako wyrażenia, więc +aby mogły zostać wywołane muszą zostać zewaluowane.

+
( // zewaluowanie funkcji znajdującej się wewnątrz nawiasów
+function() {}
+) // zwrócenie obiektu funkcji
+() // wywołanie rezultatu ewaluacji
+

Istnieją inne sposoby aby zewaluować i wykonać wyrażenie funkcyjne. Mimo że +mają inną składnię, zachowują się dokładnie tak samo.

+
// Trzy inne sposoby
+!function(){}();
++function(){}();
+(function(){}());
+

Wnioski

+

Zaleca się, aby zawsze używać anonimowych wrapperów do hermetyzacji kodu wewnątrz +jego własnej przestrzeni nazw. To nie tylko chroni kod przed kolizją nazw, ale +również wprowadza lepszą modularyzację programów.

+

Ponadto, stosowanie zmiennych globalnych jest uznawane za złą praktykę. +Wykorzystanie zmiennych globalnych wskazuje na źle napisany kod, który +jest podatny na błędy i trudny do utrzymania.

+

Tablice

Iterowanie po tablicach oraz właściwościach tablic

Mimo że tablice w JavaScript są obiektami, nie ma dobrych powodów aby używać +pętli for in do iteracji po nich. W rzeczywstości istnieje +wiele dobrych powodów przeciwko wykorzystaniu for in na tablicach.

+ +

Ponieważ pętla for in wylicza wszystkie właściwości, które są wewnątrz +łańcucha prototypów i jedynym sposobem aby wykluczyć te właściwości jest użycie +hasOwnProperty, ale wówczas pętla staje się +dwadzieście razy wolniejsza od normalnej pętli for.

+

Iteracja

+

W celu osiągnięcia najlepszej wydajności podczas iteracji po tablicach należy +użyć klasycznej pętli for.

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

W powyższym przykładzie jest jeszcze jeden dodatkowy haczyk. Jest to zbuforowanie +długości tablicy poprzez l = list.length.

+

Mimo że właściwość length jest zdefiniowana wewnątrz tablicy, istnieje nadal +dodatkowy koszt na wyszukiwanie tej właściwości przy każdej iteracji w pętli. +Chociaż najnowsze silniki JavaScript mogą zastosować w tym +przypadku optymalizację. Nie ma jednak możliwość ustalenia czy kod będzie wykonywany w jednym +z tych nowych silników, czy też nie.

+

W rzeczywistości pominięcie buforowania długości tablicy może spowodować, że pętla +będzie tylko w połowie tak szybka jak ta z buforowaniem długości.

+

Właściwość length

+

Mimo, że getter właściwości length zwraca po prostu liczbę elementów, które są +zawarte w tablicy, to setter może być użyty do skracania tablicy.

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo; // [1, 2, 3]
+

Przypisanie mniejszej długości spowoduje skrócenie tablicy, ale zwiększenie wartości +length nie ma żadnego wpływu na tablicę.

+

Wnioski

+

Aby uzyskać najlepszą wydajność zaleca się, aby zawsze używać zwykłej pętli for +i zbuforowanie właściwości length. Korzystanie z pętli for in na tablicy jest +oznaką źle napisanego kodu, który jest podatny na błędy i ma słabą wydajność.

+

Konstruktor Array

Zaleca się zawsze korzystać z literału tablicy - notacja [] - podczas tworzenia +nowych tablic, ponieważ konstruktor Array niejednoznacznie interpretuje +przekazane do niego parametry.

+
[1, 2, 3]; // Rezultat: [1, 2, 3]
+new Array(1, 2, 3); // Rezultat: [1, 2, 3]
+
+[3]; // Rezultat: [3]
+new Array(3); // Rezultat: []
+new Array('3') // Rezultat: ['3']
+

W przypadku gdy tylko jeden argument zostanie przekazany do kostruktora Array i +ten argument jest typu Number, konstruktor zwróci nową dziwną tablicę +z ustawioną właściwością length na wartość przekazaną jako argument. Należy +zauważyć, że tylko właściwość length zostanie ustawiona w ten sposób. +Rzeczywiste indeksy w tej tablicy nie zostaną zainicjalizowane.

+
var arr = new Array(3);
+arr[1]; // undefined
+1 in arr; // zwraca false, indeks nie został ustawiony
+

Możliwość ustalenia z góry długości tablicy jest użyteczna tylko w kilku +przypadkach, jak np. powtarzanie ciągu znaków, w którym unika się stosowania +pętli for.

+
// count - ilosc powtorzen
+// stringToRepeat - ciąg znaków do powtórzenia 
+new Array(count + 1).join(stringToRepeat); 
+

Wnioski

+

W miarę możliwości należy unikać używania konstruktora Array. Literały są +zdecydowanie lepszym rozwiązaniem. Są krótsze i mają bardziej precyzyjną składnię. +Zwiększają również czytelność kodu.

+

Typy

Równość i porównania

JavaScript posiada dwa różne sposoby równościowego porównywania obiektów.

+

Operator równości

+

Operator równości składa się z dwóch znaków "równa się": ==

+

JavaScript jest słabo typowanym językiem. Oznacza to, że operator równości +konwertuje typy (dokonuje koercji), aby wykonać porównanie.

+
""           ==   "0"           // false
+0            ==   ""            // true
+0            ==   "0"           // true
+false        ==   "false"       // false
+false        ==   "0"           // true
+false        ==   undefined     // false
+false        ==   null          // false
+null         ==   undefined     // true
+" \t\r\n"    ==   0             // true
+

Powyższa tabela przedstawia wyniki koercji typów. Nieprzewidywalne wyniki +porównania są głównym powodem, że stosowanie == jest powszechnie uważane za złą +praktykę. Skomplikowane reguły konwersji są powodem trudnych do wyśledzenia błędów.

+

Ponadto koercja ma również wpływ na wydajność, Na przykład gdy typ String musi zostać +przekształcony na typ Number przed porównaniem z drugą liczbą.

+

Operator ścisłej równości

+

Operator ścisłej równości składa się z trzech znaków "równa się": ===

+

Działa on dokładnie tak jak normalny operator równości, z jednym wyjątkiem - nie +dokonuje koercji typów przed porównaniem.

+
""           ===   "0"           // false
+0            ===   ""            // false
+0            ===   "0"           // false
+false        ===   "false"       // false
+false        ===   "0"           // false
+false        ===   undefined     // false
+false        ===   null          // false
+null         ===   undefined     // false
+" \t\r\n"    ===   0             // false
+

Powyższe rezultaty są o wiele bardziej przejrzyste. Powoduje to "ustatycznienie" +języka do pewnego stopnia oraz pozwala na wprowadzenie optymalizacji porównań +obiektów o różnych typach.

+

Porównywanie obiektów

+

Mimo że oba operatory == i === nazywane są operatorami równościowymi, +to zachowują się różnie, gdy jednym z operandów jest obiekt typu Object.

+
{} === {};                   // false
+new String('foo') === 'foo'; // false
+new Number(10) === 10;       // false
+var foo = {};
+foo === foo;                 // true
+

Oba operatory porównują tożsamość a nie równość, czyli będą porównywać czy +jeden i drugi operand jest tą samą instancją obiektu (podobnie jak operator +is w Pythonie i porównanie wskaźników w C).

+

Wnioski

+

Zaleca się, aby używać tylko operatora ścisłej równości. W sytuacjach gdy +potrzebna jest koercja (porównanie obiektów różnych typów), konwersja powinna +być dokonana jawnie, a nie pozostawiona trudnym regułom koercji +obowiązującym w języku.

+

Operator typeof

Operator typeof (razem z operatorem instanceof) jest +prawdopodobnie najwiekszą wadą konstrukcji języka JavaScript. Posiada on praktycznie
same wady.

+

Mimo że instanceof ma swoje wady to nadal ma ograniczone zastosowanie w praktyce, +natomiast typeof ma tylko jeden praktyczny przypadek użycia, który na dodatek +nie jest związany z sprawdzaniem typu obiektu.

+ +

Tablica typów JavaScript

+
Wartość             Klasa      Typ
+-------------------------------------
+"foo"               String     string
+new String("foo")   String     object
+1.2                 Number     number
+new Number(1.2)     Number     object
+true                Boolean    boolean
+new Boolean(true)   Boolean    object
+new Date()          Date       object
+new Error()         Error      object
+[1,2,3]             Array      object
+new Array(1, 2, 3)  Array      object
+new Function("")    Function   function
+/abc/g              RegExp     object (function w Nitro i V8)
+new RegExp("meow")  RegExp     object (function w Nitro i V8)
+{}                  Object     object
+new Object()        Object     object
+

W powyższej tabeli Typ odnosi się do wartości zwracanej przez operator typeof. +Wyraźnie widać, że zwracane wartości w ogóle nie są spójne.

+

Klasa odnosi sie do wartości wewnętrznej właściwości [[Class]] obiektu.

+ +

W celu uzyskania wartości właściwości [[Class]] trzeba skorzystać z metody +toString z Object.prototype.

+

Klasa obiektu

+

Specyfikacja zawiera dokładnie jeden sposób dostepu do wartości [[Class]], +wykorzystując Object.prototype.toString.

+
function is(type, obj) {
+    var clas = Object.prototype.toString.call(obj).slice(8, -1);
+    return obj !== undefined && obj !== null && clas === type;
+}
+
+is('String', 'test'); // true
+is('String', new String('test')); // true
+

Powyższy przykład wywołuje Object.prototype.toString z wartością +this ustawioną na obiekt, dla której wartość właściwości +[[Class]] ma zostać odczytana.

+ +

Testowanie niezdefiniowania zmiennej

+
typeof foo !== 'undefined'
+

Powyższy kod sprawdza czy foo została faktycznie zadeklarowana czy też nie. +Próba odwołania się do zmiennej spowodowała by wyrzucenie błędu ReferenceError. +Jest to jedyne praktyczne wykorzystanie operatora typeof.

+

Wnioski

+

W celu sprawdzenia typu obiektu zalecane jest skorzystanie z +Object.prototype.toString, ponieważ jest to jedyny wiarygodny sposób. Jak +pokazano w powyższej tabeli typów, niektóre wartości zwracane przez typeof nie +są zdefiniowane w specyfikacji, co za tym idzie mogą się różnić w różnych +implementacjach.

+

O ile nie operator typeof nie jest użyty do sprawdzania czy zmienna została +zdefiniowana, powinien być unikany jeśli to tylko możliwe.

+

Operator instanceof

Operator instanceof porównuje konstruktory obiektów przekazanych jako operendy. +Jest on użyteczny jedynie do porównywania obiektów utworzonych klas. Stosowanie +go na wbudowanych typach jest praktycznie tak samo bezużyteczne, jak operatora +typeof.

+

Porównywanie obiektów utworzonych klas

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // true
+new Bar() instanceof Foo; // true
+
+// poniżej kod który przypisuje do Bar.prototype obiekt funkcji Foo
+// a nie faktyczną instancję Foo
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // false
+

Stosowanie instanceof na natywnych typach

+
new String('foo') instanceof String; // true
+new String('foo') instanceof Object; // true
+
+'foo' instanceof String; // false
+'foo' instanceof Object; // false
+

Jedną ważną rzeczą, którą należy zauważyć jest to, że instanceof nie zadziała +na obiektach, które pochodzą z różnych kontekstów JavaScript (np. z różnych +dokumentów wewnątrz przeglądarki), ponieważ ich konstruktory nie będą tymi +samymi obiektami.

+

Wnioski

+

Operator instanceof powinien być używany wyłącznie podczas korzystania z obiektów +klas utworzonych, które były zdefiniowane w tym samym kontekscie JavaScriptowym. +Podobnie jak operator typeof, należy unikać korzystania +z tego operatora w innych sytuacjach.

+

Rzutowanie typów

JavaScript jest językiem słabo typowanym. Co za tym idzie, będzie stosować koercję +typów gdziekolwiek jest to możliwe.

+
// te zwracają true
+new Number(10) == 10; // Number.toString() zostanie przekształcone
+                      // z powrotem do liczby
+
+10 == '10';           // stringi zostaną przekształcone do typu Number
+10 == '+10 ';         // kolejne wariacje
+10 == '010';          // i następne
+isNaN(null) == false; // null zostanie przekształcony do 0
+                      // który oczywiście nie jest NaN
+
+// poniższe zwracają false
+10 == 010;
+10 == '-10';
+ +

Aby uniknąć powyższych problemów, należy koniecznie korzystać ze +ściełego operatora równości. Mimo, że pozwala to uniknąć wiele +typowych problemów to nadal istnieje wiele innych, które powstają na bazie słabego +typowania języka JavaScript.

+

Konstruktory typów wbudowanych

+

Konstruktory typów wbudowanych, takich jak Number lub String, zachowują się +inaczej kiedy są poprzedzone słowem kluczowym new a inaczej kiedy nie są.

+
new Number(10) === 10;     // False, Object i Number
+Number(10) === 10;         // True, Number i Number
+new Number(10) + 0 === 10; // True, ponieważ dokonano jawnej konwersji
+

Korzystanie z wbudowanych typów jak Number jako konstruktora tworzy nowy obiekt +typu Number, natomiast opuszczenie słowa kluczowego new powoduje, że funkcja +Number zachowuje się jak konwerter.

+

Ponadto, użycie literałów lub wartości nieobiektowych zaowocuje jeszcze większą +ilością rzutowań (koercją) typów.

+

Najlepszym rozwiązaniem jest jawne rzutowanie do jednego z trzech typów.

+

Rzutowanie do typu String

+
'' + 10 === '10'; // true
+

Konkatenacja pustego stringu i wartości powoduje rzutowanie do typu String.

+

Rzutowanie do typu Number

+
+'10' === 10; // true
+

Zastosowanie unarnego operatora + spowoduje rzutowanie do typu Number.

+

Rzutowanie do typu Boolean

+

Używając dwukrotnie operatora negacji, dowolna wartość może zostać zrzutowana +do typu Boolean

+
!!'foo';   // true
+!!'';      // false
+!!'0';     // true
+!!'1';     // true
+!!'-1'     // true
+!!{};      // true
+!!true;    // true
+

Jądro

Dlaczego nie należy używać eval?

Funkcja eval uruchomi podany string jako kod JavaScript w lokalnym zasięgu (scopie).

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

Niestaty, eval zostanie wykonana w lokalnym zasięgu tylko wtedy, gdy zostanie wywołana +bezpośrednio i nazwa wywoływanej funkcji równa sie eval.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

Należy unikać stosowania eval o ile to tylko możliwe. W 99.9% przypadków można +osiągnąć ten sam efekt nie używając eval.

+

eval w przebraniu

+

Funkcje wykonywane po upływie czasu setTimeout i setInterval +mogą przyjąć string jako pierwszy argument. String ten zawsze będzie wykonywany +w globalnym zasięgu, ponieważ funkcja eval jest w tym wypadku wywoływana pośrednio.

+

Problemy z bezpieczeństwem

+

Funkcja eval jest również problematyczna od strony bezpieczeństwa, ponieważ +wykonuje każdy kod, który zostanie do niej przekazany i nigdy nie należy +jej używać na stringach nieznanego lub niezaufanego pochodzenia.

+

Wnioski

+

Funkcja eval nie powinna być w ogóle używana. Każdy kod, który jej używa +powinien zostać sprawdzony pod względem działania, wydajności i bezpieczeństwa. +W przypadku gdy użycie eval jest niezbędne do działania, wówczas taki kod +należy ponownie przemyśleć i ulepszyć aby nie wymagał użycia eval.

+

undefined i null

JavaScript ma dwie różne wartości dla pustych wartości, bardziej użyteczną +z tych dwóch jest undefined.

+

Wartość undefined

+

undefined jest typem z dokładnie jedną wartością: undefined.

+

Język również definiuje globalną zmienną, która ma wartość undefined - zmienna +ta jest nazwana undefined. Jednakże jest to zmienna a nie stała, czy słowo +kluczowe. Oznacza to, że możliwe jest nadpisanie wartości tej zmiennej.

+ +

Kilka przykładów kiedy wartość undefined jest zwracana:

+
    +
  • dostęp do (niemodyfikowalnej) zmiennej globalnej undefined,
  • +
  • wyjście z funkcji, która nie ma deklaracji return,
  • +
  • deklaracja return, która nic jawnie nie zwraca,
  • +
  • poszukiwanie nieistniejącej właściwości,
  • +
  • parametr funkcji, który nie został jawnie przekazany podczas wywołania funkcji,
  • +
  • wszystko czemu została przypisana wartość undefined.
  • +
+

Obsługa przypadku zmiany wartości undefined

+

Ponieważ globalna zmienna undefined zawiera tylko kopię prawdziwej wartości typu +undefined, przypisanie nowej wartości do tej zmiennej nie zmienia wartości +typu undefined.

+

Jednak aby porównać coś z wartością undefined, trzeba odczytać wartość undefined.

+

Aby uchronić swój kod przed możliwym nadpisaniem zmiennej undefined, korzysta +się z powszechnej techniki dodania dodatkowego parametru do +anonimowego wrappera, do którego nie zostanie przekazany +argument.

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // undefined o lokalnym zasięgu znowu 
+    // odnosi się do poprawnej wartości
+
+})('Hello World', 42);
+

Kolejnym sposobem na osiągnięcie tego samego efektu jest użycie deklaracji zmiennej +wewnątrz wrappera.

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

Jedyną różnicą pomiędzy tymi sposobami są dodatkowe 4 bajty przeznaczone na słowo +kluczowe var i spację po nim.

+

Zastosowanie null

+

Podczas gdy undefined w kontekście języka jest używany jak null w sensie +tradycyjnych języków, null w JavaScript (jako literał i jako typ) jest po +prostu kolejnym typem danych.

+

Jest wykorzystywany we wnętrzu JavaScript (np. deklaracji końca łańcucha prototypów +poprzez ustawienie Foo.prototype = null), ale prawie w każdym przypadku można go +zastąpić przez undefined.

+

Automatyczne wstawianie średnika

Mimo że JavaScript ma składnię podobną do języka C, to nie wymusza stosowania +średników w kodzie źródłowym. Istnieje możliwość ich pominięcia.

+

JavaScript nie jest językiem bez średników, tak na prawdę potrzebuje +średników aby zinterpretować kod źródłowy. Jednakże parser JavaScript +automatycznie wstawia średniki o ile napotka błąd parsowania związany z +brakiem średnika.

+
var foo = function() {
+} // błąd parsowania, oczekiwany był w tym miejscu średnik
+test()
+

Parser dodaje średnik, i próbuje jeszcze raz sparsować skrypt.

+
var foo = function() {
+}; // bez błędu parser kontynuuje
+test()
+

Automatyczne wstawianie średników jest uważane za jeden z największych błędów +konstrukcji języka, ponieważ może ono zmienić zachowanie kodu.

+

Jak działa wstawianie

+

Kod poniżej nie ma żadnych średników, więc parser zdecyduje, w których miejscach +je wstawi.

+
(function(window, undefined) {
+    function test(options) {
+        log('testing!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+
+})(window)
+

Poniżej znajduje się rezultat "zgadywania" parsera.

+
(function(window, undefined) {
+    function test(options) {
+
+        // Nie wstaniony średnik, linie zostały połączone
+        log('testing!')(options.list || []).forEach(function(i) {
+
+        }); // <- wstawiony
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        ); // <- wstawiony
+
+        return; // <- wstawiony, psując deklarację return
+        { // potraktowane jako definicja bloku
+
+            // etykieta oraz pojedyncze wyrażenie
+            foo: function() {} 
+        }; // <- wstawiony
+    }
+    window.test = test; // <- wstawiony
+
+// Kolejna połączona linia
+})(window)(function(window) {
+    window.someLibrary = {}; // <- wstawiony
+
+})(window); //<- wstawiony
+ +

Parser drastycznie zmienił działanie powyższego kodu. W niektórych przypadkach +zmienił go źle.

+

Nawiasy

+

W przypadku, gdy w następnej linii znajduje się nawias, parser nie wstawi +średnika.

+
log('testing!')
+(options.list || []).forEach(function(i) {})
+

Kod ten zostanie zmieniony w poniższą linię.

+
log('testing!')(options.list || []).forEach(function(i) {})
+

Jest bardzo prawdopodobne, że log nie zwróci fukcji. Co za tym idzie +powyższy kod wyrzuci błąd TypeError oznajmując, że undefined is not a +function - undefined nie jest funkcją.

+

Wnioski

+

Zaleca się, aby nigdy nie pomijać średników, pozostawiać nawias otwierający +w tej samej linii co odpowiadająca mu definicja i nigdy nie pozostawiać deklaracji +if / else bez nawiasów - nawet, jeżeli są jednolinijkowe. Wszystkie te uwagi nie +tylko pomagają poprawić spójność kodu, ale też zapobiegają zmianie działania +kodu przez parser JavaScript.

+

Inne

setTimeout i setInterval

Ponieważ JavaScript jest asynchroniczny, istnieje możliwość zaplanowania wykonania +funkcji przy użyciu funkcji setTimeout i setInterval.

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // zwraca liczbę typu Number > 0
+

Powyższe wywołanie setTimeout zwraca ID budzika i planuje wywołanie foo za +około tysiąc milisekund. foo zostanie wykonana dokładnie jeden raz.

+

Nie ma pewności, że kod zaplanowany do wykonania wykona się dokładnie po +upłynięciu zadanego czasu podanego jako parametr do setTimeout, ponieważ zależy +to od dokładności zegara w silniku JavaScript, który wykonuje kod oraz od tego, +że inny kawałek kodu może zablokować wątek, ponieważ JavaScript jest tylko +jednowątkowy.

+

Funkcja, która została przekazana jako pierwszy parametr zostanie wykonana w +globalnym zasięgu, co oznacza, że this wewnątrz tej funkcji +będzie wskazywać na obiekt global.

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // this wskazuje na obiekt global
+        console.log(this.value); // wypisze undefined
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

Kolejkowanie wywołań z setInterval

+

Podczas gdy setTimeout wywołuje podaną funkcję tylko raz, setInterval - +jak wskazuje nazwa - będzie wykonywać funkcję w odstępach czasowych co X +milisekund. Jednakże korzystanie z tej funkcji jest odradzane.

+

Kiedy wykonywany kod zablokuje możliwość uruchomienia zaplanowanej funkcji, +setInterval będzie próbować uruchamiać daną funkcję, co będzie powodować +kolejkowanie wykonania tej samej funkcji kilkukrotnie. Może się to zdażyć +szczególnie przy krótkim interwale.

+
function foo(){
+    // coś co blokuje wykonanie na 1 sekundę 
+}
+setInterval(foo, 1000);
+

W powyższym kodzie kod foo zostanie wywołany tylko raz i zablokuje wywołanie na +jedną sekundę.

+

Podczas, gdy funkcja foo blokuje wykonanie, setInterval będzie planować kolejne +wywołania foo. W momencie, gdy pierwsze wywołanie foo się zakończy, +w kolejce do wywołania będzie już czekało kolejne dziesięć wywołań tej funkcji.

+

Radzenie sobie z możliwymi blokadami

+

Najprostszą, jak również najbardziej kontrolowaną sytuacją, jest użycie setTimeout +wewnątrz wywoływanej funkcji.

+
function foo(){
+    // coś co blokuje wykonanie na 1 sekundę
+    setTimeout(foo, 1000);
+}
+foo();
+

Powyższy kod nie tylko hermetyzuje wywołanie setTimeout, ale też zapobiega +kolejkowaniu wywołań funkcji i daje dodatkową kontrolę. W tym przypadku funkcja +foo może zdecydować czy powinna się wywołać ponownie, czy też nie.

+

Ręczne usuwanie budzików

+

Usuwanie budzików i interwałów dokonywane jest przez przekazanie odpowiedniego ID +do clearTimeout lub clearInterval, w zależności z jakiej funkcji zostało +zwrócone ID.

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

Usuwanie wszystkich budzików

+

Ponieważ nie istnieje wbudowana metoda usuwania wszystkich budzików i/lub +interwałów, do osiągnięcia tego efektu konieczne jest użycie metody 'brute force'.

+
// usunięcie "wszystkich" budzików 
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

Nadal mogą istnieć jakieś budziki, na które powyższy kawałek kodu nie zadziała. +Ponieważ ID było z innego przedziału, zamiast korzystania z metody brute force, +zaleca się śledzić wszystkie numery ID budzików, aby można je było usunąć.

+

Ukryte wykorzystanie eval

+

Do setTimeout i setInterval można również przekazać string jako pierwszy +parametr zamiast obiektu funkcji, jednakże nigdy nie należy korzystać z tej +możliwości, ponieważ wewnętrznie setTimeout i setInterval wykorzystują eval.

+ +
function foo() {
+    // zostanie wykonane 
+}
+
+function bar() {
+    function foo() {
+        // nigdy nie zostanie wywołane
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

Ponieważ eval nie zostało wywołane w tym przypadku wprost, to +string przekazany do setTimeout zostanie uruchomiony w zasięgu globalnym. +Co za tym idzie, lokalna zmienna foo z zasięgu bar nie zostanie użyta.

+

Kolejnym zaleceniem jest niestosowanie stringów do przekazywania argumentów +do funkcji, która ma zostać wywołana przez budzik.

+
function foo(a, b, c) {}
+
+// NIGDY nie należy tak robić 
+setTimeout('foo(1,2, 3)', 1000)
+
+// zamiast tego należy skorzystać z anonimowej funkcji
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

Wnioski

+

Nigdy nie należy przekazywać stringu jako parametru do setTimeout lub +setInterval. Jest to wyraźną oznaką bardzo złego kodu. Jeżeli potrzebne jest +przekazanie argumentów do funkcji, należy skorzystać z anonimowej funkcji i +wewnątrz niej dokonać przekazania argumentów.

+

Ponadto, należy unikać korzystania z setInterval, ponieważ planista może +zablokować wykonanie JavaScriptu.

+
\ No newline at end of file diff --git a/ru/index.html b/ru/index.html new file mode 100644 index 0000000..480e9a3 --- /dev/null +++ b/ru/index.html @@ -0,0 +1,1062 @@ +JavaScript Гарден

Вступление

Лицензия

JavaScript Гарден распространяется под лицензией MIT и располагается на GitHub. Если вы найдёте ошибку или опечатку, пожалуйста сообщите нам о ней или запросите права на загрузку в репозиторий. Кроме того, вы можете найти нас в комнате JavaScript среди чатов Stack Overflow.

+

Объекты

Объекты и их свойства

В JavaScript всё ведет себя, как объект, лишь за двумя исключениями — null и undefined.

+
false.toString(); // 'false'
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

Неверно считать, что числовые литералы нельзя использовать в качестве объектов — это распространённое заблуждение. Его причиной является упущение в парсере JavaScript, благодаря которому применение точечной нотации к числу воспринимается им как литерал числа с плавающей точкой.

+
2.toString(); // вызывает SyntaxError
+

Есть несколько способов обойти этот недостаток и любой из них можно использовать для того, чтобы работать с числами, как с объектами:

+
2..toString(); // вторая точка распознаётся корректно
+2 .toString(); // обратите внимание на пробел перед точкой
+(2).toString(); // двойка вычисляется заранее
+

Объекты как тип данных

+

Объекты в JavaScript могут использоваться как хеш-таблицы: подавляющей частью состоят из именованных свойств (ключей), привязанных к значениям.

+

Используя объектный литерал — нотацию {} — можно создать простой объект. Новый объект наследуется от Object.prototype и не имеет собственных свойств.

+
var foo = {}; // новый пустой объект
+
+// новый объект со свойством 'test', имеющим значение 12
+var bar = {test: 12};
+

Доступ к свойствам

+

Получить доступ к свойствам объекта можно двумя способами: используя либо точечную нотацию, либо запись квадратными скобками.

+
var foo = {name: 'kitten'}
+foo.name; // kitten
+foo['name']; // kitten
+
+var get = 'name';
+foo[get]; // kitten
+
+foo.1234; // SyntaxError
+foo['1234']; // работает
+

Обе нотации идентичны по принципу работы — одна лишь разница в том, что использование квадратных скобок позволяет устанавливать свойства динамически и использовать такие имена свойств, какие в других случаях могли бы привести к синтаксической ошибке.

+

Удаление свойств

+

Единственный способ удалить свойство у объекта — использовать оператор delete; устанавливая свойство в undefined или null, вы только заменяете связанное с ним значение, но не удаляете ключ.

+ +
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

Приведённый код выведет две строки: bar undefined и foo null — на самом деле удалено было только свойство baz и посему только оно будет отсутствовать в выводе.

+

Запись ключей

+
var test = {
+    'case': 'Я — ключевое слово, поэтому меня надо записывать строкой',
+    delete: 'Я тоже ключевое слово, так что я' // бросаю SyntaxError
+};
+

Свойства объектов могут записываться как явно символами, так и в виде закавыченных строк. В связи с другим упущением в парсере JavaScript, этот код выбросит SyntaxError во всех версиях ранее ECMAScript 5.

+

Источником ошибки является факт, что delete — это ключевое слово и поэтому его необходимо записывать как строчный литерал: ради уверенности в том, что оно будет корректно опознано более старыми движками JavaScript.

+

От перев.: И еще один пример в пользу строковой нотации, это относится к JSON:

+
// валидный JavaScript и валидный JSON
+{
+    "foo": "oof",
+    "bar": "rab"
+}
+
+// валидный JavaScript и НЕвалидный JSON
+{
+    foo: "oof",
+    bar: "rab"
+}
+

Великий Прототип

В JavaScript отсутствует классическая модель наследования — вместо неё используется прототипная модель.

+

Хотя её часто расценивают как один из недостатков JavaScript, на самом деле прототипная модель наследования намного мощнее классической. К примеру, поверх неё можно предельно легко реализовать классическое наследование, а попытки совершить обратное вынудят вас попотеть.

+

Из-за того, что JavaScript — практически единственный широко используемый язык с прототипным наследованием, придётся потратить некоторое время на осознание различий между этими двумя моделями.

+

Первое важное отличие заключается в том, что наследование в JavaScript выполняется с использованием так называемых цепочек прототипов.

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// Установим значением прототипа Bar новый экземпляр Foo
+Bar.prototype = new Foo();
+Bar.prototype.foo = 'Hello World';
+
+// Убедимся, что Bar является действующим конструктором
+Bar.prototype.constructor = Bar;
+
+var test = new Bar() // создадим новый экземпляр bar
+
+// Цепочка прототипов, которая получится в результате
+test [instance of Bar]
+    Bar.prototype [instance of Foo]
+        { foo: 'Hello World' }
+        Foo.prototype
+            { method: ... }
+            Object.prototype
+                { toString: ... /* и т.д. */ }
+

В приведённом коде объект test наследует оба прототипа: Bar.prototype и Foo.prototype; следовательно, он имеет доступ к функции method которую мы определили в прототипе Foo. Также у него есть доступ к свойству value одного уникального экземпляра Foo, который является его прототипом. Важно заметить, что код new Bar() не создаёт новый экземпляр Foo, а повторно вызывает функцию, которая была назначен его прототипом: таким образом все новые экземпляры Bar будут иметь одинаковое свойство value.

+ +

Поиск свойств

+

При обращении к какому-либо свойству объекта, JavaScript проходит вверх по цепочке прототипов этого объекта, пока не найдет свойство c запрашиваемым именем.

+

Если он достигнет верхушки этой цепочки (Object.prototype) и при этом так и не найдёт указанное свойство, вместо него вернётся значение undefined.

+

Свойство prototype

+

То, что свойство prototype используется языком для построения цепочек прототипов, даёт нам возможность присвоить любое значение этому свойству. Однако обычные примитивы, если назначать их в качестве прототипа, будут просто-напросто игнорироваться.

+
function Foo() {}
+Foo.prototype = 1; // ничего не произойдёт
+Foo.prototype = {
+    "foo":"bar"
+};
+

При этом присвоение объектов, как в примере выше, позволит вам динамически создавать цепочки прототипов.

+

Производительность

+

Поиск свойств, располагающихся относительно высоко по цепочке прототипов, может негативно сказаться на производительности, особенно в критических местах кода. Если же мы попытаемся найти несуществующее свойство, то поиск будет осуществлён вообще по всей цепочке, со всеми вытекающими последствиями.

+

Вдобавок, при циклическом переборе свойств объекта, будет обработано каждое свойство, существующее в цепочке прототипов.

+

Расширение встроенных прототипов

+

Часто встречается неверное применение прототипов — расширение прототипа Object.prototype или прототипов одного из встроенных объектов JavaScript.

+

Подобная практика нарушает принцип инкапсуляции и имеет соответствующее название — monkey patching. К сожалению, в основу многих широко распространенных фреймворков, например Prototype, положен принцип изменения базовых прототипов. Вам же стоит запомнить — от хорошей жизни прототипы встроенных объектов не меняют.

+

Единственным оправданием для расширения встроенных прототипов может быть только воссоздание возможностей более новых движков JavaScript, например функции Array.forEach, которая появилась в версии 1.6.

+

Заключение

+

Перед тем, как вы приступите к разработке сложных приложений на JavaScript, вы должны полностью осознать как работают прототипы, и как организовывать наследование на их основе. Также, помните о зависимости между длиной цепочек прототипов и производительностью — разрывайте их при необходимости. Кроме того — никогда не расширяйте прототипы встроенных объектов (ну, если только для совместимости с новыми возможностями Javascript).

+

Функция hasOwnProperty

Если вам необходимо проверить, определено ли свойство у самого объекта, а не в его цепочке прототипов, вы можете использовать метод hasOwnProperty, который все объекты наследуют от Object.prototype.

+ +

hasOwnProperty — единственная функция в JavaScript, которая позволяет получить свойства объекта без обращения к цепочке его прототипов.

+
// испортим Object.prototype
+Object.prototype.bar = 1;
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // true
+
+foo.hasOwnProperty('bar'); // false
+foo.hasOwnProperty('goo'); // true
+

Только используя `hasOwnProperty` можно гарантировать правильный результат при переборе свойств объекта. И нет иного способа для определения свойств, которые определены в самом объекте, а не где-то в цепочке его прототипов.

+

hasOwnProperty как свойство

+

JavaScript не резервирует свойство с именем hasOwnProperty. Так что, если есть потенциальная возможность, что объект может содержать свойство с таким именем, требуется использовать внешний вариант функции hasOwnProperty чтобы получить корректные результаты.

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Да прилетят драконы'
+};
+
+foo.hasOwnProperty('bar'); // всегда возвращает false
+
+// Используем метод hasOwnProperty пустого объекта
+// и передаём foo в качестве this
+({}).hasOwnProperty.call(foo, 'bar'); // true
+

Заключение

+

Единственным способом проверить существование свойства у объекта является использование метода hasOwnProperty. При этом рекомендуется использовать этот метод в каждом цикле for in вашего проекта, чтобы избежать возможных ошибок с ошибочным заимствованием свойств из прототипов родительских объектов. Также вы можете использовать конструкцию {}.hasOwnProperty.call(...) на случай, если кто-то вздумает расширить прототипы встроенных объектов.

+

Цикл for in

Как и оператор in, цикл for in проходит по всей цепочке прототипов, обходя свойства объекта.

+ +
// Испортим Object.prototype
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // печатает и bar и moo
+}
+

Так как изменить поведение цикла for in как такового не представляется возможным, то для фильтрации нежелательных свойств объекта внутри этого цикла используют метод hasOwnProperty из Object.prototype.

+ +

Использование hasOwnProperty в качестве фильтра

+
// возьмём foo из примера выше
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

Это единственная версия правильного использования цикла. Благодаря использованию hasOwnPropery будет выведено только свойство moo. Если же убрать hasOwnProperty, код становится нестабилен и могут возникнуть ошибки, особенно если кто-то изменил встроенные прототипы, такие как Object.prototype.

+

Один из самых популярных фреймворков Prototype как раз этим и славится, и если вы его подключаете, то не забудьте использовать hasOwnProperty внутри цикла for in, иначе у вас гарантированно возникнут проблемы.

+

Рекомендации

+

Рекомендация одна — всегда используйте hasOwnProperty. Пишите код, который будет в наименьшей мере зависеть от окружения, в котором он будет запущен — не стоит гадать, расширял кто-то прототипы или нет и используется ли в ней та или иная библиотека.

+

Функции

Выражения и объявление функций

Функции в JavaScript тоже являются объектами (шок, сенсация) — следовательно, их можно передавать и присваивать точно так же, как и любой другой объект. Одним из вариантов использования такой возможности является передача анонимной функции как функции обратного вызова в другую функцию — к примеру, для асинхронных вызовов.

+

Объявление function

+
// всё просто и привычно
+function foo() {}
+

В следующем примере описанная функция резервируется перед запуском всего скрипта; за счёт этого она доступна в любом месте кода, вне зависимости от того, где она определена — даже если функция вызывается до её фактического объявления в коде.

+
foo(); // сработает, т.к. функция будет создана до выполнения кода
+function foo() {}
+

function как выражение

+
var foo = function() {};
+

В этом примере безымянная и анонимная функция присваивается переменной foo.

+
foo; // 'undefined'
+foo(); // вызовет TypeError
+var foo = function() {};
+

Так как в данном примере выражение var — это определение функции, переменная с именем foo будет заранее зарезервирована перед запуском скрипта (таким образом, foo уже будет определена во время его работы).

+

Но поскольку присвоения исполняются непосредственно во время работы кода, foo по умолчанию будет присвоено значение undefined (до обработки строки с определением функции):

+
var foo; // переменная неявно резервируется
+foo; // 'undefined'
+foo(); // вызовет TypeError
+foo = function() {};
+

Выражения с именованными фунциями

+

Существует еще нюанс, касающийся именованных функций создающихся через присваивание:

+
var foo = function bar() {
+    bar(); // работает
+}
+bar(); // получим ReferenceError
+

Здесь объект bar не доступен во внешней области, так как имя bar используется только для присвоения переменной foo; однако bar можно вызвать внутри функции. Такое поведение связано с особенностью работы JavaScript с пространствами имен - имя функции всегда доступно в локальной области видимости самой функции.

+

Как работает this

В JavaScript область ответственности специальной переменной this концептуально отличается от того, за что отвечает this в других языках программирования. Различают ровно пять вариантов того, к чему привязывается this в языке.

+

1. Глобальная область видимости

+
this;
+

Когда мы используем this в глобальной области, она будет просто ссылаться на глобальный объект.

+

2. Вызов функции

+
foo();
+

Тут this также ссылается на глобальный объект.

+ +

3. Вызов метода

+
test.foo();
+

В данном примере this ссылается на test.

+

4. Вызов конструктора

+
new foo();
+

Если перед вызовом функции присутствует ключевое слово new, то данная функция будет действовать как конструктор. Внутри такой функции this будет указывать на новосозданный Object.

+

5. Переопределение this

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // массив развернётся в a = 1, b = 2, c = 3
+foo.call(bar, 1, 2, 3); // аналогично
+

Когда мы используем методы call или apply из Function.prototype, то внутри вызываемой функции this явным образом будет присвоено значение первого передаваемого параметра.

+

Исходя из этого, в предыдущем примере (строка с apply) правило #3 вызов метода не будет применёно, и this внутри foo будет присвоено bar.

+ +

Наиболее распространенные ошибки

+

Хотя большинство из примеров ниже наполнены глубоким смыслом, первый из них можно считать ещё одним упущением в самом языке, поскольку он вообще не имеет практического применения.

+
Foo.method = function() {
+    function test() {
+        // this ссылается на глобальный объект
+    }
+    test();
+}
+

Распространенным заблуждением будет то, что this внутри test ссылается на Foo, но это не так.

+

Для того, чтобы получить доступ к Foo внутри функции test, необходимо создать локальную переменную внутри method, которая и будет ссылаться на Foo.

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // Здесь используем that вместо this
+    }
+    test();
+}
+

Подходящее имя для переменной - that, его часто используют для ссылки на внешний this. В комбинации с замыканиями this можно пробрасывать в глобальную область или в любой другой объект.

+ +

Назначение методов

+

Еще одной фичей, которая не работает в JavaScript, является создание псевдонимов для методов, т.е. присвоение метода объекта переменной.

+
var test = someObject.methodTest;
+test();
+

Следуя первому правилу test вызывается как обычная функция; следовательно this внутри него больше не ссылается на someObject.

+

Хотя позднее связывание this на первый взгляд может показаться плохой идеей, но на самом деле именно благодаря этому работает наследование прототипов.

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

В момент, когда будет вызван method нового экземпляра Bar, this будет ссылаться на этот самый экземпляр.

+

Замыкания и ссылки

Одним из самых мощных инструментов JavaScript'а считаются возможность создавать замыкания — это такой приём, когда наша область видимости всегда имеет доступ к внешней области, в которой она была объявлена. Собственно, единственный механизм работы с областями видимости в JavaScript — это функции: т.о. объявляя функцию, вы автоматически реализуете замыкания.

+

Эмуляция приватных свойств

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

В данном примере Counter возвращает два замыкания: функции increment и get. Обе эти функции сохраняют ссылку на область видимости Counter и, соответственно, имеют доступ к переменной count из этой самой области.

+

Как это работает

+

Поскольку в JavaScript нельзя присваивать или ссылаться на области видимости, заполучить count извне не представляется возможным. Единственным способом взаимодействовать с ним остается использование двух замыканий.

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

В приведенном примере мы не изменяем переменную count в области видимости Counter, т.к. foo.hack не объявлен в данной области. Вместо этого будет создана или перезаписана глобальная переменная count;

+

Замыкания внутри циклов

+

Часто встречается ошибка, когда замыкания используют внутри циклов, передавая переменную индекса внутрь.

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);
+    }, 1000);
+}
+

Данный код не будет выводить числа с 0 до 9, вместо этого число 10 будет выведено десять раз.

+

Анонимная функция сохраняет ссылку на i и, когда будет вызвана функция console.log, цикл for уже закончит свою работу, а в i будет содержаться 10.

+

Для получения желаемого результата необходимо создать копию переменной i.

+

Во избежание ошибок

+

Для того, чтобы скопировать значение индекса из цикла, лучше всего использовать анонимную функцию как обёртку.

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);
+        }, 1000);
+    })(i);
+}
+

Анонимная функция-обертка будет вызвана сразу же, и в качестве первого аргумента получит i, значение которой будет скопировано в параметр e.

+

Анонимная функция, которая передается в setTimeout, теперь содержит ссылку на e, значение которой не изменяется циклом.

+

Еще одним способом реализации является возврат функции из анонимной функции-обертки, поведение этого кода будет таким же, как и в коде из предыдущего примера.

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+ +

Объект arguments

В области видимости любой функции в JavaScript есть доступ к специальной переменной arguments. Эта переменная содержит в себе список всех аргументов, переданных данной функции.

+ +

Объект arguments не является наследником Array. Он, конечно же, очень похож на массив и даже содержит свойство length — но он не наследует Array.prototype, а представляет собой Object.

+

По этой причине, у объекта arguments отсутствуют стандартные методы массивов, такие как push, pop или slice. Хотя итерация с использованием обычного цикла for по аргументам работает вполне корректно, вам придётся конвертировать этот объект в настоящий массив типа Array, чтобы применять к нему стандартные методы массивов.

+

Конвертация в массив

+

Указанный код вернёт новый массив типа Array, содержащий все элементы объекта arguments.

+
Array.prototype.slice.call(arguments);
+

Эта конвертация занимает много времени и использовать её в критических частях кода не рекомендуется.

+

Передача аргументов

+

Ниже представлен рекомендуемый способ передачи аргументов из одной функции в другую.

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // делаем здесь что-нибудь
+}
+

Другой трюк — использовать и call и apply вместе, чтобы быстро создать несвязанную обёртку:

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// Создаём несвязанную версию "method"
+// Она принимает параметры: this, arg1, arg2...argN
+Foo.method = function() {
+
+    // Результат: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+
+};
+

Формальные аргументы и индексы аргументов

+

Объект arguments создаёт по геттеру и сеттеру и для всех своих свойств и для формальных параметров функции.

+

В результате, изменение формального параметра также изменит значение соответствующего свойства объекта arguments и наоборот.

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

Мифы и правда о производительности

+

Объект arguments создаётся во всех случаях, лишь за двумя исключениями — когда он переопределён внутри функции (по имени) или когда одним из её параметров является переменная с таким именем. Неважно, используется при этом сам объект или нет.

+

Геттеры и сеттеры создаются всегда; так что их использование практически никак не влияет на производительность.

+ +

Однако, есть один момент, который может радикально понизить производительность современных движков JavaScript. Этот момент — использование arguments.callee.

+
function foo() {
+    arguments.callee; // сделать что-либо с этим объектом функции
+    arguments.callee.caller; // и с вызвавшим его объектом функции
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // При обычных условиях должна бы была быть развёрнута...
+    }
+}
+

В коде выше, функция foo не может быть развёрнута (а могла бы), потому что для корректной работы ей необходима ссылка и на себя и на вызвавший её объект. Это не только кладёт на лопатки механизм развёртывания, но и нарушает принцип инкапсуляции, поскольку функция становится зависима от конкретного контекста вызова.

+

Крайне не рекомендуется использовать arguments.callee или какое-либо из его свойств. Никогда.

+ +

Конструктор

Создание конструкторов в JavaScript также отличается от большинства других языков. Любая функция, вызванная с использованием ключевого слова new, будет конструктором.

+

Внутри конструктора (вызываемой функции) this будет указывать на новосозданный Object. Прототипом этого нового объекта будет prototype функции, которая была вызвана в качестве конструктора.

+

Если вызываемая функция не имеет явного возврата посредством return, то вернётся this — этот новый объект.

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

В этом примере Foo вызывается в виде конструктора, следовательно прототип созданного объекта будет привязан к Foo.prototype.

+

В случае, когда функция в явном виде возвращает некое значение используя return, то в результате выполнения конструктора мы получим именно его, но только если возвращаемое значение представляет собой Object.

+
function Bar() {
+    return 2;
+}
+new Bar(); // новый объект
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // возвращённый объект
+

Если же опустить ключевое слово new, то функция не будет возвращать никаких объектов.

+
function Foo() {
+    this.bla = 1; // устанавливается глобальному объекту
+}
+Foo(); // undefined
+

Этот пример в некоторых случаях всё-таки может сработать: это связано с поведением this в JavaScript — он будет восприниматься парсером как глобальный объект.

+

Фабрики

+

Если хотите избавится от необходимости использования new, напишите конструктор, возвращающий значение посредством return.

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

В обоих случаях при вызове Bar мы получим один и тот же результат — новый объект со свойством method (спасибо замыканию за это).

+

Также следует заметить, что вызов new Bar() никак не связан с прототипом возвращаемого объекта. Хоть прототип и назначается всем новосозданным объектам, но Bar никогда не возвращает этот новый объект.

+

В предыдущем примере нет функциональных отличий между вызовом конструктора с оператором new или без него.

+

Создание объектов с использованием фабрик

+

Часто рекомендуют не использовать new, поскольку если вы его забудете, это может привести к ошибкам.

+

Чтобы создать новый объект, лучше использовать фабрику и создать новый объект внутри этой фабрики.

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

Хотя данный пример и сработает, если вы забыли ключевое слово new и благодаря ему легче работать с приватными переменными, у него есть несколько недостатков

+
    +
  1. Он использует больше памяти, поскольку созданные объекты не хранят методы в прототипе и соответственно для каждого нового объекта создаётся копия каждого метода.
  2. +
  3. Чтобы эмулировать наследование, фабрике нужно скопировать все методы из другого объекта или установить прототипом нового объекта старый.
  4. +
  5. Разрыв цепочки прототипов просто по причине забытого ключевого слова new идёт в разрез с духом языка.
  6. +
+

Заключение

+

Хотя забытое ключевое слово new и может привести к багам, это точно не причина отказываться от использования прототипов. В конце концов, полезнее решить какой из способов лучше совпадает с требованиями приложения: очень важно выбрать один из стилей создания объектов и после этого не изменять ему.

+

Области видимости и пространства имён

Хотя JavaScript нормально понимает синтаксис двух фигурных скобок, окружающих блок, он не поддерживает блочную область видимости; всё что остаётся на этот случай в языке — область видимости функций.

+
function test() { // область видимости
+    for(var i = 0; i < 10; i++) { // не область видимости
+        // считаем
+    }
+    console.log(i); // 10
+}
+ +

Также JavaScript не знает ничего о различиях в пространствах имён: всё определяется в глобально доступном пространстве имён.

+

Каждый раз, когда JavaScript обнаруживает ссылку на переменную, он будет искать её всё выше и выше по областям видимости, пока не найдёт её. В случае, если он достигнет глобальной области видимости и не найдет запрошенное имя и там тоже, он ругнётся ReferenceError.

+

Проклятие глобальных переменных

+
// скрипт A
+foo = '42';
+
+// скрипт B
+var foo = '42'
+

Вышеприведённые два скрипта не приводят к одному результату. Скрипт A определяет переменную по имени foo в глобальной области видимости, а скрипт B определяет foo в текущей области видимости.

+

Повторимся, это вообще не тот же самый эффект. Если вы не используете var — то вы в большой опасности.

+
// глобальная область видимости
+var foo = 42;
+function test() {
+    // локальная область видимости
+    foo = 21;
+}
+test();
+foo; // 21
+

Из-за того что оператор var опущен внутри функции, фунция test перезапишет значение foo. Это поначалу может показаться не такой уж и большой проблемой, но если у вас имеется тысяча строк JavaScript-кода и вы не используете var, то вам на пути встретятся страшные и трудноотлаживаемые ошибки — и это не шутка.

+
// глобальная область видимости
+var items = [/* какой-то список */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // область видимости subLoop
+    for(i = 0; i < 10; i++) { // пропущенный оператор var
+        // делаем волшебные вещи!
+    }
+}
+

Внешний цикл прекратит работу сразу после первого вызова subLoop, поскольку subLoop перезаписывает глобальное значение переменной i. Использование var во втором цикле for могло бы вас легко избавить от этой ошибки. Никогда не забывайте использовать var, если только влияние на внешнюю область видимости не является тем, что вы намерены получить.

+

Локальные переменные

+

Единственный источник локальных переменных в JavaScript - это параметры функций и переменные, объявленные с использованием оператора var.

+
// глобальная область видимости
+var foo = 1;
+var bar = 2;
+var i = 2;
+
+function test(i) {
+    // локальная область видимости для функции test
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

В то время как foo и i — локальные переменные в области видимости функции test, присвоение bar переопределит значение одноимённой глобальной переменной.

+

Высасывание

+

JavaScript высасывает определения. Это значит, что оба определения с использованием var и определение function будут перенесены наверх заключающей их области видимости.

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

Этот код трансформируется ещё перед исполнением. JavaScript перемещает операторы var и определение function наверх ближайшей оборачивающей области видимости.

+
// выражения с var переместились сюда
+var bar, someValue; // по умолчанию - 'undefined'
+
+// определение функции тоже переместилось
+function test(data) {
+    var goo, i, e; // потерянная блочная область видимости
+                   // переместилась сюда
+    if (false) {
+        goo = 1;
+
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // вылетает с ошибкой TypeError,
+       // поскольку bar всё ещё 'undefined'
+someValue = 42; // присвоения не подвержены высасыванию
+bar = function() {};
+
+test();
+

Потерянная область видимости блока не только переместит операторы var вовне циклов и их тел, но и сделает результаты некоторых конструкций с if неинтуитивными.

+

В исходном коде оператор if изменял глобальную переменную goo, когда, как оказалось, он изменяет локальную переменную — в результате работы высасывания.

+

Если вы не знакомы с высасываниями, то можете посчитать, что нижеприведённый код должен породить +ReferenceError.

+
// проверить, проинициализована ли SomeImportantThing
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

Но, конечно же, этот код работает: из-за того, что оператор var был перемещён наверх глобальной области видимости

+
var SomeImportantThing;
+
+// другой код может инициализировать здесь переменную SomeImportantThing,
+// а может и нет
+
+// убедиться, что она всё ещё здесь
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

Порядок разрешения имён

+

Все области видимости в JavaScript, включая глобальную области видимости, содержат специальную, определённую внутри них, переменную this, которая ссылается на текущий объект.

+

Области видимости функций также содержат внутри себя переменную arguments, которая содержит аргументы, переданные в функцию.

+

Например, когда JavaScript пытается получить доступ к переменной foo в области видимости функции, он будет искать её по имени в такой последовательности:

+
    +
  1. Если в текущей области видимости есть выражение var foo, использовать его.
  2. +
  3. Если один из параметров функции называется foo, использовать его.
  4. +
  5. Если функция сама называется foo, использовать её.
  6. +
  7. Перейти на одну область видимости выше и начать с п. 1
  8. +
+ +

Пространства имён

+

Нередкое последствие наличия только одного глобального пространства имён — проблемы с перекрытием имён переменных. В JavaScript эту проблему легко избежать, используя анонимные обёртки.

+
(function() {
+    // самостоятельно созданное "пространство имён"
+
+    window.foo = function() {
+        // открытое замыкание
+    };
+
+})(); // сразу же выполнить функцию
+

Безымянные функции являются выражениями; поэтому, чтобы вы имели возможность их выполнить, они сперва должны быть разобраны.

+
( // разобрать функцию внутри скобок
+function() {}
+) // и вернуть объект функции
+() // вызвать результат разбора
+

Есть другие способы разбора и последующего вызова выражения с функцией; они, хоть и различаются в синтаксисе, но действуют одинаково.

+
// Два других способа
++function(){}();
+(function(){}());
+

Заключение

+

Рекомендуется всегда использовать анонимную обёртку для заключения кода в его собственное пространство имён. Это не только защищает код от совпадений имён, но и позволяет создавать более модульные программы.

+

Важно добавить, что использование глобальных переменных считается плохой практикой. Любое их использование демонстрирует плохое качество кода и может привести к трудноуловимым ошибкам.

+

Массивы

Итерации по массивам и свойства

Несмотря на то, что массивы в JavaScript являются объектами, нет достаточных оснований для использования цикла for in для итерации по элементам массива. Фактически, существует несколько весомых причин против использования for in в массивах.

+ +

Во время выполнения for in циклически перебираются все свойства объекта, находящиеся в цепочке прототипов. Единственный способ исключить ненужные свойства — использовать hasOwnProperty, а это в 20 раз медленнее обычного цикла for.

+

Итерирование

+

Для достижения лучшей производительности при итерации по массивам, лучше всего использовать обычный цикл for.

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

В примере выше есть один дополнительный приём, с помощью которого кэшируется величина длины массива: l = list.length.

+

Несмотря на то, что свойство length определено в самом массиве, поиск этого свойства накладывает дополнительные расходы на каждой итерации цикла. Пусть в этом случае новые движки JavaScript теоретически могут применить оптимизацию, но нет никакого способа узнать, будет оптимизирован код на новом движке или нет.

+

Фактически, отсутствие кэширования может привести к выполнению цикла в два раза медленнее, чем при кэшировании длины

+

Свойство length

+

Хотя геттер свойства length просто возвращает количество элементов содержащихся в массиве, сеттер можно использовать для обрезания массива.

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo; // [1, 2, 3]
+

Присвоение свойству length меньшей величины урезает массив, однако присвоение большего значения не даст никакого эффекта.

+

Заключение

+

Для оптимальной работы кода рекомендуется всегда использовать простой цикл for и кэшировать свойство length. Использование for in с массивами является признаком плохого кода, обладающего предпосылками к ошибкам и может привести к низкой скорости его выполнения.

+

Конструктор Array

Так как в конструкторе Array есть некоторая двусмысленность, касающаяся его параметров, настоятельно рекомендуется при создании массивов всегда использовать синтаксис литеральной нотации — [].

+
[1, 2, 3]; // Результат: [1, 2, 3]
+new Array(1, 2, 3); // Результат: [1, 2, 3]
+
+[3]; // Результат: [3]
+new Array(3); // Результат: []
+new Array('3') // Результат: ['3']
+

В случае, когда в конструктор Array передаётся один аргумент и этот аргумент имеет тип Number, конструктор возвращает новый, заполненный случайными значениями, массив, имеющий длину равную значению переданного аргумента. Стоит заметить, что в этом случае будет установлено только свойство length нового массива, индексы массива фактически не будут проинициализированы.

+
var arr = new Array(3);
+arr[1]; // не определён, undefined
+1 in arr; // false, индекс не был установлен
+

Поведение, которое позволяет изначально установить только размер массива, может пригодиться лишь в нескольких случаях, таких как повторение строк, за счёт чего избегается использование цикла for loop.

+
new Array(count + 1).join(stringToRepeat);
+

Заключение

+

Использование конструктора Array нужно избегать, насколько это возможно. Литералы определённо предпочтительнее — это краткая запись и она имеет более понятный синтаксис, так что при этом даже улучшается читабельность кода.

+

Типы

Равенство и сравнение

JavaScript имеет 2 различных способа сравнения значений объектов на равенство.

+

Оператор сравнения

+

Оператор сравнения состоит из двух символов равенства: ==

+

Слабая типизированность языка JavaScript подразумевает приведение обеих переменных к одному типу для того, чтобы произвести сравнение.

+
""           ==   "0"           // false
+0            ==   ""            // true
+0            ==   "0"           // true
+false        ==   "false"       // false
+false        ==   "0"           // true
+false        ==   undefined     // false
+false        ==   null          // false
+null         ==   undefined     // true
+" \t\r\n"    ==   0             // true
+

В таблице выше показаны результаты приведения типов и это главная причина, почему использование == повсеместно считается плохой практикой: оно приводит к трудностям в отслеживании ошибок из-за сложных правил преобразования типов.

+

Кроме того, приведение типов во время сравнения также влияет на производительность; например, строка должна быть преобразована в число перед сравнением с другим числом.

+

Оператор строгого равенства

+

Оператор строгого равенства состоит из трёх символов равенства: ===

+

В отличие от обычного оператора равенства, оператор строгого равенства не выполняет приведение типов между операндами.

+
""           ===   "0"           // false
+0            ===   ""            // false
+0            ===   "0"           // false
+false        ===   "false"       // false
+false        ===   "0"           // false
+false        ===   undefined     // false
+false        ===   null          // false
+null         ===   undefined     // false
+" \t\r\n"    ===   0             // false
+

Результаты выше более понятны и позволяют быстрее выявлять ошибки в коде. Это в определённой степени улучшает код, а также дает прирост производительности в случае, если операнды имеют различные типы.

+

Сравнение объектов

+

Хотя оба оператора == и === заявлены как операторы равенства, они ведут себя по-разному, когда хотя бы один из операндов является Object.

+
{} === {};                   // false
+new String('foo') === 'foo'; // false
+new Number(10) === 10;       // false
+var foo = {};
+foo === foo;                 // true
+

Здесь оба операнда сравниваются на идентичность, а не на равенство; то есть будет проверяться, являются ли операнды одним экземпляром объекта, так же как делает is в Python и сравниваются указатели в С.

+

Заключение

+

Крайне рекомендуется использовать только операторы строгого равенства. В случае, когда намечается преобразование типов, нужно сделать явное приведение и не оставлять их на совести языковых хитростей с преобразованиями.

+

Оператор typeof

Оператор typeof (вместе с instanceof) — это, вероятно, самая большая недоделка в JavaScript, поскольку, похоже, он поломан более, чем полностью.

+

Хотя instanceof еще имеет ограниченное применение, typeof на самом деле имеет только один практический случай применения, который при всём при этом не является проверкой типа объекта.

+ +

Таблица типов JavaScript

+
Значение            Класс      Тип
+-------------------------------------
+"foo"               String     string
+new String("foo")   String     object
+1.2                 Number     number
+new Number(1.2)     Number     object
+true                Boolean    boolean
+new Boolean(true)   Boolean    object
+new Date()          Date       object
+new Error()         Error      object
+[1,2,3]             Array      object
+new Array(1, 2, 3)  Array      object
+new Function("")    Function   function
+/abc/g              RegExp     object (function в Nitro/V8)
+new RegExp("meow")  RegExp     object (function в Nitro/V8)
+{}                  Object     object
+new Object()        Object     object
+

В таблице выше Тип представляет собой значение, возвращаемое оператором typeof. Как хорошо видно, это значение может быть абсолютно любым, но не логичным результатом.

+

Класс представляет собой значение внутреннего свойства [[Class]] объекта.

+ +

Для того, чтобы получить значение [[Class]], необходимо вызвать метод toString у Object.prototype.

+

Класс объекта

+

Спецификация предоставляет только один способ доступа к значению [[Class]] — используя Object.prototype.toString.

+
function is(type, obj) {
+    var clas = Object.prototype.toString.call(obj).slice(8, -1);
+    return obj !== undefined && obj !== null && clas === type;
+}
+
+is('String', 'test'); // true
+is('String', new String('test')); // true
+

В примере выше Object.prototype.toString вызывается со значением this, являющимся объектом, значение [[Class]] которого нужно получить.

+ +

Проверка переменных на определённость

+
typeof foo !== 'undefined'
+

Выше проверяется, было ли foo действительно объявлено или нет; просто обращение к переменной приведёт к ReferenceError. Это единственное, чем на самом деле полезен typeof.

+

Заключение

+

Для проверки типа объекта настоятельно рекомендуется использоватьObject.prototype.toString — это единственный надежный способ. Как показано выше в таблице типов, некоторые возвращаемые typeof значения не определены в спецификации: таким образом, они могут отличаться в различных реализациях.

+

Кроме случая проверки, была ли определена переменная, typeof следует избегать во что бы то ни стало.

+

Оператор instanceof

Оператор instanceof сравнивает конструкторы двух операндов. Это полезно только когда сравниваются пользовательские объекты. Использование на встроенных типах почти так же бесполезно, как и оператор typeof.

+

Сравнение пользовательских объектов

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // true
+new Bar() instanceof Foo; // true
+
+// Всего лишь присваиваем Bar.prototype объект функции Foo,
+// но не экземпляра Foo
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // false
+

Использование instanceof со встроенными типами

+
new String('foo') instanceof String; // true
+new String('foo') instanceof Object; // true
+
+'foo' instanceof String; // false
+'foo' instanceof Object; // false
+

Здесь надо отметить одну важную вещь: instanceof не работает на объектах, которые происходят из разных контекстов JavaScript (например, из различных документов в web-браузере), так как их конструкторы и правда не будут конструкторами тех самых объектов.

+

Заключение

+

Оператор instanceof должен использоваться только при обращении к пользовательским объектам, происходящим из одного контекста JavaScript. Так же, как и в случае оператора typeof, любое другое использование необходимо избегать.

+

Приведение типов

JavaScript — слабо типизированный язык, поэтому преобразование типов будет применяться везде, где возможно.

+
// Эти равенства — истинны
+new Number(10) == 10; // Number.toString() преобразуется
+                      // обратно в число
+
+10 == '10';           // Strings преобразуется в Number
+10 == '+10 ';         // Ещё чуток строко-безумия
+10 == '010';          // и ещё
+isNaN(null) == false; // null преобразуется в 0,
+                      // который конечно же NaN
+
+// Эти равенства — ложь
+10 == 010;
+10 == '-10';
+ +

Для того, чтобы избежать этого, настоятельно рекомендуется использовать оператор строгого равенства. Впрочем, хотя это и позволяет избежать многих распространенных ошибок, существует ещё много дополнительных вопросов, которые возникают из-за слабости типизации JavaScript.

+

Конструкторы встроенных типов

+

Конструкторы встроенных типов, например, Number и String ведут себя различным образом, в зависимости от того, вызываются они с ключевым словом new или без.

+
new Number(10) === 10;     // False, Object и Number
+Number(10) === 10;         // True, Number и Number
+new Number(10) + 0 === 10; // True, из-за неявного преобразования
+

Использование встроенных типов, например, Number, с конструктором создаёт новый экземпляр объекта Number, но использование без ключевого слова new создаст функцию Number, которая будет вести себя, как конвертер.

+

Кроме того, присутствие литералов или переменных, которые не являются объектами, приведет к еще большему насилию над типами.

+

Лучший вариант — это явное приведение к одному из трех возможных типов.

+

Приведение к строке

+
'' + 10 === '10'; // true
+

Путём добавления в начале пустой строки, значение легко приводится к строке.

+

Приведение к числовому типу

+
+'10' === 10; // true
+

Используя унарный оператор плюс можно преобразовать значение в число.

+

Приведение к булеву типу

+

Используя оператор not (!) дважды, значение может быть приведено к логическому (булеву) типу.

+
!!'foo';   // true
+!!'';      // false
+!!'0';     // true
+!!'1';     // true
+!!'-1'     // true
+!!{};      // true
+!!true;    // true
+

Нативности

Почему нельзя использовать eval

Функция eval выполняет строку кода JavaScript в локальной области видимости.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

Но eval исполняется в локальной области видимости только тогда, когда он вызывается напрямую и при этом имя вызываемой функции именно eval.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

Любой ценой избегайте использования функции eval. 99.9% случаев её "использования" могут достигаться без её участия.

+

eval под прикрытием

+

Обе функции работы с интервалами времени setTimeout и setInterval могут принимать строку в качестве первого аргумента. Эта строка всегда будет выполняться в глобальной области видимости, поскольку eval в этом случае вызывается не напрямую.

+

Проблемы с безопасностью

+

Кроме всего прочего, функция eval — это проблема в безопасности, поскольку исполняется любой переданный в неё код; никогда не следует использовать её со строками из неизвестных или недоверительных источников.

+

Заключение

+

Никогда не стоит использовать eval: любое применение такого кода поднимает вопросы о качестве его работы, производительности и безопасности. Если вдруг для работы вам необходим eval, эта часть должна тут же ставиться под сомнение и не должна использоваться в первую очередь — необходимо найти лучший способ , которому не требуются вызовы eval.

+

undefined и null

В JavaScript есть два отдельных типа для представления ничего, при этом более полезным из них является undefined.

+

Тип undefined

+

undefined — это тип с единственным возможным значением: undefined.

+

Кроме этого, в языке определена глобальная переменная со значением undefined, и эта переменная так и называется — undefined. Не являясь константой, она не является и ключевым словом. Из этого следует, что её значение можно с лёгкостью переопределить.

+ +

Несколько случаев, когда возвращается undefined:

+
    +
  • При попытке доступа к глобальной переменной undefined (если она не изменена).
  • +
  • Неявный возврат из функции при отсутствии в ней оператора return.
  • +
  • Из операторов return, которые ничего не возвращают.
  • +
  • В результате поиска несуществующего свойства у объекта (и доступа к нему).
  • +
  • Параметры, которые не были переданы в функцию явно.
  • +
  • При доступе ко всему, чьим значением является undefined.
  • +
+

Обработка изменений значения undefined

+

Поскольку глобальная переменная undefined содержит копию настоящего значения undefined, присвоение этой переменной нового значения не изменяет значения типа undefined.

+

Но при этом, чтобы сравнить что-либо со значением undefined, прежде нужно получить значение самой переменной undefined.

+

Чтобы защитить код от переопределения переменной undefined, часто используется техника анонимной обёртки, которая использует отсутствующий аргумент.

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // в локальной области видимости `undefined`
+    // снова ссылается на правильное значене.
+
+})('Hello World', 42);
+

Другой способ достичь того же эффекта — использовать определение внутри обёртки.

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

Единственная разница между этими вариантами в том, что последняя версия будет больше на 4 байта при минификации, а в первом случае внутри анонимной обёртки нет дополнительного оператора var.

+

Использование null

+

Хотя undefined в контексте языка JavaScript чаще используется в качестве традиционного null, настоящий null (и тип и литерал) является в большей или меньшей степени просто другим типом данных.

+

Он используется во внутренних механизмах JavaScript (например для определения конца цепочки прототипов за счёт присваивания Foo.prototype = null). Но в большинстве случаев тип null может быть заменён на undefined.

+

Автоматическая вставка точек с запятой

Хоть JavaScript и имеет синтаксис, подобный языкам семейства C, он при этом не принуждает вас ставить точки с запятой в исходном коде — вы всегда можете их опустить.

+

При этом JavaScript — не язык без точек с запятой, они на самом деле нужны ему, чтобы он мог разобраться в вашем коде. Поэтому парсер JavaScript автоматически вставляет их в те места, где сталкивается с ошибкой парсинга из-за их отсутствия.

+
var foo = function() {
+} // ошибка разбора, ожидается точка с запятой
+test()
+

Происходит вставка и парсер пытается снова.

+
var foo = function() {
+}; // ошибки нет, парсер продолжает
+test()
+

Автоматическая вставка точек с запятой считается одним из наибольших упущений в проекте языка, поскольку она может изменить поведение кода.

+

Как это работает

+

Приведённый код не содержит точек с запятой, так что места для их вставки остаются на совести парсера:

+
(function(window, undefined) {
+    function test(options) {
+        log('тестируем!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'здесь передадим длинную строчку',
+            'и ещё одну на всякий случай'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+
+})(window)
+

Ниже представлен результат игры парсера в "угадалки".

+
(function(window, undefined) {
+    function test(options) {
+
+        // не вставлена точка с запятой, строки были объединены
+        log('тестируем!')(options.list || []).forEach(function(i) {
+
+        }); // <- вставлена
+
+        options.value.test(
+            'здесь передадим длинную строчку',
+            'и ещё одну на всякий случай'
+        ); // <- вставлена
+
+        return; // <- вставлена, в результате 
+                //    оператор return разбит на два блока
+        { // теперь парсер считает этот блок отдельным
+
+            // метка и одинокое выражение
+            foo: function() {}
+        }; // <- вставлена
+    }
+    window.test = test; // <- вставлена
+
+// снова объединились строки
+})(window)(function(window) {
+    window.someLibrary = {}; // <- вставлена
+
+})(window); //<- вставлена
+ +

Парсер радикально подменил поведение изначального кода, а в определённых случаях он сделал абсолютно неправильные выводы.

+

"Висящие" скобки

+

Если парсер встречает "висящую" скобку, то он не вставляет точку с запятой.

+
log('тестируем!')
+(options.list || []).forEach(function(i) {})
+

Такой код трансформируется в строку

+
log('тестируем!')(options.list || []).forEach(function(i) {})
+

Чрезвычайно высоки шансы, что log возвращает не функцию; таким образом, эта строка вызовет TypeError с сообщением о том, что undefined не является функцией.

+

Заключение

+

Настоятельно рекомендуем никогда не забывать ставить точку с запятой; также рекомендуется оставлять скобки на одной строке с соответствующим оператором и никогда не опускать их для выражений с использованием if / else. Оба этих совета не только повысят читабельность вашего кода, но и предотвратят от изменения поведения кода, произведённого парсером втихую.

+

Другое

setTimeout и setInterval

Поскольку JavaScript поддерживает асинхронность, есть возможность запланировать выполнение функции, используя функции setTimeout и setInterval.

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // возвращает число > 0
+

Функция setTimeout возвращает идентификатор таймаута и планирует вызвать foo через, примерно, тысячу миллисекунд. Функция foo при этом будет вызвана ровно один раз.

+

В зависимости от разрешения таймера в используемом для запуска кода движке JavaScript, а также с учётом того, что JavaScript является однопоточным языком и посторонний код может заблокировать выполнение потока, нет никакой гарантии, что переданный код будет выполнен ровно через указанное в вызове setTimeout время.

+

Переданная первым параметром функция будет вызвана как глобальный объект — это значит, что оператор this в вызываемой функции будет ссылаться на этот самый объект.

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // this ссылается на глобальный объект
+        console.log(this.value); // выведет в лог undefined
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

Поочерёдные вызовы с использованием setInterval

+

setTimeout вызывает функцию единожды; setInterval — как и предполагает название — вызывает функцию каждые X миллисекунд. И его использование не рекомендуется.

+

В то время, когда исполняющийся код будет блокироваться во время вызова с таймаутом, setInterval будет продолжать планировать последующие вызовы переданной функции. Это может (особенно в случае небольших интервалов) повлечь за собой выстраивание вызовов функций в очередь.

+
function foo(){
+    // что-то, что выполняется одну секунду
+}
+setInterval(foo, 1000);
+

В приведённом коде foo выполнится один раз и заблокирует этим главный поток на одну секунду.

+

Пока foo блокирует код, setInterval продолжает планировать последующие её вызовы. Теперь, когда первая foo закончила выполнение, в очереди будут уже десять ожидающих выполнения вызовов foo.

+

Разбираемся с потенциальной блокировкой кода

+

Самый простой и контролируемый способ — использовать setTimeout внутри самой функции.

+
function foo(){
+    // что-то, выполняющееся одну секунду
+    setTimeout(foo, 1000);
+}
+foo();
+

Такой способ не только инкапсулирует вызов setTimeout, но и предотвращает от очередей блокирующих вызовов и при этом обеспечивает дополнительный контроль. Сама функция foo теперь принимает решение, хочет ли она запускаться ещё раз или нет.

+

Очистка таймаутов вручную

+

Удаление таймаутов и интервалов работает через передачу соответствующего идентификатора либо в функцию clearTimeout, либо в функцию clearInterval — в зависимости от того, какая функция set... использовалась для его получения.

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

Очистка всех таймаутов

+

Из-за того, что встроенного метода для удаления всех таймаутов и/или интервалов не существует, для достижения этой цели приходится использовать брутфорс.

+
// удаляем "все" таймауты
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

Вполне могут остаться таймауты, которые не будут захвачены этим произвольным числом; так что всё же рекомендуется следить за идентификаторами всех создающихся таймаутов, за счёт чего их можно будет удалять индивидуально.

+

Скрытое использование eval

+

setTimeout и setInterval могут принимать строку в качестве первого параметра. Эту возможность не следует использовать никогда, поскольку изнутри при этом производится скрытый вызов eval.

+ +
function foo() {
+    // будет вызвана
+}
+
+function bar() {
+    function foo() {
+        // никогда не будет вызывана
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

Поскольку eval в этом случае не вызывается напрямую, переданная в setTimeout строка будет выполнена в глобальной области видимости; так что локальная переменная foo из области видимости bar не будет выполнена.

+

По этим же причинам рекомендуется не использовать строку для передачи аргументов в функцию, которая должна быть вызвана из одной из двух функций, работающих с таймаутами.

+
function foo(a, b, c) {}
+
+// НИКОГДА не делайте такого
+setTimeout('foo(1,2, 3)', 1000)
+
+// Вместо этого используйте анонимную функцию
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

Заключение

+

Никогда не используйте строки как параметры setTimeout или setInterval. Это явный признак действительно плохого кода. Если вызываемой функции необходимо передавать аргументы, лучше передавать анонимную функцию, которая самостоятельно будет отвечать за сам вызов.

+

Кроме того, избегайте использования setInterval в случаях, когда его планировщик может блокировать выполнение JavaScript.

+

Пояснения

От переводчиков

Авторы этой документации требуют от читателя не совершать каких-либо ошибок и постоянно следить за качеством пишущегося кода. Мы, как переводчики и опытные программисты на JavaScript, рекомендуем прислушиваться к этим советам, но при этом не делать из этого крайность. Опыт — сын ошибок трудных, и иногда в борьбе с ошибками зарождается намного более детальное понимание предмета. Да, нужно избегать ошибок, но допускать их неосознанно — вполне нормально.

+

К примеру, в статье про сравнение объектов авторы настоятельно рекомендуют использовать только оператор строгого неравенства ===. Но мы считаем, что если вы уверены и осознали, что оба сравниваемых операнда имеют один тип, вы имеете право опустить последний символ =. Вы вольны применять строгое неравенство только в случаях, когда вы не уверены в типах операндов (!== undefined — это полезный приём). Так в вашем коде будут опасные и безопасные области, но при этом по коду будет явно видно, где вы рассчитываете на переменные одинаковых типов, а где позволяете пользователю вольности.

+

Функцию setInterval тоже можно использовать, если вы стопроцентно уверены, что код внутри неё будет исполняться как минимум в три раза быстрее переданного ей интервала.

+

С другой стороны, использование var и грамотная расстановка точек с запятой — обязательные вещи, халатное отношение к которым никак не может быть оправдано — в осознанном пропуске var (если только вы не переопределяете глобальный объект браузера... хотя зачем?) или точки с запятой нет никакого смысла.

+

Относитесь с мудростью к тому, что вы пишете — важно знать как работает именно ваш код и как это соответствует приведённым в статье тезисам — и уже из этого вы сможете делать вывод, подходит ли вам тот или иной подход или нет. Важно знать как работает прототипное наследование, но это не так необходимо, если вы используете функциональный подход или пользуетесь какой-либо сторонней библиотекой. Важно помнить о том, что у вас недостаёт какого-либо конкретного знания и что пробел следует заполнить, но если вы не используете в работе эту часть, вы всё равно можете писать хороший код — ну, если у вас есть талант.

+

Гонка за оптимизацией — это драматично и правильно, но лучше написать работающий и понятный вам код, а потом уже его оптимизировать и искать узкие места, при необходимости. Оптимизацию необходимо делать, если вы видите явные неудобства для пользователя в тех или иных браузерах или у вас есть супер-крупный проект, которым никогда не мешает оптимизация, или вы работаете с какой-либо сверхтребовательной технологией типа WebGL. Данная документация очень поможет вам в определении этих узких мест.

+
\ No newline at end of file diff --git a/site/style/garden.css b/style/garden.css similarity index 100% rename from site/style/garden.css rename to style/garden.css diff --git a/site/style/print.css b/style/print.css similarity index 100% rename from site/style/print.css rename to style/print.css diff --git a/tr/index.html b/tr/index.html new file mode 100644 index 0000000..fad12b7 --- /dev/null +++ b/tr/index.html @@ -0,0 +1,1508 @@ +JavaScript Garden

Giriş

Giriş

JavaScript Garden JavaScript programlama dilinin acayiplikleri üzerine +derlenmiş bir döküman koleksiyonudur. Henüz ustalaşmamış JavaScript +programcılarının sıkça yaptığı yanlışlar, dile has incelikler ve performans +sorunlarına karşı tavsiyeler içerir.

+

JavaScript Garden'ın amacı size JavaScript öğretmek değildir. Bu rehberde +anlatılan konuları anlamak için JavaScript dilini önceden biliyor olmanız +gerekir. Eğer JavaScript dilinin temellerini öğrenmek istiyorsanız, lütfen +Mozilla Programcı Ağı'nda bulunan mükemmel rehbere başvurun.

+

Yazarlar

+

Bu rehber, sevimli birer Stack Overflow kullanıcısı olan Ivo Wetzel (Yazım) +ve Zhang Yi Jiang (Tasarım) tarafından hazırlanmıştır.

+

Katkıda Bulunanlar

+ +

Sunum

+

JavaScript Garden GitHub üzerinden, ve ayrıca Cramer Development +tarafından desteklenen JavaScriptGarden.info adresinden sunulmaktadır.

+

Lisans

+

JavaScript Garden MIT lisansı altında yayınlanmıştır ve GitHub +üzerinde bulunmaktadır. Eğer rehberde yanlışlıklar veya yazım hatalarına +rastlarsanız lütfen sorunu bize bildirin veya bir pull request gönderin. +Bizi ayrıca Stack Overflow'da JavaScript sohbet odasında da +bulabilirsiniz.

+

Nesneler

Nesne Kullanımı ve Özellikleri

JavaScript'te iki istisna dışında her şey bir nesne olarak davranır; +bu istisnalar da null ve undefined +'dır.

+
false.toString(); // 'false'
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

Sık düşülen bir yanılgı sayı sabitlerinin nesne olarak kullanılamayacağıdır. Bu +yanılgının sebebi de JavaScript çözümleyicisinin nokta notasyonu ile girilen +sayıları bir reel sayı olarak algılama hatasıdır.

+
2.toString(); // SyntaxError hatası verir
+

Bu hatayı aşıp sayı sabitlerinin de nesne olarak davranmasını sağlamak için +uygulanabilecek bazı çözümler vardır.

+
2..toString(); // ikinci nokta doğru şekilde algılanır
+2 .toString(); // noktanın solundaki boşluğa dikkat edin
+(2).toString(); // ilk önce 2 değerlendirilir
+

Bir veri türü olarak nesneler

+

JavaScript nesneleri aynı zamanda bir Hashmap olarak da kullanılabilir, +nesneler temelde isimli özellikler ve bunlara karşılık gelen değerlerden +ibarettir.

+

Nesne sabiti ({} notasyonu) ile düz bir nesne yaratmak mümkündür. Bu yeni +nesne kalıtım ile Object.prototype 'dan türüyecektir ve +hiçbir baz özelliğe sahip olmayacaktır.

+
var foo = {}; // yeni bir boş nesne
+
+// adı 'test' ve değeri 12 olan bir özelliği sahip yeni bir nesne
+var bar = {test: 12}; 
+

Özelliklere erişmek

+

Bir nesnenin özelliklerine iki yolla erişilebilir, ya nokta notasyonu ile veya +köşeli parantez notasyonu ile.

+
var foo = {name: 'kitten'}
+foo.name; // kitten
+foo['name']; // kitten
+
+var get = 'name';
+foo[get]; // kitten
+
+foo.1234; // SyntaxError
+foo['1234']; // çalışır
+

Her iki notasyon da aynı şekilde çalışır, tek fark köşeli parantez notasyonunun +özelliklerin dinamik olarak oluşturulmasına ve normalde bir yazım hatasına yol +açabilecek özellik isimlerinin kullanılmasına izin vermesidir.

+

Özellikleri silmek

+

Bir nesnenin özelliklerinden birini silmenin tek yolu delete operatörünü +kullanmaktır; özelliğe undefined veya null değerlerini atamak sadece +özelliğin değerini kaldırır, anahtarı değil.

+
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

Yukarıdaki örnek sonuç olarak hem bar undefined hem de foo null yazacaktır. +Sadece baz özelliği kaldırılmış olacak ve çıktıda görünmeyecektir.

+

Anahtar notasyonu

+
var test = {
+    'case': 'anahtar kelime olduğu için katar olarak girildi',
+    delete: 'yine bir anahtar kelime' // SyntaxError hatası
+};
+

Nesne özellikleri düz karakterler olarak da katar notasyonu ile de +tanımlanabilir. Fakat JavaScript çözümleyicisinin bir başka tasarım hatası +yüzünden, yukarıdaki örnek ECMAScript 5 öncesinde bir SyntaxError hatası +verecektir.

+

Bu hata delete 'in bir anahtar kelime olmasından kaynaklanır, bu nedenle +eski JavaScript motorlarının bu örneği doğru algılaması için karakter katarı +notasyonu ile girilmelidir.

+

Prototip

JavaScript klasik bir kalıtım modeli değil prototip modeli kullanır.

+

Çoğu zaman bu modelin JavaScript'in zayıf yönlerinden biri olduğu söylense de, +aslında prototip model klasik modelden daha güçlüdür. Mesela prototip model +temel alınarak klasik kalıtım modeli oluşturulabilir, fakat bunun tersini yapmak +çok daha zordur.

+

Prototip kalıtım modeli kullanan tek popüler dil JavaScript olduğu için iki +model arasındaki farklılıklara alışmak biraz zaman alır.

+

İlk büyük farklılık JavaScript'te kalıtımın prototip zincirleri ile +yapılmasıdır.

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// Bar nesnesinin prototipi olarak yeni bir Foo nesnesini ata
+Bar.prototype = new Foo();
+Bar.prototype.foo = 'Hello World';
+
+// Nesne oluşturucusunun Bar olmasını sağla
+Bar.prototype.constructor = Bar;
+
+var test = new Bar() // yeni bir Bar oluştur
+
+// Sonuçta ortaya çıkan prototip zinciri
+test [bir Bar sınıfı nesnesi]
+    Bar.prototype [bir Foo sınıfı nesnesi] 
+        { foo: 'Hello World' }
+        Foo.prototype
+            { method: ... }
+            Object.prototype
+                { toString: ... /* vs. */ }
+

Yukarıda, test nesnesi hem Bar.prototype hem de Foo.prototype 'dan +türeyecektir; bu nedenle Foo 'da tanımlanmış olan method fonksiyonuna +da erişebilir. Ayrıca, prototipi olan tek Foo nesnesinin value +özelliğine de erişebilir. Dikkat edilmesi gereken bir nokta, new Bar() +ifadesinin yeni bir Foo nesnesi yaratmayıp, prototipine atanmış olan +nesneyi kullanmasıdır; bu nedenle, tüm Bar nesneleri aynı value +özelliğine sahip olacaktır.

+ +

Özelliklere bulmak

+

Bir nesnenin özelliklerine erişildiğinde, JavaScript, istenen isimdeki özelliği +bulana kadar prototip zincirinde yukarı doğru dolaşır.

+

Zincirin en üstüne ulaştığında (yani Object.prototype) ve hala istenen özelliği +bulamamışsa sonuç olarak undefined verecektir.

+

prototype özelliği

+

prototype özelliği dil tarafından prototip zincirleri oluşturmak için +kullanılsa da, bu özelliğe herhangi bir değer atamak mümkündür. Fakat +prototip olarak atanan ilkel nesne türleri göz ardı edilecektir.

+
function Foo() {}
+Foo.prototype = 1; // hiç bir etkisi olmaz
+

Bir önceki örnekte gösterildiği gibi, prototip olarak nesneler atanabilir, bu da +prototip zincirlerinin dinamik olarak oluşturulabilmesini sağlar.

+

Performans

+

Prototip zincirinin yukarısındaki özellikleri aramanın performansı kritik olan +programlarda olumsuz etkileri olabilir. Ek olarak, mevcut olmayan özelliklere +erişmeye çalışmak da tüm prototip zincirinin baştan sona taranmasına neden +olacaktır.

+

Ayrıca, bir nesnenin özellikleri üzerinde iterasyon +yapıldığında da prototip zinciri üzerindeki tüm özelliklere bakılacaktır.

+

Temel prototiplerin genişletilmesi

+

Sıklıkla yapılan bir hata Object.prototype 'ı veya diğer baz prototipleri +genişletmektir.

+

Bu tekniğe monkey patching denir ve kapsüllemeyi bozar. Bu teknik +Prototype gibi bazı popüler sistemlerde kullanılsa bile, temel nesne +türlerine standart olmayan özellikler eklenmesinin geçerli iyi bir nedeni +yoktur.

+

Temel prototipleri genişletmenin tek bir geçerli nedeni vardır, o da daha +yeni JavaScript motorlarında bulunan özelliklerin eski motorlara getirilmesidir; +mesela Array.forEach.

+

Sonuç

+

Prototip kalıtım modeli kullanan karmaşık programlar yazmadan önce bu modelin +tamamen anlaşılması şarttır. Ayrıca, prototip zincirinin uzunluğuna dikkat +edilmeli ve çok uzaması durumunda performans sorunları yaşamamak için parçalara +bölünmelidir. Bundan başka, temel prototipler yeni JavaScript motorları ile +uyumluluk sağlamak dışında bir nedenle asla genişletilmemelidir.

+

hasOwnProperty

Bir özelliğin nesnenin prototip zinciri üzerinde bir yerde +değil, kendisi üzerinde tanımlandığını belirlemek için, Object.prototype +kalıtımı ile tüm nesnelerin sahip olduğu hasOwnProperty metodunun kullanılması +gerekir.

+ +

hasOwnProperty JavaScript'te nesne özellikleri üzerinde çalışıp prototip +zincirinin tümünü dolaşmayan tek şeydir.

+
// Object.prototype'a bar özelliğini ekle
+Object.prototype.bar = 1; 
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // true
+
+foo.hasOwnProperty('bar'); // false
+foo.hasOwnProperty('goo'); // true
+

Sadece hasOwnProperty beklenen doğru sonucu verecektir, nesne özellikleri +üzerinde iterasyon yaparken bu çok önemlidir. Bir nesnenin kendisi üzerinde +değil de protip zinciri üzerinde bir yerde tanımlanmış olan özelliklerini +çıkarmanın başka hiçbir yolu yoktur.

+

hasOwnProperty özelliği

+

JavaScript hasOwnProperty adının bir özellik olarak kullanılmasını engellemez; +bu nedenle bir nesnenin bu isimde bir özelliğe sahip olması ihtimali varsa, +doğru sonuç alabilmek için hasOwnPropertyharicen kullanılmalıdır.

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Here be dragons'
+};
+
+foo.hasOwnProperty('bar'); // her zaman false verir
+
+// hasOwnProperty başka bir nesne üzerinde
+// kullanıldığında 'this' foo olur
+({}).hasOwnProperty.call(foo, 'bar'); // true
+

Sonuç

+

Bir nesnenin bir özelliği sahip olup olmadığını kontrol etmek için +kullanılabilecek tek yöntem hasOwnProperty 'dir. Aynı zamanda, nesne +prototiplerinin genişletilmesinden kaynaklanabilecek +hataların önüne geçmek için, tüm for in döngüleri ile +hasOwnProperty kullanılması tavsiye olunur.

+

for in Döngüsü

Tıpkı in operatörü gibi for in döngüsü de bir nesnenin özellikleri üzerinde +iterasyon yaparken prototip zincirini dolaşır.

+ +
// Object.prototype'a bar özelliğini ekle
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // hem bar hem de moo yazar
+}
+

for in döngüsünün davranışını değiştirmek mümkün olmadığı için, istenmeyen +özelliklerin döngünün içinde filtrelenmesi gerekir, bu da Object.prototype +nesnesinin hasOwnProperty metodu ile yapılır.

+ +

hasOwnProperty kullanarak filtrelemek

+
// yukarıdaki örnekteki foo nesnesi
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

Doğru kullanım bu yeni versiyonda gösterildiği gibidir. hasOwnProperty kontrol +edildiği için sadece moo yazacaktır. hasOwnProperty kullanılmaz ise ve +Object.prototype 'ın baz özellikleri değiştirilmişse, program bazı hatalara +yatkın olabilir.

+

Bunu yapan ve yaygın olarak kullanılan bir JavaScript sistemi Prototype +'dır. Bu sistemde hasOwnProperty kullanmayan for in döngüleri kesinlikle +hatalı sonuç verecektir.

+

Sonuç

+

hasOwnProperty her zaman kontrol edilmelidir. Programın içinde çalıştığı +ortam için, nesnelerin baz özelliklerinin değiştirilip değiştirilmediğine dair +hiçbir kabul yapılmamalıdır.

+

Fonksiyonlar

Fonksiyon Tanımlaması ve Fonksiyon İfadesi

Fonksiyonlar JavaScript'te birinci sınıf nesnelerdir, yani sıradan bir değer +gibi kullanılabilirler. Bu özellik sıklıkla bir isimsiz fonksiyonu başka bir +fonksiyona - ki bu muhtemelen asenkron bir fonksiyondur - callback olarak +geçirmekte kullanılır.

+

function tanımlaması

+
function foo() {}
+

Yukarıdaki fonksiyon tanımlaması program çalışmadan önce +yukarı taşınır ve böylece tanımlandığı kapsam içinde +her yerde (hatta tanımlanmadan önce bile) kullanılabilir.

+
foo(); // foo bu satır çalışmadan önce oluşturuldu
+function foo() {}
+

function ifadesi

+
var foo = function() {};
+

Bu örnekte isimsiz fonksiyon foo değişkenine atanır.

+
foo; // 'undefined'
+foo(); // Bu satır bir TypeError hatasına neden olur
+var foo = function() {};
+

Yukarıdaki var anahtar kelimesi bir bildirim olduğu için foo değişkeni +program çalışmadan önce yukarı alınır, program çalıştığında foo tanımlanmştır.

+

Fakat değer atamaları sadece program çalışırken gerçekleşeceği için, ilgili +satır çalıştığında, foo değişkeninin değeri varsayılan olarak +undefined olacaktır.

+

İsimli fonksiyon ifadesi

+

Bir başka özel durum isimli fonksiyon ifadesidir.

+
var foo = function bar() {
+    bar(); // Çalışır
+}
+bar(); // ReferenceError hatası verir
+

Burada bar fonksiyonuna dış kapsamdan ulaşılamaz, çünkü sadece foo +değişkenine atanmıştır; fakat iç kapsamda bar fonksiyonuna erişilebilir. +Bunun nedeni JavaScript'te isim çözümlemenin çalışma +şeklidir, fonksiyonun adına fonksiyonun içinden her zaman erişilebilir.

+

this Nasıl Çalışır

JavaScript'te this özel kelimesinin anlamı diğer programlama dillerinden +farklıdır. this kelimesinin birbirinden farklı anlamlar yüklendiği tam +beş durum vardır.

+

Genel kapsam

+
this;
+

this kelimesi genel kapsamda kullanıldığında global nesneye işaret eder.

+

Bir fonksiyon çağırma

+
foo();
+

Burada this yine global nesneye işaret eder.

+ +

Bir metod çağırma

+
test.foo(); 
+

Bu örnekte this kelimesi test 'e işaret edecektir.

+

Bir nesne oluşturucu çağırma

+
new foo(); 
+

Bir fonksiyon başında new anahtar kelimesi ile birlikte çağrılırsa bir +nesne oluşturucu olarak davranır. Bu fonksiyonun +içinde this kelimesi yeni oluşturulan Object 'e işaret eder.

+

this kelimesinin atanması

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // dizi aşağıdaki gibi açılır
+foo.call(bar, 1, 2, 3); // sonuç: a = 1, b = 2, c = 3
+

Function.prototype 'ın call veya apply metodları kullanıldığında, çağrılan +fonksiyonun içinde this 'in değeri ilk argümanın değeri olarak atanır.

+

Sonuç olarak, yukarıdaki örnekte metod çağırma durumu geçerli olmayacak, +bunun yerine foo fonksiyonu içinde this 'in değeri bar olacaktır.

+ +

Sık düşülen yanılgılar

+

Yukarıdaki durumların çoğu mantıklı görünse bile, ilk durum dilin tasarım +hatalarından biri olarak değerlendirilmelidir çünkü hiçbir pratik +kullanılımı yoktur.

+
Foo.method = function() {
+    function test() {
+        // this genel nesneye işaret eder
+    }
+    test();
+}
+

Bir başka yanılgı test fonksiyonunun içinde this 'in Foo 'ya işaret +edeceğinin sanılmasıdır, ama bu doğru değildir.

+

test fonksiyonu içinden Foo 'ya erişmenin yolu method içinde bir lokal +değişken oluşturmaktır.

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // Burada this yerine that kullanın
+    }
+    test();
+}
+

that kelimesinin dilde özel bir anlamı yoktur, ama sıklıkla dış kapsamdaki +this 'e işaret etmek için kullanılır. Bu yöntem closure +kavramı ile birlikte kullanıldığında this değerini program içinde taşımaya da +yarar.

+

Metodları değişkenlere atamak

+

JavaScript'te mevcut olmayan bir başka özellik de fonksiyon isimlendirmedir, +başka bir deyişle bir metodu bir değişkene atamak.

+
var test = someObject.methodTest;
+test();
+

İlk durum nedeniyle test artık sıradan bir fonksiyon olarak davranacaktır; bu +nedenle test fonksiyonu içinde this artık someObject 'e işaret +etmeyecektir.

+

this kelimesinin geç bağlanması ilk bakışta yanlış görünse de, aslında +prototipsel kalıtımı mümkün kılan şey budur.

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

Yukarıda Bar sınıfına ait bir nesnenin method 'u çağrıldığında this bu +nesneye işaret edecektir.

+

Closure ve Referanslar

JavaScript'in en güçlü özelliklerinden biri de closure 'lara sahip olmasıdır. +Bunun anlamı her hangi bir kapsamın her zaman kendisini içeren kapsama +erişebilmesidir. JavaScript'te tek kapsam fonksiyon kapsamı +olduğu için temelde tüm fonksiyonlar closure 'durlar.

+

Private değişkenler

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

Burada, Counter iki closure verir: increment fonksiyonu ve get +fonksiyonu. Bu iki fonksiyon da Counter fonksiyonun kapsamına ait bir +referans 'a sahiptir, ve bu nedenle söz konusu kapsamda tanımlanmış olan +count değişkenine erişebilirler.

+

Private değişkenler nasıl işler

+

JavaScript'te kapsam referanslarına erişmek yada atama yapmak mümkün olmadığı +için, dış kapsamdan count değişkenine ulaşmak mümkün değildir. Bu +değişkene ulaşmanın tek yolu yukarıdaki iki closure 'dur.

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

Bu program parçası Counter fonksiyonun kapsamındaki count değişkeninin +değerini değiştirmez, çünkü foo.hack bu kapsamda tanımlanmamıştır. +Bunun yerine global kapsamda yeni bir değişen oluşturur (yada mevcut bir +değişkeni değiştirir).

+

Döngü içinde closure

+

Sık yapılan bir hata, döngü içinde closure kullanıp döngünün indeks değişkeninin +değerinin kopyalanacağını varsaymaktır.

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);  
+    }, 1000);
+}
+

Yukarıdaki örnek çıktı olarak 0 - 9 arası sayıları vermek yerine, 10 +sayısını on kez yazacaktır.

+

İçteki isimsiz fonksiyon i değişkeninin değerine değil referansına sahiptir +ve console.log çağrıldığında, for döngüsü çoktan tamamlanmış ve i +değişkeninin değeri 10 olmuştur.

+

İstenen davranışı elde etmek için i değişkeninin değerinin kopyalanması +gerekir.

+

Referans probleminin çözümü

+

Döngünün indeks değişkeninin değerini kopyalamanın en iyi yolu bir +isimsiz fonksiyon kullanmaktır.

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);  
+        }, 1000);
+    })(i);
+}
+

Dıştaki isimsiz fonksiyon her adımda çağrılacak ve e parametresi olarak +i 'nin değerinin bir kopyası verilecektir.

+

setTimeOut fonksiyonuna verilen isimsiz fonksiyon artık e 'ye ait bir +referansa sahip olacaktır, ve referansın değeri döngü tarafından +değiştirilmeyecektir.

+

Bu davranışı başka bir yolla da elde etmek mümkündür; isimsiz fonksiyondan başka +bir fonksiyon döndürmek. Bu durumda yukarıdaki ile aynı davranış elde +edilecektir.

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+

arguments Nesnesi

JavaScript'te her fonksiyon kapsamında arguments adlı özel bir nesne +tanımlıdır. Bu nesne fonksiyon çağrılırken verilen argümanların listesini +içerir.

+ +

arguments nesnesi bir Array değildir. Bir dizinin özelliklerinin bir +kısmına sahip olsa da (length özelliği) Array.prototype sınıfından +türetilmemiştir, aslında bir Object bile değildir.

+

Bu nedenle, arguments nesnesi üzerinde push, pop ve slice gibi standart +dizi metotlarını kullanmak mümkün değildir. Klasik for döngüsü arguments +nesnesi ile kullanılabilir, ancak standart dizi metotlarını kullanmak için +gerçek bir diziye dönüştürmek gerekir.

+

Diziye dönüştürmek

+

Aşağıdaki program parçası arguments nesnesinin tüm elemanlarına sahip yeni bir +dizi verecektir.

+
Array.prototype.slice.call(arguments);
+

Bu dönüşüm yavaştır, ve performansın belirleyici olduğu durumlarda +kullanılması tavsiye olunmaz.

+

Argümanların geçirilmesi

+

Aşağıdaki örnekte, argümanların bir fonksiyondan diğerine geçirilmesi +için önerilen yöntem gösterilmiştir.

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // do stuff here
+}
+

Bir başka püf noktası da call ve apply 'ı birlikte kullanarak hızlı, +ilişkisiz fonksiyonlar yaratmaktır.

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// "method" 'un ilişkisiz bir versiyonunu yarat
+// Aldığı parametreler: this, arg1, arg2...argN
+Foo.method = function() {
+
+    // Sonuç: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+};
+

Tanımlı parametreler ve argüman indisleri

+

arguments nesnesi her iki özelliği ve fonksiyonun tanımlı parametreleri için +getter ve setter fonksiyonlar oluşturur.

+

Sonuç olarak, bir tanımlı parametrenin değerini değiştirmek arguments +nesnesindeki karşılık gelen özelliğin değerini de değiştirecektir.

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2                                                           
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

Performans mitleri ve gerçekler

+

arguments nesnesi fonksiyon kapsamında bir değişken veya tanımlı parametre +olarak kullanılmış olması durumları dışında her zaman oluşturulur. Kullanılıp +kullanılmaması fark etmez.

+

getter ve setter fonksiyonlar her zaman oluşturulur; dolayısıyla +arguments nesnesini kullanmanın performans üzerinde olumsuz bir etkisi yoktur, +özellikle de sadece arguments nesnesinin özelliklerine erişmekten ibaret +olmayan gerçek programlarda.

+ +

Fakat, modern JavaScript motorlarının performansını ciddi bir şekilde etkileyen +bir durum vardır. Bu durum arguments.callee nesnesinin kullanılmasıdır.

+
function foo() {
+    arguments.callee; // içinde olduğumuz fonksiyon nesnesi
+    arguments.callee.caller; // ve çağıran fonksiyon nesnesi
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // Normalde inline edilirdi...
+    }
+}
+

Yukarıdaki program parçasında, foo fonksiyonuna inlining uygulanması +mümkün değildir çünkü fonksiyonun hem kendisini ve kendisini çağıran fonksiyonu +bilmesi gerekmektedir. Bu yüzden hem inlining yapılamadığı için bir performans +artışı sağlanamamış hem de kapsüllenme bozulmuş olmaktadır, çünkü fonksiyon +artık kendisini çağıran kapsama bağımlı hale gelmiş olabilir.

+

arguments.callee ve özelliklerinin asla kullanılmaması +şiddetle tavsiye olunur.

+ +

Nesne Oluşturucular

JavaScript'te oluşturucular diğer dillerden farklıdır. Başında new bulunan +her fonksiyon çağrısı bir oluşturucudur.

+

Oluşturucunun (çağrılan fonksiyonun) içinde this 'in değeri yeni yaratılan +Object 'dir. Bu yeni nesnenin prototipi oluşturucu +olarak çağrılan fonksiyon nesnesinin prototipidir.

+

Çağrılan fonksiyonda bir return ifadesi yoksa, this (yani yeni nesneyi) +döndürür.

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

Yukarıdaki program Foo oluşturucusunu çağırır ve yeni yaratılan nesnenin +prototipini Foo.prototype olarak belirler.

+

Oluşturucunun içinde bir return ifadesi bulunması durumunda, ve sadece +bu değer bir Object ise oluşturucu fonksiyon verilen değeri döndürür.

+
function Bar() {
+    return 2;
+}
+new Bar(); // yeni bir Bar nesnesi
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // döndürülen nesne
+

new anahtar kelimesi ihmal edilirse, fonksiyon yeni bir nesne döndürmez.

+
function Foo() {
+    this.bla = 1; // global nesnenin özelliğini değiştirir
+}
+Foo(); // undefined
+

Yukarıdaki örnek bazı durumlarda doğru çalışıyor gibi görünebilir, ama +JavaScript'te this 'in çalışma şeklinden dolayı this +'in değeri global nesne olacaktır.

+

Nesne fabrikaları

+

new anahtar kelimesini ihmal edebilmek için oluşturucu fonksiyonun bir değer +döndürmesi gerekir.

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

Yukarıda Bar fonksiyonunu çağıran her iki ifade de aynı şeyi döndürecektir: +method adında bir closure özelliği olan yeni yaratılmış +bir nesne.

+

Başka bir nokta da new Bar() fonksiyonunun döndürülen nesnenin prototipini +etkilememesidir. Yeni nesnenin prototipi oluşturulacaktır ancak Bar bu +nesneyi döndürmez.

+

Yukarıdaki örnekte new anahtar kelimesini kullanmakla kullanmamak arasında +hiçbir bir fark yoktur.

+

Fabrikalar ile yeni nesneler oluşturmak

+

new anahtar kelimesinin kullanılmaması tavsiye edilir, çünkü unutulması +durumu hatalara sebep olabilir.

+

Bunun yerine yeni bir nesne oluşturmak için bir fabrika kullanılmalıdır.

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

Yukarıdaki örnek hem new anahtar kelimesinin unutulmasından etkilenmez hem de +private değikenlerin kullanılmasını kolaylaştırır, ama +bazı dezavantajları da vardır.

+
    +
  1. Oluşturulan nesneler bir prototip üzerinde metotlarını paylaşmadıkları +için daha fazla hafıza kullanılır.
  2. +
  3. Başka bir sınıf türetmek için fabrikanın tüm metotları başka bir nesneden +kopyalaması veya bu nesneyi yeni nesnenin prototipine yerleştirmesi gerekir.
  4. +
  5. Sadece new anahtar kelimesinin ihmal edilmesinden kaynaklanacak sorunları +gidermek için prototip zincirinden vazgeçmek dilin ruhuna aykırıdır.
  6. +
+

Sonuç

+

new anahtar kelimesini ihmal etmek hatalara neden olabilir, fakat bu +kesinlikle prototip zincirinden vazgeçmek için bir neden olamaz. Hangi +çözümün belirli bir programa uygun olduğu kararını verirken, en önemli nokta +nesne oluşturmak için belirli bir yöntemi seçip bu çözüme bağlı kalmaktır.

+

Kapsamlar ve İsim Uzayları

JavaScript'te birbiri ile eşleşen ayraçlar kullanılmasına karşın blok +kapsamı bulunmaz; bu nedenle, dilde sadece fonksiyon kapsamı mevcuttur.

+
function test() { // fonksiyon kapsamı
+    for(var i = 0; i < 10; i++) { // kapsam değil
+        // sayaç
+    }
+    console.log(i); // 10
+}
+ +

JavaScript'te isim uzayları kavramı da bulunmaz, tanımlanan herşey +genel olarak paylaşılmış tek bir isim uzayının içindedir.

+

Bir değişkene erişildiğinde, JavaScript değişkenin tanımını bulana dek yukarıya +doğru tüm kapsamlara bakar. Genel kapsama ulaşıldığı halde hala değişkenin +tanımı bulanamamışsa bir ReferenceError hatası oluşur.

+

Genel değişkenler felaketi

+
// A programı
+foo = '42';
+
+// B programı
+var foo = '42'
+

Yukarıdaki iki program birbirinden farklıdır. A programında genel kapsamda +bir foo değişkeni tanımlanmıştır, B programındaki foo değişkeni ise mevcut +kapsamda tanımlanmıştır.

+

Bu iki tanımlamanın birbirinden farklı etkileri olacaktır, var anahtar +kelimesini kullanmamanın önemli sonuçları olabilir.

+
// genel kapsam
+var foo = 42;
+function test() {
+    // lokal kapsam
+    foo = 21;
+}
+test();
+foo; // 21
+

test fonksiyonun içinde var anahtar kelimesinin atlanması genel kapsamdaki +foo değişkeninin değerini değiştirecektir. İlk bakışta bu önemsiz gibi görünse +de, binlerce satırlık bir programda var kullanılmaması korkunç ve takibi güç +hatalara neden olacaktır.

+
// genel kapsam
+var items = [/* bir dizi */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // subLoop fonksiyonun kapsamı
+    for(i = 0; i < 10; i++) { // var kullanılmamış
+        // do amazing stuff!
+    }
+}
+

Dışarıdaki döngüden subLoop fonksiyonu bir kez çağrıldıktan sonra çıkılacaktır, +çünkü subLoop i değişkeninin dış kapsamdaki değerini değiştirir. İkinci +for döngüsünde de var kullanılması bu hatayı kolayca engelleyecektir. +Bilinçli olarak dış kapsama erişilmek istenmiyorsa var ifadesi asla +atlanmamalıdır.

+

Lokal değişkenler

+

JavaScript'te lokal değişkenler sadece fonksiyon +parametreleri ve var ifadesi ile tanımlanan değişkenlerdir.

+
// genel kapsam
+var foo = 1;
+var bar = 2;
+var i = 2;
+
+function test(i) {
+    // test fonksiyonunun lokal kapsamı
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

test fonksiyonun içinde foo ve i lokal değişkenlerdir, bar değişkenine +değer atanması ise genel kapsamdaki aynı isimdeki değişkenin değerini +değiştirecektir.

+

Yukarı taşıma

+

JavaScript'te tanımlamalar yukarı taşınır. Yani hem var ifadesi hem de +function bildirimleri içindeki bulundukları kapsamın en üstüne taşınırlar.

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

Program çalışmadan önce yukarıdaki kod dönüştürülür. JavaScript, var +ifadelerini ve function bildirimlerini içinde bulundukları kapsamın en üstüne +taşır.

+
// var ifadeleri buraya taşınır
+var bar, someValue; // varsayılan değerleri 'undefined' olur
+
+// function bildirimi de yukarı taşınır
+function test(data) {
+    var goo, i, e; // blok kapsamı olmadığı için buraya taşınır
+    if (false) {
+        goo = 1;
+
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // bir TypeError hatası oluşur çünkü bar hala 'undefined'
+someValue = 42; // değer atamaları etkilenmez
+bar = function() {};
+
+test();
+

Blok kapsamının bulunmaması nedeniyle hem var ifadeleri döngülerin dışına +taşınır hem de bazı if ifadeleri anlaşılmaz sonuçlar verebilir.

+

Orijinal programda if ifadesi goo isimli genel değişkeni değiştiriyor gibi +görünüyordu, fakat yukarı taşımadan sonra anlaşıldığı gini aslında +lokal değişkeni değiştiriyor.

+

Yukarı taşıma dikkate alınmadığında aşağıdaki programın bir ReferenceError +oluşturacağı sanılabilir.

+
// SomeImportantThing değişkenine değer atanmış mı, kontrol et
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

Fakat var değişkeni genel kapsamın en üstüne taşınacağı için bu program +çalışacaktır.

+
var SomeImportantThing;
+
+// SomeImportantThing arada bir yerde atanmış olabilir
+
+// Değer atandığından emin ol
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

İsim çözümleme

+

JavaScript'te genel kapsam da dahil tüm kapsamlarda this +adında bir özel değişken tanımlanmıştır, bu değişken geçerli nesneyi gösterir.

+

Fonksiyon kapsamlarında aynı zamanda arguments adında +bir değişken tanımlanmıştır ve fonksiyonun argümanlarını içerir.

+

Örnek olarak bir fonksiyon kapsamında foo değişkenine eriğildiğinde JavaScript +isim çözümlemeyi aşağıdaki sıra ile yapacaktır:

+
    +
  1. Geçerli kapsamda bir var foo ifadesi mevcutsa bu kullanılır.
  2. +
  3. Fonksiyonun parametrelerinden birinin adı foo ise bu kullanılır.
  4. +
  5. Fonksiyonun kendisinin adı foo ise bu kullanılır.
  6. +
  7. Bir dıştaki kapsama geçilir ve yeniden 1 adımına dönülür.
  8. +
+ +

İsim uzayları

+

Tek bir genel isim uzayının bulunmasının yol açtığı yaygın sonuç isim +çakışmasıdır. JavaScript'te bu sorun isimsiz fonksiyonlar ile kolayca +önlenebilir.

+
(function() {
+    // bir "isim uzayı"
+
+    window.foo = function() {
+        // korunmasız bir closure
+    };
+
+})(); // fonksiyonu hemen çalıştır
+

İsimsiz fonksiyonlar ifade olarak değerlendirilir; +bu nedenle çağrılabilmeleri için önce değerlendirilmeleri gerekir.

+
( // parantezin içindeki fonksiyonu değerlendir
+function() {}
+) // ve fonksiyon nesnesini döndür
+() // değerlendirmenin sonucu fonksiyon nesnesini çağır
+

Bir fonksiyon ifadesini değerlendirip çağırmanın başka yolları da vardır ve +yukarıdaki ile aynı sonucu verirler.

+
// İki farklı yöntem
++function(){}();
+(function(){}());
+

Sonuç

+

Programı kendi isim uzayı ile kapsamak için her zaman isimsiz fonksiyonların +kullanılması tavsiye edilir. Böylece hem isim çakışmalarından korunulmuş olunur, +hem de programlar daha modüler halde yazılmış olur.

+

Ayrıca, genel değişkenlerin kullanılması kötü bir uygulamadır. Genel +değişkenlerin herhangi bir şekilde kullanılmış olması programın kötü yazılmış +olduğuna, hatalara eğilimli olduğuna ve sürdürülmesinin zor olacağına işaret +eder.

+

Diziler

Dizi İterasyonu ve Özellikleri

Diziler JavaScript nesneleri olmalarına rağmen, iterasyon yapmak için +for in döngüsü kullanmak için bir neden yoktur. +Aslında dizilerde for in kullanılmasına karşı bazı iyi nedenler +vardır.

+ +

for in döngüsü prototip zincirindeki tüm özellikleri dolaştığı için ve bunu +engellemenin tek yolu hasOwnProperty kullanmak +olduğu için for in döngüsü sıradan bir for döngüsünden yirmi kata kadar +daha yavaştır.

+

İterasyon

+

Dizilerde iterasyon yaparken en iyi performansı elde etmenin en iyi yolu klasik +for döngüsünü kullanmaktır.

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

Yukarıdaki örnekte bir optimizasyon var, o da dizinin uzunluğun iterasyonun +başında l = list.length ile saklanmış olması.

+

length özelliği dizinin kendisinde tariflenmiş olmasına rağmen, her adımda +bu özelliği okumanın yine de bir maliyeti vardır. Modern JavaScript motorları +bu tür durumlar için muhtemelen optimizasyon yapıyor olsa bile, programın +her zaman modern bir motorda çalışacağından emin olmak mümkün değildir.

+

Aslında, yukarıdaki örnekteki optimizasyonu uygulamamak döngünün +iki kat daha yavaş çalışmasına neden olabilir.

+

length özelliği

+

length özelliğine değer atanarak diziyi kısaltmak için kullanılabilir.

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo; // [1, 2, 3]
+

Daha küçük bir uzunluk atanması diziyi kısaltır, fakat daha büyük bir uzunluk +atanmasının dizi üzerinde bir etkisi yoktur.

+

Sonuç

+

En iyi performans için her zaman sıradan for döngüsü kullanılmalı ve +length özelliği saklanmalıdır. Dizilerde for in döngüsünün kullanılmış +olması hatalara meyilli kötü yazılmış bir programa işaret eder.

+

Array Oluşturucusu

Array oluşturucusunun parametrelerini nasıl değerlendirdiği belirsiz olduğu +için, yeni diziler oluşturulurken her zaman dizi sabitlerinin ([] +notasyonu) kullanılması tavsiye olunur.

+
[1, 2, 3]; // Sonuç: [1, 2, 3]
+new Array(1, 2, 3); // Sonuç: [1, 2, 3]
+
+[3]; // Sonuç: [3]
+new Array(3); // Sonuç: []
+new Array('3') // Sonuç: ['3']
+

Array oluşturucusuna tek bir argüman verildiğinde, ve bu argümanın türü +Number ise, oluşacak boş dizinin length özelliği argümanın +değerine eşit olacaktır. Bu şekilde oluşturulan bir dizinin sadece +length özelliği belirlenmiş olup dizi indisleri tanımsız olacaktır.

+
var arr = new Array(3);
+arr[1]; // undefined
+1 in arr; // false, indisler atanmadı
+

Dizinin uzunluğunu bu şekilde önceden belirlemek sadece bir iki durumda +kullanışlıdır. Bunlardan birisi bir döngüye gerek olmadan bir karakter +katarını tekrarlamaktır.

+
new Array(count + 1).join(stringToRepeat);
+

Sonuç

+

Array oluşturucusunun kullanılmasından mümkün olduğu kadar kaçınılmalıdır. +Bunun yerine her zaman dizi sabitleri tercih edilmelidir. Hem daha kısadırlar +hem de daha anlaşılır bir sentaksa sahiptirler; bu nedenle programın +okunabilirliğini de artırırlar.

+

Nesne Tipleri

Eşitlik ve Karşılaştırmalar

JavaScript'de nesnelerin değerlerinin eşitliğini kontrol etmenin iki farklı yolu +vardır.

+

Eşitlik operatörü

+

Eşitlik operatörü iki adet eşittir işaretinden oluşur: ==

+

JavaScript weakly typed bir dildir. Bu nedenle, eşitlik operatörü ile +değişkenleri karşılaştırırken tip dönüşümü yapar.

+
""           ==   "0"           // false
+0            ==   ""            // true
+0            ==   "0"           // true
+false        ==   "false"       // false
+false        ==   "0"           // true
+false        ==   undefined     // false
+false        ==   null          // false
+null         ==   undefined     // true
+" \t\r\n"    ==   0             // true
+

Yukarıdaki tablo tip dönüşümünün sonuçlarını verir, ve == kullanımının kötü +bir uygulama olarak değerlendirilmesinin başlıca sebebidir. Bu karmaşık dönüşüm +kuralları tespit edilmesi zor hatalara neden olur.

+

Ayrıca tip dönüşümü işin içine girdiğinde performans üzerinde de olumsuz etkisi +olur; mesela, bir katarın bir sayı ile karşılaştırılabilmesi için önce bir +sayıya dönüştürülmesi gerekir.

+

Kesin eşitlik operatörü

+

Kesin eşitlik operatörü üç adet eşittir işaretinden oluşur: ===

+

Eşitlik operatörünün aksine, keşin eşitlik operatörü karşılaştırdığı değerler +arasında tip dönüşümü yapmaz.

+
""           ===   "0"           // false
+0            ===   ""            // false
+0            ===   "0"           // false
+false        ===   "false"       // false
+false        ===   "0"           // false
+false        ===   undefined     // false
+false        ===   null          // false
+null         ===   undefined     // false
+" \t\r\n"    ===   0             // false
+

Yukarıdaki sonuçlar hem daha anlaşılırdır, hem de progamdaki hataların erkenden +ortaya çıkmasını sağlar. Bu programı bir miktar sağlamlaştırır ve ayrıca +karşılaştırılan değerlerin farklı tiplerden olması durumunda performansı da +artırır.

+

Nesneleri karşılaştırmak

+

Hem == hem de === operatörlerinin eşitlik operatörü olarak +adlandırılmasına rağmen, değerlerden en azından birinin bir Object olması +durumunda farklı davranış gösterirler.

+
{} === {};                   // false
+new String('foo') === 'foo'; // false
+new Number(10) === 10;       // false
+var foo = {};
+foo === foo;                 // true
+

Bu durumda her iki operatör de eşitlik değil aynılık karşılaştırması +yapar; yani, terimlerin aynı nesnenin örnekleri olup olmadığını kontrol +ederler, tıpkı Python dilindeki is ve C dilindeki gösterici karşılaştırması +gibi.

+

Sonuç

+

Sadece kesin eşitlik operatörünün kullanılması şiddetle tavsiye edilir. +Tip dönüşümü yapılmasının gerekli olduğu durumlarda, bu açıkça +yapılmalıdır ve dilin karmaşık dönüşüm kurallarına bırakılmamalıdır.

+

typeof Operatörü

typeof operatörü (instanceof ile birlikte) +herhalde JavaScript'in en büyük tasarım hatalarından biridir, çünkü neredeyse +tamamen arızalıdır.

+

instanceof operatörünün sınırlı kullanımı olsa da, typeof operatörünün +gerçekte tek bir pratik kullanımı vardır, ve bunun da bir nesnenin tipini +kontrol etmekle ilgili yoktur.

+ +

JavaScript tip tablosu

+
Değer               Sınıf      Tip
+-------------------------------------
+"foo"               String     string
+new String("foo")   String     object
+1.2                 Number     number
+new Number(1.2)     Number     object
+true                Boolean    boolean
+new Boolean(true)   Boolean    object
+new Date()          Date       object
+new Error()         Error      object
+[1,2,3]             Array      object
+new Array(1, 2, 3)  Array      object
+new Function("")    Function   function
+/abc/g              RegExp     object (function in Nitro/V8)
+new RegExp("meow")  RegExp     object (function in Nitro/V8)
+{}                  Object     object
+new Object()        Object     object
+

Yukarıdaki tabloda Tip sütunu typeof operatörünün verdiği sonucu gösterir. +Açıkça görülebileceği gibi, bu sonuç tutarlı olmaktan çok uzaktır.

+

Sınıf sütunu bir nesnenin dahili [[Class]] özelliğini gösterir.

+ +

[[Class]] özelliğinin değerini almak için Object.prototype 'ın toString +metodu kullanılmalıdır.

+

Bir nesnenin sınıfı

+

Spesifikasyona göre [[Class]] değerine erişmenin tek yolu +Object.prototype.toString kullanmaktır.

+
function is(type, obj) {
+    var clas = Object.prototype.toString.call(obj).slice(8, -1);
+    return obj !== undefined && obj !== null && clas === type;
+}
+
+is('String', 'test'); // true
+is('String', new String('test')); // true
+

Yukarıdaki örnekte, Object.prototype.toString çağrıldığında +this 'in değeri [[Class]] değeri aranan nesne olarak +atanmış olmaktadır.

+ +

Bir değişkenin tanımlandığını kontrol etmek

+
typeof foo !== 'undefined'
+

Yukarıdaki satır foo değişkeninin tanımlanıp tanımlanmadığını belirler; +tanımlanmamış bir değişkene erişmek bir ReferenceError hatası oluştur. +typeof operatörünün tek kullanışlı olduğu şey işte budur.

+

Sonuç

+

Bir nesnenin tipini kontrol etmek için Object.prototype.toString 'in +kullanılması şiddetle tavsiye edilir; çünkü bunu yapmanın tek güvenilir yoludur. +Yukarıdaki tip tablosunda gösterildiği gibi, typeof operatörünün bazı +sonuçları spesifikasyonda tanımlanmamıştır; bu nedenle, çeşitli platformlarda +farklılık gösterebilirler.

+

Bir değişkenin tanımlandığını kontrol etmek dışında, typeof operatörün +kullanımından her ne pahasına olursa olsun kaçınılmalıdır.

+

instanceof Operatörü

instanceof operatörü verilen iki terimin nesne oluşturucularını karşılaştırır. +Kullanışlı olduğu tek durum özel nesnelerin karşılaştırılmasıdır. Temel nesneler +üzerinde kullanıldığında neredeyse typeof operatörü kadar +yararsızdır.

+

Özel nesneleri karşılaştırmak

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // true
+new Bar() instanceof Foo; // true
+
+// Bu satır sadece Bar.prototype'a Foo fonksiyon nesnesinin atar
+// Bir Foo sınıfı nesnesine değil
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // false
+

Temel nesnelerle instanceof kullanımı

+
new String('foo') instanceof String; // true
+new String('foo') instanceof Object; // true
+
+'foo' instanceof String; // false
+'foo' instanceof Object; // false
+

Dikkat edilmesi gereken ilginç bir nokta, instanceof operatörünün farklı +JavaScript kaynaklarından gelen nesneler üzerinde çalışmamasıdır (mesela bir +internet tarayıcısının farklı dökümanları), çünkü bu durumda nesne +oluşturucuları aynı olmayacaktır.

+

Sonuç

+

instanceof operatörü sadece aynı JavaScript kaynağından gelen özel +nesneler ile kullanılmalıdır. Tıpkı typeof operatöründe +olduğu gibi, bunun dışındaki tüm kullanımlarından kaçınılmalıdır.

+

Tip Dönüşümleri

JavaScript weakly typed bir dildir, bu yüzden mümkün olan yerlerde +tip dönüşümü uygular.

+
// Bunlar true verir
+new Number(10) == 10; // Number.toString() tekrar sayıya
+                      // dönüştürülür
+
+10 == '10';           // Katarlar sayıya dönüştürülür
+10 == '+10 ';         // Bir başka katar çılgınlığı
+10 == '010';          // Ve bir tane daha 
+isNaN(null) == false; // null 0'a dönüştürülür
+                      // tabii 0 NaN değildir
+
+// Bunlar false verir
+10 == 010;
+10 == '-10';
+ +

Yukarıdakilerden kaçınmak için, kesin eşitlik operatörünün +kullanılması şiddetle tavsiye edilir. Böylece yaygın hataların çoğundan +kaçınılabilir, yine de JavaScript'in weak typing sisteminden kaynaklanan başka +sorunlar da vadır.

+

Temel tiplerin nesne oluşturucuları

+

Number ve String gibi temel tiplerin nesne oluşturucuları new anahtar +kelimesi ile kullanılıp kullanılmamalarına göre farklı davranış gösterir.

+
new Number(10) === 10;     // False, Object ve Number
+Number(10) === 10;         // True, Number ve Number
+new Number(10) + 0 === 10; // True, tip dönüşümü nedeniyle
+

Number gibi bir temel tipin nesne oluşturucusunu kullanmak yeni bir Number +nesnesi yaratacaktır, fakat new kelimesi kullanılmazsa Number fonksiyonu +bir dönüştürücü olarak davranacaktır.

+

Ayrıca, sabitler ve nesne olmayan değerler kullanılması durumunda başka tür +dönüşümler de söz konusu olacaktır.

+

En iyi seçenek üç olası tipten birine açıkça dönüşüm yapılmasıdır.

+

Karakter katarına dönüştürmek

+
'' + 10 === '10'; // true
+

Bir değerin başına boş bir katar eklenerek kolayca katara dönüştürülebilir.

+

Sayıya dönüştürmek

+
+'10' === 10; // true
+

Tek terimli toplama operatörü kullanılarak bir değer sayıya dönüştürülebilir.

+

Mantıksal değişken tipine dönüştürmek

+

Değil operatörü iki kez üst üste kullanılarak bir değer mantıksal değişken +tipine dönüştürülebilir.

+
!!'foo';   // true
+!!'';      // false
+!!'0';     // true
+!!'1';     // true
+!!'-1'     // true
+!!{};      // true
+!!true;    // true
+

Temel

Neden eval Kullanılmamalı

eval fonksiyonu bir JavaScript kodunu lokal kapsamda yürütür.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

Fakat eval sadece direkt olarak çağrıldığında ve çağrılan fonksiyonun +adı eval ise lokal kapsamda çalışır.

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

eval fonksiyonu asla kullanılmamalıdır. Kullanıldığı durumların %99.9'unda +eval kullanılmadan da istenen sonuç elde edilebilir.

+

Gizli eval

+

Zamanlama fonksiyonları setTimeout ve setInterval'ın her +ikisinin de ilk argümanları bir karakter katarıdır. Bu durumda eval dolaylı +olarak çağrıldığı için bu argüman her zaman genel kapsamda yürütülecektir.

+

Güvenlik sorunları

+

eval kendisine verilen her kodu işlettiği için aynı zamanda bir güvenlik +sorunudur ve asla kaynağı bilinmeyen yada güvenilir olmayan karakter +katarları ile kullanılmamalıdır.

+

Sonuç

+

eval asla kullanılmamalıdır, kullanan programlar ise doğruluk, performans ve +güvenlik açılarından sorgulanmalıdır. eval kullanımı gerekli görülmüşse, +programın tasarımı sorgulanmalı ve kullanılmamalı, bunun yerine eval +gerektirmeyen daha iyi bir tasarım kullanılmalıdır.

+

undefined ve null

JavaScript'te tanımsız anlamına gelen iki değer vardır, ve bunlardan +undefined daha kullanışlıdır.

+

undefined değeri

+

undefined bir değişken türüdür ve tek bir değere sahip olabilir: undefined.

+

JavaScript'te ayrıca değeri undefined olan bir de genel kapsam değişkeni +tanımlanmıştır ve bu değişkenin adı da undefined'dır. Fakat bu değişken +bir sabit yada dilin anahtar kelimelerinden biri değildir. Yani bu +değişkenin değeri kolayca değiştirilebilir.

+ +

undefined değerinin verildiği durumlara bazı örnekler:

+
    +
  • Genel kapsamlı undefined değişkeninin (değiştirilmedi ise) değeri
  • +
  • return ifadesi içermeyen fonksiyonların verdiği değer
  • +
  • Bir değer döndürmeyen return ifadeleri
  • +
  • Mevcut olmayan nesne özellikleri
  • +
  • Değer atanmamış fonksiyon parametreleri
  • +
  • Değeri undefined olarak atanmış değişkenler
  • +
+

undefined değerinin değiştirilmesi durumu

+

Genel kapsamdaki undefined değişkeni asıl undefined değerinin kopyasını +tuttuğu için, bu değeri değiştirmek undefined değişken türünün değerini +değiştirmez.

+

Fakat, bir şeyi undefined ile karşılaştırmak için önce undefined'ın değerini +geri almak gerekir.

+

Programı undefined değişkeninin değiştirilmesi olasılığına karşı korumak için +uygulanan yaygın bir yöntem isimsiz bir fonksiyona +kullanılmayan bir parametre eklemektir.

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // lokal kapsamda undefined değişkeni
+    // yine undefined değerine sahip
+
+})('Hello World', 42);
+

Benzer bir yöntem yine isimsiz fonksiyonun içinde değer atanmamış bir değişken +deklare etmektir.

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

Buradaki tek fark program sıkıştırılırsa ortaya çıkacaktır, eğer fonksiyonun +başka bir yerinde var ifadesi kullanılmıyorsa fazladan 4 bayt kullanılmış +olacaktır.

+

null kullanımı

+

JavaScript dilinde undefined geleneksel null yerine kullanılmaktadır, asıl +null (hem null değişmezi hem de değişken türü) ise kabaca başka bir +veri türüdür.

+

null JavaScript içinde kapalı olarak kullanılır (mesela prototip zincirinin +sonuna gelindiği Foo.prototype = null ile belirtilir), fakat hemen her durumda +bunun yerine undefined kullanılabilir.

+

Otomatik Noktalı Virgül İlavesi

JavaScript sentaksı C'ye benzese de, noktalı virgül kullanılması +zorunlu değildir.

+

Fakat JavaScript noktalı virgül kullanmayan bir dil değildir, hatta +programı anlayabilmek için noktalı virgüllere ihtiyaç duyar. Bu yüzden +JavaScript gramer çözümleyicisi eksik bir noktalı virgül yüzünden bir +hata ile karşılaştığında otomatik olarak eksik noktalı virgülleri +ekler.

+
var foo = function() {
+} // hata, noktalı virgül gerekiyor
+test()
+

Eklemeden sonra çözümleme tekrarlanır.

+
var foo = function() {
+}; // hata ortadan kalktı, çözümleme devam edebilir
+test()
+

Noktalı virgüllerin bu şekilde otomatik olarak eklenmesi JavaScript'in +en büyük tasarım hatalarından biri olarak kabul edilir, çünkü programın +davranışını değiştirmesi mümkündür.

+

Ekleme nasıl olur

+

Aşağıdaki örnekte hiç noktalı virgül yok, bu yüzden nereye noktalı virgül +eklenmesi gerektiğini gramer çözümleyicinin karar vermesi gerekiyor.

+
(function(window, undefined) {
+    function test(options) {
+        log('testing!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+
+})(window)
+

Çözümleyicinin "tahmin" oyununun sonucu aşağıdadır.

+
(function(window, undefined) {
+    function test(options) {
+
+        // Eklenmedi, satırlar birleştirildi
+        log('testing!')(options.list || []).forEach(function(i) {
+
+        }); // <- eklendi
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        ); // <- eklendi
+
+        return; // <- eklendi, return ifadesi bozuldu
+        { // bir blok olarak değerlendirildi
+
+            // bir yer etiketi ve bir ifade
+            foo: function() {} 
+        }; // <- eklendi
+    }
+    window.test = test; // <- eklendi
+
+// Burada da satırlar birleştirildi
+})(window)(function(window) {
+    window.someLibrary = {}; // <- eklendi
+
+})(window); //<- eklendi
+ +

Çözümleyici yukarıdaki program parçasının davranışını büyük ölçüde değiştirdi, +belirli durumlarda da grameri değerlendirirken yanlış kararlar verdi.

+

Satır başındaki parantezler

+

Bir satırın parantez ile başlaması durumunda, çözümleyici noktalı virgül +eklemez.

+
log('testing!')
+(options.list || []).forEach(function(i) {})
+

Yukarıdaki program parçası aşağıdaki tek satıra dönüşür.

+
log('testing!')(options.list || []).forEach(function(i) {})
+

Büyük ihtimalle yukarıdaki log bir fonksiyon döndürmüyordur; +bu nedenle, yukarıdaki satır undefined is not a function hata mesajı ile bir +TypeError oluştumasına neden olacaktır.

+

Sonuç

+

Noktalı virgüllerin hiç bir zaman ihmal edilmemesi tavsiye edilir, ayrıca +ayraçların kendilerinden önceki ifade ile aynı satırda tutulması ve tek satırlık +if ve else ifadelerinde bile ayraçların ihmal edilmemesi önerilir. Her iki +önlem de hem programın tutarlılığını artıracak, hem de JavaScript +çözümleyicisinin programın davranışını değiştirmesini engelleyecektir.

+

delete Operatörü

Kısacası, genel kapsamda tanımlanmış değişkenleri, fonksiyonları ve DontDelete +niteliğine sahip bazı başka şeyleri silmek imkansızdır.

+

Genel kapsam ve fonksiyon kapsamı

+

Bir değişken veya fonksiyon genel kapsamda veya +fonksiyon kapsamında tanımlandığında aktivasyon nesnesinin +veya global nesnenin bir özelliği olacaktır. Bu tür özelliklerin bir takım +nitelikleri vardır ve bunlardan biri DontDelete niteliğidir. Genel kapsamda ve +fonksiyon kapsamında tanımlanan değişkenler ve fonksiyonlar yaratıldıklarında +her zaman DontDelete niteliğine sahip olacaktır, ve bu nedenle silinemezler.

+
// genel kapsam değişkeni:
+var a = 1; // DontDelete niteliğine sahip
+delete a; // false
+a; // 1
+
+// normal bir fonksiyon:
+function f() {} // DontDelete niteliğine sahip
+delete f; // false
+typeof f; // "function"
+
+// başka bir değişkene atamak işe yaramaz:
+f = 1;
+delete f; // false
+f; // 1
+

Açıkça tanımlanan özellikler

+

Açıkça tanımlanan özellikleri silmek mümkündür.

+
// tanımlanan özellik:
+var obj = {x: 1};
+obj.y = 2;
+delete obj.x; // true
+delete obj.y; // true
+obj.x; // undefined
+obj.y; // undefined
+

Yukarıdaki örnekte obj.x ve obj.y silinebilir çünkü DontDelete niteliğine +sahip değillerdir. Aynı nedenle aşağıdakini yapmak da mümkündür:

+
// IE hariç çalışır:
+var GLOBAL_OBJECT = this;
+GLOBAL_OBJECT.a = 1;
+a === GLOBAL_OBJECT.a; // true - genel değişken
+delete GLOBAL_OBJECT.a; // true
+GLOBAL_OBJECT.a; // undefined
+

Burada a'yı silmek için bir hile kullanıyoruz. this +burada genel nesneye işaret ediyor ve a değişkenini onun özelliği olarak +atıyoruz, ve böylece onu silebiliyoruz.

+

IE (en azından 6-8) bazı hatalar içerdiğinden yukarıdaki örnek çalışmayacaktır.

+

Fonksiyon argümanları ve önceden tanımlı özellikler

+

Fonksiyonlara verilen argümanlar, arguments nesnesi +ve önceden tanımlı özellikler de DontDelete niteliğine sahiptir.

+
// fonksiyon argümanları ve özellikler:
+(function (x) {
+  delete arguments; // false
+  typeof arguments; // "object"
+
+  delete x; // false
+  x; // 1
+
+  function f(){}
+  delete f.length; // false
+  typeof f.length; // "number"
+})(1);
+

Host nesneler

+

Host nesneler üzerinde kullanıldığında delete operatörünün davranışı belirsiz +olabilir. Standarda göre host nesneler istedikleri davranışı uygulayabilirler.

+

Sonuç

+

delete operatörünün davranışı genellikle belirsizdir ve güvenle kullanılabileceği +tek yer sıradanan nesneler üzerinde açıkça tanımlanan özelliklerdir.

+

Diğer

setTimeout ve setInterval

JavaScript asenkron olduğu için setTimeout ve setInterval kullanarak bir +fonksiyonun ileri bir zamanda çalışmasını sağlamak mümkündür.

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // 0'dan büyük bir sayı verir
+

Yukarıdaki örnekte setTimeout fonksiyonu çağrıldığında, oluşturulan +zamanlayıcı tanımlayan bir ID sayısı verir ve foo fonksiyonu yaklaşık +bin milisaniye sonra çalıştırılmak üzere programlanır. foo fonksiyonu +tam olarak bir kez çağrılacaktır.

+

Kullanılan JavaScript motorunun zamanlayıcı hassasiyetine bağlı olarak, ve +ayrıca JavaScript tek thread ile çalıştığı ve çalışan başka program +parçaları bu tek thread 'i bloke edeceği için, setTimeout ile belirlenen +erteleme süresinin tam olarak gerçekleşeceği hiçbir şekilde garanti +edilemez.

+

İlk argüman olarak verilen fonksiyon global nesne tarafından çağrılacaktır, +yani çağrılan fonksiyonun içinde this bu nesneye işaret +edecektir.

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // this global nesneye işaret eder
+        console.log(this.value); // undefined yazar
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

setInterval ile fonksiyon çağrılarının yığılması

+

setTimeout verilen fonksiyonu bir kez çağırırken, setInterval (adından da +anlaşılacağı gibi) verilen fonksiyonu her X milisaniyede bir çağırır. +Fakat kullanılması önerilmez.

+

Mevcut program parçası çalışırken zamanlama bloke olduğu halde, setInterval +verilen fonksiyonu çağırmaya devam edecektir. Bu da, özellikle küçük aralıklarla +kullanıldığında, fonksiyon çağrılarının istiflenmesine neden olur.

+
function foo(){
+    // 1 saniye süren bir işlem
+}
+setInterval(foo, 1000);
+

Yukarıdaki örnekte foo fonksiyonu bir kez çağrılıp bir saniye boyunca bloke +edecektir.

+

foo programı bloke etmişken, setInterval fonksiyon çağrılarını zamanlamaya +devam edecektir. foo tamamlandığında, çalıştırılmayı bekleyen on çağrı +daha olacaktır.

+

Bloke eden programlarla başa çıkmak

+

En kolay ve kontrol edilebilir çözüm, setTimeout 'u fonksiyonun içinde +kullanmaktır.

+
function foo(){
+    // 1 saniye süren bir işlem
+    setTimeout(foo, 1000);
+}
+foo();
+

Bu örnekte hem setTimeout çağrısı fonksiyonun kendisi içinde kapsanmış olmakta, +hem de fonksiyon çağrılarının istiflenmesinin önüne geçilerek daha fazla kontrol +sağlanmaktadır. Artık foo fonksiyonunun kendisi tekrar çalışmak isteyip +istemediğine karar verebilir.

+

Zamanlayıcıları iptal etmek

+

Zamanlayıcıları iptal etmek için ilgili ID sayıları ile kullanılan zamanlayıcı +fonksiyonuna karşılık gelen clearTimeout ve clearInterval fonksiyonlarından +biri kullanılır.

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

Tüm zamanlayıcıları iptal etmek

+

Tüm zamanlayıcıları iptal etmenin dahili bir yolu olmadığı için, bu amaca +ancak kaba kuvvetle ulaşılabilir.

+
// "tüm" zamanlayıcıları iptal et
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

Bu rastgele seçilmiş sayıdan etkilenmeyen zamanlayıcılar kalabilir; bu yüzden +tüm zamanlayıcı ID'lerinin saklanarak, teker teker iptal edilmeleri tavsiye +edilir.

+

eval fonksiyonun gizli kullanımı

+

setTimeout ve setInterval fonksiyonları ilk parametreleri olarak bir katar +da kabul eder. Bu özellik asla kullanılmamalıdır, çünkü bu durumda dahili +olarak eval kullanılır.

+ +
function foo() {
+    // setTimeOut ile bu fonksiyon çağrılacaktır
+}
+
+function bar() {
+    function foo() {
+        // bu fonksiyon çağrılmayacaktır
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

Bu durumda eval direkt olarak çağrılmadığı için, setTimeout +fonksiyonuna verilen katar genel kapsamda çalıştırılacaktır; bu nedenle, +bar fonksiyonu kapsamındaki lokal foo değişkenini kullanmayacaktır.

+

Zamanlama fonksiyonlarına verilen fonksiyona argüman sağlamak için de bir katar +kullanılması tavsiye edilmez.

+
function foo(a, b, c) {}
+
+// ASLA bu şekilde kullanılmamalı
+setTimeout('foo(1, 2, 3)', 1000)
+
+// Bunu yerine isimsiz bir fonksiyon kullanın
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

Sonuç

+

setTimeout veya setInterval fonksiyonlarına asla bir katar parametre +verilmemelidir. Bu kullanım çok kötü bir programa işaret eder. Çağrılan +fonksiyona argümanlar verilmesinin gerektiği durumlarda gerçek çağrıyı içinde +bulunduran bir isimsiz fonksiyon kullanılmalıdır.

+

Ayrıca, setInterval fonksiyonu çalışan JavaScript programı tarafından bloke +olmadığı için tercih edilmemelidir.

+
\ No newline at end of file diff --git a/zh/index.html b/zh/index.html new file mode 100644 index 0000000..7c62a22 --- /dev/null +++ b/zh/index.html @@ -0,0 +1,1255 @@ +JavaScript 秘密花园

简介

对象

对象使用和属性

JavaScript 中所有变量都是对象,除了两个例外 nullundefined

+
false.toString(); // 'false'
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

一个常见的误解是数字的字面值(literal)不是对象。这是因为 JavaScript 解析器的一个错误, +它试图将点操作符解析为浮点数字面值的一部分。

+
2.toString(); // 出错:SyntaxError
+

有很多变通方法可以让数字的字面值看起来像对象。

+
2..toString(); // 第二个点号可以正常解析
+2 .toString(); // 注意点号前面的空格
+(2).toString(); // 2先被计算
+

对象作为数据类型

+

JavaScript 的对象可以作为哈希表使用,主要用来保存命名的键与值的对应关系。

+

使用对象的字面语法 - {} - 可以创建一个简单对象。这个新创建的对象从 Object.prototype +继承下面,没有任何自定义属性

+
var foo = {}; // 一个空对象
+
+// 一个新对象,拥有一个值为12的自定义属性'test'
+var bar = {test: 12}; 
+

访问属性

+

有两种方式来访问对象的属性,点操作符或者中括号操作符。

+
var foo = {name: 'kitten'}
+foo.name; // kitten
+foo['name']; // kitten
+
+var get = 'name';
+foo[get]; // kitten
+
+foo.1234; // SyntaxError
+foo['1234']; // works
+

两种语法是等价的,但是中括号操作符在下面两种情况下依然有效

+
    +
  • 动态设置属性
  • +
  • 属性名不是一个有效的变量名(译者注比如属性名中包含空格,或者属性名是 JS 的关键词)
  • +
+ +

删除属性

+

删除属性的唯一方法是使用 delete 操作符;设置属性为 undefined 或者 null 并不能真正的删除属性, +而仅仅是移除了属性和值的关联。

+
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

上面的输出结果有 bar undefinedfoo null - 只有 baz 被真正的删除了,所以从输出结果中消失。

+

属性名的语法

+
var test = {
+    'case': 'I am a keyword so I must be notated as a string',
+    delete: 'I am a keyword too so me' // 出错:SyntaxError
+};
+

对象的属性名可以使用字符串或者普通字符声明。但是由于 JavaScript 解析器的另一个错误设计, +上面的第二种声明方式在 ECMAScript 5 之前会抛出 SyntaxError 的错误。

+

这个错误的原因是 delete 是 JavaScript 语言的一个关键词;因此为了在更低版本的 JavaScript 引擎下也能正常运行, +必须使用字符串字面值声明方式。

+

原型

JavaScript 不包含传统的类继承模型,而是使用 prototypal 原型模型。

+

虽然这经常被当作是 JavaScript 的缺点被提及,其实基于原型的继承模型比传统的类继承还要强大。 +实现传统的类继承模型是很简单,但是实现 JavaScript 中的原型继承则要困难的多。 +(It is for example fairly trivial to build a classic model on top of it, while the +other way around is a far more difficult task.)

+

由于 JavaScript 是唯一一个被广泛使用的基于原型继承的语言,所以理解两种继承模式的差异是需要一定时间的。

+

第一个不同之处在于 JavaScript 使用原型链的继承方式。

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// 设置Bar的prototype属性为Foo的实例对象
+Bar.prototype = new Foo();
+Bar.prototype.foo = 'Hello World';
+
+// 修正Bar.prototype.constructor为Bar本身
+Bar.prototype.constructor = Bar;
+
+var test = new Bar() // 创建Bar的一个新实例
+
+// 原型链
+test [Bar的实例]
+    Bar.prototype [Foo的实例] 
+        { foo: 'Hello World' }
+        Foo.prototype
+            {method: ...};
+            Object.prototype
+                {toString: ... /* etc. */};
+

上面的例子中,test 对象从 Bar.prototypeFoo.prototype 继承下来;因此, +它能访问 Foo 的原型方法 method。同时,它也能够访问那个定义在原型上的 Foo 实例属性 value。 +需要注意的是 new Bar() 不会创造出一个新的 Foo 实例,而是 +重复使用它原型上的那个实例;因此,所有的 Bar 实例都会共享相同value 属性。

+ +

属性查找

+

当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止。

+

到查找到达原型链的顶部 - 也就是 Object.prototype - 但是仍然没有找到指定的属性,就会返回 undefined

+

原型属性

+

当原型属性用来创建原型链时,可以把任何类型的值赋给它(prototype)。 +然而将原子类型赋给 prototype 的操作将会被忽略。

+
function Foo() {}
+Foo.prototype = 1; // 无效
+

而将对象赋值给 prototype,正如上面的例子所示,将会动态的创建原型链。

+

性能

+

如果一个属性在原型链的上端,则对于查找时间将带来不利影响。特别的,试图获取一个不存在的属性将会遍历整个原型链。

+

并且,当使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问。

+

扩展内置类型的原型

+

一个错误特性被经常使用,那就是扩展 Object.prototype 或者其他内置类型的原型对象。

+

这种技术被称之为 monkey patching 并且会破坏封装。虽然它被广泛的应用到一些 JavaScript 类库中比如 Prototype, +但是我仍然不认为为内置类型添加一些非标准的函数是个好主意。

+

扩展内置类型的唯一理由是为了和新的 JavaScript 保持一致,比如 Array.forEach

+ +

总结

+

在写复杂的 JavaScript 应用之前,充分理解原型链继承的工作方式是每个 JavaScript 程序员必修的功课。 +要提防原型链过长带来的性能问题,并知道如何通过缩短原型链来提高性能。 +更进一步,绝对不要扩展内置类型的原型,除非是为了和新的 JavaScript 引擎兼容。

+

hasOwnProperty 函数

为了判断一个对象是否包含自定义属性而不是原型链上的属性, +我们需要使用继承自 Object.prototypehasOwnProperty 方法。

+ +

hasOwnProperty 是 JavaScript 中唯一一个处理属性但是查找原型链的函数。

+
// 修改Object.prototype
+Object.prototype.bar = 1; 
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // true
+
+foo.hasOwnProperty('bar'); // false
+foo.hasOwnProperty('goo'); // true
+

只有 hasOwnProperty 可以给出正确和期望的结果,这在遍历对象的属性时会很有用。 +没有其它方法可以用来排除原型链上的属性,而不是定义在对象自身上的属性。

+

hasOwnProperty 作为属性

+

JavaScript 不会保护 hasOwnProperty 被非法占用,因此如果一个对象碰巧存在这个属性, +就需要使用外部hasOwnProperty 函数来获取正确的结果。

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Here be dragons'
+};
+
+foo.hasOwnProperty('bar'); // 总是返回 false
+
+// 使用其它对象的 hasOwnProperty,并将其上下文设置为foo
+({}).hasOwnProperty.call(foo, 'bar'); // true
+

结论

+

当检查对象上某个属性是否存在时,hasOwnProperty唯一可用的方法。 +同时在使用 for in loop 遍历对象时,推荐总是使用 hasOwnProperty 方法, +这将会避免原型对象扩展带来的干扰。

+

for in 循环

in 操作符一样,for in 循环同样在查找对象属性时遍历原型链上的所有属性。

+ +
// 修改 Object.prototype
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // 输出两个属性:bar 和 moo
+}
+

由于不可能改变 for in 自身的行为,因此有必要过滤出那些不希望出现在循环体中的属性, +这可以通过 Object.prototype 原型上的 hasOwnProperty 函数来完成。

+ +

使用 hasOwnProperty 过滤

+
// foo 变量是上例中的
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

这个版本的代码是唯一正确的写法。由于我们使用了 hasOwnProperty,所以这次输出 moo。 +如果不使用 hasOwnProperty,则这段代码在原生对象原型(比如 Object.prototype)被扩展时可能会出错。

+

一个广泛使用的类库 Prototype 就扩展了原生的 JavaScript 对象。 +因此,当这个类库被包含在页面中时,不使用 hasOwnProperty 过滤的 for in 循环难免会出问题。

+

总结

+

推荐总是使用 hasOwnProperty。不要对代码运行的环境做任何假设,不要假设原生对象是否已经被扩展了。

+

函数

函数声明与表达式

函数是JavaScript中的一等对象,这意味着可以把函数像其它值一样传递。 +一个常见的用法是把匿名函数作为回调函数传递到异步函数中。

+

函数声明

+
function foo() {}
+

上面的方法会在执行前被 解析(hoisted),因此它存在于当前上下文的任意一个地方, +即使在函数定义体的上面被调用也是对的。

+
foo(); // 正常运行,因为foo在代码运行前已经被创建
+function foo() {}
+

函数赋值表达式

+
var foo = function() {};
+

这个例子把一个匿名的函数赋值给变量 foo

+
foo; // 'undefined'
+foo(); // 出错:TypeError
+var foo = function() {};
+

由于 var 定义了一个声明语句,对变量 foo 的解析是在代码运行之前,因此 foo 变量在代码运行时已经被定义过了。

+

但是由于赋值语句只在运行时执行,因此在相应代码执行之前, foo 的值缺省为 undefined

+

命名函数的赋值表达式

+

另外一个特殊的情况是将命名函数赋值给一个变量。

+
var foo = function bar() {
+    bar(); // 正常运行
+}
+bar(); // 出错:ReferenceError
+

bar 函数声明外是不可见的,这是因为我们已经把函数赋值给了 foo; +然而在 bar 内部依然可见。这是由于 JavaScript 的 命名处理 所致, +函数名在函数内总是可见的。

+

this 的工作原理

JavaScript 有一套完全不同于其它语言的对 this 的处理机制。 +在种不同的情况下 ,this 指向的各不相同。

+

全局范围内

+
this;
+

当在全部范围内使用 this,它将会指向全局对象。

+ +

函数调用

+
foo();
+

这里 this 也会指向全局对象。

+ +

方法调用

+
test.foo(); 
+

这个例子中,this 指向 test 对象。

+

调用构造函数

+
new foo(); 
+

如果函数倾向于和 new 关键词一块使用,则我们称这个函数是 构造函数。 +在函数内部,this 指向新创建的对象。

+

显式的设置 this

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // 数组将会被扩展,如下所示
+foo.call(bar, 1, 2, 3); // 传递到foo的参数是:a = 1, b = 2, c = 3
+

当使用 Function.prototype 上的 call 或者 apply 方法时,函数内的 this 将会被 +显式设置为函数调用的第一个参数。

+

因此函数调用的规则在上例中已经不适用了,在foo 函数内 this 被设置成了 bar

+ +

常见误解

+

尽管大部分的情况都说的过去,不过第一个规则(译者注这里指的应该是第二个规则,也就是直接调用函数时,this 指向全局对象) +被认为是JavaScript语言另一个错误设计的地方,因为它从来就没有实际的用途。

+
Foo.method = function() {
+    function test() {
+        // this 将会被设置为全局对象(译者注:浏览器环境中也就是 window 对象)
+    }
+    test();
+}
+

一个常见的误解是 test 中的 this 将会指向 Foo 对象,实际上不是这样子的。

+

为了在 test 中获取对 Foo 对象的引用,我们需要在 method 函数内部创建一个局部变量指向 Foo 对象。

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // 使用 that 来指向 Foo 对象
+    }
+    test();
+}
+

that 只是我们随意起的名字,不过这个名字被广泛的用来指向外部的 this 对象。 +在 闭包 一节,我们可以看到 that 可以作为参数传递。

+

方法的赋值表达式

+

另一个看起来奇怪的地方是函数别名,也就是将一个方法赋值给一个变量。

+
var test = someObject.methodTest;
+test();
+

上例中,test 就像一个普通的函数被调用;因此,函数内的 this 将不再被指向到 someObject 对象。

+

虽然 this 的晚绑定特性似乎并不友好,但是这确实基于原型继承赖以生存的土壤。

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

method 被调用时,this 将会指向 Bar 的实例对象。

+

闭包和引用

闭包是 JavaScript 一个非常重要的特性,这意味着当前作用域总是能够访问外部作用域中的变量。 +因为 函数 是 JavaScript 中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。

+

模拟私有变量

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

这里,Counter 函数返回两个闭包,函数 increment 和函数 get。 这两个函数都维持着 +对外部作用域 Counter 的引用,因此总可以访问此作用域内定义的变量 count.

+

为什么不可以在外部访问私有变量

+

因为 JavaScript 中不可以对作用域进行引用或赋值,因此没有办法在外部访问 count 变量。 +唯一的途径就是通过那两个闭包。

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

上面的代码不会改变定义在 Counter 作用域中的 count 变量的值,因为 foo.hack 没有 +定义在那个作用域内。它将会创建或者覆盖全局变量 count

+

循环中的闭包

+

一个常见的错误出现在循环中使用闭包,假设我们需要在每次循环中调用循环序号

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);  
+    }, 1000);
+}
+

上面的代码不会输出数字 09,而是会输出数字 10 十次。

+

console.log 被调用的时候,匿名函数保持对外部变量 i 的引用,此时 for循环已经结束, i 的值被修改成了 10.

+

为了得到想要的结果,需要在每次循环中创建变量 i拷贝

+

避免引用错误

+

为了正确的获得循环序号,最好使用 匿名包裹器译者注其实就是我们通常说的自执行匿名函数)。

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);  
+        }, 1000);
+    })(i);
+}
+

外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。

+

当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。

+

有另一个方法完成同样的工作;那就是从匿名包装器中返回一个函数。这和上面的代码效果一样。

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+

arguments 对象

JavaScript 中每个函数内都能访问一个特别变量 arguments。这个变量维护着所有传递到这个函数中的参数列表。

+ +

arguments 变量不是一个数组(Array)。 +尽管在语法上它有数组相关的属性 length,但它不从 Array.prototype 继承,实际上它是一个对象(Object)。

+

因此,无法对 arguments 变量使用标准的数组方法,比如 push, pop 或者 slice。 +虽然使用 for 循环遍历也是可以的,但是为了更好的使用数组方法,最好把它转化为一个真正的数组。

+

转化为数组

+

下面的代码将会创建一个新的数组,包含所有 arguments 对象中的元素。

+
Array.prototype.slice.call(arguments);
+

这个转化比较,在性能不好的代码中不推荐这种做法。

+

传递参数

+

下面将参数从一个函数传递到另一个函数,是推荐的做法。

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // do stuff here
+}
+

另一个技巧是同时使用 callapply,创建一个快速的解绑定包装器。

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// Create an unbound version of "method" 
+// 输入参数为: this, arg1, arg2...argN
+Foo.method = function() {
+
+    // 结果: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+};
+

译者注:上面的 Foo.method 函数和下面代码的效果是一样的:

+
Foo.method = function() {
+    var args = Array.prototype.slice.call(arguments);
+    Foo.prototype.method.apply(args[0], args.slice(1));
+};
+

自动更新

+

arguments 对象为其内部属性以及函数形式参数创建 gettersetter 方法。

+

因此,改变形参的值会影响到 arguments 对象的值,反之亦然。

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2                                                           
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

性能真相

+

arguments 对象总会被创建,除了两个特殊情况 - 作为局部变量声明和作为形式参数。 +而不管它是否有被使用。

+

argumentsgetterssetters 方法总会被创建;因此使用 arguments 对性能不会有什么影响。 +除非是需要对 arguments 对象的属性进行多次访问。

+ +

译者注MDC 中对 strict mode 模式下 arguments 的描述有助于我们的理解,请看下面代码:

+
// 阐述在 ES5 的严格模式下 `arguments` 的特性
+function f(a) {
+  "use strict";
+  a = 42;
+  return [a, arguments[0]];
+}
+var pair = f(17);
+assert(pair[0] === 42);
+assert(pair[1] === 17);
+

然而,的确有一种情况会显著的影响现代 JavaScript 引擎的性能。这就是使用 arguments.callee

+
function foo() {
+    arguments.callee; // do something with this function object
+    arguments.callee.caller; // and the calling function object
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // Would normally be inlined...
+    }
+}
+

上面代码中,foo 不再是一个单纯的内联函数 inlining译者注:这里指的是解析器可以做内联处理), +因为它需要知道它自己和它的调用者。 +这不仅抵消了内联函数带来的性能提升,而且破坏了封装,因此现在函数可能要依赖于特定的上下文。

+

因此强烈建议大家不要使用 arguments.callee 和它的属性。

+ +

构造函数

JavaScript 中的构造函数和其它语言中的构造函数是不同的。 +通过 new 关键字方式调用的函数都被认为是构造函数。

+

在构造函数内部 - 也就是被调用的函数内 - this 指向新创建的对象 Object。 +这个新创建的对象的 prototype 被指向到构造函数的 prototype

+

如果被调用的函数没有显式的 return 表达式,则隐式的会返回 this 对象 - 也就是新创建的对象。

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

上面代码把 Foo 作为构造函数调用,并设置新创建对象的 prototypeFoo.prototype

+

显式的 return 表达式将会影响返回结果,但仅限于返回的是一个对象。

+
function Bar() {
+    return 2;
+}
+new Bar(); // 返回新创建的对象
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // 返回的对象
+

译者注new Bar() 返回的是新创建的对象,而不是数字的字面值 2。 +因此 new Bar().constructor === Bar,但是如果返回的是数字对象,结果就不同了,如下所示

+
function Bar() {
+    return new Number(2);
+}
+new Bar().constructor === Number
+

译者注这里得到的 new Test()是函数返回的对象,而不是通过new关键字新创建的对象,因此:

+
(new Test()).value === undefined
+(new Test()).foo === 1
+

如果 new 被遗漏了,则函数不会返回新创建的对象。

+
function Foo() {
+    this.bla = 1; // 获取设置全局参数
+}
+Foo(); // undefined
+

虽然上例在有些情况下也能正常运行,但是由于 JavaScript 中 this 的工作原理, +这里的 this 指向全局对象

+

工厂模式

+

为了不使用 new 关键字,构造函数必须显式的返回一个值。

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

上面两种对 Bar 函数的调用返回的值完全相同,一个新创建的拥有 method 属性的对象被返回, +其实这里创建了一个闭包

+

还需要注意, new Bar()不会改变返回对象的原型(译者注也就是返回对象的原型不会指向 Bar.prototype)。 +因为构造函数的原型会被指向到刚刚创建的新对象,而这里的 Bar 没有把这个新对象返回(译者注:而是返回了一个包含 method 属性的自定义对象)。

+

在上面的例子中,使用或者不使用 new 关键字没有功能性的区别。

+

译者注上面两种方式创建的对象不能访问 Bar 原型链上的属性,如下所示:

+
var bar1 = new Bar();
+typeof(bar1.method); // "function"
+typeof(bar1.foo); // "undefined"
+
+var bar2 = Bar();
+typeof(bar2.method); // "function"
+typeof(bar2.foo); // "undefined"
+

通过工厂模式创建新对象

+

我们常听到的一条忠告是不要使用 new 关键字来调用函数,因为如果忘记使用它就会导致错误。

+

为了创建新对象,我们可以创建一个工厂方法,并且在方法内构造一个新对象。

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

虽然上面的方式比起 new 的调用方式不容易出错,并且可以充分利用私有变量带来的便利, +但是随之而来的是一些不好的地方。

+
    +
  1. 会占用更多的内存,因为新创建的对象不能共享原型上的方法。
  2. +
  3. 为了实现继承,工厂方法需要从另外一个对象拷贝所有属性,或者把一个对象作为新创建对象的原型。
  4. +
  5. 放弃原型链仅仅是因为防止遗漏 new 带来的问题,这似乎和语言本身的思想相违背。
  6. +
+

总结

+

虽然遗漏 new 关键字可能会导致问题,但这并不是放弃使用原型链的借口。 +最终使用哪种方式取决于应用程序的需求,选择一种代码书写风格并坚持下去才是最重要的。

+

作用域与命名空间

尽管 JavaScript 支持一对花括号创建的代码段,但是并不支持块级作用域; +而仅仅支持 函数作用域

+
function test() { // 一个作用域
+    for(var i = 0; i < 10; i++) { // 不是一个作用域
+        // count
+    }
+    console.log(i); // 10
+}
+ +

译者注如果 return 对象的左括号和 return 不在一行上就会出错。

+
// 译者注:下面输出 undefined
+function add(a, b) {
+    return 
+        a + b;
+}
+console.log(add(1, 2));
+

JavaScript 中没有显式的命名空间定义,这就意味着所有对象都定义在一个全局共享的命名空间下面。

+

每次引用一个变量,JavaScript 会向上遍历整个作用域直到找到这个变量为止。 +如果到达全局作用域但是这个变量仍未找到,则会抛出 ReferenceError 异常。

+

隐式的全局变量

+
// 脚本 A
+foo = '42';
+
+// 脚本 B
+var foo = '42'
+

上面两段脚本效果不同。脚本 A 在全局作用域内定义了变量 foo,而脚本 B 在当前作用域内定义变量 foo

+

再次强调,上面的效果完全不同,不使用 var 声明变量将会导致隐式的全局变量产生。

+
// 全局作用域
+var foo = 42;
+function test() {
+    // 局部作用域
+    foo = 21;
+}
+test();
+foo; // 21
+

在函数 test 内不使用 var 关键字声明 foo 变量将会覆盖外部的同名变量。 +起初这看起来并不是大问题,但是当有成千上万行代码时,不使用 var 声明变量将会带来难以跟踪的 BUG。

+
// 全局作用域
+var items = [/* 数组 */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // subLoop 函数作用域
+    for(i = 0; i < 10; i++) { // 没有使用 var 声明变量
+        // 干活
+    }
+}
+

外部循环在第一次调用 subLoop 之后就会终止,因为 subLoop 覆盖了全局变量 i。 +在第二个 for 循环中使用 var 声明变量可以避免这种错误。 +声明变量时绝对不要遗漏 var 关键字,除非这就是期望的影响外部作用域的行为。

+

局部变量

+

JavaScript 中局部变量只可能通过两种方式声明,一个是作为函数参数,另一个是通过 var 关键字声明。

+
// 全局变量
+var foo = 1;
+var bar = 2;
+var i = 2;
+
+function test(i) {
+    // 函数 test 内的局部作用域
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

fooi 是函数 test 内的局部变量,而对 bar 的赋值将会覆盖全局作用域内的同名变量。

+

变量声明提升(Hoisting)

+

JavaScript 会提升变量声明。这意味着 var 表达式和 function 声明都将会被提升到当前作用域的顶部。

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

上面代码在运行之前将会被转化。JavaScript 将会把 var 表达式和 function 声明提升到当前作用域的顶部。

+
// var 表达式被移动到这里
+var bar, someValue; // 缺省值是 'undefined'
+
+// 函数声明也会提升
+function test(data) {
+    var goo, i, e; // 没有块级作用域,这些变量被移动到函数顶部
+    if (false) {
+        goo = 1;
+
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // 出错:TypeError,因为 bar 依然是 'undefined'
+someValue = 42; // 赋值语句不会被提升规则(hoisting)影响
+bar = function() {};
+
+test();
+

没有块级作用域不仅导致 var 表达式被从循环内移到外部,而且使一些 if 表达式更难看懂。

+

在原来代码中,if 表达式看起来修改了全部变量 goo,实际上在提升规则被应用后,却是在修改局部变量

+

如果没有提升规则(hoisting)的知识,下面的代码看起来会抛出异常 ReferenceError

+
// 检查 SomeImportantThing 是否已经被初始化
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

实际上,上面的代码正常运行,因为 var 表达式会被提升到全局作用域的顶部。

+
var SomeImportantThing;
+
+// 其它一些代码,可能会初始化 SomeImportantThing,也可能不会
+
+// 检查是否已经被初始化
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

译者注在 Nettuts+ 网站有一篇介绍 hoisting 的文章,其中的代码很有启发性。

+
// 译者注:来自 Nettuts+ 的一段代码,生动的阐述了 JavaScript 中变量声明提升规则
+var myvar = 'my value';  
+
+(function() {  
+    alert(myvar); // undefined  
+    var myvar = 'local value';  
+})();  
+

名称解析顺序

+

JavaScript 中的所有作用域,包括全局作用域,都有一个特别的名称 this 指向当前对象。

+

函数作用域内也有默认的变量 arguments,其中包含了传递到函数中的参数。

+

比如,当访问函数内的 foo 变量时,JavaScript 会按照下面顺序查找:

+
    +
  1. 当前作用域内是否有 var foo 的定义。
  2. +
  3. 函数形式参数是否有使用 foo 名称的。
  4. +
  5. 函数自身是否叫做 foo
  6. +
  7. 回溯到上一级作用域,然后从 #1 重新开始。
  8. +
+ +

命名空间

+

只有一个全局作用域导致的常见错误是命名冲突。在 JavaScript中,这可以通过 匿名包装器 轻松解决。

+
(function() {
+    // 函数创建一个命名空间
+
+    window.foo = function() {
+        // 对外公开的函数,创建了闭包
+    };
+
+})(); // 立即执行此匿名函数
+

匿名函数被认为是 表达式;因此为了可调用性,它们首先会被执行。

+
( // 小括号内的函数首先被执行
+function() {}
+) // 并且返回函数对象
+() // 调用上面的执行结果,也就是函数对象
+

有一些其他的调用函数表达式的方法,比如下面的两种方式语法不同,但是效果一模一样。

+
// 另外两种方式
++function(){}();
+(function(){}());
+

结论

+

推荐使用匿名包装器译者注也就是自执行的匿名函数)来创建命名空间。这样不仅可以防止命名冲突, +而且有利于程序的模块化。

+

另外,使用全局变量被认为是不好的习惯。这样的代码倾向于产生错误和带来高的维护成本。

+

数组

数组遍历与属性

虽然在 JavaScript 中数组是对象,但是没有好的理由去使用 for in 循环 遍历数组。 +相反,有一些好的理由不去使用 for in 遍历数组。

+ +

由于 for in 循环会枚举原型链上的所有属性,唯一过滤这些属性的方式是使用 hasOwnProperty 函数, +因此会比普通的 for 循环慢上好多倍。

+

遍历

+

为了达到遍历数组的最佳性能,推荐使用经典的 for 循环。

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

上面代码有一个处理,就是通过 l = list.length 来缓存数组的长度。

+

虽然 length 是数组的一个属性,但是在每次循环中访问它还是有性能开销。 +可能最新的 JavaScript 引擎在这点上做了优化,但是我们没法保证自己的代码是否运行在这些最近的引擎之上。

+

实际上,不使用缓存数组长度的方式比缓存版本要慢很多。

+

length 属性

+

length 属性的 getter 方式会简单的返回数组的长度,而 setter 方式会截断数组。

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo; // [1, 2, 3]
+

译者注: +在 Firebug 中查看此时 foo 的值是: [1, 2, 3, undefined, undefined, undefined] +但是这个结果并不准确,如果你在 Chrome 的控制台查看 foo 的结果,你会发现是这样的: [1, 2, 3] +因为在 JavaScript 中 undefined 是一个变量,注意是变量不是关键字,因此上面两个结果的意义是完全不相同的。

+
// 译者注:为了验证,我们来执行下面代码,看序号 5 是否存在于 foo 中。
+5 in foo; // 不管在 Firebug 或者 Chrome 都返回 false
+foo[5] = undefined;
+5 in foo; // 不管在 Firebug 或者 Chrome 都返回 true
+

length 设置一个更小的值会截断数组,但是增大 length 属性值不会对数组产生影响。

+

结论

+

为了更好的性能,推荐使用普通的 for 循环并缓存数组的 length 属性。 +使用 for in 遍历数组被认为是不好的代码习惯并倾向于产生错误和导致性能问题。

+

Array 构造函数

由于 Array 的构造函数在如何处理参数时有点模棱两可,因此总是推荐使用数组的字面语法 - [] - 来创建数组。

+
[1, 2, 3]; // 结果: [1, 2, 3]
+new Array(1, 2, 3); // 结果: [1, 2, 3]
+
+[3]; // 结果: [3]
+new Array(3); // 结果: [] 
+new Array('3') // 结果: ['3']
+
+// 译者注:因此下面的代码将会使人很迷惑
+new Array(3, 4, 5); // 结果: [3, 4, 5] 
+new Array(3) // 结果: [],此数组长度为 3
+ +

由于只有一个参数传递到构造函数中(译者注:指的是 new Array(3); 这种调用方式),并且这个参数是数字,构造函数会返回一个 length 属性被设置为此参数的空数组。 +需要特别注意的是,此时只有 length 属性被设置,真正的数组并没有生成。

+ +
var arr = new Array(3);
+arr[1]; // undefined
+1 in arr; // false, 数组还没有生成
+

这种优先于设置数组长度属性的做法只在少数几种情况下有用,比如需要循环字符串,可以避免 for 循环的麻烦。

+
new Array(count + 1).join(stringToRepeat);
+ +

结论

+

应该尽量避免使用数组构造函数创建新数组。推荐使用数组的字面语法。它们更加短小和简洁,因此增加了代码的可读性。

+

类型

相等与比较

JavaScript 有两种方式判断两个值是否相等。

+

等于操作符

+

等于操作符由两个等号组成:==

+

JavaScript 是弱类型语言,这就意味着,等于操作符会为了比较两个值而进行强制类型转换

+
""           ==   "0"           // false
+0            ==   ""            // true
+0            ==   "0"           // true
+false        ==   "false"       // false
+false        ==   "0"           // true
+false        ==   undefined     // false
+false        ==   null          // false
+null         ==   undefined     // true
+" \t\r\n"    ==   0             // true
+

上面的表格展示了强制类型转换,这也是使用 == 被广泛认为是不好编程习惯的主要原因, +由于它的复杂转换规则,会导致难以跟踪的问题。

+

此外,强制类型转换也会带来性能消耗,比如一个字符串为了和一个数字进行比较,必须事先被强制转换为数字。

+

严格等于操作符

+

严格等于操作符由个等号组成:===

+

不像普通的等于操作符,严格等于操作符不会进行强制类型转换。

+
""           ===   "0"           // false
+0            ===   ""            // false
+0            ===   "0"           // false
+false        ===   "false"       // false
+false        ===   "0"           // false
+false        ===   undefined     // false
+false        ===   null          // false
+null         ===   undefined     // false
+" \t\r\n"    ===   0             // false
+

上面的结果更加清晰并有利于代码的分析。如果两个操作数类型不同就肯定不相等也有助于性能的提升。

+

比较对象

+

虽然 ===== 操作符都是等于操作符,但是当其中有一个操作数为对象时,行为就不同了。

+
{} === {};                   // false
+new String('foo') === 'foo'; // false
+new Number(10) === 10;       // false
+var foo = {};
+foo === foo;                 // true
+

这里等于操作符比较的不是值是否相等,而是是否属于同一个身份;也就是说,只有对象的同一个实例才被认为是相等的。 +这有点像 Python 中的 is 和 C 中的指针比较。

+

结论

+

强烈推荐使用严格等于操作符。如果类型需要转换,应该在比较之前显式的转换, +而不是使用语言本身复杂的强制转换规则。

+

typeof 操作符

typeof 操作符(和 instanceof 一起)或许是 JavaScript 中最大的设计缺陷, +因为几乎不可能从它们那里得到想要的结果。

+

尽管 instanceof 还有一些极少数的应用场景,typeof 只有一个实际的应用(译者注这个实际应用是用来检测一个对象是否已经定义或者是否已经赋值), +而这个应用却不是用来检查对象的类型。

+ +

JavaScript 类型表格

+
Value               Class      Type
+-------------------------------------
+"foo"               String     string
+new String("foo")   String     object
+1.2                 Number     number
+new Number(1.2)     Number     object
+true                Boolean    boolean
+new Boolean(true)   Boolean    object
+new Date()          Date       object
+new Error()         Error      object
+[1,2,3]             Array      object
+new Array(1, 2, 3)  Array      object
+new Function("")    Function   function
+/abc/g              RegExp     object (function in Nitro/V8)
+new RegExp("meow")  RegExp     object (function in Nitro/V8)
+{}                  Object     object
+new Object()        Object     object
+

上面表格中,Type 一列表示 typeof 操作符的运算结果。可以看到,这个值在大多数情况下都返回 "object"。

+

Class 一列表示对象的内部属性 [[Class]] 的值。

+ +

为了获取对象的 [[Class]],我们需要使用定义在 Object.prototype 上的方法 toString

+

对象的类定义

+

JavaScript 标准文档只给出了一种获取 [[Class]] 值的方法,那就是使用 Object.prototype.toString

+
function is(type, obj) {
+    var clas = Object.prototype.toString.call(obj).slice(8, -1);
+    return obj !== undefined && obj !== null && clas === type;
+}
+
+is('String', 'test'); // true
+is('String', new String('test')); // true
+

上面例子中,Object.prototype.toString 方法被调用,this 被设置为了需要获取 [[Class]] 值的对象。

+

译者注Object.prototype.toString 返回一种标准格式字符串,所以上例可以通过 slice 截取指定位置的字符串,如下所示:

+
Object.prototype.toString.call([])    // "[object Array]"
+Object.prototype.toString.call({})    // "[object Object]"
+Object.prototype.toString.call(2)    // "[object Number]"
+ +

译者注这种变化可以从 IE8 和 Firefox 4 中看出区别,如下所示:

+
// IE8
+Object.prototype.toString.call(null)    // "[object Object]"
+Object.prototype.toString.call(undefined)    // "[object Object]"
+
+// Firefox 4
+Object.prototype.toString.call(null)    // "[object Null]"
+Object.prototype.toString.call(undefined)    // "[object Undefined]"
+

测试为定义变量

+
typeof foo !== 'undefined'
+

上面代码会检测 foo 是否已经定义;如果没有定义而直接使用会导致 ReferenceError 的异常。 +这是 typeof 唯一有用的地方。

+

结论

+

为了检测一个对象的类型,强烈推荐使用 Object.prototype.toString 方法; +因为这是唯一一个可依赖的方式。正如上面表格所示,typeof 的一些返回值在标准文档中并未定义, +因此不同的引擎实现可能不同。

+

除非为了检测一个变量是否已经定义,我们应尽量避免使用 typeof 操作符。

+

instanceof 操作符

instanceof 操作符用来比较两个操作数的构造函数。只有在比较自定义的对象时才有意义。 +如果用来比较内置类型,将会和 typeof 操作符 一样用处不大。

+

比较自定义对象

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // true
+new Bar() instanceof Foo; // true
+
+// 如果仅仅设置 Bar.prototype 为函数 Foo 本身,而不是 Foo 构造函数的一个实例
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // false
+

instanceof 比较内置类型

+
new String('foo') instanceof String; // true
+new String('foo') instanceof Object; // true
+
+'foo' instanceof String; // false
+'foo' instanceof Object; // false
+

有一点需要注意,instanceof 用来比较属于不同 JavaScript 上下文的对象(比如,浏览器中不同的文档结构)时将会出错, +因为它们的构造函数不会是同一个对象。

+

结论

+

instanceof 操作符应该仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。 +正如 typeof 操作符一样,任何其它的用法都应该是避免的。

+

类型转换

JavaScript 是弱类型语言,所以会在任何可能的情况下应用强制类型转换

+
// 下面的比较结果是:true
+new Number(10) == 10; // Number.toString() 返回的字符串被再次转换为数字
+
+10 == '10';           // 字符串被转换为数字
+10 == '+10 ';         // 同上
+10 == '010';          // 同上 
+isNaN(null) == false; // null 被转换为数字 0
+                      // 0 当然不是一个 NaN(译者注:否定之否定)
+
+// 下面的比较结果是:false
+10 == 010;
+10 == '-10';
+ +

为了避免上面复杂的强制类型转换,强烈推荐使用严格的等于操作符。 +虽然这可以避免大部分的问题,但 JavaScript 的弱类型系统仍然会导致一些其它问题。

+

内置类型的构造函数

+

内置类型(比如 NumberString)的构造函数在被调用时,使用或者不使用 new 的结果完全不同。

+
new Number(10) === 10;     // False, 对象与数字的比较
+Number(10) === 10;         // True, 数字与数字的比较
+new Number(10) + 0 === 10; // True, 由于隐式的类型转换
+

使用内置类型 Number 作为构造函数将会创建一个新的 Number 对象, +而在不使用 new 关键字的 Number 函数更像是一个数字转换器。

+

另外,在比较中引入对象的字面值将会导致更加复杂的强制类型转换。

+

最好的选择是把要比较的值显式的转换为三种可能的类型之一。

+

转换为字符串

+
'' + 10 === '10'; // true
+

将一个值加上空字符串可以轻松转换为字符串类型。

+

转换为数字

+
+'10' === 10; // true
+

使用一元的加号操作符,可以把字符串转换为数字。

+

译者注字符串转换为数字的常用方法:

+
+'010' === 10
+Number('010') === 10
+parseInt('010', 10) === 10  // 用来转换为整数
+
++'010.2' === 10.2
+Number('010.2') === 10.2
+parseInt('010.2', 10) === 10
+

转换为布尔型

+

通过使用 操作符两次,可以把一个值转换为布尔型。

+
!!'foo';   // true
+!!'';      // false
+!!'0';     // true
+!!'1';     // true
+!!'-1'     // true
+!!{};      // true
+!!true;    // true
+

核心

为什么不要使用 eval

eval 函数会在当前作用域中执行一段 JavaScript 代码字符串。

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

但是 eval 只在被直接调用并且调用函数就是 eval 本身时,才在当前作用域中执行。

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

译者注上面的代码等价于在全局作用域中调用 eval,和下面两种写法效果一样:

+
// 写法一:直接调用全局作用域下的 foo 变量
+var foo = 1;
+function test() {
+    var foo = 2;
+    window.foo = 3;
+    return foo;
+}
+test(); // 2
+foo; // 3
+
+// 写法二:使用 call 函数修改 eval 执行的上下文为全局作用域
+var foo = 1;
+function test() {
+    var foo = 2;
+    eval.call(window, 'foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

任何情况下我们都应该避免使用 eval 函数。99.9% 使用 eval 的场景都有不使用 eval 的解决方案。

+

伪装的 eval

+

定时函数 setTimeoutsetInterval 都可以接受字符串作为它们的第一个参数。 +这个字符串总是在全局作用域中执行,因此 eval 在这种情况下没有被直接调用。

+

安全问题

+

eval 也存在安全问题,因为它会执行任意传给它的代码, +在代码字符串未知或者是来自一个不信任的源时,绝对不要使用 eval 函数。

+

结论

+

绝对不要使用 eval,任何使用它的代码都会在它的工作方式,性能和安全性方面受到质疑。 +如果一些情况必须使用到 eval 才能正常工作,首先它的设计会受到质疑,这不应该是首选的解决方案, +一个更好的不使用 eval 的解决方案应该得到充分考虑并优先采用。

+

undefinednull

JavaScript 有两个表示‘空’的值,其中比较有用的是 undefined

+

undefined 的值

+

undefined 是一个值为 undefined 的类型。

+

这个语言也定义了一个全局变量,它的值是 undefined,这个变量也被称为 undefined。 +但是这个变量不是一个常量,也不是一个关键字。这意味着它的可以轻易被覆盖。

+ +

下面的情况会返回 undefined 值:

+
    +
  • 访问未修改的全局变量 undefined
  • +
  • 由于没有定义 return 表达式的函数隐式返回。
  • +
  • return 表达式没有显式的返回任何内容。
  • +
  • 访问不存在的属性。
  • +
  • 函数参数没有被显式的传递值。
  • +
  • 任何被设置为 undefined 值的变量。
  • +
+

处理 undefined 值的改变

+

由于全局变量 undefined 只是保存了 undefined 类型实际的副本, +因此对它赋新值不会改变类型 undefined 的值。

+

然而,为了方便其它变量和 undefined 做比较,我们需要事先获取类型 undefined 的值。

+

为了避免可能对 undefined 值的改变,一个常用的技巧是使用一个传递到匿名包装器的额外参数。 +在调用时,这个参数不会获取任何值。

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // 局部作用域里的 undefined 变量重新获得了 `undefined` 值
+
+})('Hello World', 42);
+

另外一种达到相同目的方法是在函数内使用变量声明。

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

这里唯一的区别是,在压缩后并且函数内没有其它需要使用 var 声明变量的情况下,这个版本的代码会多出 4 个字节的代码。

+ +

null 的用处

+

JavaScript 中的 undefined 的使用场景类似于其它语言中的 null,实际上 JavaScript 中的 null 是另外一种数据类型。

+

它在 JavaScript 内部有一些使用场景(比如声明原型链的终结 Foo.prototype = null),但是大多数情况下都可以使用 undefined 来代替。

+

自动分号插入

尽管 JavaScript 有 C 的代码风格,但是它强制要求在代码中使用分号,实际上可以省略它们。

+

JavaScript 不是一个没有分号的语言,恰恰相反上它需要分号来就解析源代码。 +因此 JavaScript 解析器在遇到由于缺少分号导致的解析错误时,会自动在源代码中插入分号。

+
var foo = function() {
+} // 解析错误,分号丢失
+test()
+

自动插入分号,解析器重新解析。

+
var foo = function() {
+}; // 没有错误,解析继续
+test()
+

自动的分号插入被认为是 JavaScript 语言最大的设计缺陷之一,因为它改变代码的行为。

+

工作原理

+

下面的代码没有分号,因此解析器需要自己判断需要在哪些地方插入分号。

+
(function(window, undefined) {
+    function test(options) {
+        log('testing!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+})(window)
+

下面是解析器"猜测"的结果。

+
(function(window, undefined) {
+    function test(options) {
+
+        // 没有插入分号,两行被合并为一行
+        log('testing!')(options.list || []).forEach(function(i) {
+
+        }); // <- 插入分号
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        ); // <- 插入分号
+
+        return; // <- 插入分号, 改变了 return 表达式的行为
+        { // 作为一个代码段处理
+            foo: function() {} 
+        }; // <- 插入分号
+    }
+    window.test = test; // <- 插入分号
+
+// 两行又被合并了
+})(window)(function(window) {
+    window.someLibrary = {}; // <- 插入分号
+})(window); //<- 插入分号
+ +

解析器显著改变了上面代码的行为,在另外一些情况下也会做出错误的处理

+

前置括号

+

在前置括号的情况下,解析器不会自动插入分号。

+
log('testing!')
+(options.list || []).forEach(function(i) {})
+

上面代码被解析器转换为一行。

+
log('testing!')(options.list || []).forEach(function(i) {})
+

log 函数的执行结果极大可能不是函数;这种情况下就会出现 TypeError 的错误,详细错误信息可能是 undefined is not a function

+

结论

+

建议绝对不要省略分号,同时也提倡将花括号和相应的表达式放在一行, +对于只有一行代码的 if 或者 else 表达式,也不应该省略花括号。 +这些良好的编程习惯不仅可以提到代码的一致性,而且可以防止解析器改变代码行为的错误处理。

+

其它

setTimeoutsetInterval

由于 JavaScript 是异步的,可以使用 setTimeoutsetInterval 来计划执行函数。

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // 返回一个大于零的数字
+

setTimeout 被调用时,它会返回一个 ID 标识并且计划在将来大约 1000 毫秒后调用 foo 函数。 +foo 函数只会被执行一次

+

基于 JavaScript 引擎的计时策略,以及本质上的单线程运行方式,所以其它代码的运行可能会阻塞此线程。 +因此没法确保函数会在 setTimeout 指定的时刻被调用。

+

作为第一个参数的函数将会在全局作用域中执行,因此函数内的 this 将会指向这个全局对象。

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // this 指向全局对象
+        console.log(this.value); // 输出:undefined
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

setInterval 的堆调用

+

setTimeout 只会执行回调函数一次,不过 setInterval - 正如名字建议的 - 会每隔 X 毫秒执行函数一次。 +但是却不鼓励使用这个函数。

+

当回调函数的执行被阻塞时,setInterval 仍然会发布更多的回调指令。在很小的定时间隔情况下,这会导致回调函数被堆积起来。

+
function foo(){
+    // 阻塞执行 1 秒
+}
+setInterval(foo, 1000);
+

上面代码中,foo 会执行一次随后被阻塞了一分钟。

+

foo 被阻塞的时候,setInterval 仍然在组织将来对回调函数的调用。 +因此,当第一次 foo 函数调用结束时,已经有 10 次函数调用在等待执行。

+

处理可能的阻塞调用

+

最简单也是最容易控制的方案,是在回调函数内部使用 setTimeout 函数。

+
function foo(){
+    // 阻塞执行 1 秒
+    setTimeout(foo, 1000);
+}
+foo();
+

这样不仅封装了 setTimeout 回调函数,而且阻止了调用指令的堆积,可以有更多的控制。 +foo 函数现在可以控制是否继续执行还是终止执行。

+

手工清空定时器

+

可以通过将定时时产生的 ID 标识传递给 clearTimeout 或者 clearInterval 函数来清除定时, +至于使用哪个函数取决于调用的时候使用的是 setTimeout 还是 setInterval

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

清除所有定时器

+

由于没有内置的清除所有定时器的方法,可以采用一种暴力的方式来达到这一目的。

+
// 清空"所有"的定时器
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

可能还有些定时器不会在上面代码中被清除(译者注如果定时器调用时返回的 ID 值大于 1000), +因此我们可以事先保存所有的定时器 ID,然后一把清除。

+

隐藏使用 eval

+

setTimeoutsetInterval 也接受第一个参数为字符串的情况。 +这个特性绝对不要使用,因为它在内部使用了 eval

+ +
function foo() {
+    // 将会被调用
+}
+
+function bar() {
+    function foo() {
+        // 不会被调用
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

由于 eval 在这种情况下不是被直接调用,因此传递到 setTimeout 的字符串会自全局作用域中执行; +因此,上面的回调函数使用的不是定义在 bar 作用域中的局部变量 foo

+

建议不要在调用定时器函数时,为了向回调函数传递参数而使用字符串的形式。

+
function foo(a, b, c) {}
+
+// 不要这样做
+setTimeout('foo(1,2, 3)', 1000)
+
+// 可以使用匿名函数完成相同功能
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

结论

+

绝对不要使用字符串作为 setTimeout 或者 setInterval 的第一个参数, +这么写的代码明显质量很差。当需要向回调函数传递参数时,可以创建一个匿名函数,在函数内执行真实的回调函数。

+

另外,应该避免使用 setInterval,因为它的定时执行不会被 JavaScript 阻塞。

+
\ No newline at end of file diff --git a/zhtw/index.html b/zhtw/index.html new file mode 100644 index 0000000..753f38e --- /dev/null +++ b/zhtw/index.html @@ -0,0 +1,1197 @@ +JavaScript 庭院

簡介

簡介

JavaScript 庭院 是一個不斷更新的文件,最主要是要去了解一些 Javascript 比較古怪的部份。 +給一些意見來防止遇到一些常見的錯誤和一些難以發現的問題,以及性能問題和不好的習慣。 +初學者也可以藉此去了解 Javascript 這項語言的特性。

+

JavaScript 庭院 並 不是 要教導你 Javascript 的語言。 +如果要能夠理解這篇文章的內容,你需要事先學習 JavaScript 的基礎知識。 +在 Mozilla 開發者網路中有一系列非常棒的學習guide

+

作者

+

這個使用手冊是來自於 Stack Overflow 的使用者, Ivo Wetzel +(寫作) 和 Zhang Yi Jiang (設計).

+

貢獻者

+ +

繁體中文翻譯

+ +

存在

+

JavaScript 庭院 存在於 GitHub, 但是 Cramer Development 讓我們有一個存放地 JavaScriptGarden.info.

+

許可

+

JavaScript 庭院是在 MIT license 許可協議下發佈,並存在於 +GitHub. 如果你有發現錯誤或是打字上的錯誤 新增一個任務 或者發一個請求。 你也可以在 StackOverflow 的 JavaScript room 上面找到我們。

+

物件

物件的使用和屬性

每個變數可以表現像 JavaScript 物件,除了 nullundefined

+
false.toString(); // 'false'
+[1, 2, 3].toString(); // '1,2,3'
+
+function Foo(){}
+Foo.bar = 1;
+Foo.bar; // 1
+

一個常見的誤解就是字面值(literal)不是物件。這是因為 JavaScript 編譯器的一個錯誤,它試圖把 點操作符 解析為浮點數的字面值的一部分。

+
2.toString(); // 出錯: SyntaxError
+

有很多變通方法可以讓數字的字面值看起來像物件。

+
2..toString(); // 第二個點號可以正常解析
+2 .toString(); // 注意點號前面的空格
+(2).toString(); // 2 先被計算
+

物件做為數據類型

+

JavaScript 的物件可以作為 Hashmaps使用,主要用來保存命名的鍵與值的對應關係。

+

使用物件的字面語法 - {} - 可以創建一個簡單的物件。 這個新創建的物件從 Object.prototype 繼承,下面,沒有任何 自定義屬性

+
var foo = {}; // 一個空的物件
+
+// 一個新的物件,有值為 12 的自定義屬性 'test'
+var bar = {test: 12}; 
+

訪問屬性

+

有兩種訪問物件的屬性,點操作或是中括號操作。

+
var foo = {name: 'kitten'}
+foo.name; // kitten
+foo['name']; // kitten
+
+var get = 'name';
+foo[get]; // kitten
+
+foo.1234; // SyntaxError
+foo['1234']; // works
+

兩種語法是相等的,唯一的差別是,使用中括號允許你動態的設定屬性,使用點操作不允許屬性為變數,否則會造成語法錯誤

+

刪除屬性

+

唯一刪除屬性的方式就是用 delete 操作符。設置屬性為 undefined 或是 null 只有刪除的屬性和值的關聯,沒有真的刪掉屬性

+
var obj = {
+    bar: 1,
+    foo: 2,
+    baz: 3
+};
+obj.bar = undefined;
+obj.foo = null;
+delete obj.baz;
+
+for(var i in obj) {
+    if (obj.hasOwnProperty(i)) {
+        console.log(i, '' + obj[i]);
+    }
+}
+

上面的輸出結果有 bar undefinedfoo null +只有 baz 真正被刪除而已,所以從輸出結果中消失。

+

屬姓名的語法

+
var test = {
+    'case': 'I am a keyword, so I must be notated as a string',
+    delete: 'I am a keyword, so me too' // raises SyntaxError
+};
+

物件的屬性名可以使用字符串或是普通的宣告。但是由於 JavaScript 編譯器有個另外一個錯誤設計。 +上面的兩種方式在 ECMAScript 5之前都會拋出 SyntaxError 的錯誤。

+

這個錯誤的原因是 delete 是 JavaScript 語言的一個 關鍵字 因此為了在更低的版本能執行最好用 string literal

+

Prototype

JavaScript 不包含原本繼承的模型。然而它使用的是原型模型*。

+

然而常常有人提及 JavaScript 的缺點,就是基於原本繼承模型比類繼承更強大。 +現實傳統的類繼承模型是很簡單。但是在 JavaScript 中實現元繼承則要困難很多。

+

由於 JavaScript 是唯一一個被廣泛使用的基於原型繼承的語言,所以我們必須要花時間來理解這兩者的不同。

+

第一個不同之處在於 JavaScript 使用 原型鏈 的繼承方式。

+ +
function Foo() {
+    this.value = 42;
+}
+Foo.prototype = {
+    method: function() {}
+};
+
+function Bar() {}
+
+// 設置 Bar 的 prototype 屬性為 Foo 的實例對象
+Bar.prototype = new Foo();
+Bar.prototype.foo = 'Hello World';
+
+// 修正 Bar.prototype.constructor 為 Bar 本身
+Bar.prototype.constructor = Bar;
+
+var test = new Bar() // 開啟一個新的實例
+
+// 原型鏈
+test [instance of Bar]
+    Bar.prototype [instance of Foo] 
+        { foo: 'Hello World' }
+        Foo.prototype
+            { method: ... }
+            Object.prototype
+                { toString: ... /* etc. */ }
+

上面的例子中,物件 test 會繼承來自 Bar.prototypeFoo.prototype。因此它可以進入來自 Foo 原型的方法 method。 +同時它也可以訪問 那個 定義在原型上的 Foo 實例屬性 value

+

要注意的是 new Bar() 沒有 創立一個新的 Foo 實例,它重複利用的原本的 prototype。因此, Bar 的實例會分享到 相同value 屬性。

+ +

屬性查詢

+

當查詢一個物件的屬性時,JavaScript 會 向上 查詢,直到查到指定名稱的屬性為止。

+

如果他查到原型鏈的頂部 - 也就是 Object.prototype - 但是仍然沒有指定的屬定,就會返回 undefined

+

原型屬性

+

當原型屬性用來建造原型鏈,它還是有可能去把 任意 類型的值給它

+
function Foo() {}
+Foo.prototype = 1; // 無效
+

分派物件,在上面的例子中,將會動態的創建原型鏈。

+

效能

+

如果看在屬性在原型鏈的上端,對於查詢都會有不利的影響。特別的,試圖獲取一個不存在的屬性將會找遍所有原型鏈。

+

並且,當使用 迴圈找尋所有物件的屬性時,原型鏈上的 所有 屬性都會被訪問。

+

擴展 Native Prototype

+

一個經常發生的錯誤,那就是擴展 Object.prototype 或者是其他內建類型的原型物件。

+

這種技術叫做 monkey patching 並且會破壞 封裝。雖然被廣泛的應用到一些 Javascript 的架構,像是 Prototype , 但仍然沒有好的理由新增一個 非標準 的功能去搞亂內建型別

+

擴展內置類型的 唯一 理由是為了和新的 JavaScript 保持一致,比如說 Array.forEach

+

總結

+

在寫複雜的程式碼的時候,要 充分理解 所有程式繼承的屬性還有原型鏈。 +還要堤防原型鏈過長帶來的性能問題,並知道如何通過縮短原型鏈來提高性能。 +絕對 不要使用 native prototype` 除非是為了和新的 JavaScript 引擎作兼容。

+

hasOwnProperty

為了判斷一個物件是否包含 自定義 屬性而 不是 原形上的屬性,我們需要使用繼承 Object.prototypehasOwnProperty 方法。

+ +

hasOwnProperty 是 JavaScript 中唯一一個處理屬性但是 找原型鏈的函式。

+
// 修改 Object.prototype
+Object.prototype.bar = 1; 
+var foo = {goo: undefined};
+
+foo.bar; // 1
+'bar' in foo; // true
+
+foo.hasOwnProperty('bar'); // false
+foo.hasOwnProperty('goo'); // true
+

只有 hasOwnProperty 給予正確的結果,這對進入物件的屬性很有效果,沒有 其他方法可以用來排除原型上的屬性,而不是定義在物件 自己 上的屬性。

+

hasOwnProperty 作為屬性

+

JavaScript 不會 保護 hasOwnProperty被占用,因此如果碰到存在這個屬性,就需要使用 外部hasOwnProperty 來獲取正確的結果。

+
var foo = {
+    hasOwnProperty: function() {
+        return false;
+    },
+    bar: 'Here be dragons'
+};
+
+foo.hasOwnProperty('bar'); // 永遠返回 false
+
+// 使用其他對象的 hasOwnProperty,並將其上下設置為 foo
+({}).hasOwnProperty.call(foo, 'bar'); // true
+

結論

+

當檢查一個物件是否存在的時候, hasOwnProperty唯一 可用的方法。 +同時在使用 for in loop +建議使用 hasOwnProperty 避免 原型所帶來的干擾。

+

for in 迴圈

就像其他的 in 操作符一樣, for in 循環也進入所有在物件中的屬性

+ +
// 修改 Object.prototype
+Object.prototype.bar = 1;
+
+var foo = {moo: 2};
+for(var i in foo) {
+    console.log(i); // 輸出兩個屬性:bar 和 moo
+}
+

由於不可能改變 for in 本身的行為,因為有必要過濾出那些不希望在迴圈出現的屬性,這可以用 Object.prototype 原型上的 hasOwnProperty 的函數來完成。

+ +

hasOwnProperty 來過濾

+
// foo 變數是上面範例中的
+for(var i in foo) {
+    if (foo.hasOwnProperty(i)) {
+        console.log(i);
+    }
+}
+

這個版本的程式碼是唯一正確的寫法。由於我們使用了 hasOwnProperty,這次 輸出 moo。 +如果不只用這個程式碼在原型物件中(比如 Object.prototype)被擴展可能會出錯。

+

一個廣泛的模組 Prototype就礦展了圓型的 JavaScript 物件。 +因此,但這模組包含在頁面中時,不使用 hasOwnProperty 過濾的 for in 尋難免會出問題。

+

總結

+

推薦 總是 使用 hasOwnProperty。不要對程式碼的環境做任何假設,不要假設原生的對象是否被擴張

+

函式

函式的宣告和表達方式

函式在 JavaScript 是第一等物件。這表示他們可以把函式當做值一樣傳遞。 +一個常見的用法是用 匿名函式 當做一個回傳去呼叫另一個函式,這是一種非同步函式

+

函式的宣告

+
function foo() {}
+

上面的函式在被執行之前會被 解析(hoisted),因此它可以在 任意 的地方都是 有宣告的 ,就算是在比這個函式還早呼叫。

+
foo(); // 可以執行,因為 foo 已經在運行前就被建立
+function foo() {}
+

function 的表達式

+
var foo = function() {};
+

這個例子把一個 匿名 函式賦值給變數 foo

+
foo; // 'undefined'
+foo(); // 錯誤: TypeError
+var foo = function() {};
+

由於 var 已經宣告變數 foo 在所有的程式碼執行之前。 +所以 foo已經在程式運行前就已經被定義過了。 +但是因為賦值只會在運行時去職情,所以在程式碼執行前,foo 的值還沒被宣告所以為 undefined

+

命名函式的賦值表達式

+

另一個特殊狀況就勢將一個命名函式賦值給一個變數。

+
var foo = function bar() {
+    bar(); // 可以運行
+}
+bar(); // 錯誤:ReferenceError
+

bar 不可以在外部的區域被執行,因為它只有在 foo 的函式內才可以去執行。 +然而在 bar 內部還是可以看見。這是由於 JavaScript的 命名處理所致。 +函式名在函式內 可以去使用。

+

this 的工作原理

JavaScript 有移到完全部屬於其他語言處理 this 的處理機制。 +在 種物同的情況下, this 指向的個不相同

+

全域變數

+
this;
+

如果再全域範圍內使用 this,會指向 全域 的物件

+

呼叫一個函式

+
foo();
+

這裡 this 也會指向 全域 對象。

+ +

方法調用

+
test.foo(); 
+

這個例子中, this 指向 test 物件。

+

呼叫一個建構函式

+
new foo(); 
+

如果函式傾向用 new 關鍵詞使用,我們稱這個函式為 建構函式。 +在函式內部, this 指向 新物件的創立

+

顯示的設置 this

+
function foo(a, b, c) {}
+
+var bar = {};
+foo.apply(bar, [1, 2, 3]); // Array 會被擴展,如下所示
+foo.call(bar, 1, 2, 3); // 傳遞參數 a = 1, b = 2, c = 3
+

當使用 function.prototype 上的 call 或只 apply 方法時,函式內的 this 將會被 顯示設置 為函式調用的第一個參數。

+

As a result, in the above example the method case does not apply, and this +inside of foo will be set to bar.

+ +

常見誤解

+

While most of these cases make sense, the first can be considered another +mis-design of the language because it never has any practical use.

+
Foo.method = function() {
+    function test() {
+        // this is set to the global object
+    }
+    test();
+}
+

A common misconception is that this inside of test refers to Foo; while in +fact, it does not.

+

In order to gain access to Foo from within test, it is necessary to create a +local variable inside of method that refers to Foo.

+
Foo.method = function() {
+    var that = this;
+    function test() {
+        // Use that instead of this here
+    }
+    test();
+}
+

that is just a normal variable name, but it is commonly used for the reference to an +outer this. In combination with closures, it can also +be used to pass this values around.

+

Assigning Methods

+

Another thing that does not work in JavaScript is function aliasing, which is +assigning a method to a variable.

+
var test = someObject.methodTest;
+test();
+

Due to the first case, test now acts like a plain function call; therefore, +this inside it will no longer refer to someObject.

+

While the late binding of this might seem like a bad idea at first, in +fact, it is what makes prototypal inheritance work.

+
function Foo() {}
+Foo.prototype.method = function() {};
+
+function Bar() {}
+Bar.prototype = Foo.prototype;
+
+new Bar().method();
+

When method gets called on an instance of Bar, this will now refer to that +very instance.

+

Closures 和 References

JavaScript 有一個很重要的特徵就是 closures +因為有 Closures,所以作用域 永遠 能夠去訪問作用區間外面的變數。 +函數區間 是JavaScript 中唯一擁有自生作用域的結構,因此 Closures 的創立需要依賴函數

+

模仿私有變數

+
function Counter(start) {
+    var count = start;
+    return {
+        increment: function() {
+            count++;
+        },
+
+        get: function() {
+            return count;
+        }
+    }
+}
+
+var foo = Counter(4);
+foo.increment();
+foo.get(); // 5
+

這裡,Counter 返回兩個 Closures,函數 increment 還有 get。這兩個函數都維持著對外部作用域 Counter 的引用,因此總可以訪問作用域的變數 count

+

為什麼不可以在外部訪問私有變數

+

因為 Javascript 不可以 對作用域進行引用或賦值。因此外部的地方沒有辦法訪問 count 變數。 +唯一的途徑就是經過那兩個 Closures

+
var foo = new Counter(4);
+foo.hack = function() {
+    count = 1337;
+};
+

在上面的例子中 count 不會 改變到 Counter 裡面的 count 的值。因為 foo.hack 沒有在 那個 作用域內被宣告。它只有會覆蓋或者建立在一個 全域 的變數 count

+

在循環內的 Closures

+

一個常見的錯誤就是在 Closures 中使用迴圈,假設我們要使用每次迴圈中所使用的進入變數

+
for(var i = 0; i < 10; i++) {
+    setTimeout(function() {
+        console.log(i);  
+    }, 1000);
+}
+

在上面的例子中它 不會 輸出數字從 09,但只會出現數字 10 十次。 +在 console.log 被呼叫的時候,這個 匿名 函數中保持一個 參考 到 i ,此時 for迴圈已經結束, i 的值被修改成了 10。 +為了要達到想要的結果,需要在每次創造 副本 來儲存 i 的變數。

+

避免引用錯誤

+

為了要有達到正確的效果,最好是把它包在一個 +匿名函數.

+
for(var i = 0; i < 10; i++) {
+    (function(e) {
+        setTimeout(function() {
+            console.log(e);  
+        }, 1000);
+    })(i);
+}
+

匿名外部的函數被呼叫,並把 i 作為它第一個參數,此時函數內 e 變數就擁有了一個 i 的拷貝。 +當傳遞給 setTimeout 這個匿名函數執行時,它就擁有了對 e 的引用,而這個值 不會 被循環改變。 +另外有一個方法也可以完成這樣的工作,那就是在匿名函數中返回一個函數,這和上面的程式碼有同樣的效果。

+
for(var i = 0; i < 10; i++) {
+    setTimeout((function(e) {
+        return function() {
+            console.log(e);
+        }
+    })(i), 1000)
+}
+

arguments 物件

所有函數在 JavaScript 中都可以有個特別的參數 arguments。 +這個變數掌握了一列傳入函數中的參數

+ +

arguments 物件 不是 一個 Array,雖然都有很多 Array 的語法 - 就像是 length 屬性 - 但是它沒有繼承來自 Array.prototype 事實上它繼承 object

+

由於這些原因,這 不可能 用 Array 的一些功能像是 pushpop或是 slicearguments。 +但是像 for 迴圈這些迴圈都是可以用的,如果真的需要使用一些標準的 Array 功能可以先把它轉成真的 Array 再去使用。

+

轉為 Array

+

下面的程式可以回傳一個新的 Array 包含所有的元素在 Arguments的物件中

+
Array.prototype.slice.call(arguments);
+

這種轉化方式比較 ,不建議使用這種作法如果再追求效率的程式中。

+

傳遞參數

+

下面是建議用這種方式去傳參數到另一個函數

+
function foo() {
+    bar.apply(null, arguments);
+}
+function bar(a, b, c) {
+    // 在這裡做一些事情
+}
+

另一個技巧是用 callapply 放在一起來創造一個更快的解綁定包裝器

+
function Foo() {}
+
+Foo.prototype.method = function(a, b, c) {
+    console.log(this, a, b, c);
+};
+
+// Create an unbound version of "method" 
+// 輸入的參數: this, arg1, arg2...argN
+Foo.method = function() {
+
+    // 結果: Foo.prototype.method.call(this, arg1, arg2... argN)
+    Function.call.apply(Foo.prototype.method, arguments);
+};
+

自動更新

+

Arguments 物件創造的 gettersetter 的函數方法,可以被視為原本函數的變數。

+

因此,改變了一個變數會跟著改變它的值而且也間接的改變稻香對應的 arguments 的物件,反之亦然。

+
function foo(a, b, c) {
+    arguments[0] = 2;
+    a; // 2
+
+    b = 4;
+    arguments[1]; // 4
+
+    var d = c;
+    d = 9;
+    c; // 3
+}
+foo(1, 2, 3);
+

性能

+

arguments 總是會被宣告,但除了兩個情況,一個是在一個函式中或是在其中一個參入。而不論他是否有被使用。

+

getterssetter 會永遠被創造。然而,他們對任何性能都沒有影響,除非對它的屬性有多次的訪問

+ +

然而會有一種情況來降低 JavaScript 引擎的效能。就是使用 arguments.callee

+
function foo() {
+    arguments.callee; // 做一些在這個函數物件
+    arguments.callee.caller; // 然後呼叫這個函數物件
+}
+
+function bigLoop() {
+    for(var i = 0; i < 100000; i++) {
+        foo(); // 通常會在內聯
+    }
+}
+

在上面的程式中, foo 不再是一個單存的互聯函數 +因為它需要知道他自己和它的調用者。 +這不僅減低了它的性能,而且還破壞的封裝

+

強烈建議不要使用 arguments.callee 或是其他它的屬性

+ +

建構函式

JavaScript 中的建構函式和其他語言中的建構函式是不同的。 +用 new 的關鍵字方式調用的函式都被認為是建構函式。 +在建構函式內部 - 被呼叫的函式 - this 指向一個新建立的 objectprototype 這是一個新的物件一個被指向函式的 prototype 的建構函式。

+

如果被使用的函式沒有明顯的呼叫 return 的表達式,它會回傳一個隱性的 this 的新物件。

+
function Foo() {
+    this.bla = 1;
+}
+
+Foo.prototype.test = function() {
+    console.log(this.bla);
+};
+
+var test = new Foo();
+

在上面的例子中 Foo 建立一個建構函式,並設立一個 prototype 來創建一個新的物件叫 Foo.prototype。 +這個情況下它顯示的 return 一個表達式,但他 返回一個 Object

+
function Bar() {
+    return 2;
+}
+new Bar(); // 返回一個新物件
+
+function Test() {
+    this.value = 2;
+
+    return {
+        foo: 1
+    };
+}
+new Test(); // 回傳物件
+

如果 new 的關鍵字被忽略,函式就 不會 回傳一個新的物件。

+
function Foo() {
+    this.bla = 1; // 獲取一個全域的參數
+}
+Foo(); // undefined
+

雖然上面有些情況也能正常運行,但是由於 JavaScript 中 this 的工作原理,這裡的 this 指向 全域對象

+

工廠模式

+

為了不使用 new 關鍵字,建構函式必須顯性的返回一個值。

+
function Bar() {
+    var value = 1;
+    return {
+        method: function() {
+            return value;
+        }
+    }
+}
+Bar.prototype = {
+    foo: function() {}
+};
+
+new Bar();
+Bar();
+

上面兩個呼叫 Bar 的方法回傳的值都一樣,一個新創建的擁有 method 屬性被返回,這裡創建了一個 Closure.

+

還有注意, new Bar()不會 改變返回物件的原型。 +因為建構函式的原型會指向剛剛創立的新物件,而在這裡的 Bar 沒有把這個新物件返回。 +在上面的例子中,使用或者不使用 new 關鍵字沒有什麼功能性的區別

+

通過工廠模式創建的新對象

+

常聽到建議 不要 使用 new,因為如果忘記如何使用它會造成錯誤。 +為了創建一個新的物件,我們可以用工廠方法,來創造一個新的物件在那個方法中。

+
function Foo() {
+    var obj = {};
+    obj.value = 'blub';
+
+    var private = 2;
+    obj.someMethod = function(value) {
+        this.value = value;
+    }
+
+    obj.getPrivate = function() {
+        return private;
+    }
+    return obj;
+}
+

雖然上面的方式比起 new 的調用方式更不容易出錯,並且可以充分的使用 私有變數所帶來的便利,但是還是有一些不好的地方

+
    +
  1. 會占用更多的記憶體,因為創建的物件 沒有 辦法放在在同一個原型上。
  2. +
  3. 為了要用繼承的方式,工廠方法需要複製所有的屬性或是把一個物件作為新的物件的原型。
  4. +
  5. 放棄原型鏈僅僅是因為防止遺漏 new 所帶來的問題,這與語言本身的思想鄉違背。
  6. +
+

結語

+

雖然遺漏 new 關鍵字可能會導致問題,但這並 不是 放棄只用原型的藉口。 +最終使用哪種方式取決於應用程式的需求,選擇一種程式語言風格並堅持下去才是最重要的。

+

作用域和命名空間

儘管 JavaScript 支持一個大括號創建的程式碼,但並不支持塊級作用域。 +而僅僅支援 函式作用域

+
function test() { // 一個作用域
+    for(var i = 0; i < 10; i++) { // 不是一個作用域
+        // 算數
+    }
+    console.log(i); // 10
+}
+ +

JavaScript 中沒有寫示的命名空間定義,這代表著它所有定義的東西都是 全域共享 在同一個命名空間下。

+

每次引用一個變數,JavaScript 會向上找整個作用域直到找到這個變數為止。 +如果在全域中無法找到那個變數,它會拋出 ReferenceError 錯誤碼。

+

全域變數的壞處

+
// script A
+foo = '42';
+
+// script B
+var foo = '42'
+

上面兩個腳本 不會 有同樣的效果。腳本 A 在 全域 空間定義了變數 foo,腳本 B 定義了 foo 在目前的區間內。

+

再次強調,上面的效果是 完全不同,不使用 var 會導致隱性的全域變數。

+
// 全域作用區
+var foo = 42;
+function test() {
+    // 局部作用區
+    foo = 21;
+}
+test();
+foo; // 21
+

在函數 test 中部使用 var 會覆蓋到原本在外面的 foo。 +雖然看起來不是什麼大問題,但是當程式有幾千行的時候沒有使用 var 會照成難以追蹤的臭蟲。

+
// 全域作用域
+var items = [/* some list */];
+for(var i = 0; i < 10; i++) {
+    subLoop();
+}
+
+function subLoop() {
+    // subLoop 的作用域
+    for(i = 0; i < 10; i++) { // 缺少了 var
+        // 做一些事情
+    }
+}
+

在外面的迴圈在呼叫第一次 subLoop 之後就會停止,因為 subLoop 全域變數中的 i 被覆蓋了。 +在第二次使用 for 迴圈的時候,使用 var 就可以避免這種錯誤。 +在宣告變數的時候 絕對不要 忘記 var,除非就是 希望他的效果 是取改變外部的作用域。

+

局部變數

+

在 javascript 中能用兩種方式來宣告局部變數。 +函式 參數和透過 var 來宣告變數。

+
// 全域變數
+var foo = 1;
+var bar = 2;
+var i = 2;
+
+function test(i) {
+    // 函式 test 內部的局部作用域
+    i = 5;
+
+    var foo = 3;
+    bar = 4;
+}
+test(10);
+

fooi 是它的局部變數在 test 函式中,但是在 bar 的賦值會覆蓋全區域的作用域內的同名變數。

+

變數宣告

+

JavaScript 會 提昇 變數宣告, 這代表著 varfunction 的圈告都會被提升到當前作用域的頂端。

+
bar();
+var bar = function() {};
+var someValue = 42;
+
+test();
+function test(data) {
+    if (false) {
+        goo = 1;
+
+    } else {
+        var goo = 2;
+    }
+    for(var i = 0; i < 100; i++) {
+        var e = data[i];
+    }
+}
+

在上面的程式碼會被轉化在執行之前。 JavaScript 會把 var,和 function 宣告,放到最頂端最接近的作用區間

+
// var 被移到這裡
+var bar, someValue; //  值等於 'undefined'
+
+// function 的宣告也被搬上來
+function test(data) {
+    var goo, i, e; // 沒有作用域的也被搬至頂端
+    if (false) {
+        goo = 1;
+
+    } else {
+        goo = 2;
+    }
+    for(i = 0; i < 100; i++) {
+        e = data[i];
+    }
+}
+
+bar(); // 出錯:TypeError , bar 還是 'undefined'
+someValue = 42; // 賦值語句不會被提昇規則影響
+bar = function() {};
+
+test();
+

沒有作用域區間不只會把 var 放到迴圈之外,還會使得 if 表達式更難看懂。

+

在一般的程式中,雖然 if 表達式中看起來修改了 全域變數 goo,但實際上在提昇規則被運用後,卻是在修改 局部變數

+

如果沒有提昇規則的話,可能會出現像下面的看起來會出現 ReferenceError 的錯誤。

+
// 檢查 SomeImportantThing 是否已經被初始化
+if (!SomeImportantThing) {
+    var SomeImportantThing = {};
+}
+

但是它沒有錯誤,因為 var 的表達式會被提升到 全域作用域 的頂端。

+
var SomeImportantThing;
+
+// 有些程式,可能會初始化。
+SomeImportantThing here, or not
+
+// 檢查是否已經被初始化。
+if (!SomeImportantThing) {
+    SomeImportantThing = {};
+}
+

名稱解析順序

+

JavaScript 中所有的作用區,包括 全域作用域,都有一個特殊的名字 this, 在它們裡面被定義,指向當前的物件

+

函式作用域也有一個名稱叫做 arguments, 定義它們,其中包括傳到函式內的參數。

+

例如,它們開始試著進入到 foo 的作用域裡面, JavaScript 會依照下面的順序去查詢:

+
    +
  1. 當作用域內是否有 var foo 的定義。
  2. +
  3. 函式形式參數是否有使用 foo 名稱定義。
  4. +
  5. 函式自身是剖叫做 foo
  6. +
  7. 回溯到上一個層級然後再從第一個開始往下去查。
  8. +
+ +

命名空間

+

只有一個全域作用域會導致常見的錯誤是命名衝突。在 JavaScript 中可以透過 匿名包裝器 來解決。

+
(function() {
+    // 自己本身的匿名空間
+
+    window.foo = function() {
+        // 對外公開的函式
+    };
+
+})(); // 馬上執行這個匿名函式
+

匿名函式被認為是 表達式因此為了要可以調用,它們會先被執行。

+
( // 小括號內的先被執行
+function() {}
+) // 回傳函數對象
+() // 調用上面的執行結果
+

還有其他方式也可以像上面一樣調用函式的方式達到

+
!function(){}()
++function(){}()
+(function(){}());
+// and so on...
+

結語

+

建議最好是都用 匿名包裝器 來封裝你的程式碼在自己的命名區間內。這不僅是要防止命名衝突也可以使得程序更有模組化。

+

另外,全域變數是個 不好的 習慣,因為它會帶來錯誤和更難去維護。

+

陣列

Array 迴圈和屬性

雖然在 Javascript 中 Array 都是 Objects,但是沒有好的理由要使用他 +在 for in 的迴圈中。事實上有很多原因要避免使用 for in 在 Array 之中

+ +

因為 for in 迴圈會列舉所有在原型 Array 上的屬性因為他會使用hasOwnProperty, 這會使得 Array 比原本的 for 迴圈慢上二十幾倍

+

迴圈

+

為了要達到最好的性能所以最好使用 for 迴圈來讀取一個 Array 裡面的數值。

+
var list = [1, 2, 3, 4, 5, ...... 100000000];
+for(var i = 0, l = list.length; i < l; i++) {
+    console.log(list[i]);
+}
+

在上面的例子中利用 l = list.length 來處理 Array 的長度問題。

+

雖然 length 屬性是屬於 Array 中其中一個屬性,但是他還使有一定的性能消耗在每次循環的訪問。 +近期 Javascript 使用 may 來解決在這上面的效率問題,但是在現在的引擎上還不一定有支援。

+

實際上,不使用暫存 Array 長度的方式比使用暫存的版本還要慢很多。

+

length 的屬性

+

length 屬性中的 getter 直接回傳在 Array 之中的程度,而 setter 可以用來 刪除 Array。

+
var foo = [1, 2, 3, 4, 5, 6];
+foo.length = 3;
+foo; // [1, 2, 3]
+
+foo.length = 6;
+foo.push(4);
+foo; // [1, 2, 3, undefined, undefined, undefined, 4]
+

在上面的例子可以看到,如果給的長度比較小他就會去刪除 Array 中的數值。如果比較大的話,他就會自己增加一些 undefined 的數值進去

+

結語

+

為了達到更好的效率,建議使用 for 迴圈還有暫存 length 的屬性。 +而 for in 迴圈則是會讓程式中有更多的錯誤和性能問題。

+

Array 的建構函式

Array 的建構函式在處理參數上一直有模糊的地帶,所以建議使用 array的字面語法來使用 - [] - 來新增一個的Array

+
[1, 2, 3]; // 結果: [1, 2, 3]
+new Array(1, 2, 3); // 結果: [1, 2, 3]
+
+[3]; // 結果: [3]
+new Array(3); // 結果: []
+new Array('3') // 結果: ['3']
+

在上面的範例 new Array(3) 當只有一個參數傳入到 Array 的建構函數 +且那個參數事宜個數字,建構函數會回傳空值 +但是 Array 長度的屬性會變成跟那個參數一樣(以此範例來看他回傳的長度為 3) +注意 只有他長度的屬性會被設定,整個 Array裡面的數值都不會初始化

+
var arr = new Array(3);
+arr[1]; // undefined
+1 in arr; // false, 數值沒有被設定進去
+

被設定用來當做 Array 的長度只有少數情況使用 +先設定 Array 的長度可以用一下的範例來避免使用 for loop 的麻煩

+
new Array(count + 1).join(stringToRepeat);
+

結語

+

Array 的建構函式需要避免,建議使用字面語法。因為他們比較簡短、也更增加閱讀性

+

類型

相等與比較

JavaScript 有兩個不同的方式來比較兩個物件是否相等。

+

等於操作符

+

等於操作符是由兩個等號組成: ==

+

JavaScript 是一個 弱類型 語言。這代表它會為了比較兩個值而做 強制類型轉換

+
""           ==   "0"           // false
+0            ==   ""            // true
+0            ==   "0"           // true
+false        ==   "false"       // false
+false        ==   "0"           // true
+false        ==   undefined     // false
+false        ==   null          // false
+null         ==   undefined     // true
+" \t\r\n"    ==   0             // true
+

上面的表格可以看出來這些結果強制轉換類型,這也代表說用 == 是一個不好的習慣,因為它會很難追蹤問題由於它複雜的規則。

+

此外,也有效率上面的問題在強制轉換類型。 +例如說一個字串會被轉成數字來和別的數字做比較。

+

嚴格等於操作符

+

不像普通的等於操作符 === 不會做強制類型轉換。

+
""           ===   "0"           // false
+0            ===   ""            // false
+0            ===   "0"           // false
+false        ===   "false"       // false
+false        ===   "0"           // false
+false        ===   undefined     // false
+false        ===   null          // false
+null         ===   undefined     // false
+" \t\r\n"    ===   0             // false
+

上面的結果比較清楚,也有利於程式碼的分析。如果這兩個操作數的類型不一樣都就不會相等,有助於它性能的提昇。

+

比較物件

+

雖然 ===== 都是等於操作符,但其中有一個操作數為物件時,它的行為就會不同。

+
{} === {};                   // false
+new String('foo') === 'foo'; // false
+new Number(10) === 10;       // false
+var foo = {};
+foo === foo;                 // true
+

在這裡等於操作符比較 不是 值的相等,而是否是 相同 的身分。 +有點像 Python 的 is 和 C 中的指標。

+

結論

+

強烈建議使用 嚴格等於 +如果要轉換類型,應該要在 explicitly的時候轉換,而不是在語言本身用複雜的轉換規則。

+

typeof 操作符

typeof 操作符 (和 +instanceof) 可能是最大的設計錯誤在 JavaScript,因為它幾乎不可能從它們那裡得到想要的結果。

+

雖然 instanceof 還是有一些限制上的使用, typeof 只有一個實際上的運傭情形,但是 不是 用在檢查物件的類型。

+ +

JavaScript 類型表格

+
Value               Class      Type
+-------------------------------------
+"foo"               String     string
+new String("foo")   String     object
+1.2                 Number     number
+new Number(1.2)     Number     object
+true                Boolean    boolean
+new Boolean(true)   Boolean    object
+new Date()          Date       object
+new Error()         Error      object
+[1,2,3]             Array      object
+new Array(1, 2, 3)  Array      object
+new Function("")    Function   function
+/abc/g              RegExp     object (function in Nitro/V8)
+new RegExp("meow")  RegExp     object (function in Nitro/V8)
+{}                  Object     object
+new Object()        Object     object
+

上面的表格中, Type 這一系列表示 typeof 的操作符的運算結果。可以看到,這個值的大多數情況下都返回物件。

+

Class 表示物件內部的屬性 [[Class]] 的值。

+ +

為了獲取對象的 [[Class]],我們可以使用定義在 Object.prototype 上的方法 toString

+

物件的類定義

+

JavaScript 標準文檔只給出了一種獲取 [[Class]] 值的方法,那就是使用 Object.prototype.toString

+
function is(type, obj) {
+    var clas = Object.prototype.toString.call(obj).slice(8, -1);
+    return obj !== undefined && obj !== null && clas === type;
+}
+
+is('String', 'test'); // true
+is('String', new String('test')); // true
+

上面的例子中,**Object.prototype.toStringthis的值來來調用被設置需要獲取 [[Class]] 值的物件。

+ +

測試未定義變數

+
typeof foo !== 'undefined'
+

上面的例子確認 foo 是否真的被宣告。如果沒有定義會導致 ReferenceError 這是 typeof 唯一有用的地方

+

結語

+

為了去檢查一個物件,強烈建議去使用 Object.prototype.toString 因為這是唯一可以依賴的方式。 +正如上面所看到的 typeof 的亦先返回值在標準文檔中未定義,因此不同的引擎可能不同。

+

除非為了檢測一個變數是否定義,我們應該避免是用 typeof 操作符。

+

instanceof 操作符

instanceof 操作符用來比較兩個建構函數的操作數。只有在比較字定義的物件時才有意義。這和 typeof operator一樣用處不大。

+

比較定意義物件

+
function Foo() {}
+function Bar() {}
+Bar.prototype = new Foo();
+
+new Bar() instanceof Bar; // true
+new Bar() instanceof Foo; // true
+
+// This just sets Bar.prototype to the function object Foo,
+// but not to an actual instance of Foo
+Bar.prototype = Foo;
+new Bar() instanceof Foo; // false
+

instanceof 比較內置類型

+
new String('foo') instanceof String; // true
+new String('foo') instanceof Object; // true
+
+'foo' instanceof String; // false
+'foo' instanceof Object; // false
+

有一點需要注意的, instanceof 不能用來物件來自上下文不同的屬性(例如:瀏覽器中不同的文檔結構),因為它的建構函數不一樣。

+

In Conclusion

+

instanceof 操作符應該 用來比較同一個 JavaScript 上下文定意義的物件。 +正如 typeof操作符一樣,任何其他用法都要避免。

+

類型轉換

JavaScript 是一個 弱類型 的程式語言,所以在 任何 情況下都可以 強制類型轉換

+
// 這些都是真
+new Number(10) == 10; // Number.toString() is converted
+                      // back to a number
+
+10 == '10';           // Strings gets converted to Number
+10 == '+10 ';         // More string madness
+10 == '010';          // And more 
+isNaN(null) == false; // null converts to 0
+                      // which of course is not NaN
+
+// 下面都假
+10 == 010;
+10 == '-10';
+ +

為了去避免上驗的事件發生,我們會用 嚴格等於操作符 這是強烈建議。 +因為它可以避免很多常見的問題,但 JavaScript 的弱類型系同仍然會導致一些其他問題。

+

內置類型的建構函式

+

內置類型(比如 NumberString)在被調用時,使用或不使用 new 的結果完全不同。

+
new Number(10) === 10;     // False, Object and Number
+Number(10) === 10;         // True, Number and Number
+new Number(10) + 0 === 10; // True, due to implicit conversion
+

使用內置類型 Number 作為建構函式會建造一個新的 Number 物件,而在不使用 new 關鍵字的 Number 函式更像是一個數字轉換器。

+

另外,在比較中引入物件的字面值會導致更加複雜的強制類型轉換。

+

最好的方式是比較值的 顯示 的轉換成最有可能的三種形態

+

轉換成字符串

+
'' + 10 === '10'; // true
+

將一個值加上空字符串可以輕鬆轉為字符串類型。

+

轉換成一個數字

+
+'10' === 10; // true
+

使用 一元 的加號操作符,可以把字符串轉為數字。

+

轉換成一個 Bool

+

通過使用 操作符兩字,可以把一個值轉換為 Bool。

+
!!'foo';   // true
+!!'';      // false
+!!'0';     // true
+!!'1';     // true
+!!'-1'     // true
+!!{};      // true
+!!true;    // true
+

核心

為什麼不要使用 eval

因為 eval 函數會在 Javascript 的區域性的區間執行那段程式碼。

+
var foo = 1;
+function test() {
+    var foo = 2;
+    eval('foo = 3');
+    return foo;
+}
+test(); // 3
+foo; // 1
+

但是, eval 只接受直接的呼叫而且那個函數只能叫做 eval,才能在一個區段中執行。

+
var foo = 1;
+function test() {
+    var foo = 2;
+    var bar = eval;
+    bar('foo = 3');
+    return foo;
+}
+test(); // 2
+foo; // 3
+

所有的 eval 都應該去比免試用。有 99.9% 的使用情況都可以 不必 使用到而達到同等效果。

+

偽裝的 eval

+

定時函數 setTimeoutsetInterval 都可以接受一個字串當做他們第一個參數。這些字串 永遠 都會在全域範圍內執行,因此在這種情況下 eval 沒有被直接的使用。

+

安全上的顧慮

+

eval 同樣有安全上的問題,因為所有的程式碼都可以被直接執行。 +而他不應去執行一串未知的字串或是來自不幸任的來源。

+

結語

+

eval 應該永遠不要去只用它,任何的程式在被他執行後都有性能和安全上的考慮。如果有情況需要去使用他,他都不應該列為第一順位的解決方法。

+

應該有更好的方法能夠去使用,但是最好都不要去使用 eval

+

undefinednull

JavaScript 中有兩個表示空值的方式, nullundefinedundefined式比較常用的一種。

+

undefined 的值

+

undefined 是一個值為 undefined 的類型。

+

語言中也定義了一個全域變數,它的值為 undefined,這個變數的被稱作 undefined 。 +這個變數 不是 一個常數,也不是一個關鍵字。這表示它的值可以被輕易的覆蓋。

+ +

這裡有一些例子會回傳 undefined 的值:

+
    +
  • 進入尚未修改的全域變數 undefined
  • +
  • 進入一個宣告但 尚未 初始化的變數。
  • +
  • return 表示式中沒有返回任何內容。
  • +
  • 呼叫不存在的屬性。
  • +
  • 函式參數沒有被傳遞數值。
  • +
  • 任何被被設定為 undefined 的變數。
  • +
  • 任何表達式中形式為 void(expression)
  • +
+

處理 undefined 值的改變

+

由於全域變數 undefined 只有保存 undefined 類型實際值的一個副本,指定了一個新的值並 不會 改變 undefined類型裡面的值。

+

為了避免去改變 undefined 的值,常用的技巧就是加上一個新的變數到 匿名包裝器。在使用的時候,這個參數不會接受任何的值。

+
var undefined = 123;
+(function(something, foo, undefined) {
+    // undefined 在區域區間內得到了 `undefined` 的值
+
+})('Hello World', 42);
+

另外一個可以得到同樣的效果就是在內部宣告一個變數

+
var undefined = 123;
+(function(something, foo) {
+    var undefined;
+    ...
+
+})('Hello World', 42);
+

唯一的不同就是在下者會多 4 個多 bytes 用來壓縮檔案,而且函數內野沒有其他需要使用 var

+

使用 null

+

JavaScript 中所使用的 undefined 類似別的語言中的 null , 但實際上在 JavaScript 中的 null 算是另外一個類型。

+

它在 JavaScript 有些可以使用的地方 (例如說宣告一個原型的終結,例如 Foo.prototype = null )。 +但是在大部分的時候可以用 undefined,來取代。

+

自動插入分號

雖然 JavaScript 有 C 語言的語法,但是他不強制一定要加上分號。 +所以分號可以被忽略。

+

Javascript 並 不是 一個不需要分號的語言。實際上,它需要分號來讓程式碼更容易被理解。因此 Javascript 的編譯器中遇到了缺少分號的情形,它會自動的在程式碼中插入分號。

+
var foo = function() {
+} // 編輯錯誤,因沒分號
+test()
+

這時候編譯器在編輯的時候,會自動的加上分號,然後重新編輯。

+
var foo = function() {
+}; // 沒有錯誤,編輯繼續
+test()
+

自動的加入分號是被認為 最大 的設計缺陷之一,因為它能改變程式碼的行為。

+

工作原理

+

下面的程式碼中沒有使用任何的分號,所以編譯器需要去決定在哪些地方加入分號。

+
(function(window, undefined) {
+    function test(options) {
+        log('testing!')
+
+        (options.list || []).forEach(function(i) {
+
+        })
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        )
+
+        return
+        {
+            foo: function() {}
+        }
+    }
+    window.test = test
+
+})(window)
+
+(function(window) {
+    window.someLibrary = {}
+
+})(window)
+

下面的程式碼是編譯器 猜測 的結果。

+
(function(window, undefined) {
+    function test(options) {
+
+        // 沒有加入分號,兩行被合併為一行
+        log('testing!')(options.list || []).forEach(function(i) {
+
+        }); // <- 插入分號
+
+        options.value.test(
+            'long string to pass here',
+            'and another long string to pass'
+        ); // <- 插入分號
+
+        return; // <- 插入分號,改變了 return 的表達行為
+        { // 作為另一個程式碼的處理
+
+            // 被當做一個獨立的函數來看
+            foo: function() {} 
+        }; // <- 插入分號
+    }
+    window.test = test; // <- 插入分號
+
+// 兩行又被合併
+})(window)(function(window) {
+    window.someLibrary = {}; // <- 插入分號
+
+})(window); //<- 插入分號
+ +

編譯器在上面的程式碼中改變了原本程式碼的行為。在一些情況下,會做出 錯誤的行為

+

前置括號

+

在這種前置括號的情況下,編譯器 不會 自動的插入分號。

+
log('testing!')
+(options.list || []).forEach(function(i) {})
+

上面的程式碼被編譯器轉為只有一行程式

+
log('testing!')(options.list || []).forEach(function(i) {})
+

以上的範例中 log很大 的可能 不是 回傳一個函數。然而這個情況下會出現 TypeError 的錯誤或是會出現 undefined is not a function .

+

結語

+

建議永遠 不要 忽略分號。同樣的也建議大括號應在他對應的表達式在同一行。在 if... else...的表達式中也是如此,不應省略大括號。 +這個習慣可以不僅僅是讓你的程式更一致,也可以避免編譯器因為改變程式而出錯。

+

delete 控制符

簡單來說,那是 不可能 去刪除一個全域變數,函式和其他東西在 JavaScript 中有一個 DontDelete 的屬性

+

全域和函式

+

當一個變數或是一個函式在一個全域範圍被定義或是在一個 funciton scope ,這些屬性可能是動態的物件或是全域的物件。這些特性有一系列的屬性。其中一個就是 DontDelete。 +在這些變數和函式的宣告都會有一個屬性叫 DontDelete,這會使得它無法被刪除。

+
// 全域變數
+var a = 1; // DontDelete 屬性被建立
+delete a; // false
+a; // 1
+
+// normal function:
+function f() {} // DontDelete 屬性被建立
+delete f; // false
+typeof f; // "function"
+
+// reassigning doesn't help:
+f = 1;
+delete f; // false
+f; // 1
+

明確的屬性

+

明確的屬性可以被簡單的刪除。

+
// explicitly set property:
+var obj = {x: 1};
+obj.y = 2;
+delete obj.x; // true
+delete obj.y; // true
+obj.x; // undefined
+obj.y; // undefined
+

在上面的例子中, obj.xobj.y 可以被刪除是因為他們沒有 DontDelete 的屬性。 +所以下面的例子也可以這樣用。

+
// 可以運作,除了 IE:
+var GLOBAL_OBJECT = this;
+GLOBAL_OBJECT.a = 1;
+a === GLOBAL_OBJECT.a; // true - just a global var
+delete GLOBAL_OBJECT.a; // true
+GLOBAL_OBJECT.a; // undefined
+

這裡我們想要去刪除 athis 這裡指向一個全域的物件,和我們明確了地定義 a 是它的屬性,所以可以刪除它。

+

IE 有些臭蟲,所以上面的程式碼無法使用(至少 6~8)

+

函式的參數和內建

+

函式的普通參數,arguments object 還有一些內建的屬性都有 DontDelete 的建立

+
// function 參數和屬性
+(function (x) {
+
+  delete arguments; // false
+  typeof arguments; // "object"
+
+  delete x; // false
+  x; // 1
+
+  function f(){}
+  delete f.length; // false
+  typeof f.length; // "number"
+
+})(1);
+

接受物件

+

控制符可以接受無法預測的物件。由於一些特別的情況,會允許它能夠 delete

+

結語

+

delete 控制符通常都有難以預料的行為,所以我們只可以安全的刪除顯著的屬性在普通的物件上。

+

其他

setTimeoutsetInterval

由於 Javascript 是一個非同步傳輸的系統,因此可以執行一個函式用 setTimeoutsetInterval

+ +
function foo() {}
+var id = setTimeout(foo, 1000); // returns a Number > 0
+

setTimeout 被呼叫,它會回傳一個 ID 標準並是計畫在將來 大約 1000 毫秒後在在去呼叫 foo 函式。 +foo 函式只會被執行 一次

+

基於 JavaScript 引擎的計時策略,以及基本的單線程運行的方式,所以其他的程式碼可以被阻塞。 +因此 沒法確保函式會在 setTimeout 指定的時可被調用。

+

第一個參數被函式呼叫的會在 全域物件 被呼叫,這代表 this在這個函式會指向全域物件。

+
function Foo() {
+    this.value = 42;
+    this.method = function() {
+        // 指向全域
+        console.log(this.value); // 會跑出 undefined
+    };
+    setTimeout(this.method, 500);
+}
+new Foo();
+ +

setInterval 的堆調用

+

setTimeout 只會在函式上跑一次而已, setInterval - 則會在每隔 X 毫秒執行函式一次。但不鼓勵這種寫法。

+

當回傳函式的執行被阻塞時, setInterval 仍然會發佈更多的回傳函式。在很小的定時間隔情況像會使得回傳函式被堆疊起來。

+
function foo(){
+    // 執行 1 秒
+}
+setInterval(foo, 1000);
+

上面的程式中, foo 會執行一次然後被阻塞了一分鐘

+

foo 被阻塞的時候 setInterval 還是會組織將對回傳函式的調用。因此當第一次 foo 函式調用結束時,已經有 10 次函式的調用在等待執行。

+

處理可能被阻塞的調用

+

最簡單的解決方法,也是最容易控制的解決方法,就是在函式中使用 setTimeout

+
function foo(){
+    // something that blocks for 1 second
+    setTimeout(foo, 1000);
+}
+foo();
+

這樣不只封裝了 setTimeout,也防止了堆疊的呼叫,還有給它更多的控制。 foo 可以去決定要不要繼續執行。

+

手動清理 Timeouts

+

清除 timeouts 所產生的 ID 標準傳遞給 clearTimeoutclearInterval 函式來清除定時, +至於使用哪個函式取決於調用的時候使用的是 setTimeout 還是 setInterval

+
var id = setTimeout(foo, 1000);
+clearTimeout(id);
+

清除所有 Timeouts

+

由於沒有一個內建的方法可以一次清空所有的 timeouts 和 intervals,所以只有用暴力法來達到這樣的需求。

+
// clear "all" timeouts
+for(var i = 1; i < 1000; i++) {
+    clearTimeout(i);
+}
+

可能還有一些定石器不會在上面的代碼中被清除,因此我們可以事先保存所有的定時器 ID,然後一把清除。

+
// clear "all" timeouts
+var biggestTimeoutId = window.setTimeout(function(){}, 1),
+i;
+for(i = 1; i <= biggestTimeoutId; i++) {
+    clearTimeout(i);
+}
+

隱藏使用 eval

+

setTimeout and setInterval can also take a string as their first parameter. +This feature should never be used because it internally makes use of eval.

+ +
function foo() {
+    // will get called
+}
+
+function bar() {
+    function foo() {
+        // never gets called
+    }
+    setTimeout('foo()', 1000);
+}
+bar();
+

Since eval is not getting called directly in this case, the string +passed to setTimeout will be executed in the global scope; thus, it will +not use the local variable foo from the scope of bar.

+

It is further recommended to not use a string to pass arguments to the +function that will get called by either of the timeout functions.

+
function foo(a, b, c) {}
+
+// NEVER use this
+setTimeout('foo(1, 2, 3)', 1000)
+
+// Instead use an anonymous function
+setTimeout(function() {
+    foo(a, b, c);
+}, 1000)
+ +

In Conclusion

+

A string should never be used as the parameter of setTimeout or +setInterval. It is a clear sign of really bad code, when arguments need +to be supplied to the function that gets called. An anonymous function should +be passed that then takes care of the actual call.

+

Furthermore, the use of setInterval should be avoided because its scheduler is not +blocked by executing JavaScript.

+
\ No newline at end of file