前端面试——继承


js中每个类都有三个部分:

  1. 构造函数内的,这是供实例化对象复制用的
  2. 构造函数外的,直接通过点语法添加的,这是供类使用的,实例化对象访问不到
  3. 类的原型上的,实例化对象可以通过其原型链间接访问到,供所有实例化对象共用 然而,继承中所涉及的不仅仅是一个对象,并且js也没有继承这一现有的机制,那它如何实现的呢?

类式继承

<code>// 声明父类
function SuperClass() {
\tthis.superValue = true;
}
// 父类添加共有方法
SuperClass.prototype.getSuperValue = function(){
\treturn this.superValue;
};
// 声明子类
function SubClass() {
\tthis.subValue = false;
}
// 继承父类
SubClass.prototype = new SuperClass();
// 为子类添加共有方法
SubClass.prototype.getSubValue = function(){
\treturn this.subValue;
};
var instance1 = new SubClass();
console.log(instance.getSuperValue()); // true
console.log(instance.getSubValue()); // false/<code>

缺点:

由于子类通过其原型prototype对父类实例化,继承了父类。所以父类中的共有属性如果是引用类型,就会在子类中被所有实例共用,因此一个子类的实例在修改从父类构造函数中继承来的共有属性时,就会直接影响到其他的子类。

由于子类实现的继承是通过其原型prototype对父类的实例化实现的,因此在创建父类的时候,是无法向父类传递参数的,因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化。

构造函数继承

<code>// 声明父类
function SuperClass(id) {
\tthis.books = ['js', 'html', 'css'];
\tthis.id = id;
}
SuperClass.prototype.showBooks = function () {
\tconsole.log(this.books);
}
// 声明子类
function SubClass (id) {
\tSuperClass.call(this, id);
}
var instance1 = new SubClass(10);
var instance2 = new SubClass(11);
instance1.books.push('设计模式');
console.log(instance1.books); // ['js', 'html', 'css', '设计模式']
console.log(instance1.id); // 10
console.log(instance2.books); // ['js', 'html', 'css']
console.log(instance2.id); // 11
console.log(instance1.showBooks()); // TypeError/<code>

缺点:

SuperClass.call(this, id);是构造函数式继承的精华,由于这种类型的继承没有涉及原型prototype,所以父类的原型方法不会被子类继承。而如果想被子类继承就必须要放在构造函数中,这样创建出来的每个实例就会单独拥有一份而不能共用,这样就违背了代码复用原则。

3.组合继承

<code>// 声明父类
function SuperClass(name) {
\tthis.books = ['js', 'html', 'css'];
\tthis.name = name;
}
SuperClass.prototype.getName = function () {
\tconsole.log(this.name);
}
// 声明子类
function SubClass (name, time) {
\tSuperClass.call(this, name);
\tthis.time = time;
}
SubClass.prototype = new SuperClass();
subClass.prototype.getTime = function() {
\tconsole.log(this.time);
}
var instance1 = new SubClass('js book', 2014);
instance1.books.push('设计模式');
console.log(instance1.books); // ['js', 'html', 'css', '设计模式']
instance1.getName(); // js book
instance2.getTime(); // 2014
var instance2 = new SubClass('css book', 2011);
console.log(instance2.books); // ['js', 'html', 'css']
instance2.getName(); // css book
instance2.getTime(); // 2011 /<code>

缺点:

在使用构造函数继承时执行了一遍父类的构造器,而在实现子类原型的类式继承时又调用了一遍父类构造函数,因此父类构造函数调用了两遍。

4.原型式继承

<code>function inheritObject(o) {
// 声明一个过渡函数对象
function F() {}
// 过渡对象的原型继承父对象
F.prototype = o;
// 返回过渡对象的实例,该实例的原型继承了父对象
return new F();
}/<code>

它是对类式继承的一种封装,类式继承中的问题在这里也会存在,不过这种方式由于F过渡类的构造函数中无内容,所以开销比较小,使用起来比较方便。如果你觉得有必要可以将F过渡类缓存起来,不必每次创建一个新过渡类F。当然这种顾虑也是不必要的,随着这种思想的深入,后来就出现了Object.create()方法。

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。

