原型链继承
function Animal() {
this.name = 'animal';
this.type = ['pig', 'cat'];
}
// 为父类添加共有方法
Animal.prototype.greet = function(sound) {
console.log(sound);
}
// 声明子类
function Dog() {
this.name = 'dog';
}
// 继承父类
Dog.prototype = new Animal();
var dog = new Dog();
dog.greet('汪汪'); // "汪汪"
console.log(dog.type); // ["pig", "cat"]
在上面中,创建了一个构造函数Animal
,并且它的原型中还有一个greet
方法。接着又让Dog.prototype
成为Animal
的实例,意味着Dog.prototype
会继承Animal
原型的方法greet
,同时dog
又是Dog
的实例,意味着dog
会继承Dog.prototype
的方法,所以dog
才有greet
的方法。
//原型链继承
Animal.prototype --> Dog.prototype --> dog
构造函数继承
基本思想:即在子类型构造函数的内部调用超类型构造函数(父亲辈,爷爷辈)
function Father(){
this.colors = ["red","blue","green"];
}
function Son(){
Father.call(this);//继承了Father,且向父类型传递参数
}
var instance1 = new Son();
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
var instance2 = new Son();
console.log(instance2.colors);//"red,blue,green" 可见引用类型值是独立的
很明显,借用构造函数一举解决了原型链的两大问题:
其一, 保证了原型链中引用类型值的独立,不再被所有实例共享;
其二, 子类型创建时也能够向父类型传递参数。
但是。函数复用不可用,加重内存负担。构造函数的技术也很少单独使用。
组合继承(原型链继承+构造函数继承)
// 声明父类
function Animal(color) {
this.name = 'animal';
this.type = ['pig','cat'];
this.color = color;
}
// 添加共有方法
Animal.prototype.greet = function(sound) {
console.log(sound);
}
// 声明子类
function Dog(color) {
// 构造函数继承
Animal.apply(this, arguments);
}
// 类式继承
Dog.prototype = new Animal();
var dog = new Dog('白色');
var dog2 = new Dog('黑色');
dog.type.push('dog');
console.log(dog.color); // "白色"
console.log(dog.type); // ["pig", "cat", "dog"]
console.log(dog2.type); // ["pig", "cat"]
console.log(dog2.color); // "黑色"
dog.greet('汪汪'); // "汪汪"
原型链继承可以拿到父构造函数的属性和方法;构造函数继承可以修改父构造函数的内容。它综合了类式继承和构造函数继承的优点,同时去除了缺陷。
我们访问到的引用类型(比如上面的type)其实是通过apply
复制到子类中的,所以不会发生共享。
这种组合继承也是有点小缺陷的,那就是它调用了两次父类的构造函数。
寄生组合式继承(据说是最好用的继承方式)
前面讲过,组合继承是 JavaScript 最常用的继承模式; 不过, 它也有自己的不足. 组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数: 一次是在创建子类型原型的时候, 另一次是在子类型构造函数内部. 寄生组合式继承就是为了降低调用父类构造函数的开销而出现的 .
其背后的基本思路是: 不必为了指定子类型的原型而调用超类型的构造函数
function Animal(color) {
this.color = color;
this.name = 'animal';
this.type = ['pig', 'cat'];
}
Animal.prototype.greet = function(sound) {
console.log(sound);
}
function Dog(color) {
Animal.apply(this, arguments);
this.name = 'dog';
}
/* 注意下面两行 */
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.getName = function() {
console.log(this.name);
}
var dog = new Dog('白色');
var dog2 = new Dog('黑色');
dog.type.push('dog');
console.log(dog.color); // "白色"
console.log(dog.type); // ["pig", "cat", "dog"]
console.log(dog2.type); // ["pig", "cat"]
console.log(dog2.color); // "黑色"
dog.greet('汪汪'); // "汪汪"
在上面的例子中,我们并不像构造函数继承一样直接将父类Animal
的一个实例赋值给Dog.prototype
,而是使用Object.create()
进行一次浅拷贝,将父类原型上的方法拷贝后赋给Dog.prototype
,这样子类上就能拥有了父类的共有方法,而且少了一次调用父类的构造函数。
这里还需注意一点,由于对Animal的原型进行了拷贝后赋给Dog.prototype,因此Dog.prototype上的constructor
属性也被重写了,所以我们要修复这一个问题:
Dog.prototype.constructor = Dog;
extends继承
Class和
extends
是在ES6中新增的,Class
用来创建一个类,extends
用来实现继承
//原先的写法
function Student(name) {
this.name = name;
}
Student.prototype.hello = function () {
alert('Hello, ' + this.name + '!');
}
//用新的class关键字来编写Student
class Student {
constructor(name) {
this.name = name;
}
hello() {
alert('Hello, ' + this.name + '!');
}
}
//继承
class PrimaryStudent extends Student {
constructor(name, grade) {
super(name); // 记得用super调用父类的构造方法!
this.grade = grade;
}
myGrade() {
alert('I am at grade ' + this.grade);
}
}
注意PrimaryStudent
的定义也是class
关键字实现的,而extends
则表示原型链对象来自Student
。子类的构造函数可能会与父类不太相同,例如,PrimaryStudent
需要name
和grade
两个参数,并且需要通过super(name)
来调用父类的构造函数,否则父类的name
属性无法正常初始化。
PrimaryStudent
已经自动获得了父类Student
的hello
方法,我们又在子类中定义了新的myGrade
方法。
子类必须在constructor方法中调用super方法。如果不调用super方法,子类就得不到this对象
ES6引入的class和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码。
网友评论