Nota de seguridad de red 2: nuevo, esto en js

Objeto de instancia y nuevo comando

cual es el objeto

La programación orientada a objetos (Programación orientada a objetos, abreviada como POO) es actualmente el paradigma de programación principal. Abstrae varias relaciones complejas en el mundo real en objetos individuales y luego completa la simulación del mundo real mediante la división del trabajo y la cooperación entre objetos.

Cada objeto es un centro funcional con una clara división del trabajo y puede completar tareas como recibir información, procesar datos y enviar información. Los objetos se pueden reutilizar y personalizar mediante el mecanismo de herencia. Por lo tanto, la programación orientada a objetos tiene las características de flexibilidad, reutilización del código y alta modularidad, y es fácil de mantener y desarrollar. En comparación con la programación de procedimientos tradicional (programación de procedimientos) que consta de una serie de funciones o instrucciones, es más adecuada para cooperación entre varias personas Grandes proyectos de software.

Entonces, ¿qué es exactamente un "objeto"? Lo entendemos en dos niveles.

(1) Un objeto es una abstracción de un solo objeto.

Un libro, un automóvil y una persona pueden ser objetos, al igual que una base de datos, una página web y una conexión a un servidor remoto. Cuando los objetos se abstraen en objetos, la relación entre objetos se convierte en la relación entre objetos, de modo que se puede simular la situación real y se puede realizar la programación de objetos.

(2) Un objeto es un contenedor que encapsula propiedades y métodos.

Las propiedades son el estado del objeto y los métodos son el comportamiento del objeto (realizar algún tipo de tarea). Por ejemplo, podemos abstraer un animal en animalun objeto, usar "atributos" para registrar qué animal es y usar "métodos" para representar ciertos comportamientos de los animales (correr, cazar, descansar, etc.).

Constructor

El primer paso en la programación orientada a objetos es generar objetos. Como se mencionó anteriormente, un objeto es una abstracción de una sola entidad. Por lo general, se necesita una plantilla para representar las características comunes de un determinado tipo de objeto, y luego el objeto se genera de acuerdo con esta plantilla.

Los lenguajes de programación típicos orientados a objetos (como C++ y Java) tienen el concepto de "clase". La llamada "clase" es la plantilla del objeto y el objeto es la instancia de la "clase". Sin embargo, el sistema de objetos del lenguaje JavaScript no se basa en "clases", sino en constructores y cadenas de prototipos.

El lenguaje JavaScript utiliza constructores como plantillas para objetos. El llamado "constructor" es una función especialmente utilizada para generar objetos de instancia. Es la plantilla del objeto, que describe la estructura básica del objeto de instancia. Un constructor puede generar múltiples objetos de instancia, todos los cuales tienen la misma estructura.

Un constructor es simplemente una función ordinaria, pero con sus propias características y uso.

var Vehicle = function () {
    
    
  this.price = 1000;
};

En el código anterior, Vehiclees el constructor. Para distinguirlo de las funciones ordinarias, la primera letra del nombre del constructor suele estar en mayúscula.

Hay dos características del constructor.

  • La palabra clave se utiliza dentro del cuerpo de la función this, que representa la instancia del objeto que se generará.
  • Al generar objetos, newse deben utilizar comandos.

El constructor básicamente no devuelve un valor.

Los comandos se introducen primero new.

nuevo comando

uso básico

newLa función del comando es ejecutar el constructor y devolver un objeto de instancia.

var Vehicle = function () {
    
    
  this.price = 1000;
};

var v = new Vehicle();
v.price // 1000

El código anterior usa newel comando para permitir que el constructor Vehiclegenere un objeto de instancia y lo guarde en una variable v. Este objeto de instancia recién generado Vehicleobtiene pricepropiedades del constructor. newCuando se ejecuta el comando, el interior del constructor thisrepresenta el objeto de instancia recién generado, this.pricelo que indica que el objeto de instancia tiene un priceatributo con un valor de 1000.

Cuando se utilizan newcomandos, el constructor también puede aceptar parámetros según sea necesario.

var Vehicle = function (p) {
    
    
  this.price = p;
};

var v = new Vehicle(500);
v.price // 500

newEl comando en sí puede ejecutar el constructor, por lo que el siguiente constructor puede tener paréntesis o no. Las siguientes dos líneas de código son equivalentes, pero para indicar que se trata de una llamada a función, se recomiendan paréntesis.

// 推荐的写法
var v = new Vehicle();
// 不推荐的写法
var v = new Vehicle;

Una pregunta natural es: ¿ newqué sucede si olvidas usar el comando y simplemente llamas al constructor?

