类的创建(ES5)
new一个function,在这个function的prototype里面增加属性和方法。
下面来创建一个Animal类:
// 定义一个动物类
function Animal (name) {
this.name = name || 'Animal';
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};
这样就生成了一个Animal类,实例化生成对象后,有方法和属性。
类的继承
ES5的继承时通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。
ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this。
ES6 extends
在所定义的子类构造器内添加首行代码 super(),用于保留对父类构造器内属性的继承。且将实例化出对象的实参,传进super()内 被形参所接收。子类和父类的属性和方法互不影响。constructor
表示构造函数
class Person {
constructor(name) { this.name = name }
say() { console.log(this.name) }
}
class Student extends Person {
constructor(name, id) {
//一定要调用super()
super(name);
this.id = id;
}
say1() { console.log(this.name, this.id) }
}
原型链继承
function Cat(){ }
//原型链继承核心
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
介绍:在这里我们可以看到new了一个空对象,这个空对象指向Animal并且Cat.prototype指向了这个空对象,这种就是基于原型链的继承。
特点:非常纯粹的继承关系,实例是子类的实例,也是父类的实例;父类新增原型方法/原型属性,子类都能访问到;简单,易于实现
缺点:要想为子类新增属性和方法,不能放到构造器中;无法实现多继承;创建子类实例时,**无法向父类构造函数传参,**且所有实例共享父类实例的属性,若父类共有属性为引用类型,一个子类实例更改父类构造函数共有属性时会导致继承的共有属性发生变化
构造继承
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
特点:解决了子类实例共享父类引用属性的问题;创建子类实例时,可以向父类传递参数;可以实现多继承(call多个父类对象)
缺点:实例并不是父类的实例,只是子类的实例;只能继承父类的实例属性和方法,不能继承原型属性/方法;无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
组合继承
相当于构造继承和原型链继承的组合体。通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name){
//重点① 构造继承
Animal.call(this);
this.name = name || 'Tom';
}
//重点② 原型链继承
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
特点:可以继承实例属性/方法,也可以继承原型属性/方法;既是子类的实例,也是父类的实例;每个新实例引入的构造函数是私有的;可传参;函数可复用
缺点:会执行两次父类的构造函数,消耗较大内存,子类的构造函数会代替原型上的那个父类构造函数
原型式继承
原理:类似Object.create,用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象,结果是将子对象的prototype指向父对象
var parent = {
names: ['a']
}
function copy(object) {
function F() {}
F.prototype = object;
return new F();
}
var child = copy(parent);
缺点:
- 原型链继承多个实例的引用类型属性指向相同,存在篡改的可能。
- 无法传递参数
寄生式继承
原理:
寄生式继承的思路与(寄生)构造函数和工厂模式类似, 即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象. 如下
二次封装原型式继承,并拓展
function createObject(obj) {
//copy()为原型式继承中的function
var o = copy(obj);
o.getNames = function() {
console.log(this.names);
return this.names;
}
return o;
}
优点:可添加新的属性和方法
缺点:
- 没有用到原型,无法复用.
寄生组合继承
原理:改进组合继承,利用寄生式继承的思想继承原型
组合继承是 JavaScript 最常用的继承模式; 不过, 它也有自己的不足. 组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数: 一次是在创建子类型原型的时候, 另一次是在子类型构造函数内部. 寄生组合式继承就是为了降低调用父类构造函数的开销而出现的
function inheritPrototype(subClass, superClass) {
// 复制一份父类的原型(创建对象)
var p = copy(superClass.prototype);
// 修正构造函数(增强对象)
p.constructor = subClass;
// 设置子类原型(赋值对象)
subClass.prototype = p;
}
function Parent(name, id){
this.id = id;
this.name = name;
this.list = ['a'];
this.printName = function(){
console.log(this.name);
}
}
Parent.prototype.sayName = function(){
console.log(this.name);
};
function Child(name, id){
Parent.call(this, name, id);
// Parent.apply(this, arguments);
}
inheritPrototype(Child, Parent);
优点:效率更高,原型链保持不变。目前最佳的引用类型继承。
学习参考:
https://github.com/mqyqingfeng/Blog/issues/16
Javascript红宝书