こんにちは、Habr! そもそも、JavaScriptのクラスと継承の悲惨さにうんざりしています! 大規模なJSプロジェクトに数千時間を費やした後、私にはそれが明らかになりました。 特に、Yii2を使用してバックエンドからフロントエンドに切り替える場合。 実際、Yii2およびphpには、実際のクラス、実際の保護/プライベートフィールド、特性、あらゆる種類の依存性注入および動作があります。 そして、これらすべての直後に、クラスを作成するためにそのようなNewClass.js
ファイルを作成しますが、JavaScriptにはこれが含まれていないことがNewClass.js
ます。 さらに、クラスは、プロトタイプ/機能の継承、ES6クラス、外部ライブラリを使用したさまざまな糖など、数百の異なる方法で作成できます。 それから私は「これで十分だ!」と自分に言いました。
彼らは現代の基準で何を提供してくれますか?
ES6では、 class {}
構文を使用して、すべての言語でより馴染みのある方法でクラスを記述する機能が導入されました。 ただし、これは古いプロトタイプの継承を使用したクラスのより一般的な表記法であり、クラスプロパティへのアクセス修飾子の保護も民営化も行われていません。 最新のES2017規格では、まだそうではありません。
自転車
もちろん、私は自転車のコレクターになりたくありませんでした。最初にしたことは、ライブラリのバージョンに座る前に、既存のソリューションを探し始めたことです。 そして、以下で説明することはすべて私の発見ではありません。 他の情報源とモーツァルトライブラリのアイデアで自転車のフレームを既に見つけました。 後者を強調したいと思います。それは、ほぼ現実のクラスを実現するという考え方をさらに発展させるための良い基盤として役立ったからです。
機能の概要
この記事をREADMEプロジェクトの改作にしないために、機能の簡単なリストのみを説明し、使用例を示します。以下では、このすべての魔法の仕組みを説明します。
var Figure = Class.create(function ($public, $protected, _) { $public.x = 0; $public.y = 0; $protected.name = 'figure'; $protected.init = function (x, y) { _(this).id = 123; // private this.x = x; this.y = y; }; $protected.protectedMethod = function () { console.log('protectedMethod: ', this.id, this.name, this.self.x, this.self.y); }; this.square = function (circle) { return 2 * Math.PI * circle.radius; } }); var Circle = Class.create(Figure, function ($public, $protected, _) { $public.radius = 10; $public.publicMethod = function () { console.log('publicMethod: ', _(this).id, _(this).name, this.radius); _(this).protectedMethod(); }; }); var circle = new Circle(2, 7); circle.radius = 5; circle.publicMethod(); // publicMethod: undefined figure 5 / protectedMethod: 123 figure 2 7 console.log(Circle.square(circle)); // 31.415926536
var Layer = Class.create(function ($public, $protected, _) { $protected.uid = null; $protected.init = function () { _(this).uid = Date.now(); } }); var Movable = Class.create(function ($public, $protected, _) { $public.x = 0; $public.y = 0; $protected.init = function (x, y) { this.x = x; this.y = y; } $public.move = function () { this.x++; this.y++; } }); var MovableLayer = Class.create([Layer, Movable], function ($public, $protected, _, $super) { $protected.init = function (x, y) { $super.get(Layer).init.apply(this, arguments); $super.get(Movable).init.apply(this, arguments); } }); var layer = new MovableLayer(); // console.log(layer instanceof Layer, layer instanceof Movable); // true false console.log(Class.is(layer, Layer), Class.is(layer, Movable)); // true true
var Human = Class.create(function ($public, $protected, _) { $protected.birthday = null; $public.getBirthday = function () { return _(this).birthday; }; $public.setBirthday = function (day) { _(this).birthday = day; }; $public.getAge = function () { var date = new Date(_(this).birthday); return Math.floor((Date.now() - date.getTime()) / (1000 * 3600 * 24 * 365)); }; }); var human = new Human(); human.birthday = '1975-05-01'; console.log(human.age);
var SortableMixin = function ($public, $protected, _) { $public.sort = function () { _(this).data.sort(); }; }; var Archive = Class.create(null, SortableMixin, function ($public, $protected, _) { $protected.init = function () { _(this).data = [3, 9, 7, 2]; }; $public.outData = function () { console.log(_(this).data); }; }); var archive = new Archive(); archive.sort(); archive.outData(); // [2, 3, 7, 9]
焦点を当てる
JavaScriptのオブジェクトにはそのプロパティへのアクセス設定がないため、保護されたデータを非表示にすることで、保護/プライベートに似た動作をシミュレートできます。 通常の機能的継承では、これはコンストラクター自体をロックすることで行われ、クラスの各インスタンスに対してすべてのメソッドが作成されます。
var SomeClass = function () { var privateProperty = 'data'; this.someMethod = function () { return privateProperty; }; }; var data = []; for (var i = 0; i < 10000; i++) { data.push(new SomeClass()); }
このコードが実行されると、オブジェクト自体に加えて、さらに10,000個のsomeMethod関数がメモリ内に作成され、メモリを大きく食いつぶします。 同時に、コンストラクターの外部で関数宣言を行うことはそれほど簡単ではありません。この場合、関数はprivatePropertyにアクセスできなくなるからです。
この問題を解決するには、メソッドの関数を1回だけ宣言し、 this
オブジェクトへのポインターを犠牲にしてのみ保護されたデータを受け取る必要があります。
var SomeClass; (function () { var privateData = []; var counter = -1; SomeClass = function () { this.uid = ++counter; }; SomeClass.prototype.someMethod = function () { var private = privateData[this.uid]; }; })();
これは良いですが、まだ悪いです。 まず、特定の識別子uidは外部からアクセス可能です。 そして第二に、ガベージコレクターはprivateData配列の内容を消去せず、ゆっくりですが確実にメモリを消去します。 2つの問題を一度に解決するために、注目すべきMapクラスとWeakMapクラスがES6に登場しました。
マップはほぼ同じ配列ですが、それらとは異なり、JavaScriptオブジェクトはキーとして渡すことができます。 WeakMapは私たちにとってより興味深いものです-これもMapと同じですが、WeakMapとは異なり、WeakMapはガベージコレクターがそれに陥るオブジェクトをクリーニングすることを妨げません。
書き直します:
var SomeClass; (function () { var privateData = new WeakMap(); SomeClass = function () {}; SomeClass.prototype.someMethod = function () { var private = privateData.get(this); }; })();
だから私たちはプライベートになりました。 保護の実装では、すべてがはるかに複雑です-保護されたデータを保存するには、すべての派生クラスの特定の共通ストレージに配置する必要がありますが、同時に、すべてのプロパティではなく、その中で宣言されているクラスのみに特定のクラスへのアクセスを許可します 繰り返しますが、そのようなストレージとしてWeakMapを使用し、キーとしてオブジェクトのプロトタイプを使用します。
SomeClass.prototype.someMethod = function () { var protected = protectedData.get(Object.getPrototypeOf(this)); };
クラス自体にある保護されたプロパティのみへのアクセスを制限するために、保護されたデータを持つオブジェクト自体をクラスに与えるのではなく、ゲッターとセッターを宣言することによって必要なプロパティがメインオブジェクトから取得される関連オブジェクト:
var allProtectedData = { notAllowed: 'secret', allowed: 'not_secret' }; var currentProtectedData = {}; Object.defineProperties(currentProtectedData, { allowed: { get: function () { return allProtectedData.allowed; }, set: function (v) { allProtectedData.allowed = v; }, } }); currentProtectedData.allowed = 'readed'; console.log(allProtectedData.allowed, currentProtectedData.allowed, currentProtectedData.notAllowed); // readed readed undefined
それはそれがどのように機能するかについてです。
さて、それは美しさと可能性、そして出来上がりでこれらすべてを比較検討するだけです!
おわりに
READMEプロジェクトの機能の詳細な説明があります。 ご清聴ありがとうございました!