En este caso, el constructor se convierte en una función ordinaria y no genera un objeto de instancia. Y debido a las razones que se mencionarán más adelante, thisrepresentar el objeto global en este momento provocará algunos resultados inesperados.

var Vehicle = function (){
    
    
  this.price = 1000;
};

var v = Vehicle();
v // undefined
price // 1000

En el código anterior, Vehicleal llamar al constructor, olvidé agregar newel comando. Como resultado, dado que la función Vehículo no tiene valor de retorno, la variable vy undefinedla pricepropiedad se convierten en una variable global . newPor lo tanto, se debe tener mucho cuidado para evitar llamadas directas a constructores sin un comando.

Para garantizar que el constructor deba newusarse con el comando, una solución es usar el modo estricto dentro del constructor, es decir, agregar la primera línea use strict. En este caso, una vez que olvide usar newel comando, llamar directamente al constructor informará un error.

function Fubar(foo, bar){
    
    
  'use strict';
  this._foo = foo;
  this._bar = bar;
}

Fubar()
// TypeError: Cannot set property '_foo' of undefined

El código anterior Fubares un constructor y use strictel comando garantiza que la función se ejecute en modo estricto. Porque en modo estricto, el thisobjeto interno de la función no puede apuntar al objeto global, que es igual a por defecto undefined, y se informará un error si no se newllama (JavaScript no permite undefinedagregar atributos).

Otra solución es juzgar si se debe usar el comando dentro del constructor newy devolver un objeto de instancia directamente si se descubre que no se usa.

function Fubar(foo, bar) {
    
    
  if (!(this instanceof Fubar)) {
    
    
    return new Fubar(foo, bar);
  }

  this._foo = foo;
  this._bar = bar;
}

Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1

El constructor en el código anterior newobtendrá el mismo resultado sin importar si el comando se agrega o no.

El principio del nuevo mando.

Cuando se utiliza un comando new, la función que lo sigue ejecuta los siguientes pasos en secuencia.

  1. Cree un objeto vacío como instancia del objeto que se devolverá.
  2. Apunte el prototipo de este objeto vacío a las propiedades del constructor prototype.
  3. Asigne este objeto vacío a thisla palabra clave dentro de la función.
  4. Comience a ejecutar el código dentro del constructor.

En otras palabras, dentro del constructor, thisse refiere a un objeto vacío recién generado y todas thislas operaciones específicas se realizarán en este objeto vacío. La razón por la que un constructor se llama "constructor" significa que el propósito de esta función es manipular un objeto vacío (es decir, thisun objeto) y "construirlo" en lo que necesita.

Si hay una declaración dentro del constructor returny returnva seguida de un objeto, newel comando devolverá returnel objeto especificado por la declaración; de lo contrario, devolverá returnel thisobjeto independientemente de la declaración.

var Vehicle = function () {
    
    
  this.price = 1000;
  return 1000;
};

(new Vehicle()) === 1000
// false

En el código anterior, la Vehicledeclaración del constructor returndevuelve un valor. En este momento, newel comando ignorará esta returndeclaración y devolverá el thisobjeto "construido".

Sin embargo, si returnla declaración devuelve un thisobjeto nuevo no relacionado, newel comando devuelve el objeto nuevo en lugar thisdel objeto. Este punto necesita especial atención.

var Vehicle = function (){
    
    
  this.price = 1000;
  return {
    
     price: 2000 };
};

(new Vehicle()).price
// 2000

VehicleEn el código anterior, la declaración del constructor returndevuelve un nuevo objeto. newEl comando devuelve este objeto, no thisel objeto.

Por otro lado, si el comando thisse usa en una función normal (una función sin palabras clave dentro) new, devolverá un objeto vacío.

function getMessage() {
    
    
  return 'this is a message';
}

var msg = new getMessage();

msg // {}
typeof msg // "object"

En el código anterior, getMessagees una función ordinaria que devuelve una cadena. Al usar newcomandos en él, obtienes un objeto vacío. Esto se debe a que newlos comandos siempre devuelven un objeto, ya sea el objeto de instancia o returnel objeto especificado por la declaración. En este caso, returnla declaración devuelve una cadena, por lo que newel comando ignora la declaración.

newEl proceso interno simplificado del comando se puede representar mediante el siguiente código.

function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
    
    
  // 将 arguments 对象转为数组
  var args = [].slice.call(arguments);
  // 取出构造函数
  var constructor = args.shift();
  // 创建一个空对象,继承构造函数的 prototype 属性
  var context = Object.create(constructor.prototype);
  // 执行构造函数
  var result = constructor.apply(context, args);
  // 如果返回结果是对象,就直接返回,否则返回 context 对象
  return (typeof result === 'object' && result != null) ? result : context;
}

