Heim  >  Artikel  >  Web-Frontend  >  Eine ausführliche Erklärung, warum das neue Schlüsselwort nicht in JavaScript verwendet werden sollte

Eine ausführliche Erklärung, warum das neue Schlüsselwort nicht in JavaScript verwendet werden sollte

黄舟
黄舟Original
2017-03-15 17:35:131578Durchsuche

Das neue Schlüsselwort in JavaScript kann Instanziierung und Vererbung implementieren, aber ich persönlich denke, dass die Verwendung des neuen Schlüsselworts nicht die beste Vorgehensweise ist und es eine benutzerfreundlichere Implementierung geben kann. In diesem Artikel werden die Probleme bei der Verwendung des Schlüsselworts „new“ vorgestellt und anschließend erläutert, wie eine Reihe von objektorientierten -Vorgängen im Zusammenhang mit „new“ gekapselt werden, um eine schnellere und leichter verständliche Implementierung zu ermöglichen.

Traditionelle Instanziierung und Vererbung

Angenommen, wir haben zwei Klassen, <a href="http://www.php.cn/wiki/164.html" target="_blank ">Class<code><a href="http://www.php.cn/wiki/164.html" target="_blank">Class</a>:function Class() {}:function Class() {} und SubClass:function SubClass(){}, SubClass muss von Class erben. Die traditionelle Methode wird im Allgemeinen gemäß den folgenden Schritten organisiert und implementiert:

  • Die eigenen Methoden und Eigenschaften der Unterklasse müssen ebenfalls in ihrem eigenen Prototypattribut

  • platziert werden

    Das Prototyp-Attribut (Proto) des Prototypobjekts von SubClass muss auf den Prototyp von Class verweisen

Auf diese Weise wird aufgrund der Eigenschaften der Prototypkette die Instanz von SubClass kann auf die Klassenmethode zurückgeführt werden, um eine Vererbung zu erreichen:

new SubClass()      Object.create(Class.prototype)
    |                    |
    V                    V
SubClass.prototype ---> { }
                        { }.proto ---> Class.prototype

Geben Sie ein konkretes Beispiel: Im folgenden Code führen wir die folgenden Dinge aus:

  • Definieren Eine übergeordnete Klasse heißt Human

  • Definieren Sie eine Unterklasse namens Man, die von Human erbt.

  • Die Unterklasse erbt alle Attribute von übergeordnete Klasse und ruft den Konstruktor der Klasse auf

// 构造函数/基类
function Human(name) {
    this.name = name;
}

/* 
    基类的方法保存在构造函数的prototype属性中
    便于子类的继承
*/
Human.prototype.say = function () {
    console.log("say");
}

/*
    道格拉斯的object方法(等同于object.create方法)
*/
function object(o) {
    var F = function () {};
    F.prototype = o;
    return new F();
}

// 子类构造函数
function Man(name, age) {
    // 调用父类的构造函数
    Human.call(this, name);
    // 自己的属性age
    this.age = age;
}

// 继承父类的方法
Man.prototype = object(Human.prototype);
Man.prototype.constructor = Man;

// 实例化子类
var man = new Man("Lee", 22);
console.log(man);
// 调用父类的say方法:
man.say();

DEMO

Durch den obigen Code, wir Kann die traditionelle Instanziierung und mehrere Merkmale der Vererbung zusammenfassen:

  • Die „Klasse“ in der traditionellen Methode muss ein Konstruktor sein.

  • Attribute und Methoden werden an Prototypattribute gebunden und die Vererbung wird mithilfe von Prototypmerkmalen implementiert.

  • Verwenden Sie das Schlüsselwort new, um ein Objekt zu instanziieren.

Warum bin ich mir so sicher, dass die Object.create-Methode mit der Objektmethode von Douglas konsistent ist? Denn auf MDN ist die Objektmethode eine Polyfill-Lösung für Object.create:

  • Object.create

  • Douglas Crockfür d's Objektmethode

Nachteile des neuen Schlüsselworts

In „Javascript: The Good Parts“ glaubt Douglas, dass es vermieden werden sollte. Verwenden Sie das neue Schlüsselwort:

