关于javascriptz中的‘类’,可以总计为3个部分:
1.构造函数内的,这是供实例化对象复制用的;
2.构造函数外的,直接通过点语法添加的,这是供类使用的,实例化的对象是访问不到的;
3.类原型中的,实例化对象可以通过原型链间接的访问到,也是供实例对象所共有的。
关于类的继承大致可以分为以下几种。
一、子类的原型对象--类式继承
思路:将父类的实例化对象作为子类的prototype
// 子类的原型对象 -- 类式继承 // 声明父类 function SuperClass(){ this.superValue = true; this.books = [1,2,3]; } SuperClass.prototype.getSuperValue = function(){ return this.superValue } // 声明子类 function SubClass(){ this.subValue = false; } // 继承父类 SubClass.prototype = new SuperClass(); //{superValue:true,books:[1,2,3]} // SubClass.prototype.getSubValue = function(){ return this.subValue; }
此类继承是最简单的继承方法,但是也有缺点。
1.由于子类通过原型prototype对父类实例化,继承了父类,所以父类中的共有属性如果是引用类型,就会在子类的所有实例中共有,如果一个子类的实例修改子类的原型从父类继承来的共有属性就会影响到其他的子类。
var i1 = new SubClass(); var i2 = new SubClass(); i1.books === i2.books // true 因为都是通过原型链找到的 SubClass.prototype 这个对象 i1.books.push(4); console.log(i2.books); // [1,2,3,4] i1实例修改了books属性,i2的books属性也会被影响
2.由于子类实现的继承是靠原型prototype对父类实例化实现的,因此在创建父类的时候无法向父类传递参数的。因此在实例化子类实例的时候,无法定制父类中的属性(也就是所有的子类实例共用同一个父类的实例,即继承时传递的参数或者父类构造函数中默认的)
二、创建即继承--构造函数继承
思路:在子类构造器内 调用父类构造器
// 声明父类 function SuperClass(id){ this.books = [1,2,3]; this.id = id; } // 声明子类 function SubClass(id){ // 继承父类 SuperClass.call(this,id) // ... }
该类型的继承即通过在子类的构造器中通过call调用父类的构造函数,来改变this的指向,达到继承父类构造函数共有属性。
优点:
每一个子类的实例,可以通过传入id来指定当前实例的id属性值,实现了属性的私有化;
缺点:
1.这种继承并没有涉及原型,所以父类原型上的方法和属性不会被继承;
2.要被继承的方法和属性只能放在父类的构造函数中,但是创建出来的每个实例都单独含有一份,不能实现共有,违背了代码复用的原则。
三、组合继承
上面的两种继承模式的特点为:类式继承是通过子类的原型prototype对父类实例化来实现的。构造函数式继承是通过在子类的构造函数作用环境中执行一次父类的构造函数来实现的。组合继承是组合这两种模式的优点来实现的。
function SuperClass(name){ this.name = name; this.books = [1,2,3]; } function SubClass(name){ // 构造函数继承 继承父类构造函数的共有属性 SuperClass.call(this,name) } // 类式继承 继承父类原型上的方法 SubClass.prototype = new SuperClass();
该模式的继承的缺点很明确即调用了两次父类构造函数。
四、洁净的继承者--原型式继承
// 原型式继承 function inheritObject(obj){ // 声明一个过渡函数对象 function F(){} // 过渡对象的原型继承父对象 F.prototype = obj; // 返回过渡 对象的一个 实例,该实例的原型继承了父对象 return new F() }
其实原型式继承就是对类式继承的一个封装,其实其中的过渡对象就相当于类式继承的子类,只不过在原型中作为一个过渡对象出现的,目的是为了创建要返回的新的实例化对象。
因为是对类式继承的封装,所以类式继承的缺点在这里也存在,即父类对象的引用类型的属性被共用。
五、寄生式继承
// 声明基对象 var book = { name : "hyh", books:[1,2,3] } function createBook(obj){ // 通过原型继承的方法创建新对象 var o = new inheritObject(obj) // 拓展新对象 o.getName = function(){ return this.name } // 返回拓展后新对象 return o }
var newBook = createBook(book)
该模式的继承就是对原型继承进行的二次封装,并且在封装的过程中继承的对象进行了拓展,这样新创建的对象不仅仅有父类中的属性和方法而且还添加了新的属性和方法。
六、终极继承者--寄生组合式继承
该模式是对寄生式继承(寄生式继承依托于原型继承,原型继承与类式继承相像)与构造函数继承的组合。这里的寄生式继承不是对对象的处理,而是对类的原型(实质上也是对象)。
/** * 终极继承者 -- 寄生组合式继承 * SubClass 子类 * SuperClass 父类 */ function inheritPrototype(SubClass, SuperClass){ // 复制一份父类的原型副本保存在变量中 var p = inheritObject(SuperClass.prototype); // 修正因为重写子类原型导致子类的 constructor 属性被修改 p.constructor = SubClass; SubClass.prototype = p; } // 原型式继承 function inheritObject(obj){ // 声明一个过渡函数对象 function F(){} // 过渡对象的原型继承传入的对象(即父构造函数的原型) F.prototype = obj; // 返回过渡对象的一个实例,该实例的原型继承了传入的对象 return new F() }
function inheritPrototype(SubClass, SuperClass){ // 复制一份父类的原型副本保存在变量中 var p = inheritObject(SuperClass.prototype); // 修正因为重写子类原型导致子类的 constructor 属性被修改 p.constructor = SubClass; SubClass.prototype = p; } // 定义父类构造函数 function SuperClass(name){ this.name = name; this.arr = [1,2,3]; } // 定义父类的原型方法 SuperClass.prototype.getName = function(){ return this.name; } // 定义子类 function SubClass(name, age){ // 构造函数式继承 继承父类的共有属性(构造函数内部的属性) SuperClass.call(this, name); // 子类新增的属性 this.age = age; } // 寄生式继承父类原型 inheritPrototype(SubClass, SuperClass); // 这里子类给原型添加方法只能通过点的语法来添加,不会会覆盖前面的原型对象 SubClass.prototype.getTime = function(){ return this.name } // 创建两个子类实例 var i1 = new SubClass("hyh",20); var i2 = new SubClass("qls",18); i1.arr.push(4) console.log(i1.arr) // [1,2,3,4] console.log(i2.arr) // [1,2,3]