// 实例
var actor = _new(Person, '张三', 28);

esta palabra clave

significado

thisLas palabras clave son un punto gramatical muy importante. No es exagerado decir que la mayoría de las tareas de desarrollo no pueden llevarse a cabo sin comprender sus implicaciones.

Como se mencionó en el capítulo anterior, thisse puede utilizar en constructores para representar objetos de instancia. Además, thistambién se puede utilizar en otras ocasiones. Pero no importa cuál sea la ocasión, thishay una cosa en común: siempre devuelve un objeto.

En pocas palabras, thises el objeto donde reside la propiedad o método "actualmente".

this.property

En el código anterior, thisrepresenta propertyel objeto donde se encuentra actualmente el atributo.

A continuación se muestra un ejemplo práctico.

var person = {
    
    
  name: '张三',
  describe: function () {
    
    
    return '姓名:'+ this.name;
  }
};

person.describe()
// "姓名:张三"

En el código anterior, this.namesignifica nameel objeto donde se encuentra el atributo. Dado que this.namese describellama en un método y describeel objeto actual donde se encuentra el método es person, thisapunta a person, this.namees decir person.name.

Dado que las propiedades de un objeto se pueden asignar a otro objeto, el objeto actual donde se encuentra la propiedad es mutable, es decir, el thispuntero a es mutable.

var A = {
    
    
  name: '张三',
  describe: function () {
    
    
    return '姓名:'+ this.name;
  }
};

var B = {
    
    
  name: '李四'
};

B.describe = A.describe;
B.describe()
// "姓名:李四"

En el código anterior, A.describeel atributo está asignado a B, por lo que B.describesignifica que describeel objeto actual donde se encuentra el método es B, por lo que this.nameapunta a B.name.

Al refactorizar un poco este ejemplo, thisel apuntamiento dinámico de 's se puede ver más claramente.

function f() {
    
    
  return '姓名:'+ this.name;
}

var A = {
    
    
  name: '张三',
  describe: f
};

var B = {
    
    
  name: '李四',
  describe: f
};

A.describe() // "姓名:张三"
B.describe() // "姓名:李四"

En el código anterior, las palabras clave fse utilizan dentro de la función thisy los punteros son diferentes según fel objeto en el que se encuentra this.

Siempre que la función esté asignada a otra variable, thisel puntero cambiará.

var A = {
    
    
  name: '张三',
  describe: function () {
    
    
    return '姓名:'+ this.name;
  }
};

var name = '李四';
var f = A.describe;
f() // "姓名:李四"

En el código anterior, A.describesi se asigna a una variable f, la interna thisapuntará al fobjeto donde se está ejecutando (en este caso, el objeto de nivel superior).

Veamos otro ejemplo de programación web.

<input type="text" name="age" size=3 onChange="validate(this, 18, 99);">

<script>
function validate(obj, lowval, hival){
      
      
  if ((obj.value < lowval) || (obj.value > hival))
    console.log('Invalid Value!');
}
</script>

El código anterior es un cuadro de entrada de texto. Cada vez que el usuario ingresa un valor, onChangese llamará a la función de devolución de llamada para verificar si el valor está dentro del rango especificado. El navegador pasará el objeto actual a la función de devolución de llamada, por lo que thissignifica pasar el objeto actual (es decir, el cuadro de texto), y luego se this.valuepodrá leer el valor de entrada del usuario.

En resumen, en el lenguaje JavaScript, todo es un objeto y el entorno operativo también es un objeto, por lo que las funciones se ejecutan en un objeto, que es thisel objeto (entorno) donde se ejecuta la función. Esto no confundirá a los usuarios, pero JavaScript admite el cambio dinámico del entorno de ejecución, es decir, thisel puntero es dinámico y no hay forma de determinar de antemano a qué objeto apuntar, lo que es lo que más confunde a los principiantes.

sustancia

La razón por la que el lenguaje JavaScript tiene este diseño está relacionada con la estructura de datos en la memoria.

var obj = {
    
     foo:  5 };

El código anterior asigna un objeto a una variable obj. El motor JavaScript primero generará un objeto en la memoria { foo: 5 }y luego asignará la dirección de memoria del objeto a una variable obj. En otras palabras, una variable objes una dirección (referencia). Si desea leer más tarde obj.foo, el motor primero objobtiene la dirección de memoria, luego lee el objeto original de la dirección y devuelve sus fooatributos.

El objeto original se almacena en una estructura de diccionario y cada nombre de atributo corresponde a un objeto de descripción de atributo. Por ejemplo, foolas propiedades del ejemplo anterior en realidad se guardan en el siguiente formulario.