MDN链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create

5.寄生式继承

<code>function inheritObject(o) {
// 声明一个过渡函数对象
function F() {}
// 过渡对象的原型继承父对象
F.prototype = o;

// 返回过渡对象的实例,该实例的原型继承了父对象
return new F();
}
// 声明基对象
var book = {
\tname: 'js',
\talikeBook: ['css', 'html']
}
function createBook(obj) {
\t// 通过原型继承方式创建新对象
\tvar o = new inheritObject(obj);
\t// 拓展新对象
\to.getName = function () {
\t\tconsole.log(name);
\t}
\t// 返回拓展后的新对象
\treturn o;
}/<code>

寄生式继承是对原型继承的第二次封装,并且在第二次封装的过程中对继承的对象进行了拓展,这样新创建的对象不仅具有父类中的属性和方法而且还添加新的属性和方法,而这种思想的作用也是为了寄生组合式继承模式的实现。

6.寄生组合式继承

6.1 方法一:

<code>function inheritObject(o) {
// 声明一个过渡函数对象
function F() {}
// 过渡对象的原型继承父对象
F.prototype = o;
// 返回过渡对象的实例,该实例的原型继承了父对象

return new F();
}
function inheritPrototype(subClass, superClass) {
// 复制一份父类的副本保存在变量中
var p = inheritObject(superClass.prototype);
// 修正因为重写子类原型导致子类的constructor属性被修改
p.constructor = subClass;
subClass.prototype = p;
}
// 声明父类
function SuperClass(name) {
\tthis.name = name;
\tthis.colors = ['red', 'orange', 'blue'];
}
// 父类添加共有方法
SuperClass.prototype.getName = function(){
\tconsole.log(this.name);
};
// 声明子类
function SubClass(name, time) {
\t// 构造函数式继承
\tSuperClass.call(this, name);
\t// 子类新增属性
\tthis.time = time;
}
// 寄生式继承父类原型
inheritPrototype(SubClass, SuperClass);
// 子类新增原型方法
SubClass.prototype.getTime = function(){
\tconsole.log(this.time);
};/<code>

6.2 方法二:

<code>// 声明父类
function SuperClass(name) {
\tthis.name = name;
\tthis.colors = ['red', 'orange', 'blue'];
}
// 父类添加共有方法
SuperClass.prototype.getName = function(){

\tconsole.log(this.name);
};
// 声明子类
function SubClass(name, time) {
\t// 构造函数式继承
\tSuperClass.call(this, name);
\t// 子类新增属性
\tthis.time = time;
}
// 继承父类原型(区别于方法一之处)
SubClass.prototype = Object.create(SuperClass.prototype);
SubClass.prototype.constructor = SubClass;
// 子类新增原型方法
SubClass.prototype.getTime = function(){
\tconsole.log(this.time);
};/<code>

7.ES6继承

<code>class Animal {
//构造函数,里面写上对象的属性
constructor(props) {
this.name = props.name || 'Unknown';
}
//方法写在后面
eat() {//父类共有的方法
console.log(this.name + " will eat pests.");
}
}
//class继承
class Bird extends Animal {
// 构造函数
constructor(props,myAttribute) {
\t// 调用实现父类的构造函数
\tsuper(props); // 相当于获得父类的this指向
\tthis.type = props.type || "Unknown";
\tthis.attr = myAttribute; // 自己的私有属性
}
fly() { // 自己私有的方法

\tconsole.log(this.name + " are friendly to people.");
}
myattr() { // 自己私有的方法
\tconsole.log(this.type+'---'+this.attr);
}
}
// 通过new实例化
var myBird = new Bird({ name: '小燕子', type: 'Egg'},'Bird');
myBird.eat();
myBird.fly();
myBird.myattr();/<code>


前端面试——继承


分享到:


相關文章: