Heim > Web-Frontend > js-Tutorial > Detaillierte Erklärung des Konstruktors, der Klassen in js in traditionelle Klassenmuster umwandelt (mit Beispielen)

Detaillierte Erklärung des Konstruktors, der Klassen in js in traditionelle Klassenmuster umwandelt (mit Beispielen)

不言
Freigeben: 2018-09-21 10:41:17
Original
2328 Leute haben es durchsucht

Was dieser Artikel bringt, ist eine detaillierte Erklärung des Konstruktors (mit Beispielen) über die Umwandlung von Klassen in JS in traditionelle Klassenmuster. Ich hoffe, dass er einen gewissen Referenzwert hat hilfreich sein.

Vorwort

Die auf Prototypen basierende „Klasse“ von JS hat Programmierer, die auf das Front-End umgestiegen sind, immer erstaunt. Das Aufkommen der Klassenschlüsselwortdefinition ähnelt jedoch dem traditionellen Modus hat einige Front-End-Kollegen gemacht Sie bedauerten es zutiefst und hinterließen nacheinander Nachrichten: „Gib mir mein einzigartiges JS zurück“, „Baue ein paar unwesentliche Dinge“, „Ich habe keine Klassen, also muss ich mich auf die Klassen anderer Leute verlassen.“ “, und sogar „Ich habe den Beruf gewechselt“ und so weiter. Es ist normal, Emotionen zu haben, denn neues Wissen bedeutet mehr Zeit- und Energieaufwand und man kann es nicht einfach mit geschlossenen Augen genießen.

Allerdings bewegt sich die Achse der Geschichte weiter vorwärts. Eines ist beim Unterricht sicher: „Bitte, es ist nicht so, dass ich es nicht verstehe, es ist nur so, dass ich es nicht verstehe.“ „Ich möchte es nicht verstehen. Bitte ändern Sie die Frage!“ Obwohl „class“ nur syntaktischer Zucker ist, verbessert „extends“ die Vererbung recht gut. Andererseits sollten neue Funktionen, die in Zukunft in „Klassen“ erscheinen könnten, von Klassen statt von Konstruktoren getragen werden. Niemand ist sich sicher, wie schön es in Zukunft sein wird. Also, komm schon, trink langsam diese dampfende Schüssel brauner Zucker-Ingwer-Suppe

1 Klasse

Es gibt kein Klassenkonzept in ECMAScript, unsere Instanzen werden von Konstruktoren basierend auf Prototypen von Objekten generiert dynamische Eigenschaften und Methoden. Um jedoch internationalen Standards zu entsprechen und es einfacher und eleganter zu beschreiben, wird weiterhin das Wort „Klasse“ verwendet. JS-Klassen entsprechen also Konstruktoren. Die ES6-Klasse ist nur syntaktischer Zucker, und die durch ihre Definition generierten Objekte sind immer noch Konstruktoren. Aber um es vom Konstruktormuster zu unterscheiden, nennen wir es Klassenmuster. Für die Lernklasse sind Kenntnisse über Konstrukteure und Prototypobjekte erforderlich. Weitere Informationen finden Sie auf Baidu.

// --- 使用构造函数
function C () {
  console.log('New someone.');
}

C.a = function () { return 'a'; }; // 静态方法

C.prototype.b = function () { return 'b'; }; // 原型方法


// --- 使用class
class C {
  static a() { return 'a'; } // 静态方法
  
  constructor() { console.log('New someone.'); } // 构造方法
  
  b() { return 'b'; } // 原型方法
};
Nach dem Login kopieren

1.1 Vergleich mit Variablen

Die Schlüsselwortklasse ähnelt der Schlüsselwortfunktion, die eine Funktion definiert. Es gibt zwei Arten der Definition: Deklaration und Ausdruck (anonym und benannt). Die Art der durch deklarative Ausdrücke definierten Variablen unterscheidet sich von der von Funktionen. Sie ähneln eher let und const. Sie werden nicht im Voraus analysiert, haben keine Variablenheraufstufung, sind nicht mit dem globalen Bereich verknüpft und haben temporäre Totzonen. Die von der Klassendefinition generierte Variable ist ein Konstruktor und daher kann die Klasse im sofortigen Ausführungsmodus geschrieben werden.

// --- 声明式
class C {}
function F() {}

// --- 匿名表达式
let C = class {};
let F = function () {};

// --- 命名表达式
let C = class CC {};
let F = function FF() {};

// --- 本质是个函数
class C {}
console.log(typeof C); // 'function'
console.log(Object.prototype.toString.call(C)); // '[object Function]'
console.log(C.hasOwnProperty('prototype')); // true

// --- 不存在变量提升
C; // 报错,不存在C。
class C {}
// 存在提前解析和变量提升
F; // 不报错,F已被声明和赋值。
function F() {}