{
    
    
  foo: {
    
    
    [[value]]: 5
    [[writable]]: true
    [[enumerable]]: true
    [[configurable]]: true
  }
}

Tenga en cuenta que fooel valor del atributo se almacena en valueel atributo del objeto de descripción del atributo.

Esta estructura es muy clara: el problema es que el valor del atributo puede ser una función.

var obj = {
    
     foo: function () {
    
    } };

En este momento, el motor guardará la función en la memoria por separado y luego asignará la dirección de la función al atributo foodel valueatributo.

{
    
    
  foo: {
    
    
    [[value]]: 函数的地址
    ...
  }
}

Dado que una función es un valor único, se puede ejecutar en diferentes entornos (contextos).

var f = function () {
    
    };
var obj = {
    
     f: f };

// 单独执行
f()

// obj 环境执行
obj.f()

JavaScript permite referencias a otras variables del entorno actual dentro del cuerpo de una función.

var f = function () {
    
    
  console.log(x);
};

En el código anterior, las variables se utilizan en el cuerpo de la función x. Esta variable la proporciona el entorno de ejecución.

Ahora surge el problema, dado que la función se puede ejecutar en diferentes entornos operativos, es necesario que haya un mecanismo para obtener el entorno operativo actual (contexto) dentro del cuerpo de la función. Por lo tanto, thisapareció y su propósito de diseño es hacer referencia al entorno operativo actual de la función dentro del cuerpo de la función.

var f = function () {
    
    
  console.log(this.x);
}

En el código anterior, el cuerpo de la función this.xse refiere al entorno operativo actual x.

var f = function () {
    
    
  console.log(this.x);
}

var x = 1;
var obj = {
    
    
  f: f,
  x: 2,
};

// 单独执行
f() // 1

// obj 环境执行
obj.f() // 2

En el código anterior, la función fse ejecuta en el entorno global, this.xapuntando al entorno global ; se ejecuta xen el entorno, apuntando a .objthis.xobj.x

ocasiones de uso

thisExisten principalmente las siguientes ocasiones de uso.

(1) Entorno global

Utilizado por el entorno global this, se refiere al objeto de nivel superior window.

this === window // true

function f() {
    
    
  console.log(this === window);
}
f() // true

El código anterior muestra que, ya sea que esté dentro de una función o no, siempre que se ejecute en el entorno global, thisse refiere al objeto de nivel superior window.

(2) Constructor

En el constructor this, se refiere al objeto de instancia.

var Obj = function (p) {
    
    
  this.p = p;
};

El código anterior define un constructor Obj. Dado que thisapunta al objeto de instancia, se define dentro del constructor this.p, lo que equivale a definir un patributo del objeto de instancia.

var o = new Obj('Hello World!');
o.p // "Hello World!"

(3) Método de objeto

Si el método del objeto contiene this, thisel puntero es el objeto donde se ejecuta el método. Asignar este método a otro objeto cambiará thisel puntero.

Sin embargo, esta regla no es fácil de entender. Consulte el código a continuación.

var obj ={
    
    
  foo: function () {
    
    
    console.log(this);
  }
};

obj.foo() // obj

En el código anterior, obj.foocuando se ejecuta el método, su thispuntero interno obj.

Sin embargo, los siguientes usos cambiarán thisla dirección.

// 情况一
(obj.foo = obj.foo)() // window
// 情况二
(false || obj.foo)() // window
// 情况三
(1, obj.foo)() // window

En el código anterior, obj.fooes un valor. Cuando realmente se llama a este valor, el entorno operativo ya no es obj, sino el entorno global, por lo que thisya no apunta a él obj.

Se puede entender que dentro del motor JavaScript objy obj.fooalmacenadas en dos direcciones de memoria, llamadas dirección uno y dirección dos. obj.foo()Al llamar de esta manera, la dirección 2 se llama desde la dirección 1, por lo que el entorno operativo de la dirección 2 es la dirección 1, que thisapunta a obj. Sin embargo, en los tres casos anteriores, se saca y llama directamente a la dirección 2. En este caso, el entorno operativo es el entorno global, por lo que apunta thisal entorno global. Los tres casos anteriores son equivalentes al código siguiente.

// 情况一
(obj.foo = function () {
    
    
  console.log(this);
})()
// 等同于
(function () {
    
    
  console.log(this);
})()

// 情况二
(false || function () {
    
    
  console.log(this);
})()

// 情况三
(1, function () {
    
    
  console.log(this);
})()

Si thisel método no está en la primera capa del objeto, thissolo apunta al objeto de la capa actual y no hereda la capa superior.

