ホームページ  >  記事  >  ウェブフロントエンド  >  jsのプライベートメンバーの包括的な分析(コード付き)

jsのプライベートメンバーの包括的な分析(コード付き)

不言
不言オリジナル
2018-08-14 11:09:521992ブラウズ

この記事は、js のプライベート メンバーの包括的な分析を提供します (コード付き)。必要な友人が参考になれば幸いです。

JavaScript のクラス フィールド宣言 (JavaScript クラスのフィールド宣言) はステージ 3 に入りました。これには、OOP 開発者が非常に懸念している項目であるプライベート フィールドが含まれています。 JavaScript がプライベート メンバーを持たなかったのは当然のことであり、この提案は JavaScript に新たな課題をもたらします。しかし同時に、JavaScript は ES2015 がリリースされた時点ですでにプライベート化の問題を検討していたため、プライベート メンバーを実装する基盤がないわけではありません。

ピット

まず、ピットを掘ります。これは JS コードの一部です。BusinessView は、フォームとマップのレイアウトという 2 つのことを行う必要があります。 BusinessView 中要干两件事情,即对表单和地图进行布局。

代表将 _ 前缀约定为私有
class BaseView {
    layout() {
        console.log("BaseView Layout");
    }
}

class BusinessView extends BaseView  {
    layout() {
        super.layout();
        this._layoutForm();
        this._layoutMap();
    }

    _layoutForm() {
        // ....
    }

    _layoutMap() {
        // ....
    }
}

然后,由于业务的发展,发现有很多视图都存在地图布局。这里选用继承的方式来实现,所以从 BusinessView 中把地图相关的内容抽象成一个基类叫 MapView

class MapView extends BaseView {
    layout() {
        super.layout();
        this._layoutMap();
    }

    _layoutMap() {
        console.log("MapView layout map");
    }
}

class BusinessView extends MapView {
    layout() {
        super.layout();
        this._layoutForm();
        this._layoutMap();
    }

    _layoutForm() {
        // ....
    }

    _layoutMap() {
        console.log("BusinessView layout map");
    }
}

上面这两段代码是很典型的基于继承的 OOP 思想,本意是期望各个层次的类都可以通过 layout() 来进行各层次应该负责的布局任务。但理想和现实总是有差距的,在 JavaScript 中运行就会发现 BusinessView._layoutMap() 被执行了两次,而 MapView._layoutMap() 未执行。为什么?

虚函数

JavaScript 中如果在祖先和子孙类中定义了相同的名称的方法,默认会调用子孙类中的这个方法。如果想调用祖先类中的同名方法,需要在子孙类中通过 super. 来调用。

这里可以分析一下这个过程:

在子类创建对象的时候,其类和所有祖先类的定义都已经加载了。这个时候

  • 调用 BusinessView.layout()

  • 找到 super.layout(),开始调用 MapView.layout()

  • MapView.layout() 中调用this._layoutMap()

    • 于是从当前对象(BusinessView 对象)寻找 _layoutMap()

    • 找到,调用它

你看,由于 BusinessView 定义了 _layoutMap,所以压根都没去搜索原型链。对的,这是基于原型关系的 OOP 的局限。如果我们看看 C# 的处理过程,就会发现有所不同

  • 调用 BusinessView.layout()

  • 找到 base.layout(),开始调用 MapView.layout()

  • MapView.layout() 中调用 this._layoutMap()

    • 如果是,往子类找到最后一个重载(override)函数,调用

    • 如果不是,直接调用

    • MapView 中找到 _layoutMap()

    • 检查是否虚函数

发现区别了吗?关键是在于判断“虚函数”。

然而,这跟私有成员又有什么关系呢?因为私有函数肯定不是虚函数,所以在 C# 中,如果将 _layoutMap 定义为私有,那 MapView.layout() 调用的就一定是 MapView._layoutMap()