// --- 自执行模式
let c = new (class {
})();
let f = new (function () {
})();
Nach dem Login kopieren

1.2 Vergleich mit Objekten

Die Form des Klasseninhalts (innerhalb von {}) ähnelt der von Objektliteralen. Im Klasseninhalt können jedoch nur Methoden definiert werden, keine Attribute. Die Form von Methoden kann nur Funktionsabkürzungen sein und Methoden können nicht durch Kommas getrennt werden. Der Methodenname kann ein Ausdruck in Klammern oder ein Symbolwert sein. Methoden sind in drei Kategorien unterteilt: Konstruktormethoden (Konstruktormethoden), Prototypmethoden (existiert im Prototypattribut des Konstruktors) und statische Methoden (existiert im Konstruktor selbst).

class C {
  // 原型方法a
  a() { console.log('a'); }
  // 构造方法,每次生成实例时都会被调用并返回新实例。
  constructor() {}
  // 静态方法b,带static关键字。
  static b() { console.log('b'); }
  // 原型方法,带括号的表达式
  ['a' + 'b']() { console.log('ab'); }
  // 原型方法,使用Symbol值
  [Symbol.for('s')]() { console.log('symbol s'); }
}

C.b(); // b

let c = new C();
c.a(); // a
c.ab(); // ab
c[Symbol.for('s')](); // symbol s
Nach dem Login kopieren

kann Attribute nicht direkt definieren und tut dies auch not Repräsentationsklassen können keine Prototypen oder statischen Eigenschaften haben. Durch das Auflösen der Klasse wird ein Konstruktor erstellt. Fügen Sie der Klasse also einfach Eigenschaften hinzu, so wie Sie dem Konstruktor Eigenschaften hinzufügen würden. Es ist einfacher und wird empfohlen, Getter-Funktionen nur zum Definieren schreibgeschützter Eigenschaften zu verwenden. Warum kann ich Eigenschaften nicht direkt festlegen? Ist die Technologie unausgereift? Hofft der Beamte, eine bestimmte Idee zu vermitteln? Oder ist es nur eine zufällige Frage des Autors?

// --- 直接在C类(构造函数)上修改
class C {}
C.a = 'a';
C.b = function () { return 'b'; };
C.prototype.c = 'c';
C.prototype.d = function () { return 'd'; };

let c = new C();
c.c; // c
c.d(); // d

// --- 使用setter和getter
// 定义只能获取不能修改的原型或静态属性
class C {
  get a() { return 'a'; }
  static get b() { return 'b'; }
}

let c = new C();
c.a; // a
c.a = '1'; // 赋值没用,只有get没有set无法修改。
Nach dem Login kopieren

1.3 Vergleich mit Konstruktoren

Das Folgende ist der Code, der Konstruktoren und Klassen verwendet, um dieselbe Funktion zu erreichen. Intuitiv vereinfachen Klassen den Code und machen den Inhalt aggregierter. Der Konstruktormethodenkörper entspricht dem Funktionskörper der Konstruktorfunktion. Wenn diese Methode nicht explizit definiert ist, wird standardmäßig eine leere Konstruktormethode hinzugefügt, um eine neue Instanz zurückzugeben. Wie ES5 kann es auch so angepasst werden, dass ein anderes Objekt anstelle einer neuen Instanz zurückgegeben wird. Obwohl die Klasse

// --- 构造函数
function C(a) {
  this.a = a;
}

// 静态属性和方法
C.b = 'b';
C.c = function () { return 'c'; };

// 原型属性和方法
C.prototype.d = 'd';
C.prototype.e = function () { return 'e'; };
Object.defineProperty(C.prototype, 'f', { // 只读属性
  get() {
    return 'f';
  }
});

// --- 类
class C {
  static c() { return 'c'; }
  
  constructor(a) {
    this.a = a;
  }
  
  e() { return 'e'; }
  get f() { return 'f'; }
}

C.b = 'b';
C.prototype.d = 'd';
Nach dem Login kopieren

eine Funktion ist, kann sie nur über new Instanzen generieren und nicht direkt aufgerufen werden. Alle innerhalb der Klasse definierten Methoden sind nicht aufzählbar, und die dem Konstruktor selbst und dem Prototyp hinzugefügten Eigenschaften und Methoden sind aufzählbar. Innerhalb einer Klasse definierte Methoden befinden sich standardmäßig im strikten Modus und müssen nicht explizit deklariert werden. Die oben genannten drei Punkte erhöhen die Genauigkeit der Klasse. Leider gibt es immer noch keine Möglichkeit, private Eigenschaften und Methoden direkt zu definieren.

// --- 能否直接调用
class C {}
C(); // 报错