Wenn Sie beim Aufrufen einer Konstruktorfunktion vergessen, das neue Präfix einzuschließen, wird dieses nicht an das neue Objekt gebunden. Leider wird es an das globale Objekt gebunden. Anstatt also Ihr neues Objekt zu erweitern, werden Sie globale Variablen überlasten. Es gibt keine Kompilierungswarnung und keine Laufzeitwarnung.

Die allgemeine Idee ist Wenn Sie das Schlüsselwort new vergessen, obwohl Sie new verwenden sollten, kann dies zu Problemen führen.

Natürlich verursacht jedes Schlüsselwort, das Sie vergessen, eine Reihe von Problemen. Wenn man einen Schritt zurücktritt, ist dieses Problem völlig vermeidbar:

function foo()
{   
   // 如果忘了使用关键字,这一步骤会悄悄帮你修复这个问题
   if ( !(this instanceof foo) )
      return new foo();

   // 构造函数的逻辑继续……
}

oder die allgemeinere Ausnahme auslösen einfach

function foo()
{
    if ( !(this instanceof arguments.callee) ) 
       throw new Error("Constructor called as a function");
}

oder folgen Sie der Lösung von John Resig, sich vorzubereiten eine makeClass-Factory-Funktion und fügen Sie die meisten Initialisierungsfunktionen in eine Init-Methode anstelle des Konstruktors selbst ein:

// makeClass - By John Resig (MIT Licensed)
function makeClass(){
  return function(args){
    if ( this instanceof arguments.callee ) {
      if ( typeof this.init == "function" )
        this.init.apply( this, args.callee ? args : arguments );
    } else
      return new arguments.callee( arguments );
  };
}

Meiner Meinung nach ist das neue Schlüsselwort keine gute Wahl. Der Hauptgrund für die Praxis ist:

…neu ist ein Überbleibsel aus der Zeit, als JavaScript eine Java-ähnliche Syntax akzeptierte, um an „Popularität“ zu gewinnen, und wir haben es als kleinen Bruder von Java vorangetrieben, da es eine Komplementärsprache wie Visual Basic war Douglas beschrieb das Problem damals wie folgt: „Diese Indirektion sollte die Sprache klassisch ausgebildeten Programmierern vertrauter machen, was aber nicht gelang, wie wir an der sehr geringen Meinung der Java-Programmierer sehen können.“ Das Konstruktormuster von JavaScript gefiel der klassischen Masse nicht. Daher gab es nur sehr wenige Programmierer, die wussten, wie man die Sprache effektiv nutzt. Deshalb führten sie das neue Schlüsselwort ein. Javascript soll durch seine Prototypical-Funktionen Instanziierung und Vererbung implementieren, aber das neue Schlüsselwort macht es unscheinbar.

把传统方法加以改造

既然new关键字不够友好,那么我们有两个办法可以解决这个问题:一是完全抛弃new关键字,二是把含有new关键字的操作封装起来,只向外提供友好的接口。下面将介绍第二种方法的实现思路,把传统方法加以改造。

我们开始构造一个最原始的基类Class(类似于JavaScript中的Object类),并且只向外提供两个接口:

  • Class.extend 用于拓展子类

  • Class.create 用于创建实例

// 基类
function Class() {}

// 将extend和create置于prototype对象中,以便子类继承
Class.prototype.extend = function () {};
Class.prototype.create = function () {};

// 为了能在基类上直接以.extend的方式进行调用
Class.extend = function (props) {
    return this.prototype.extend.call(this, props);
}

extend和create的具体实现:

Class.prototype.create = function (props) {
    /*
        create实际上是对new的封装;
        create返回的实例实际上就是new构造出的实例;
        this即指向调用当前create的构造函数;
    */
    var instance = new this();
    /*
        绑定该实例的属性
    */
    for (var name in props) {
        instance[name] = props[name];
    }
    return instance;
}

Class.prototype.extend = function (props) {
    /*
        派生出来的新的子类
    */
    var SubClass = function () {};
    /*
        继承父类的属性和方法,
        当然前提是父类的属性都放在prototype中
        而非上面create方法的“实例属性”中
    */
    SubClass.prototype = Object.create(this.prototype);
    // 并且添加自己的方法和属性
    for (var name in props) {
        SubClass.prototype[name] = props[name];
    }
    SubClass.prototype.constructor = SubClass;

    /*
        介于需要以.extend的方式和.create的方式调用:
    */
    SubClass.extend = SubClass.prototype.extend;
    SubClass.create = SubClass.prototype.create;

    return SubClass;
}

