1. 原型链继承
在原型搜索机制中,构造函数,实例,和原型对象的关系为,原型对象是构造函数的一个属性prototype,原型对象中有一个属性constructor指回构造函数,构造函数生成的实例中具有指针指向构造函数上的原型对象。
当搜索一个属性/方法时,先搜索实例本身,如果实例本身没有找到,再从指针找到其指向的原型对象,向上查找的特性与作用域链相似。也就是说,如果在实例中设置了要查找的属性名,就不会再向上查找。这时候如果没找到,并且该原型对象中也有一个指向另一个原型对象的指针,就从指针向上搜索原型的原型,直到原型链的末端,也就是原型不再包含指向另一个原型的指针为止。
特点:
- 父类新增的原型属性/方法,子类都能够访问到
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
缺点:
- 来自原型对象的所有属性被所有实例共享
- 创建子类实例时,无法向父类构造函数传参
2. 构造函数继承
在子类构造函数中调用父类构造函数。
function Animal(){
this.categorys = ['cat', 'rabbit'];
}
function Dog(){
//继承Animal
Animal.call(this);
}
var d1 = new Dog();//d1调用Dog构造函数,其内部this指向d1,所以Animal.call(this)就相当于Animal.call(d1),就相当于d1.Animal()。最后d1去跳用Animal方法时,Animal内部的this就指向d1.那么Animal内部this上的所有属性和方法,都被拷贝到了d1上。所以,每个实例都具有自己的categorys属性副本,互不影响。
d1.categorys.push('dog');
console.log(d1.categorys);//[ 'cat', 'rabbit', 'dog' ]
var d2 = new Dog();
console.log(d2.categorys);//[ 'cat', 'rabbit', 'dog' ]
特点:
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call多个父类对象)
缺点:
- 必须在构造函数中定义方法,无法实现函数复用
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 实例并不是父类的实例,只是子类的实例
3. 组合继承(伪经典继承)
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
function Animal(){
this.categorys = ['cat', 'rabbit'];
}
Animal.prototype.sayCategorys = function(){
console.log(this.categorys);
}
function Dog(){
Animal.call(this);//构造函数继承
}
Dog.prototype = new Animal();//原型链继承,先执行
var d1 = new Dog();//调用Dog构造函数,在实例中创建categorys属性,屏蔽了原型上的categorys属性
d1.categorys.push('dog');
console.log(d1.categorys);//[ 'cat', 'rabbit', 'dog' ]
var d2 = new Dog();
console.log(d2.categorys);//[ 'cat', 'rabbit', 'dog' ]
d1.sayCategorys();//[ 'cat', 'rabbit', 'dog' ]
特点:
- 既是子类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
缺点:
调用了两次父类构造函数,生成了两份实例
4. 寄生组合继承
通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
function Animal(){
this.categorys = ['cat', 'rabbit'];
}
Animal.prototype.sayCategorys = function(){
console.log(this.categorys);
}
function Dog(){
Animal.call(this);
}
//Object.create()为ES5方法,相当于以下函数(如果浏览器不兼容)
if(!Object.create){
Object.create = function(proto){
function F(){};
F.prototype = proto;
return new F();
}
}
// Dog.prototype = new Animal();
Dog.prototype = Object.create(Animal.prototype);//将Animal的原型对象赋值给Dog的原型对象
var d1 = new Dog();
d1.categorys.push('dog');
console.log(d1.categorys);//[ 'cat', 'rabbit', 'dog' ]
var d2 = new Dog();
console.log(d2.categorys);//[ 'cat', 'rabbit', 'dog' ]
d1.sayCategorys();//[ 'cat', 'rabbit', 'dog' ]
- Object.create 是创建一个新对象,使用现有的对象来提供新创建对象的 proto。意思就是生成一个新对象,该新对象的 proto(原型) 指向现有对象。
- new 生成的是构造函数的一个实例,实例继承了构造函数及其 prototype(原型属性)上的属性和方法。
new关键字创建的对象会保留原构造函数的属性,而用Object.create()创建的对象不会。
缺点:
如果在执行Object.create()方法之前,Dog.prototype上有定义的属性/方法,将会被覆盖
5. 拷贝继承
把一个对象中的属性或者方法直接复制到另一个对象中
function extend(obj, cloneObj){
if(typeof obj !== 'object'){
return false;
}
cloneObj = cloneObj || {};
for(var i in obj){
if(typeof obj[i] === 'object' && typeof obj[i] !== null){
cloneObj[i] = obj[i] instanceof Array? []:{};
extend(obj[i], cloneObj[i]);
} else {
cloneObj[i] = obj[i];
}
}
return cloneObj;
}
var obj = {
a: 1,
b: 2,
c: {
a: 1,
b: 2,
c: {
a: 1,
b: 2
}
}
}
var obj1 = extend(obj)
obj.c.c.c = 3
console.log(obj)
console.log(obj1)