function C() {}
C(); // 可以


// --- 是否可枚举
class C {
  static a() {} // 不可枚举
  b() {} // 不可枚举
}

C.c = function () {}; // 可枚举
C.prototype.d = function () {}; // 可枚举

isEnumerable(C, ['a', 'c']); // a false, c true
isEnumerable(C.prototype, ['b', 'd']); // b false, d true

function isEnumerable(target, keys) {
  let obj = Object.getOwnPropertyDescriptors(target);
  keys.forEach(k => {
    console.log(k, obj[k].enumerable);
  });
}


// --- 是否为严格模式
class C {
  a() {
    let is = false;
    try {
      n = 1;
    } catch (e) {
      is = true;
    }
    console.log(is ? 'true' : 'false');
  }
}

C.prototype.b = function () {
  let is = false;
  try {
    n = 1;
  } catch (e) {
    is = true;
  }
  console.log(is ? 'true' : 'false');
};

let c = new C();
c.a(); // true,是严格模式。
c.b(); // false,不是严格模式。
Nach dem Login kopieren

Fügen Sie das Schlüsselwort static vor der Methode hinzu, um anzugeben, dass es sich bei der Methode um eine statische Methode handelt. Sie existiert in der Klasse selbst und kann von der Instanz nicht direkt aufgerufen werden. Dies zeigt in einer statischen Methode auf die Klasse selbst. Da sie sich auf unterschiedlichen Objekten befinden, können statische Methoden und Prototypmethoden denselben Namen haben. ES6 fügt einen neuen Befehl new.target hinzu, der auf den Konstruktor oder die Klasse nach new verweist. Für die Verwendung dieses Befehls gelten bestimmte Einschränkungen.

// --- static
class C {
  static a() { console.log(this === C); }
  a() { console.log(this instanceof C); }
}

let c = new C();
C.a(); // true
c.a(); // true


// --- new.target
// 构造函数
function C() {
  console.log(new.target);
}

C.prototype.a = function () { console.log(new.target); };

let c = new C(); // 打印出C
c.a(); // 在普通方法中为undefined。

// --- 类
class C {
  constructor() { console.log(new.target); }
  a() { console.log(new.target); }
}

let c = new C(); // 打印出C
c.a(); // 在普通方法中为undefined。

// --- 在函数外部使用会报错
new.target; // 报错
Nach dem Login kopieren

2 erweitert

Die klassische Vererbungsmethode in ES5 ist die parasitäre kombinierte Vererbung. Die Unterklasse erbt die Eigenschaften und Methoden der übergeordneten Klasseninstanz bzw. des Prototyps. Das Wesen der Vererbung in ES6 ist ebenfalls dasselbe, aber die Implementierungsmethode hat sich geändert, wie im folgenden Code gezeigt. Es ist ersichtlich, dass die Vererbung auf dem Prototyp eine Form ist, die der traditionellen Sprache mithilfe des Schlüsselworts „Extens“ näher kommt, und die Vererbung auf der Instanz darin besteht, die Formgebung der Unterklasse durch Aufrufen von super abzuschließen. Oberflächlich betrachtet ist der Ansatz einheitlicher und prägnanter.

class C1 {
  constructor(a) { this.a = a; }
  b() { console.log('b'); }
}

class C extends C1 { // 继承原型数据
  constructor() {
    super('a'); // 继承实例数据
  }
}
Nach dem Login kopieren

2.1 与构造函数对比

使用extends继承,不仅仅会将子类的prototype属性的原型对象(__proto__)设置为父类的prototype,还会将子类本身的原型对象(__proto__)设置为父类本身。这意味着子类不单单会继承父类的原型数据,也会继承父类本身拥有的静态属性和方法。而ES5的经典继承只会继承父类的原型数据。不单单是财富,连老爸的名气也要获得,不错不错。

class C1 {
  static get a() { console.log('a'); }
  static b() { console.log('b'); }
}

class C extends C1 {
}
// 等价,没有构造方法会默认添加。
class C extends C1 {
  constructor(...args) {
    super(...args);
  }
}

let c = new C();
C.a; // a,继承了父类的静态属性。
C.b(); // b,继承了父类的静态方法。
console.log(Object.getPrototypeOf(C) === C1); // true,C的原型对象为C1
console.log(Object.getPrototypeOf(C.prototype) === C1.prototype); // true,C的prototype属性的原型对象为C1的prototype
Nach dem Login kopieren

ES5中的实例继承,是先创造子类的实例对象this,再通过call或apply方法,在this上添加父类的实例属性和方法。当然也可以选择不继承父类的实例数据。而ES6不同,它的设计使得实例继承更为优秀和严谨。