仍然以Human和Man类举例使用说明:

var Human = Class.extend({
    say: function () {
        console.log("Hello");
    }
});

var human = Human.create();
console.log(human)
human.say();

var Man = Human.extend({
    walk: function () {
        console.log("walk");
    }
});

var man = Man.create({
    name: "Lee",
    age: 22
});

console.log(man);
// 调用父类方法
man.say();

man.walk();

DEMO

至此,基本框架已经搭建起来,接下来继续补充功能。

  1. 我们希望把构造函数独立出来,并且统一命名为init。就好像Backbone.js中每一个view都有一个initialize方法一样。这样能让初始化更灵活和标准化,甚至可以把init构造函数借出去

  2. 我还想新增一个子类方法调用父类同名方法的机制,比如说在父类和子类的中都定义了一个say方法,那么只要在子类的say中调用this.callSuper()就能调用父类的say方法了。例如:

// 基类
var Human = Class.extend({
    /*
        你需要在定义类时定义构造方法init
    */
    init: function () {
        this.nature = "Human";
    },
    say: function () {
        console.log("I am a human");
    }
})

var Man = Human.extend({
    init: function () {
        this.sex = "man";
    },
    say: function () {
        // 调用同名的父类方法
        this.callSuper();
        console.log("I am a man");
    }
});

那么Class.create就不仅仅是new一个构造函数了:

Class.create = Class.prototype.create = function () {
    /*
        注意在这里我们只是实例化一个构造函数
        而非最后返回的“实例”,
        可以理解这个实例目前只是一个“壳”
        需要init函数对这个“壳”填充属性和方法
    */
    var instance = new this();

    /*
        如果对init有定义的话
    */
    if (instance.init) {
        instance.init.apply(instance, arguments);
    }
    return instance;
}

实现在子类方法调用父类同名方法的机制,我们可以借用John Resig的方案:

Class.extend = Class.prototype.extend = function (props) {
    var SubClass = function () {};
    var _super = this.prototype;
     SubClass.prototype = Object.create(this.prototype);
     for (var name in props) {
        // 如果父类同名属性也是一个函数
        if (typeof props[name] == "function" 
            && typeof _super[name] == "function") {
            // 重新定义用户的同名函数,把用户的函数包装起来
            SubClass.prototype[name] 
                = (function (super_fn, fn) {
                return function () {

                    // 如果用户有自定义callSuper的话,暂存起来
                    var tmp = this.callSuper;
                    // callSuper即指向同名父类函数
                    this.callSuper = super_fn;
                    /*
                        callSuper即存在子类同名函数的上下文中
                        以this.callSuper()形式调用
                    */
                    var ret = fn.apply(this, arguments);
                    this.callSuper = tmp;

                    /*
                        如果用户没有自定义的callsuper方法,则delete
                    */
                    if (!this.callSuper) {
                        delete this.callSuper;
                    }

                    return ret;
                }
            })(_super[name], props[name])  
        } else {
            // 如果是非同名属性或者方法
            SubClass.prototype[name] = props[name];    
        }

        ..
    }

    SubClass.prototype.constructor = SubClass; 
}

最后给出一个完整版,并且做了一些优化:

function Class() {}

Class.extend = function extend(props) {

    var prototype = new this();
    var _super = this.prototype;

    for (var name in props) {

        if (typeof props[name] == "function" 
            && typeof _super[name] == "function") {

            prototype[name] = (function (super_fn, fn) {
                return function () {
                    var tmp = this.callSuper;

                    this.callSuper = super_fn;

                    var ret = fn.apply(this, arguments);

                    this.callSuper = tmp;

                    if (!this.callSuper) {
                        delete this.callSuper;
                    }
                    return ret;
                }
            })(_super[name], props[name])
        } else {
            prototype[name] = props[name];    
        }
    }

    function Class() {}

    Class.prototype = prototype;
    Class.prototype.constructor = Class;

    Class.extend =  extend;
    Class.create = Class.prototype.create = function () {

        var instance = new this();

        if (instance.init) {
            instance.init.apply(instance, arguments);
        }

        return instance;
    }

    return Class;
}