虚函数的概念有点小复杂。不过可以简单理解为,如果一个成员方法被声明为虚函数,在调用的时候就会延着其虚函数链找到最后的重载来进行调用。

JavaScript 中虽然约定 _ 前缀的是私有,那也只是君子之约,它实质上仍然不是私有。君子之约对人有效,计算机又不知道你有这个约定……。但是,如果 JavaScript 真的实现了私有成员,那么计算机就知道了,_layoutMap() 是个私有方法,应该调用本类中的定义,而不是去寻找子类中的定义。

解决当下的私有化问题

JavaScript 当下没有私有成员,但是我们又需要切时有效地解决私有成员问题,怎么办?当然有办法,用 Symbol_ プレフィックスをプライベート

const MapView = (() => {
    const _layoutMap = Symbol();

    return class MapView extends BaseView {
        layout() {
            super.layout();
            this[_layoutMap]();
        }

        [_layoutMap]() {
            console.log("MapView layout map");
        }
    }
})();

const BusinessView = (() => {
    const _layoutForm = Symbol();
    const _layoutMap = Symbol();

    return class BusinessView extends MapView {
        layout() {
            super.layout();
            this[_layoutForm]();
            this[_layoutMap]();
        }

        [_layoutForm]() {
            // ....
        }

        [_layoutMap]() {
            console.log("BusinessView layout map");
        }
    }
})();
として表しますその後、ビジネスの発展により、多くのビューにマップ レイアウトがあることが判明しました。ここでは継承メソッドが使用されているため、マップ関連のコンテンツは BusinessView から MapView という基本クラスに抽象化されます。
const _layoutMap = Symbol();

export class MapView extends BaseView {
    layout() {
        super.layout();
        this[_layoutMap]();
    }

    [_layoutMap]() {
        console.log("MapView layout map");
    }
}
上記の 2 つのコードは非常に典型的なものです。継承に基づく OOP のアイデアにより、すべてのレベルのクラスが layout() を使用して、各レベルが担当するレイアウト タスクを実行できるようになります。しかし、理想と現実の間には常にギャップがあります。JavaScript で実行すると、MapView._layoutMap() が 2 回実行されるのに対し、BusinessView._layoutMap() は 2 回実行されることがわかります。実行されていません。それはなぜでしょうか?

仮想関数🎜🎜 JavaScriptでは、祖先クラスと子孫クラスに同じ名前のメソッドが定義されている場合、デフォルトで子孫クラスのこのメソッドが呼び出されます。祖先クラスで同じ名前のメソッドを呼び出したい場合は、子孫クラスの super. を介して呼び出す必要があります。 🎜🎜ここでこのプロセスを分析できます: 🎜🎜 サブクラスがオブジェクトを作成すると、そのクラスとすべての祖先クラスの定義がロードされます。この時点で 🎜
  • 🎜 BusinessView.layout() を呼び出します🎜
  • 🎜 super.layout( ) MapView.layout() の呼び出しを開始します🎜
  • 🎜MapView.layout() を呼び出し、this._layoutMap( ) 🎜
    • 🎜つまり、現在のオブジェクトから (BusinessView code> Object) <code>_layoutMap() を探しています🎜
    • 🎜見つかったので呼び出してください🎜
🎜ほら、 のせいです>BusinessView code> は <code>_layoutMap を定義しているため、プロトタイプ チェーンはまったく検索されません。はい、これはプロトタイプ関係に基づく OOP の制限です。 C# の処理を​​見てみると、何か違うことがわかります🎜
  • 🎜BusinessView.layout() を呼び出します🎜
  • >🎜 base.layout() を見つけて MapView.layout() の呼び出しを開始します🎜
  • 🎜MapView.layout() <code>this._layoutMap() を呼び出します🎜
    • 🎜 はいの場合、サブクラス内の最後のオーバーライド関数を見つけて 🎜
    • 🎜 を呼び出します。そうでない場合は、MapView で 🎜
    • 🎜 を直接呼び出します。 _layoutMap()🎜
    • 🎜仮想関数かどうか確認してください🎜
