Heim >Web-Frontend >js-Tutorial >Kenntnisse in objektorientierten JavaScript- und prototypischen Javascript-Kenntnissen
ECMAScript verfügt über zwei Entwicklungsmodelle: 1. Funktional (prozedural); 2. Objektorientiert (OOP);
1 Objekt erstellen
1. Gewöhnliche Erstellung von Objekten
// 创建一个对象,然后给这个对象新的属性和方法; var box = new Object(); // 创建一个Object对象; box.name = 'lee'; // 创建一个name属性并赋值; box.age = 100; box.run = function(){ // 创建一个run()方法并返回值; return this.name+this.age+'运行中...'; } console.log(box.run()); // 输入属性和方法的值; // 缺点:想创建类似的对象,就会产生大量的代码;
// 这种方法就是为了解决实例化对象产生大量代码重复的问题; function createObject(name,age){ // 集中创建函数体; var obj = new Object; // 函数体内创建Object; obj.name = name; obj.age = age; obj.run = function(){ return this.name+this.age+"运行中..."; }; return obj; } var box1 = createObject("lee",100); // 实例化;调用函数并传参; var box2 = createObject("jack",200); // 实例二; console.log(box1.run()+box2.run()); // 实例保持相对独立; // 缺点:对象与实例的识别问题;无法搞清楚它们到底是那个对象的实例; console.log(typeof box1); // Object;
// ECMAScript采用构造函数(构造方法)可用来创建特定的对象; function Box(name,age){ // 构造函数模式; this.name = name; // this代表对象Box; this.age = age; this.run = function(){ return this.name+this.age+"运行中..."; }; } var box1 = new Box("lee",100); // 要创建对象的实例必须用new操作符; var box2 = new Box("jack",200); // box1和box2都是Box对象的实例; console.log(box1 instanceof Box); // true;很清晰的识别box1从属于Box; // 使用构造函数,即解决了重复实例化的问题,有解决了对象识别的问题;
Der Unterschied zwischen der Verwendung von Konstruktor- und Factory-Mustern:
(1). Die Konstruktormethode zeigt das erstellte Objekt (neues Objekt) nicht an;
(2). Weisen Sie diesem Objekt direkt Eigenschaften und Methoden zu
(3). Keine Return-Anweisung;1 //Konstruktorspezifikation:
(1). Der Funktionsname (Funktionsbox) und der Name des Instanziierungskonstrukts (neue Box) sind gleich und werden großgeschrieben
(2). Um ein Instanzobjekt über den Konstruktor zu erstellen, müssen Sie den neuen Operator
verwenden
// 构造函数和普通函数的区别: var box = new Box('lee',100); // 构造模式调用; Box('lee',200); // 普通模式调用,无效; var o = new Object(); Box.call(o,'jack',200); // 对象冒充调用; // 将Box对象作用域扩充到对象o;Box()方法的运行环境已经变成了对象o里;Problem mit Konstruktor:
Wenn Sie den Konstruktor zum Erstellen jeder Instanz verwenden, müssen die Methoden im Konstruktor für jede Instanz neu erstellt werden;
Da Funktionen in ECMAScript Objekte sind, wird jedes Mal, wenn eine Funktion definiert wird, ein Objekt instanziiert
Das Erstellen von Funktionen auf diese Weise führt zu unterschiedlichen Bereichsketten und Bezeichnerauflösungen;
Zwei Prototypen
// Zweck: Enthält Eigenschaften und Methoden, die von allen Instanzen eines bestimmten Typs gemeinsam genutzt werden können;
// Verständnis: Prototyp ist das Prototypobjekt des Objekts, das durch Aufrufen des Konstruktors
erstellt wurde
// Der Vorteil der Verwendung eines Prototyps besteht darin, dass alle Objektinstanzen die darin enthaltenen Eigenschaften und Methoden teilen können
// Mit anderen Worten, Sie müssen keine Objektinformationen (Eigenschaften/Methoden) im Konstruktor definieren, sondern können diese Informationen direkt zum Prototyp hinzufügen
1. Prototypmodus (Prototyp fügt Attribute und Methoden hinzu)
1.原型模式 function Box(){} // 声明构造函数; Box.prototype.name = 'Lee'; // 在原型里添加属性和方法; Box.prototype.age = 100; Box.prototype.run = function() { return this.name+this.age+'运行中...'; }; var box1 = new Box(); var box2 = new Box(); console.log(box1.run==box2.run); // =>true;方法引用的地址保持一致; // 在原型中多了两个属性,这两个原型属性都是创建对象时自动生成的; // 1.__proto__:构造函数指向原型对象的一个指针;它的作用:指向构造函数的原型的属性constructor; 14// IE浏览器在脚本访问__proto__会不能识别; 15 // 判断一个实例对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试; console.log(Box.prototype.isPrototypeOf(box)); // =>true; 只要实例化对象,即都会指向; // 原型模式的执行流程: // 1.先查找构造函数对象的实例里的属性或方法,若有,立刻返回; // 2.若构造函数对象的实例里没有,则去它的原型对象里找,若有,就返回; // 虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原型中的值; var box1 = new Box(); console.log(box1.name); // Lee; 原型里的值; bo1.name = 'jack'; console.log(box1.name); // Jack;实例自己赋的值; var box2 = new Box(); console.log(box2.name); // Lee;原型里的值;没有被box1修改; // 如果想要box1继续访问原型里的值,可以把构造函数里的属性删除即可; delete box1.name; // 删除实例自己的属性; console.log(box1.name); // Lee; 原型里原来的值;2. Prototyp und im Betreiber
Wie kann festgestellt werden, ob sich die Eigenschaft in der Instanz des Konstruktors oder im Prototyp befindet? Sie können die Funktion hasOwnProperty() verwenden, um zu überprüfen;
console.log(box.hasOwnProperty('name')); // Wenn eine Instanz vorhanden ist, geben Sie true zurück, andernfalls geben Siezurück Der in-Operator gibt true zurück, wenn über das Objekt auf die angegebene Eigenschaft zugegriffen werden kann, unabhängig davon, ob die Eigenschaft in der Instanz oder im Prototyp vorhanden ist console.log('name' in box); // =>true, existiert in der Instanz oder im Prototyp 3. Einfachere Prototyp-Syntax (Prototyp-Literal-Modus)
3. Einfachere Prototypensyntax (Prototyp-Literal-Modus)
4. Dynamischer Charakter des Prototyps (durch Neuschreiben werden vorherige Inhalte überschrieben)
function Box(){}; Box.prototype = { // 以字面量形式创建包含属性和方法的新对象; name:'Lee', age:100, run:function(){ return this.name+this.age+'运行中...'; } }; // 使用构造函数创建原型对象和使用字面量创建原型对象在使用上基本相同; // 但是,使用字面量创建的原型对象使用constructor属性不会指向实例,而是指向原型对象Object;构造函数的方式则相反; var box = new Box(); console.log(box instanceof Box); console.log(box instanceof Object); console.log(box.constructor == Box); // 字面量方式,返回false; console.log(box.constructor == Object); // 字面量方式,返回true; // 如果想让字面量方式的constructor指向实例对象: Box.prototype = { constructor:Box, // 直接强制指向即可; } // PS:字面量方式为什么constructor会指向Object? // 因为Box.prototype={}这种字面量写法就是创建一个新对象; // 而每创建一个函数,就会同时创建它的prototype,这个对象也会自动获取constructor属性; // 所以,新对象的constructor重写了Box原来的constructor,因此指向了新对象, // 那个新对象没有指定构造函数,那么就默认为是Object;
5. Prototyp des nativen Objekts
// 原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型; function Box(){}; Box.prototype = { constructor:Box, name:'Lee', age:100, run:function(){ return this.age+'运行中...'; } }; Box.prototype = { // 原型重写了,覆盖了之前的原型; age:200, run:function(){ return this.age+'运行中...'; } } var box = new Box(); console.log(box.run()); // =>200运行中...; // 重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;对象实例引用的仍然是最初的原型;// Und der integrierte Referenztyp selbst verwendet auch Prototypen;
console.log(String.prototype.substring); // =>function substring() { [native code] };
6. Probleme mit Prototypobjekten
7. Verwenden Sie eine Kombination aus Konstruktormuster (Daten, die nicht von Objekten gemeinsam genutzt werden) und Prototypmuster (Daten, die von Objekten gemeinsam genutzt werden)
// 原型模式创建对象缺点:省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的; // 而原型最大的有点就是共享,属性共享; // 但是,如果原型中的属性包含引用类型(对象),共享就会存在一定问题; function Box(){}; Box.prototype = { constructor:Box, name:'Lee', age:100, family:['father','mother'], run:function(){ return this.name+this.age+this.family; } }; var box1 = new Box(); box1.family.push('sister'); // 为box1的family属性添加了sister;而这个属性被共享到原型了; console.log(box1.run()); // =>Lee100father,mother,sister; var box2 = new Box(); console.log(box2.run()); // =>Lee100father,mother,sister; // 数据共享导致实例化出的数据不能保存自己的特性;
8. Dynamischer Prototypmodus (Kapselung des Prototyps im Konstruktor)
// 为了解决构造传参和共享问题,组合构造函数+原型模式: function Box(name,age){ // 不共享的使用构造函数; this.name = name; this.age = age; this.family = ['father','moter']; }; Box.prototype = { // 共享的使用原型模式; constructor:Box, run:function(){ return this.name+this.age+this.family; } }; // PS:这种混合模式很好的解决了传参和引用共享的大难题;是创建对象比较好的方法;
9. Parasitärer Konstruktor
// 原型模式,不管是否调用了原型中的共享方法,它都会初始化原型中的方法; // 并且在声明一个对象时,构造函数+原型让人感觉怪异;最好把构造函数和原型封装到一起; function Box(name,age){ // 将所有信息封装到构造函数体内; this.name = name; this.age = age; // 当第一次调用构造函数时,run()方法不存在,然后执行初始化原型; // 当第二次调用,就不会初始化,并且第二次创建新对象,原型也不会载初始化; // 这样既得到了封装,又实现了原型方法共享,并且属性都保持独立; if(typeof this.run != 'function'){ // 仅在第一次调用时初始化; Box.prototype.run = function (){ return this.name+this.age+'运行中...'; }; } }; var box = new Box('lee',10); console.log(box.run()); // PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联系;
Drei Vererbungen
// 寄生构造函数,其实就是工厂模式+构造模式;这种模式比较通用,但不能确定对象关系; function Box(name,age){ var obj = new Object(); obj.name = name; obj.age = age; obj.run = function (){ return this.name+this.age+'运行中...'; }; return obj; }
2. Die Beziehung zwischen Prototyp und Instanz;
// 继承是面向对象中一个比较核心的概念; // 其他正统面向对象语言都会用两种方式实现继承:一个是接口实现,一个是继承; // 而ECMAScript只支持继承,不支持接口实现,而实现继承的方式依靠原型链完成; // 实质:利用原型让一个引用类型继承另一个引用类型的属性和方法; // 原型继承链:Box ==>> Desk ==>> Table; function Box(){ // Box构造; this.name = 'Lee'; } function Desk(){ // Desk构造; this.age = 100; } Desk.prototype = new Box(); // 通过创建Box实例,并赋值给Desk.prototype实现的;通过原型,形成链条; // 实质是:重写了Desk的原型对象,取而代之的是一个新类型Box的实例; // 也就是说原来存在于Box实例中的属性和方法,现在也存在与Desk.prototype中了; var desk = new Desk(); console.log(desk.age); // 100; console.log(desk.name); // =>Lee; function Table(){ this.level = 'AAA'; } Table.prototype = new Desk(); // 继续原型链继承;Table继承了Desk; var table = new Table(); console.log(table.name); // Lee;
//Um das Problem der Referenzfreigabe und der Unfähigkeit, Parameter an Supertypen zu übergeben, zu lösen;
// PS:以上原型链继承缺少一环,那就是Object,所有的构造函数都继承自Object; // 而继承Object是自动完成的,并不需要手动继承; console.log(table instanceof Object); // =>true; console.log(desk instanceof Table); // =>false;Desk是Table的超类; console.log(table instanceof Desk); // =>true; console.log(table instanceof Box); // =>true; // 在JS中,被继承的函数称为超类型(父类,基类); // 继承的函数称为子类型(子类,派生类); // 继承问题: // 字面量重写原型会中断关系; // 子类型无法给超类型传递参数;
// Obwohl das Ausleihen von Konstruktoren das Problem der gemeinsamen Nutzung von Referenzen und der Unfähigkeit, Parameter an Supertypen zu übergeben, löst, werden keine Prototypen verwendet und eine Wiederverwendung ist daher erforderlich
// 在子类型构造函数的内部调用超类型构造函数; function Box(age){ this.name = ['Lee','Jack','Hello']; this.age = age; } function Desk(age){ // 继承了Box;同时还传递了参数; // 这样一来,就会在新Desk对象上执行Box()函数中定义的所有对象初始化代码; Box.call(this,age); // 对象冒充,Desk继承Box,并可以给超类型传参; // 为了确保Box构造函数不会重写子类型的属性,可以在超类型构造函数后,再添加应该在子类型中定义的属性; this.height = 175; } var desk = new Desk(200); // 向Desk()函数传参,再通过函数冒用向Box()函数传参; console.log(desk.age); // =>200; console.log(desk.name); // =>['Lee','Jack','Hello']; desk.name.push('AAA'); // =>添加的新数据,只添加给desk; console.log(desk.name); // =>['Lee','Jack','Hello','AAA'];5. Prototypische Vererbung?
// 使用原型链实现对原型属性和方法的继承; // 通过借用构造函数来实现对实例属性的继承; // 这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有他自己的属性; function Box(age){ // 构造函数; this.name = ['Lee','Jack','Hello']; this.age = age; } Box.prototype.run = function(){ // 原型; return this.name+this.age; } function Desk(age){ Box.call(this,age); // 继承属性; 对象冒充; 将Box对象的作用域扩充到Desk中,Desk就会继承Box里的属性和方法; } Desk.prototype = new Box(); // 继承方法; 原型链继承; var desk = new Desk(100); console.log(desk.run()); // =>Lee,Jack,Hello100 // 最常用的继承模式;6. Parasitäre Vererbung?
// 这种继承借助原型并基于已有的对象创建对象,同时还不必因此创建自定义类型; function obj(o){ // 传递一个字面量函数; function F(){}; // 创建一个构造函数; F.prototype = o; // 把字面量函数赋值给构造函数的原型; return new F(); // 返回实例化的构造函数; } var box = { // 字面量对象; name:'Lee', arr:['brother','sisiter'] }; var box1 = obj(box); console.log(box1.name); // =>Lee; box1.name = 'Jack'; console.log(box1.name); // =>Jack; console.log(box1.arr); // =>brother,sister; box1.arr.push('father'); // console.log(box1.arr); // =>brother,sister,father; var box2 = obj(box); console.log(box2.name); // =>Lee; console.log(box2.arr); // =>brother,sister,father;引用类型共享了;7. Parasitäre kombinierte Vererbung?
Vier Zusammenfassung
1. Objekt erstellen
Objekte können während der Codeausführung erstellt und erweitert werden und sind daher eher dynamisch als streng definierte Einheiten
Wenn keine Klassen vorhanden sind, können Objekte mit den folgenden Mustern erstellt werden:
(1). Factory-Muster: Verwenden Sie einfache Funktionen, um Objekte zu erstellen, Eigenschaften und Methoden zu den Objekten hinzuzufügen und die Objekte dann zurückzugeben
Dieses Muster wurde später durch das Konstruktormuster ersetzt;
(2). Konstruktormodus: Sie können den Referenztyp anpassen und den neuen Operator verwenden, um auf einen Blick eine integrierte Objektinstanz zu erstellen
Nachteile: Jedes seiner Mitglieder kann nicht wiederverwendet werden, auch nicht die Funktionen. Da Funktionen nicht auf ein beliebiges Objekt beschränkt werden können, gibt es keinen Grund, Funktionen nicht zwischen mehreren Objekten zu teilen
(3) Prototyp-Modus: Verwenden Sie das Prototyp-Attribut der Funktion, um die Attribute und Methoden anzugeben, die gemeinsam genutzt werden sollen
Wenn Sie das Konstruktormuster und das Prototypmuster in Kombination verwenden, verwenden Sie den Konstruktor zum Definieren von Instanzeigenschaften und den Prototyp zum Definieren gemeinsamer Eigenschaften und Methoden;
Der Aufbau der Prototypenkette erfolgt durch Zuweisen einer Instanz eines Typs zum Prototyp eines anderen Konstruktors Untertypen können auf alle Eigenschaften und Methoden des Obertyps zugreifen; Das Problem mit der Prototypenkette besteht darin, dass Objektinstanzen alle geerbten Eigenschaften und Methoden gemeinsam nutzen und daher nicht für die alleinige Verwendung geeignet sind;
Lösung: Leihen Sie sich den Konstruktor aus, dh rufen Sie den Supertyp-Konstruktor innerhalb des Subtyp-Konstruktors auf Auf diese Weise kann jede Instanz ihre eigenen Eigenschaften haben und außerdem sicherstellen, dass nur der Konstruktor zum Definieren des Typs verwendet wird;
Das am häufigsten verwendete Vererbungsmuster ist die kompositorische Vererbung; es nutzt die Prototypenkette, um gemeinsame Eigenschaften und Methoden zu erben, und erbt Instanzeigenschaften durch Ausleihen von Konstruktoren;
3. Vererbungsmodus
(2). Parasitäre Vererbung: Erstellen Sie ein Objekt basierend auf einem Objekt oder einigen Informationen, erweitern Sie das Objekt und geben Sie es schließlich zurück Um das Ineffizienzproblem zu lösen, das durch das kombinierte Vererbungsmuster aufgrund mehrerer Aufrufe des Supertyp-Konstruktors verursacht wird, kann dieses Muster zusammen mit der kombinierten Vererbung verwendet werden; (3) Parasitäre kombinierte Vererbung: Sie kombiniert die Vorteile der parasitären Vererbung und der kombinierten Vererbung und ist der effektivste Weg, eine typbasierte Vererbung zu implementieren;