下面是测试的代码。为了验证上面代码的健壮性,故意实现了三层继承:

var Human = Class.extend({
    init: function () {
        this.nature = "Human";
    },
    say: function () {
        console.log("I am a human");
    }
})

var human = Human.create();
console.log(human);
human.say();

var Man = Human.extend({
    init: function () {
        this.callSuper();
        this.sex = "man";
    },
    say: function () {
        this.callSuper();
        console.log("I am a man");
    }
});

var man = Man.create();
console.log(man);
man.say();

var Person = Man.extend({
    init: function () {
        this.callSuper();
        this.name = "lee";
    },
    say: function () {
        this.callSuper();
        console.log("I am Lee");
    }
})

var person = Person.create();
console.log(person);
person.say();

DEMO

是时候彻底抛弃new关键字了

如果不使用new关键字,那么我们需要转投上两节中反复使用的Object.create来生产新的对象

假设我们有一个矩形对象:

var Rectangle = {
    area: function () {
        console.log(this.width * this.height);
    }
};

借助Object.create,我们可以生成一个拥有它所有方法的对象:

var rectangle = Object.create(Rectangle);

生成之后,我们还可以给这个实例赋值长宽,并且取得面积值

var rect = Object.create(Rectangle);
rect.width = 5;
rect.height = 9;
rect.area();

注意这个过程我们没有使用new关键字,但是我们相当于实例化了一个对象(rectangle),给这个对象加上了自己的属性,并且成功调用了类(Rectangle)的方法。

但是我们希望能自动化赋值长宽,没问题,那就定义一个create方法:

var Rectangle = {
    create: function (width, height) {
      var self = Object.create(this);
      self.width = width;
      self.height = height;
      return self;
    },
    area: function () {
        console.log(this.width * this.height);
    }
};

使用方式如下:

var rect = Rectangle.create(5, 9);
rect.area();

在纯粹使用Object.create的机制下,我们已经完全抛弃了构造函数这个概念。一切都是对象,一个类也可以是对象,这个类的实例不过是一个它自己的复制品。

下面看看如何实现继承。我们现在需要一个正方形,继承自这个长方形

var Square = Object.create(Rectangle);

Square.create = function (side) {
  return Rectangle.create.call(this, side, side);
}

实例化它:

var sq = Square.create(5);
sq.area();

这种做法其实和我们第一种最基本的类似

function Man(name, age) {
    Human.call(this, name);
    this.age = age;
}

上面的方法还是太复杂了,我们希望进一步自动化,于是我们可以写这么一个extend函数

function extend(extension) {
    var hasOwnProperty = Object.hasOwnProperty;
    var object = Object.create(this);

    for (var property in extension) {
      if (hasOwnProperty.call(extension, property) || typeof object[property] === "undefined") {
        object[property] = extension[property];
      }
    }

    return object;
}

/*
    其实上面这个方法可以直接绑定在原生的Object对象上:Object.prototype.extend
    但个人不推荐这种做法
*/

var Rectangle = {
    extend: extend,
    create: function (width, height) {
      var self = Object.create(this);
      self.width = width;
      self.height = height;
      return self;
    },
    area: function () {
        console.log(this.width * this.height);
    }
};

这样当我们需要继承时,就可以像前几个方法一样用了

var Square = Rectangle.extend({
    // 重写实例化方法
    create: function (side) {
         return Rectangle.create.call(this, side, side);
    }
})

var s = Square.create(5);
s.area();

结束语

本文对去new关键字的方法做了一些罗列,但工作还远远没有结束,有非常多的地方值得拓展,比如:如何重新定义instance of方法,用于判断一个对象是否是一个类的实例?如何在去new关键字的基础上继续实现多继承?希望本文的内容在这里只是抛砖引玉,能够开拓大家的思路。


Das obige ist der detaillierte Inhalt vonEine ausführliche Erklärung, warum das neue Schlüsselwort nicht in JavaScript verwendet werden sollte. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
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