JS 继承

本文深入探讨JavaScript中的多种继承模式,包括原型链、借用构造函数、组合继承等,并详细解析每种模式的工作原理及其优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在阅读红宝书,刚好重新过来一遍继承,拿来和大家分享下,继承是JS核心之一,面试必问。
在继承之前大家需要了解的就是js创建对象,常见的创建对象方式有工厂模式,构造模式,原型模式。

                    **理解原型对象**

1.只要创建了一个函数,就会为该函数创建一个prototype属性,指向函数的原型对象
2.原型对象会自动获得一个constructor属性,指向所在的构造函数
3.每个对象实例都包含一个内部属性,该属性指向原型对象
4.通过构造函数,还可以继续为原型对象添加其他属性和方法。
5.当给对象实例添加一个属性时,这个属性会屏蔽原型对象中保存的同名属性,
即添加这个属性只会阻止我们访问原型中的属性,而不会修改那个属性


最主要的创建对象的方法
组合使用构造函数模式和原型模式

创建自定义类型最常见的方式,定义引用类型的一种默认模式。

思想:

构造函数模式:用于定义实例属性
原型模式:用于定义方法和共享的属性
每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。支持向构造函数传递参数。

function Person(name,age){
    this.name = name;
    this.age = age;
    this.friends = ["小红","小明"];
}

Person.prototype = {
    constructor : Person,
    getAge : function(){
        console.log(this.age);
    }
}

var zhangsan = new Person("zhangsan",23);
var lisi = new Person("lisi",30);

zhangsan.friends.push("小三");
console.log(zhangsan.friends); //["小红","小明","小三"]
console.log(lisi.friends); //["小红","小明"]
console.log(zhangsan.friends === lisi.friends); //false
console.log(zhangsan.getAge === lisi.getAge); //true

有了以上这些知识我们再来看继承

由于函数没有签名,在ECMAScript中无法实现接口继承,只支持实现继承,而实现继承主要是依靠原型链来实现。

继承:

原型链 借用构造函数 组合继承 原型式继承 寄生式继承 寄生组合式继承

JavaScript主要通过原型链实现继承。使用最多的是组合继承。

原型链:

通过将一个类型的实例赋值给另一个构造函数的原型实现。(问题对象实例共享所有继承的属性和方法,不适宜单独使用)

借用构造函数:

在子类型构造函数的内部调用超类型构造函数。(每个实例都具有自己的属性)

组合继承:

使用原型链继承共享的属性和方法,通过借用构造函数继承实例属性。

原型式继承:

可以不必预先定义构造函数的情况下继承(执行对给定对象的浅复制,复制得到的副本还可以进一步改造)

寄生式继承:

与原型式继承非常相似,也是基于某个对象或某些信息创建一个对象,然后增强对象,返回对象。(为了解决组合继承由于多次调用超类构造函数而导致低效率问题,可将这个模式与组合继承一起使用。)

寄生组合式继承:

集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式。

原型链

每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数想指针(constructor),而实例对象都包含一个指向原型对象的内部指针(proto)。如果让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针(proto),另一个原型也包含着一个指向另一个构造函数的指针(constructor)。假如另一个原型又是另一个类型的实例……这就构成了实例与原型的链条。

function animal(){  
    this.type = "animal";  
}  
animal.prototype.getType = function(){  
    return this.type;  
}  

function dog(){  
    this.name = "dog";  
}  
dog.prototype = new animal();  

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

var xiaohuang = new dog();
//原型链关系  
xiaohuang.__proto__ === dog.prototype  
dog.prototype.__proto__ === animal.prototype  
animal.prototype.__proto__ === Object.prototype  
Object.prototype.__proto__ === null  

缺点:

包含引用类型值的原型。
在创建子类型的实例时,不能向超类型的构造函数中传递参数。(没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数)

借用构造函数

优点:

解决原型中包含引用类型值带来的问题。
可以在子类型构造函数中向超类型构造函数传递参数。

思想:
在子类构造函数内部调用超类构造函数。
通过apply()和call()方法可以在新建的对象上执行构造函数。

function Animal(){
    this.colors = ["red","blue"];
}

function Dog(){
    //继承Animal
    Animal.call(this);
}

var xiaohuang = new Dog();
xiaohuang.colors.push("yellow");
console.log(xiaohuang.colors): // ["red","blue","yellow"]

var xiaohei = new Dog();
xiaohei .colors.push("black");
console.log(xiaohei .colors); // ["red","blue","black"]

//Dog的每个实例都会有自己的colors属性副本
//传递参数
//优点:在子类构造函数中向父类构造函数传递参数(相比于原型链的优点)

function Animal(name){
    this.name = name;
}

function Dog(){
    //继承Animal,同时还传递了参数
    Animal.call(this,"xiaohuang");
    this.age = 10; //实例属性
}