🎜違いは見つかりましたか?重要なのは、「仮想関数」を決定することです。 🎜🎜しかし、これはプライベートメンバーとどのような関係があるのでしょうか?プライベート関数は明らかに仮想関数ではないため、C# では、_layoutMap がプライベートとして定義されている場合、MapView.layout()MapView()。 🎜🎜仮想関数の概念は少し複雑です。ただし、メンバー メソッドが仮想関数として宣言されている場合、それが呼び出されるとき、その仮想関数チェーンに従って最後に呼び出すオーバーロードを見つけることは簡単に理解できます。 🎜🎜<code>_ プレフィックスは JavaScript ではプライベートであることが合意されていますが、これは単なる紳士協定であり、本質的にはプライベートではありません。紳士協定は人々に対して有効ですが、コンピュータはあなたがこの協定を結んでいることを知りません...ただし、JavaScript が実際にプライベート メンバーを実装している場合、コンピュータは _layoutMap() がプライベート メソッドであることを認識し、サブクラスで定義を探すのではなく、このクラスの定義を呼び出す必要があります。 🎜🎜現在の民営化問題の解決🎜🎜JavaScript 現在、プライベートメンバーはいませんが、プライベートメンバーの問題を迅速かつ効果的に解決する必要があります。もちろん、Symbol とクロージャを使用してそれを解決する方法があります。 🎜🎜ここでのクロージャは、関数内でクロージャを生成するためのガイドではないことに注意してください。読み続けてください 🎜🎜まず第一に、この民営化問題を柔軟な方法で検討していることを明確にしましょう。祖先クラスの呼び出し元が特定のメソッドを呼び出すと、最初にサブクラス内を検索しません。この問題は文法的に解決できません。JavaScript は特定のインスタンスから名前を指定する方法を逆に探す必要があります。しかし、メソッド名が見つからない場合はどうすればよいでしょうか? 🎜

之所以能找到,因为方法名是字符串。一个字符串在全局作用域内都表示着同样的意义。但是 ES2015 带来了 Symbol,它必须实例化,而且每次实例化出来一定代表着不同的标识 —— 如果我们将类定义在一个闭包中,在这个闭包中声明一个 Symbol,用它来作为私有成员的名称,问题就解决了,比如

const MapView = (() => {
    const _layoutMap = Symbol();

    return class MapView extends BaseView {
        layout() {
            super.layout();
            this[_layoutMap]();
        }

        [_layoutMap]() {
            console.log("MapView layout map");
        }
    }
})();

const BusinessView = (() => {
    const _layoutForm = Symbol();
    const _layoutMap = Symbol();

    return class BusinessView extends MapView {
        layout() {
            super.layout();
            this[_layoutForm]();
            this[_layoutMap]();
        }

        [_layoutForm]() {
            // ....
        }

        [_layoutMap]() {
            console.log("BusinessView layout map");
        }
    }
})();

而现代基于模块的定义,甚至连闭包都可以省了(模块系统会自动封闭作用域)

const _layoutMap = Symbol();

export class MapView extends BaseView {
    layout() {
        super.layout();
        this[_layoutMap]();
    }

    [_layoutMap]() {
        console.log("MapView layout map");
    }
}
const _layoutForm = Symbol();
const _layoutMap = Symbol();

export class BusinessView extends MapView {
    layout() {
        super.layout();
        this[_layoutForm]();
        this[_layoutMap]();
    }

    [_layoutForm]() {
        // ....
    }

    [_layoutMap]() {
        console.log("BusinessView layout map");
    }
}

改革过后的代码就可以按预期输出了:

BaseView Layout
MapView layout map
BusinessView layout map

相关推荐:

js中对执行上下文以及变量对象的解析

js中字符方法以及字符串操作方法的总结(附代码)

以上がjsのプライベートメンバーの包括的な分析(コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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