var a = {
    
    
  p: 'Hello',
  b: {
    
    
    m: function() {
    
    
      console.log(this.p);
    }
  }
};

a.b.m() // undefined

En el código anterior, a.b.mel método está en ala segunda capa del objeto, y el interior del método thisno apunta a, sino apunta a.b, porque la ejecución real es el siguiente código.

var b = {
    
    
  m: function() {
    
    
   console.log(this.p);
  }
};

var a = {
    
    
  p: 'Hello',
  b: b
};

(a.b).m() // 等同于 b.m()

Si desea lograr el efecto deseado, solo puede escribirlo de la siguiente manera.

var a = {
    
    
  b: {
    
    
    m: function() {
    
    
      console.log(this.p);
    },
    p: 'Hello'
  }
};

Si asigna el método dentro del objeto anidado a una variable en este momento, seguirá thisapuntando al objeto global.

var a = {
    
    
  b: {
    
    
    m: function() {
    
    
      console.log(this.p);
    },
    p: 'Hello'
  }
};

var hello = a.b.m;
hello() // undefined

En el código anterior, mes un método dentro del objeto multicapa. Para simplificar, se asigna a hellouna variable y, cuando se llama al resultado, thisapunta al objeto de nivel superior. Para evitar este problema, solo puedes masignar el objeto donde se encuentra hello, de modo que cuando lo llames, thisel puntero al mismo no cambie.

var hello = a.b;
hello.m() // Hello

Puntos a tener en cuenta

Evite múltiples capas de esto

Dado que thisel puntero a es indeterminado, nunca incluya varias capas en una función this.

var o = {
    
    
  f1: function () {
    
    
    console.log(this);
    var f2 = function () {
    
    
      console.log(this);
    }();
  }
}

o.f1()
// Object
// Window

El código anterior contiene dos capas this. Después de la ejecución, la primera capa apunta al objeto oy la segunda capa apunta al objeto global, porque la ejecución real es el siguiente código.

var temp = function () {
    
    
  console.log(this);
};

var o = {
    
    
  f1: function () {
    
    
    console.log(this);
    var f2 = temp();
  }
}

Una solución alternativa es utilizar una thisvariable en el segundo nivel que apunte al nivel exterior.

var o = {
    
    
  f1: function() {
    
    
    console.log(this);
    var that = this;
    var f2 = function() {
    
    
      console.log(that);
    }();
  }
}

o.f1()
// Object
// Object

El código anterior define una variable that, que se fija en la capa exterior thisy luego se usa en la capa interior , por lo que el apuntamiento thatno cambiará.this

thisDe hecho, es una práctica muy común usar un valor fijo de una variable y luego llamar a esta variable en la función interna, asegúrese de dominarlo.

JavaScript proporciona un modo estricto, que también puede evitar este tipo de problemas. En modo estricto, si el objeto dentro de la función thisapunta al objeto de nivel superior, se informará un error.

var counter = {
    
    
  count: 0
};
counter.inc = function () {
    
    
  'use strict';
  this.count++
};
var f = counter.inc;
f()
// TypeError: Cannot read property 'count' of undefined

En el código anterior, el método adopta el modo estricto inca través de la declaración. En este momento , una vez que el objeto interno apunta al objeto de nivel superior, se informará un error.'use strict'this

Evite esto en los métodos de manejo de matrices.

Los métodos mapy de la matriz foreachpermiten que se proporcione una función como argumento. Esta función no debe usarse internamente this.

var o = {
    
    
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    
    
    this.p.forEach(function (item) {
    
    
      console.log(this.v + ' ' + item);
    });
  }
}

o.f()
// undefined a1
// undefined a2

En el código anterior, foreachla función de devolución de llamada del método thisen realidad apunta al windowobjeto, por lo que o.vno se puede obtener el valor. thisLa razón es la misma que la multicapa del párrafo anterior , es decir, la capa interior thisno apunta al exterior, sino al objeto de nivel superior.

Una forma de resolver este problema, como se mencionó anteriormente, es utilizar una fijación de variable intermedia this.

var o = {
    
    
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    
    
    var that = this;
    this.p.forEach(function (item) {
    
    
      console.log(that.v+' '+item);
    });
  }
}

o.f()
// hello a1
// hello a2

Otra forma es fijar su entorno de ejecución como segundo parámetro del método this.foreach

var o = {
    
    
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    
    
    this.p.forEach(function (item) {
    
    
      console.log(this.v + ' ' + item);
    }, this);
  }
}

o.f()
// hello a1
// hello a2

Evite esto en la función de devolución de llamada.

Las devoluciones de llamadas thisa menudo cambian los indicadores y es mejor evitarlas.

var o = new Object();
o.f = function () {
    
    
  console.log(this === o);
}

