JavaScript 继承

今天整理面试题的时候看见一道题叫讲一下继承,虽然继承之前也看过书,也在用,可是竟然没法总结性、系统地回答这个问题,因而赶忙把《JavaScript设计模式》扒拉出来看看。程序员

为何须要继承

在设计类的时候,可以减小重复性代码,而且能尽可能弱化对象间的耦合。能够在现有类的基础上进行设计并充分利用已经具有的各类方法,而对设计的修改也更为轻松。
继承一共有三种方式:类式继承、原型式继承、掺元类面试

类式继承

JavaScript能够被装扮成使用类式继承的语言。经过用函数来声明类、用关键字 new 来建立实例,JavaScript中的对象也能模仿Java或C++中的对象。
首先创造一个简单的类声明,及对该类的实例化:设计模式

// 建立 Person 类
function Person (name) {
    this.name = name;
}

Person.prototype.getName = function () {
    return this.name;
};

// 实例化 Person 类
var reader = new Person ('John Smith');
reader.getName();   // John Smith

建立继承 Person 的类 Author :数组

// 建立继承 Person 的类 Author
function Author (name,books) {
    Person.call(this,name);
    this.book = books;
}

// 派生子类
Author.prototype = new Person();
Author.prototype.constructor = Author;
Author.prootype.getBooks = function () {
    return this.book;
};

在默认状况下,全部原型对象都会自动得到一个 constructor (构造函数)属性,这个属性是一个指向 prototype 属性所在函数的指针。
为了简化类的声明。能够把派生子类的整个过程包装在一个名为 extend 的函数中,做用是基于一个给定的类结构建立一个新的类。函数

// extend 函数
function extend(subClass,superClass) {
    var F = function() {};
    F.prototype = superClass.prototype;
    subClass.prototype = new F();
    subClass.prototype.constructor = subClass;
    
    // 确保超类的 constructor 属性被设置正确
    subClass.superclass = superClass.prototype;
    if(superClass.prototype.constructor == Object.prototype.constructor) {
        superClass.prototype.constructor = superClass;
    }
}

那么 Author 的继承能够改写为:学习

function Author (name,books) {
    Author.superClass.contructor.call(this,name);
    this.books = books;
}
extend(Author,Person);   // 调用 extend 函数

Author.prootype.getBooks = function () {
    return this.book;
};

原型式继承

原型式继承和类式继承大相径庭。在学习原型式继承时,最好忘掉本身关于类和实例的一切知识,只从对象的角度来考虑。
在使用原型式继承时,不须要用类来定义对象的结构,只需直接建立一个对象便可。这个对象随后能够被新的对象重用,这得益于原型链查找的工做机制,该对象被称为原型对象。
下面使用原型式继承来从新设计 Person 和 Authorthis

// Person 原型对象
var Person = {
    name: 'default name',
    getName: function() {
        return this.name;
    }
};

Person 如今是一个对象字面量,其中定义了全部类 Person 对象都要具有的属性和方法,并为他们提供了默认值。clone 函数能够用来建立新的类 Person 对象,该函数会建立一个空对象,而该对象的原型对象被设置为 Person。prototype

// Author 原型对象
var Author = clone(Person);
Author.books = [];   // Default value
Author.getBooks = function () {
    return this.books;
};

一个克隆并不是原型对象的一份彻底独立的副本,它只是一个以那个对象为原型对象的空对象。对继承而来的成员有读和写的不对等性:设计

var authorClone = clone(Author);
alert(authorClone.name);   // default name (链接到 Person.name)
authorClone.name = 'new name';
alert(authorClone.name);   // new name (链接到 authorClone.name)

authorClone.books.push('new book');   // 在这里,想authorClone.books数组添加
                                      // 新元素其实是把这个元素添加到
                                      // Author.books数组中。
authorClone.books = [];
authorClone.books.push('new book');

这也就说明了为何必须为经过引用传递的数据类型的属性建立新的副本。在以上例子中,向authorClone.books数组添加新元素其实是把这个元素添加到Author.books数组中。这可不是什么好事,由于对那个值的修改不只会影响到 Author,并且会影响到全部机场了Author但还未改写那个属性的默认值的对象。在改变全部那些数组和对象的成员以前,必须先为其建立新的副本。指针

相似继承和原型式继承的对比

包括JavaScript程序员在内的整个程序员群体对类式继承都比较熟悉。原型式继承更能节约内存。在原型链中查找成员的方式使得全部克隆出来的对象都共享每一个属性和惟一一份实例,只有在直接设置了某个克隆出来的对象的属性和方法时,状况才会有所变化。而相似继承方式中建立的每个对象在内存中都有本身的一套属性的副本。

相关文章
相关标签/搜索