在ES6的实例继承中,是先调用super方法创建父类的this(依旧指向子类)和添加父类的实例数据,再通过子类的构造函数修饰this,与ES5正好相反。ES6规定在子类的constructor方法里,在使用到this之前,必须先调用super方法得到子类的this。不调用super方法,意味着子类得不到this对象。

class C1 {
  constructor() {
    console.log('C1', this instanceof C);
  }
}

class C extends C1 {
  constructor() {
    super(); // 在super()之前不能使用this,否则报错。
    console.log('C');
  }
}

new C(); // 先打印出C1 true,再打印C。
Nach dem Login kopieren

2.2 super

关键字super比较奇葩,在不同的环境和使用方式下,它会指代不同的东西(总的说可以指代对象或方法两种)。而且在不显式的指明是作为对象或方法使用时,比如console.log(super),会直接报错。

作为函数时。super只能存在于子类的构造方法中,这时它指代父类构造函数。

作为对象时。super在静态方法中指代父类本身,在构造方法和原型方法中指代父类的prototype属性。不过通过super调用父类方法时,方法的this依旧指向子类。即是说,通过super调用父类的静态方法时,该方法的this指向子类本身;调用父类的原型方法时,该方法的this指向该(子类的)实例。而且通过super对某属性赋值时,在子类的原型方法里指代该实例,在子类的静态方法里指代子类本身,毕竟直接在子类中通过super修改父类是很危险的。

很迷糊对吧,疯疯癫癫的,还是结合着代码看吧!

class C1 {
  static a() {
    console.log(this === C);
  }
  b() {
    console.log(this instanceof C);
  }
}

class C extends C1 {
  static c() {
    console.log(super.a); // 此时super指向C1,打印出function a。
    
    this.x = 2; // this等于C。
    super.x = 3; // 此时super等于this,即C。
    console.log(super.x); // 此时super指向C1,打印出undefined。
    console.log(this.x); // 值已改为3。

    super.a(); // 打印出true,a方法的this指向C。
  }

  constructor() {
    super(); // 指代父类的构造函数
    
    console.log(super.c); // 此时super指向C1.prototype,打印出function c。

    this.x = 2; // this等于新实例。
    super.x = 3; // 此时super等于this,即实例本身。
    console.log(super.x); // 此时super指向C1.prototype,打印出undefined。
    console.log(this.x); // 值已改为3。

    super.b(); // 打印出true,b方法的this指向实例本身。
  }
}
Nach dem Login kopieren

2.3 继承原生构造函数

使用构造函数模式,构建继承了原生数据结构(比如Array)的子类,有许多缺陷的。一方面由上文可知,原始继承是先创建子类this,再通过父类构造函数进行修饰,因此无法获取到父类的内部属性(隐藏属性)。另一方面,原生构造函数会直接忽略call或apply方法传入的this,导致子类根本无法获取到父类的实例属性和方法。

function MyArray(...args) {
  Array.apply(this, args);
}

MyArray.prototype = Array.prototype;
// MyArray.prototype.constructor = MyArray;

let arr = new MyArray(1, 2, 3); // arr为对象,没有储存值。
arr.push(4, 5); // 在arr上新增了0,1和length属性。
arr.map(d => d); // 返回数组[4, 5]
arr.length = 1; // arr并没有更新,依旧有0,1属性,且arr[1]为5。
Nach dem Login kopieren

创建类的过程,是先构造一个属于父类却指向子类的this(绕口),再通过父类和子类的构造函数进行修饰。因此可以规避构造函数的问题,获取到父类的实例属性和方法,包括内部属性。进而真正的创建原生数据结构的子类,从而简单的扩展原生数据类型。另外还可以通过设置Symbol.species属性,使得衍生对象为原生类而不是自定义子类的实例。

class MyArray extends Array { // 实现是如此的简单
  static get [Symbol.species]() { return Array; }
}

let arr = new MyArray(1, 2, 3); // arr为数组,储存有1,2,3。
arr.map(d => d); // 返回数组[1, 2, 3]
arr.length = 1; // arr正常更新,已包含必要的内部属性。
Nach dem Login kopieren

需要注意的是继承Object的子类。ES6改变了Object构造函数的行为,一旦发现其不是通过new Object()这种形式调用的,构造函数会忽略传入的参数。由此导致Object子类无法正常初始化,但这不是个大问题。

class MyObject extends Object {
  static get [Symbol.species]() { return Object; }
}

let o = new MyObject({ id: 1 });
console.log(o.hasOwnPropoty('id')); // false,没有被正确初始化
Nach dem Login kopieren

Das obige ist der detaillierte Inhalt vonDetaillierte Erklärung des Konstruktors, der Klassen in js in traditionelle Klassenmuster umwandelt (mit Beispielen). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage