目次
従来のインスタンス化と継承
把传统方法加以改造
是时候彻底抛弃new关键字了
结束语
ホームページ ウェブフロントエンド jsチュートリアル JavaScript で new キーワードを使用しない理由の詳細な説明

JavaScript で new キーワードを使用しない理由の詳細な説明

Mar 15, 2017 pm 05:35 PM

JavaScript の new キーワードはインスタンス化と継承を実装できますが、個人的には new キーワードの使用はベスト プラクティスではなく、より使いやすい実装があると思います。この記事では、new キーワードの使用に関する問題を紹介し、その後、new に関連付けられた一連の オブジェクト指向 操作をカプセル化し、より高速で理解しやすい実装を提供する方法を紹介します。

従来のインスタンス化と継承

2 つのクラス、<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() {}SubClass:function SubClass(){}: function Class( ) {} と SubClass:function SubClass(){} の場合、サブクラスはクラスから継承する必要があります。従来のメソッドは一般に次のように構成され、実装されます:

  • 継承されたプロパティとメソッドは、Class

  • SubClass

    独自のメソッドのプロトタイプ属性に配置する必要があり、属性も配置する必要があります自身のプロトタイプ属性

  • SubClassのプロトタイプオブジェクトのプロトタイプ(proto)属性は、クラスのプロトタイプを指している必要があります

このように、プロトタイプチェーンの特性により、サブクラスのインスタンスを追跡することができます継承を実現するために Class メソッドに追加します:

new SubClass()      Object.create(Class.prototype)
    |                    |
    V                    V
SubClass.prototype ---> { }
                        { }.proto ---> Class.prototype
ログイン後にコピー

具体的な例を示します: 次のコードでは、次のことを行います:

  • Human

  • という名前の親クラスを定義します

  • Man という名前のクラスを定義します サブクラスは以下を継承しますHuman

    サブクラスは親クラスのすべての属性を継承し、親クラスの
  • コンストラクター
を呼び出してこのサブクラスをインスタンス化します

// 构造函数/基类
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
  • 上記のコードを通じて、インスタンス化の伝統的ないくつかの特徴を要約することができますおよび継承:

  • 従来のメソッドの「クラス」はコンストラクターである必要があります。

  • 属性とメソッドはプロトタイプ属性にバインドされ、継承はプロトタイプの特性を利用して実装されます。

new キーワードを使用してオブジェクトをインスタンス化します。

  • なぜ Object.create メソッドが Douglas のオブジェクト メソッドと一致していると確信できるのでしょうか? MDN では、オブジェクト メソッドは Object.create の Polyfill ソリューションであるためです。 : The Good Parts" では、Douglas は、新しいキーワードの使用は避けるべきであると考えています:

  • コンストラクター関数を呼び出すときに新しい接頭辞を
  • インクルード

    するのを忘れると、これは新しいオブジェクトにバインドされません。悲しいことにこれはグローバル オブジェクトにバインドされるため、新しいオブジェクトを拡張する代わりに、グローバル変数を上書きすることになります。これは、コンパイル時の警告も、実行時の警告もありません

    。一般的な考え方は、new を使用する必要があるときに new キーワードを忘れると、何らかの問題が発生するということです。
もちろん、キーワードを使用し忘れると、一連の問題が発生します。一歩下がって考えると、この問題は完全に回避可能です:

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

   // 构造函数的逻辑继续……
}
ログイン後にコピー

またはより一般的な

例外をスロー

する
function foo()
{
    if ( !(this instanceof arguments.callee) ) 
       throw new Error("Constructor called as a function");
}
ログイン後にコピー

か、John Resig の計画に従って、ほとんどの初期化関数を実行する makeClass ファクトリ関数を準備します それを init に置きますコンストラクター自体ではなく、メソッドです:

私の意見では、new キーワードが良い習慣ではない主な理由は次のとおりです:

…new は、JavaScript が「人気」を得るために Java のような構文を受け入れた時代の名残です。そして、Visual Basic のような補完的な言語が当時の Microsoft の言語ファミリーであったように、私たちはそれを Java の弟として推し進めていました

Douglas は、この問題を次のように説明しました。この言語は、古典的な訓練を受けたプログラマにとってはより馴染みのあるものに見えますが、JavaScript に対する Java プログラマの評価が非常に低いことからわかるように、JavaScript のコンストラクタ パターンは古典的な人々の興味を引くものではありませんでした。その結果、この言語を効果的に使用する方法を知っているプログラマーはほとんどいません

簡単に言うと、JavaScript は市場に応えるために作成された典型的な言語です 新しいキーワードは、人々に考えさせる必要があるために導入されました。それはJavaに似ているということです。 JavaScript は、そのプロトタイプ機能を通じてインスタンス化と継承を実装することになっていますが、新しいキーワードにより、それが目立たなくなります。

把传统方法加以改造

既然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关键字的基础上继续实现多继承?希望本文的内容在这里只是抛砖引玉,能够开拓大家的思路。