// jQuery 的写法
$('#button').on('click', o.f);

En el código anterior, después de hacer clic en el botón, se mostrará la consola false. La razón es que esta vez thisya no apunta al oobjeto, sino al objeto DOM del botón, porque fel método se llama en el contexto del objeto del botón. Esta sutil diferencia se pasa por alto fácilmente en la programación, lo que genera errores difíciles de detectar.

Para resolver este problema, se pueden utilizar los siguientes métodos para thisvincular el objeto, es decir, fijarlo thisa un determinado objeto y reducir la incertidumbre.

El método que une este

thisEl cambio dinámico de , por supuesto, crea una gran flexibilidad para JavaScript, pero también hace que la programación sea difícil y ambigua. En ocasiones, es necesario thisarreglarlo para evitar situaciones inesperadas. JavaScript proporciona callestos tres applymétodos bindpara cambiar/arreglar thisel apuntamiento.

Función.prototipo.call()

Para el método de una instancia de función call, puede especificar el thispuntero interno de la función (es decir, el alcance donde se ejecuta la función) y luego llamar a la función en el alcance especificado.

var obj = {
    
    };

var f = function () {
    
    
  return this;
};

f() === window // true
f.call(obj) === obj // true

En el código anterior, fcuando la función se ejecuta en el entorno global, thisapunta al entorno global (el navegador es windowun objeto); callel método puede cambiar thisel apuntamiento, especificar thisel objeto apuntador objy luego objejecutar la función en el alcance de el objeto f.

callEl parámetro del método, que debería ser un objeto. Si el parámetro está vacío nully undefined, el objeto global se pasa de forma predeterminada.

var n = 123;
var obj = {
    
     n: 456 };

function a() {
    
    
  console.log(this.n);
}

a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456

En el código anterior, si la palabra clave aen la función apunta al objeto global, el resultado devuelto es . Si el método se utiliza para apuntar la palabra clave a un objeto, el resultado devuelto es . Se puede ver que si el método no tiene parámetros, o el parámetro es o , equivale a apuntar al objeto global.this123callthisobj456callnullundefined

Si callel parámetro del método es un valor original, el valor original se convertirá automáticamente en el objeto contenedor correspondiente y luego se pasará al callmétodo.

var f = function () {
    
    
  return this;
};

f.call(5)
// Number {[[PrimitiveValue]]: 5}

En el código anterior, callel parámetro 5no es un objeto, se convertirá automáticamente en un objeto contenedor ( Numberinstancia) y se vinculará finternamente this.

callLos métodos también pueden aceptar múltiples parámetros.

func.call(thisValue, arg1, arg2, ...)

callEl primer parámetro de es thisel objeto al que se apuntará y los siguientes parámetros son los parámetros necesarios cuando se llama a la función.

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

add.call(this, 1, 2) // 3

En el código anterior, callel método especifica el entorno actual vinculante (objeto) adddentro de la función this, y los parámetros son 1y 2, por lo que addse obtiene después de ejecutar la función 3.

callUna aplicación de los métodos es llamar a métodos nativos en objetos.

var obj = {
    
    };
obj.hasOwnProperty('toString') // false

// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
    
    
  return true;
};
obj.hasOwnProperty('toString') // true

Object.prototype.hasOwnProperty.call(obj, 'toString') // false

En el código anterior, hasOwnPropertyes objun método heredado por el objeto, si se anula este método, no se obtendrá el resultado correcto. callEl método puede resolver este problema: coloca hasOwnPropertyla definición original del método objen el objeto y lo ejecuta, por lo que si objhay un método con el mismo nombre o no, no afectará el resultado.

Función.prototipo.aplicar()

applyLa función del método calles similar al método, que consiste en cambiar thisel puntero y luego llamar a la función. La única diferencia es que recibe una matriz como parámetro cuando se ejecuta la función y el formato de uso es el siguiente.

func.apply(thisValue, [arg1, arg2, ...])

applyEl primer parámetro del método también es thisel objeto al que apuntar. Si se establece en nullo undefined, equivale a especificar el objeto global. El segundo parámetro es una matriz, y todos los miembros de la matriz se pasan como parámetros a la función original por turno. Los parámetros de la función original calldeben agregarse uno por uno en el método, pero en applyel método deben agregarse en forma de matriz.

function f(x, y){
    
    
  console.log(x + y);
}

f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2

En el código anterior, fla función originalmente acepta dos parámetros, applypero después de usar el método, puede aceptar una matriz como parámetro.

Usando esto, puedes hacer algunas aplicaciones interesantes.

(1) Encuentre el elemento más grande de la matriz

