JavaScript におけるクラスと継承の出現は、JavaScript が大規模開発の基準に達したことを示しています。ECMAScript 4 になる前から、クラスやモジュールなどを導入しようとしましたが、あまりにも多くの機能を導入しすぎたためです。 JavaScript が混乱して拒否されました。ただし、これはクラスを ES6 に遅らせるだけです。これまでのところ、JavaScript には実際のクラスがありません。ただし、最近ではクラス ファクトリがフレームワークの標準機能になっており、誰もが自分のフレームワークで好みのスタイルを選択できるように、さまざまなクラスの実装を紹介します。
1.クラスの JavaScript サポート
他の言語では、クラスのインスタンスはコンストラクター new を通じて作成する必要があります。 Javaを意図的に模倣した言語として。 JavaScript には新しい演算子があり、すべての関数をコンストラクターとして使用できます。コンストラクターは通常のメソッドと何ら変わりません。 Node、Element、HTMLElement、HTMLParagraphElement などの充実したエコシステムを構築するために、ブラウザーは明らかに継承関係を使用して一部のメソッドや属性の共有を容易にするため、JavaScript は他の言語からプロトタイプ メカニズムを借用します。プロトタイプは、すべての関数の特別なオブジェクト プロパティとして存在します。関数が new 演算子 new を使用してその「子」、つまり「インスタンス」を生成すると、このインスタンスという名前のオブジェクトにはこの関数のプロトタイプ オブジェクトのすべてのメンバーが含まれるため、すべてのインスタンス オブジェクトがメソッドまたはプロパティのセットを共有することがわかります。 JavaScript におけるいわゆる「クラス」は、この Prototype オブジェクトを変更することで、ネイティブ オブジェクトを他の定義された「クラス」と区別します。ブラウザでは、Node クラスは Object に基づいて変更され、Element は Node に基づいて、HTMLElement は Element に基づいて変更されます。...業務ビジネスと比較して、独自のクラスを作成して再利用と共有を実現できます。
function A(){ } A.prototype = { aa:"aa", method:function(){ } }; var a = new A; var b = new A; console.log(a.aa === b.aa); console.log(a.method === b.method)
一般に、プロトタイプで定義されたメソッドをプロトタイプ メソッドと呼びます。これは、差別化を図るために、コンストラクターで直接指定することができます。特権メソッドと呼ばれます。それが属性である場合、それは特権属性と呼ばれます。それらの各インスタンスにはコピーがあり、相互には影響を受けません。したがって、データを操作するための共有メソッドはプロトタイプに配置し、プライベート属性は特権属性に配置することが一般的です。ただし、これに置いたとしても自由にアクセスできるので、関数本体のスコープに入れておきます。この時点で、それは真の私有地になります。
function A() { var count = 0; this.aa = "aa"; this.method = function() { return count; } this.obj = {} } A.prototype = { aa:"aa", method:function(){ } }; var a = new A; var b = new A; console.log(a.aa === b.aa);//true 由于aa的值为基本类型,比较值 console.log(a.obj === b.obj) //false 引用类型,每次进入函数体都要重新创建,因此都不一样。 console.log(a.method === b.method); //false
特権メソッドまたは属性はプロトタイプのメソッドまたは属性のみをカバーするため、特権メソッドを削除する限り、同じ名前のプロトタイプのメソッドまたは属性にアクセスできます。
delete a.method; delete b.method; console.log(a.method === A.prototype.method);//true console.log(a.method === b.method); //true
Java言語ではプロトタイプメソッドも特権メソッドもインスタンスメソッドの属性です Javaにはクラスメソッドとクラス属性というものもあります。 JavaScript を使用してシミュレートするのは非常に簡単で、関数上で直接定義するだけです。
A.method2 = function(){} //类方法 var c = new A; console.log(c.method2); //undefined
次に、継承の実装を見てみましょう。上で述べたように、この属性が後で追加されるか、プロトタイプ全体が置き換えられるかに関係なく、プロトタイプにあるものはすべて、そのインスタンスに何かを持ちます。このプロトタイプ オブジェクトを別のクラスのプロトタイプに置き換えると、そのクラスのすべてのプロトタイプ メンバーを簡単に取得できます。
function A() {}; A.prototype = { aaa : 1 } function B() {}; B.prototype = A.prototype; var b = new B; console.log(b.aaa); //=> 1; A.prototype.bb = 2; console.log(b.bb) //=> 2;
同じオブジェクトを参照しているため、クラス A のプロトタイプを変更すると、クラス B のプロトタイプを変更するのと同じことになります。したがって、オブジェクトを 2 つのクラスに割り当てることはできません。これを行うには 2 つの方法があります。
方法 1:
の for を使用して、親クラスのプロトタイプ メンバーをサブクラスのプロトタイプに 1 つずつ割り当てます。
方法 2: サブクラスのプロトタイプを親クラスから直接取得しない。まず、親クラスのプロトタイプを関数に代入し、その関数のインスタンスをサブクラスのプロトタイプとして使用します。
方法 1 では、通常、mixin のようなメソッドを実装する必要があります。このメソッドは、一部の書籍ではコピー継承と呼ばれています。利点は、単純で直接的なことですが、欠点は、instanceof 検証を通過できないことです。これを行うには、Prototype.js の extend メソッドが使用されます。
function extend (des, source) { //des = destination for (var property in source) des[property] = source[property]; return des; }
方法 2 は、プロトタイプに頭を使うため、プロトタイプ継承と呼ばれます。以下はテンプレートです
function A() {}; A.prototype = { aa:function(){ alert(1) } } function bridge() { }; bridge.prototype = A.prototype; function B() {} B.prototype = new bridge(); var a = new A; var b = new B; console.log(a == b) //false 证明成功分开原型 console.log(A.prototype == B.prototype) //true 子类共享父类的原型方法 console.log(a.aa === b.aa); //为父类动态添加新的方法 A.prototype.bb = function () { alert(2) } //true,继承父类的方法 B.prototype.cc = function (){ alert(3) } //false 父类未必有子类的new实例 console.log(a.cc === b.cc) //并且它能够正常通过javascript自带的验证机制instanceof console.log(b instanceof A) ;//true console.log(b instanceof B) ; //true
メソッド 2 は、instanceof 検証に合格できます。es5 には、プロトタイプの継承を実装するためのこのメソッドが組み込まれています。2 番目のパラメーターを考慮しない場合、これは次のコードとほぼ同じです。
Object.create = function (o) { function F() {} F.prototype = o; return new F(); }
上記のメソッドでは、親クラスのプロトタイプをパラメータとして渡す必要があり、サブクラスのプロトタイプを返します
しかし、まだ何かが欠けています。サブクラスは親クラスの継承を継承するだけでなく、独自のものも持っています。さらに、プロトタイプ継承では、サブクラスが親クラスのメンバーや特権メンバーを継承することはできません。 。クラス メンバーなどは手動で追加する必要があります。上記の extend メソッドを使用でき、特権メンバーは apply を通じてサブクラス コンストラクターに実装できます。
function inherit(init, Parent, proto){ function Son(){ Parent.apply(this, argument); //先继承父类的特权成员 init.apply(this, argument); //在执行自己的构造器 } } //由于Object.create是我们伪造的,因此避免使用第二个参数 Son.prototype = Object.create(Parent.prototype,{}); Son.prototype.toString = Parent.prototype.toString; //处理IEbug Son.prototype.valueOf = Parent.prototype.valueOf; //处理IEbug Son.prototype.constructor = Son; //确保构造器正常指向,而不是Object extend(Son, proto) ;//添加子类的特有的原型成员 return Son;
下面,做一组实验,测试下实例的回溯机制。当我们访问对象的一个属性,那么他先寻找其特权成员,如果有同名就返回,没有就找原型,再没有,就找父类的原型...我们尝试将它的原型临时修改下,看它的属性会变成那个。
function A(){ } A.prototype = { aa:1 } var a = new A; console.log(a.aa) ; //=>1 //将它的所有原型都替换掉 A.prototype = { aa:2 } console.log(a.aa); //=>1 //于是我们想到每个实例都有一个constructor方法,指向其构造器 //而构造器上面正好有我们的原型,javascript引擎是不是通过该路线回溯属性呢 function B(){ } B.prototype = { aa:3 } a.constructor = B; console.log(a.aa) //1 表示不受影响
因此类的实例肯定通过另一条通道进行回溯,翻看ecma规范可知每一个对象都有一个内部属性[[prototype]],它保存这我们new它时的构造器所引用的Prototype对象。在标准浏览器与IE11里,它暴露了一个叫__proto__属性来访问它。因此,只要不动__proto__上面的代码怎么动,a.aa始终坚定不毅的返回1.
再看一下,new时操作发生了什么。
1.创建了一个空对象 instance
2.instance.__proto__ = intanceClass.prototype
3.将构造函数里面的this = instance
4.执行构造函数里的代码
5.判定有没有返回值,没有返回值就返回默认值为undefined,如果返回值为复合数据类型,则直接返回,否则返回this
于是有了下面的结果。
function A(){ console.log(this.__proto__.aa); //1 this.aa = 2 } A.prototype = {aa:1} var a = new A; console.log(a.aa) a.__proto__ = { aa:3 } console.log(a.aa) //=>2 delete a. aa; //删除特权属性,暴露原型链上的同名属性 console.log(a.aa) //=>3
有了__proto__,我们可以将原型设计继承设计得更简单,我们还是拿上面的例子改一改,进行试验
function A() {} A.prototype = { aa:1 } function bridge() {} bridge.prototype = A.prototype; function B(){} B.prototype = new bridge(); B.prototype.constructor = B; var b = new B; B.prototype.cc = function(){ alert(3) } //String.prototype === new String().__proto__ => true console.log(B.prototype.__proto__ === A.prototype) //true console.log(b.__proto__ == B.prototype); //true console.log(b.__proto__.__proto__ === A.prototype); //true 得到父类的原型对象
因为b.__proto__.constructor为B,而B的原型是从bridge中得来的,而bride.prototype = A.prototype,反过来,我们在定义时,B.prototype.__proto__ = A.prototype,就能轻松实现两个类的继承.
__proto__属性已经加入es6,因此可以通过防止大胆的使用
2.各种类工厂的实现。
上节我们演示了各种继承方式的实现,但都很凌乱。我们希望提供一个专门的方法,只要用户传入相应的参数,或按照一定简单格式就能创建一个类。特别是子类。
由于主流框架的类工厂太依赖他们庞杂的工具函数,而一个精巧的类工厂也不过百行左右
相当精巧的库,P.js
使用版:https://github.com/jiayi2/factoryjs
这是一个相当精巧的库,尤其调用父类的同名方法时,它直接将父类的原型抛在你面前,连_super也省了。
var P = (function(prototype, ownProperty, undefined) { return function P(_superclass /* = Object */, definition) { // handle the case where no superclass is given if (definition === undefined) { definition = _superclass; _superclass = Object; } // C is the class to be returned. // // When called, creates and initializes an instance of C, unless // `this` is already an instance of C, then just initializes `this`; // either way, returns the instance of C that was initialized. // // TODO: the Chrome inspector shows all created objects as `C` // rather than `Object`. Setting the .name property seems to // have no effect. Is there a way to override this behavior? function C() { var self = this instanceof C ? this : new Bare; self.init.apply(self, arguments); return self; } // C.Bare is a class with a noop constructor. Its prototype will be // the same as C, so that instances of C.Bare are instances of C. // `new MyClass.Bare` then creates new instances of C without // calling .init(). function Bare() {} C.Bare = Bare; // Extend the prototype chain: first use Bare to create an // uninitialized instance of the superclass, then set up Bare // to create instances of this class. var _super = Bare[prototype] = _superclass[prototype]; var proto = Bare[prototype] = C[prototype] = C.p = new Bare; // pre-declaring the iteration variable for the loop below to save // a `var` keyword after minification var key; // set the constructor property on the prototype, for convenience proto.constructor = C; C.extend = function(def) { return P(C, def); } return (C.open = function(def) { if (typeof def === 'function') { // call the defining function with all the arguments you need // extensions captures the return value. def = def.call(C, proto, _super, C, _superclass); } // ...and extend it if (typeof def === 'object') { for (key in def) { if (ownProperty.call(def, key)) { proto[key] = def[key]; } } } // if no init, assume we're inheriting from a non-Pjs class, so // default to using the superclass constructor. if (!('init' in proto)) proto.init = _superclass; return C; })(definition); } // as a minifier optimization, we've closured in a few helper functions // and the string 'prototype' (C[p] is much shorter than C.prototype) })('prototype', ({}).hasOwnProperty);
我们尝试创建一个类:
var Dog = P (function(proto, superProto){ proto.init = function(name) { //构造函数 this.name = name; } proto.move = function(meters){ //原型方法 console.log(this.name + " moved " + meters + " m.") } }); var a = new Dog("aaa") var b = new Dog("bbb"); //无实例变化 a.move(1); b.move(2);
我们在现在的情况下,可以尝试创建更简洁的定义方式
var Animal = P (function(proto, superProto){ proto.init = function(name) { //构造函数 this.name = name; } proto.move = function(meters){ //原型方法 console.log(this.name + " moved " + meters + " m.") } }); var a = new Animal("aaa") var b = new Animal("bbb"); //无实例变化 a.move(1); b.move(2); //............... var Snake = P (Animal, function(snake, animal){ snake.init = function(name, eyes){ animal.init.call(this, arguments); //调运父类构造器 this.eyes = 2; } snake.move = function() { console.log('slithering...'); animal.move.call(this, 5); //调运父类同名方法 } }); var s = new Snake("snake", 1); s.move(); console.log(s.name); console.log(s.eyes);
私有属性演示,由于放在函数体内集中定义,因此安全可靠!
var Cobra = P (Snake, function(cobra){ var age = 1;//私有属性 //这里还可以编写私有方法 cobra.glow = function(){ //长大 return age++; } }); var c = new Cobra("cobra"); console.log(c.glow()); //1 console.log(c.glow()); //2 console.log(c.glow()); //3
以上所述就是本文的全部内容了,希望大家能够喜欢。