var xiaohuang = new Dog();
console.log(xiaohuang .name);  //"xiaohuang”
console.log(xiaohuang .age);    //10

缺点:

构造函数模式存在的问题—方法都在构造函数中定义,函数复用无从谈起。
在超类型的原型中定义的方法,在子类型是不可见的。
结果所有的类型都只能使用构造函数模式
借用构造函数很少单独使用

组合继承

组合继承/伪经典继承–最常用的继承模式

优点:

将原型链和借用构造函数组合到一起,发挥两者之长。
思想:
使用原型链实现对原型属性和方法的继承,(实现了函数的复用)
通过借用构造函数实现对实例属性的继承 (保证每个实例都有自己的属性)

function Animal(name){
    this.name = name;
    this.colors = ["red","blue"];
}

Animal.prototype.getName = function(){
    console.log(this.name);
}

function Dog(name,age){
    //继承属性
    Animal.call(this,name);
    this.age = age;
}

Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
Dog.prototype.getAge = function(){
    console.log(this.age);
}

var xiaohuang = new Dog("xiaohuang",10);
xiaohuang.colors.push("yellow");
console.log(xiaohuang.colors); //"red","blue","yellow"
xiaohuang.getAge(); //10
xiaohuang.getName(); //"xiaohuang"

var xiaohei = new dog("xioahei",3);
console.log(xiaohei.colors); //"red","blue"
xiaohei.getAge(); //3
xiaohei.getName(); //"xioahei"

解释:
animal的构造函数定义了name和colors两个属性

dog的原型定义了getName()方法。
dog构造函数在调用animal构造函数时传入了name参数,
又定义了自己的属性age

将animal实例的赋值给dog的原型,又在新原型上添加方法getAge(),两个不同的实例可以分别拥有自己的属性name,colors,又可以使用相同的方法getName()

instanceof,isPrototypeOf()也能够识别基于组合继承创建的对象。

缺点:

无论什么情况下,都会调用两次超类型构造函数。
(1.创建子类原型的时候 2.子类型构造函数内部) 看 寄生组合式继承
有两组 name和colors属性,dog的原型中有,dog的实例中有,实例中会屏蔽原型的的两个同名属性。

原型式继承

思想:
没有严格意义上的构造函数。
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型。
必须要有一个对象可以作为另一个对象的基础。

function object(o){
    function F(){};
    F.prototype = o;
    return new F();
};
//object对传入的对象进行了一次浅复制

var xiaoming = {
    name : "xiaoming",
    friends : ["f1","f2"]
};

var xiaohong = object(xiaoming);

xiaohong.__proto__ === xiaoming; //true
xiaohong.name = "xiaohong";
xiaohong.friends.push("f3");

var xiaohua = "xiaohua";
xiaohua.friends.push("f4");

console.log(xiaoming.friends);//["f1","f2","f3","f4"]

缺点 :

和原型模式一样,包含引用类型值的属性始终都会共享相应的值

寄生式继承

思路:
与寄生构造函数和工厂模式类似,
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象。

function createAnother(o){
    var clone = object(o);  //通过调用函数创建一个新对象
    clone.sayHi = function(){ //以某种方式来增强这个对象
        alert("hi");
    };
    return clone;  //返回这个对象
}

var person = {
    name : "fangfang",
    friends : ["f1","f2"]
};

var anotherperson = createAnother(person);
anotherperson.sayhi(); //"hi"
//anotherperson具有person的所有属性和方法,还有自己的sayhi方法

缺点:
适用场景:在主要考虑对象,而不是自定义类型和构造函数的情况

寄生组合式继承

最理想的继承方式

优点:

不必为了指定子类型的原型而调用父类型的构造函数(解决了组合继承至少两次调用超类构造函数)
(只调用了一次animal构造函数,避免了dog.prototype上面创建多余的属性)
(还能正常使用instanceof和isPrototypeOf())
思想:
通过借用构造函数来继承属性,
通过原型链的混成形式来继承方法

不必为了指定子类型的原型而调用父类型的构造函数,
我们所需的只是父类原型的一个副本。
就是使用寄生式继承来继承父类的原型,然后将结果指定给子类型的原型

function inheritPrototype(Dog,Animal){
    var prototype = Object(Animal.prototype); //创建父类原型的一个副本
    prototype.constructor = Dog; //副本添加constructor属性
    Dog.prototype = prototype;  //把副本赋值给子类原型
}

function Animal(name){
    this.name = name;
    this.colors = ["red","blue"];
}
Animal.prototype.getName = function(){
    console.log(this.name);
}

function Dog(name,age){
    Animal.call(this,name);
    this.age = age;
}

inheritPrototype(Dog,Animal);

Dog.prototype.getAge = function(){
    console.log(this.age);
}

//此时 Animal.prototype === Dog.prototype

https://github.com/zgfang1993/blog/issues

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值