JavaScript no proporciona una función para encontrar el elemento más grande de una matriz. Combinando applymétodos y Math.maxmétodos, puede devolver el elemento más grande de una matriz.

var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15

(2) Cambie los elementos vacíos de la matriz aundefined

A través applydel método, use Arrayel constructor para convertir los elementos vacíos de la matriz en undefined.

Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]

La diferencia entre elementos vacíos y undefinedes que el forEachmétodo de matriz omitirá elementos vacíos, pero no omitirá undefined. Por lo tanto, al atravesar los elementos internos, obtendrás resultados diferentes.

var a = ['a', , 'b'];

function print(i) {
    
    
  console.log(i);
}

a.forEach(print)
// a
// b

Array.apply(null, a).forEach(print)
// a
// undefined
// b

(3) Convertir objetos tipo matriz

Además, utilizando slicelos métodos de los objetos de matriz, un objeto similar a una matriz (como argumentsun objeto) se puede convertir en una matriz real.

Array.prototype.slice.apply({
    
    0: 1, length: 1}) // [1]
Array.prototype.slice.apply({
    
    0: 1}) // []
Array.prototype.slice.apply({
    
    0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({
    
    length: 1}) // [undefined]

Los parámetros del método en el código anterior applyson todos objetos, pero los resultados devueltos son todos matrices, lo que sirve para convertir objetos en matrices. Como se puede ver en el código anterior, el requisito previo para que este método funcione es que el objeto procesado debe tener lengthatributos y claves numéricas correspondientes.

(4) El objeto que vincula la función de devolución de llamada.

El ejemplo anterior del evento de clic en el botón se puede reescribir de la siguiente manera.

var o = new Object();

o.f = function () {
    
    
  console.log(this === o);
}

var f = function (){
    
    
  o.f.apply(o);
  // 或者 o.f.call(o);
};

// jQuery 的写法
$('#button').on('click', f);

En el código anterior, después de hacer clic en el botón, se mostrará la consola true. Dado que apply()el método (o call()método) no solo vincula el objeto donde se ejecuta la función, sino que también ejecuta la función inmediatamente, la declaración vinculante debe escribirse en el cuerpo de una función. Una forma más concisa de escribir es utilizar el método que se describe a continuación bind().

Función.prototipo.bind()

bind()Los métodos se utilizan para vincular el cuerpo de una función thisa un objeto y luego devolver una nueva función.

var d = new Date();
d.getTime() // 1481869925657

var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.

En el código anterior, d.getTime()asignamos el método a una variable print, luego lo llamamos print()e informamos un error. Esto se debe a que getTime()dentro del método this, Datela instancia del objeto vinculado se asigna a la variable printy el interior thisya no apunta a Datela instancia del objeto.

bind()El método puede resolver este problema.

var print = d.getTime.bind(d);
print() // 1481869925657

En el código anterior, bind()el método vincula getTime()las partes internas del método thisal dobjeto y luego este método se puede asignar de forma segura a otras variables.

bindEl parámetro del método es thisel objeto a vincular, el siguiente es un ejemplo más claro.

var counter = {
    
    
  count: 0,
  inc: function () {
    
    
    this.count++;
  }
};

var func = counter.inc.bind(counter);
func();
counter.count // 1

En el código anterior, counter.inc()los métodos se asignan a variables func. En este momento, se debe utilizar bind()un método para vincular inc()el interno ; de lo contrario, se producirá un error.thiscounter

thisTambién es posible vincularse a otros objetos.

var counter = {
    
    
  count: 0,
  inc: function () {
    
    
    this.count++;
  }
};

var obj = {
    
    
  count: 100
};
var func = counter.inc.bind(obj);
func();
obj.count // 101

En el código anterior, bind()el método vincula inc()el interior del método thisal objobjeto. Como resultado, después de llamar funca la función, lo que se incrementa es la propiedad objinterna .count

bind()También puede aceptar más parámetros y vincularlos a los parámetros de la función original.

var add = function (x, y) {
    
    
  return x * this.m + y * this.n;
}

var obj = {
    
    
  m: 2,
  n: 2
};

var newAdd = add.bind(obj, 5);
newAdd(5) // 20

En el código anterior, bind()además de vincular el objeto, el método thistambién vincula add()el primer parámetro de la función y luego devuelve una nueva función , que puede ejecutarse siempre que acepte un parámetro más.x5newAdd()y

Si bind()el primer parámetro del método es nullo undefined, igual estará thisvinculado al objeto global y la función thisapuntará al objeto de nivel superior (para navegadores window) cuando se ejecute.

function add(x, y) {
    
    
  return x + y;
}

var plus5 = add.bind(null, 5);
plus5(10) // 15

En el código anterior, add()no hay ninguna función interna . El propósito principal de thisusar el método es vincular parámetros . Cada vez que ejecute una nueva función en el futuro , solo necesita proporcionar otro parámetro . Y como no hay inside , el primer parámetro es , pero si es otro objeto aquí, no tiene ningún efecto.bind()xplus5()yadd()thisbind()null

bind()Existen algunas precauciones para utilizar el método.

(1) Devolver una nueva función cada vez

bind()Cada vez que se ejecuta el método, devuelve una nueva función, lo que crea algunos problemas. Por ejemplo, al escuchar eventos, no se puede escribir de la siguiente manera.

element.addEventListener('click', o.m.bind(o));

En el código anterior, una función anónima generada por clickel método de enlace de eventos . bind()Esto hace que sea imposible desvincular, por lo que el siguiente código no es válido.

element.removeEventListener('click', o.m.bind(o));

La forma correcta es escribirlo así:

var listener = o.m.bind(o);
element.addEventListener('click', listener);
//  ...
element.removeEventListener('click', listener);

(2) Combinado con la función de devolución de llamada

Las devoluciones de llamada son uno de los patrones más comunes en JavaScript, pero un error común es thistratar los métodos contenidos directamente como devoluciones de llamada. La solución es utilizar bind()métodos que se counter.inc()unan counter.

var counter = {
    
    
  count: 0,
  inc: function () {
    
    
    'use strict';
    this.count++;
  }
};

function callIt(callback) {
    
    
  callback();
}

callIt(counter.inc.bind(counter));
counter.count // 1

En el código anterior, callIt()el método llama a la función de devolución de llamada. En este momento, si pasa directamente , el interno apuntará al objeto global counter.incal llamar . Después de usar el método para vincular , no tendrás este problema, siempre apuntando a .counter.inc()thisbind()counter.inccounterthiscounter

Hay otra situación que es más sutil, es decir, algunos métodos de matriz pueden aceptar una función como parámetro. Es probable que los indicadores internos thisde estas funciones sean incorrectos.

var obj = {
    
    
  name: '张三',
  times: [1, 2, 3],
  print: function () {
    
    
    this.times.forEach(function (n) {
    
    
      console.log(this.name);
    });
  }
};

obj.print()
// 没有任何输出

En el código anterior, lo obj.printinterno apunta , no hay ningún problema con esto. Sin embargo, la función de devolución de llamada del método apunta al objeto global, por lo que no hay forma de obtener el valor. Se puede ver más claramente cambiándolo un poco.this.timesthisobjforEach()this.name

obj.print = function () {
    
    
  this.times.forEach(function (n) {
    
    
    console.log(this === window);
  });
};

obj.print()
// true
// true
// true

La solución a este problema también es mediante bind()la vinculación de métodos this.

obj.print = function () {
    
    
  this.times.forEach(function (n) {
    
    
    console.log(this.name);
  }.bind(this));
};

obj.print()
// 张三
// 张三
// 张三

(3) call()Uso en combinación con métodos

Usando bind()métodos, puede reescribir las formas de uso de algunos métodos nativos de JavaScript, tomando slice()los métodos de matriz como ejemplo.

[1, 2, 3].slice(0, 1) // [1]
// 等同于
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]

En el código anterior, el método de matriz divide otra matriz slicedesde el interior de acuerdo con la posición inicial y final especificadas. [1, 2, 3]La esencia de esto es [1, 2, 3]llamar Array.prototype.slice()a un método, para que pueda callexpresar este proceso con un método y obtener el mismo resultado.

call()Los métodos son esencialmente Function.prototype.call()métodos de llamada, por lo que la expresión anterior se puede bind()reescribir usando métodos.

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]

El significado del código anterior es que se convertirá Array.prototype.sliceen Function.prototype.callel objeto donde se encuentra el método y se convertirá en cuando se llame Array.prototype.slice.call. También se puede utilizar una escritura similar para otros métodos de matriz.

var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);

var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]

pop(a)
a // [1, 2, 3]

Si va un paso más allá y Function.prototype.callvincula el método al Function.prototype.bindobjeto, significa que bindel formulario de llamada también se puede reescribir.

function f() {
    
    
  console.log(this.v);
}

var o = {
    
     v: 123 };
var bind = Function.prototype.call.bind(Function.prototype.bind);
bind(f, o)() // 123

El significado del código anterior es vincular Function.prototype.bindel método a Function.prototype.callél, por lo que bindel método se puede usar directamente sin usarlo en la instancia de la función.

Supongo que te gusta

Origin blog.csdn.net/weixin_54986292/article/details/131956353
Recomendado
Clasificación