以上がJavaScript で new キーワードを使用しない理由の詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 Dec 17, 2023 pm 02:54 PM

WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 はじめに: 技術の継続的な発展により、音声認識技術は人工知能の分野の重要な部分になりました。 WebSocket と JavaScript をベースとしたオンライン音声認識システムは、低遅延、リアルタイム、クロスプラットフォームという特徴があり、広く使用されるソリューションとなっています。この記事では、WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法を紹介します。

WebSocket と JavaScript: リアルタイム監視システムを実装するための主要テクノロジー WebSocket と JavaScript: リアルタイム監視システムを実装するための主要テクノロジー Dec 17, 2023 pm 05:30 PM

WebSocketとJavaScript:リアルタイム監視システムを実現するためのキーテクノロジー はじめに: インターネット技術の急速な発展に伴い、リアルタイム監視システムは様々な分野で広く利用されています。リアルタイム監視を実現するための重要なテクノロジーの 1 つは、WebSocket と JavaScript の組み合わせです。この記事では、リアルタイム監視システムにおける WebSocket と JavaScript のアプリケーションを紹介し、コード例を示し、その実装原理を詳しく説明します。 1.WebSocketテクノロジー

JavaScript と WebSocket を使用してリアルタイムのオンライン注文システムを実装する方法 JavaScript と WebSocket を使用してリアルタイムのオンライン注文システムを実装する方法 Dec 17, 2023 pm 12:09 PM

JavaScript と WebSocket を使用してリアルタイム オンライン注文システムを実装する方法の紹介: インターネットの普及とテクノロジーの進歩に伴い、ますます多くのレストランがオンライン注文サービスを提供し始めています。リアルタイムのオンライン注文システムを実装するには、JavaScript と WebSocket テクノロジを使用できます。 WebSocket は、TCP プロトコルをベースとした全二重通信プロトコルで、クライアントとサーバー間のリアルタイム双方向通信を実現します。リアルタイムオンラインオーダーシステムにおいて、ユーザーが料理を選択して注文するとき

WebSocketとJavaScriptを使ったオンライン予約システムの実装方法 WebSocketとJavaScriptを使ったオンライン予約システムの実装方法 Dec 17, 2023 am 09:39 AM

WebSocket と JavaScript を使用してオンライン予約システムを実装する方法 今日のデジタル時代では、ますます多くの企業やサービスがオンライン予約機能を提供する必要があります。効率的かつリアルタイムのオンライン予約システムを実装することが重要です。この記事では、WebSocket と JavaScript を使用してオンライン予約システムを実装する方法と、具体的なコード例を紹介します。 1. WebSocket とは何ですか? WebSocket は、単一の TCP 接続における全二重方式です。

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 Dec 17, 2023 pm 05:13 PM

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 はじめに: 今日、天気予報の精度は日常生活と意思決定にとって非常に重要です。テクノロジーの発展に伴い、リアルタイムで気象データを取得することで、より正確で信頼性の高い天気予報を提供できるようになりました。この記事では、JavaScript と WebSocket テクノロジを使用して効率的なリアルタイム天気予報システムを構築する方法を学びます。この記事では、具体的なコード例を通じて実装プロセスを説明します。私たちは

簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 Jan 05, 2024 pm 06:08 PM

JavaScript チュートリアル: HTTP ステータス コードを取得する方法、特定のコード例が必要です 序文: Web 開発では、サーバーとのデータ対話が頻繁に発生します。サーバーと通信するとき、多くの場合、返された HTTP ステータス コードを取得して操作が成功したかどうかを判断し、さまざまなステータス コードに基づいて対応する処理を実行する必要があります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法を説明し、いくつかの実用的なコード例を示します。 XMLHttpRequestの使用

JavaScriptでinsertBeforeを使用する方法 JavaScriptでinsertBeforeを使用する方法 Nov 24, 2023 am 11:56 AM

使用法: JavaScript では、insertBefore() メソッドを使用して、DOM ツリーに新しいノードを挿入します。このメソッドには、挿入される新しいノードと参照ノード (つまり、新しいノードが挿入されるノード) の 2 つのパラメータが必要です。

JavaScript と WebSocket: 効率的なリアルタイム画像処理システムの構築 JavaScript と WebSocket: 効率的なリアルタイム画像処理システムの構築 Dec 17, 2023 am 08:41 AM

JavaScript は Web 開発で広く使用されているプログラミング言語であり、WebSocket はリアルタイム通信に使用されるネットワーク プロトコルです。 2 つの強力な機能を組み合わせることで、効率的なリアルタイム画像処理システムを構築できます。この記事では、JavaScript と WebSocket を使用してこのシステムを実装する方法と、具体的なコード例を紹介します。まず、リアルタイム画像処理システムの要件と目標を明確にする必要があります。リアルタイムの画像データを収集できるカメラ デバイスがあるとします。

See all articles