javaScript 继承的几种方式

本文深入探讨JavaScript中的六种继承模式,包括原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承和寄生组合继承。每种模式的特点、优缺点及其实现方式都被详细解析。

1.原型链继承:

先梳理原型链的概念:ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的内部指针,相应的,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。即原型链。

原型链继承就是将父类的实例赋值给子类的原型属性:

//声明父类
function SuperClass(){
    this.name = 'aaa';
}
SuperClass.prototype.getName = function(){
    return this.name;
}

//子类
function SubClass(){
    this.age = 18;
}

//继承父类
SubClass.prototype = new SuperClass();
SubClass.prototype.getAge = function(){
    return this.age;
}

该继承有两个缺点:

1.在通过原型来继承来实现继承时,原型实际上会变成另一个类型的实例,于是原先的实例属性也就顺理成章的变成了现在的原型上的属性了。所以说父类中的共有属性要是引用类型,就会在子类中被所有的实例所共用,因此一个子类的实例更改子类原型从父类构造函数中继承来的共有属性就会直接影响到其他子类:

//声明父类
function SuperClass(){
    this.arr = ['a','b','c','d']
}
SuperClass.prototype.getArr = function(){
    return this.arr;
}
//子类
function SubClass(){}
//继承父类
SubClass.prototype = new SuperClass();
var s = new SubClass();
console.log(s.getArr());//["a", "b", "c", "d"]
var s2 = new SubClass();
s2.arr.push('123');
console.log(s.getArr());// ["a", "b", "c", "d", "123"]

从上面可以看出我们改变的是实例 s2 的 arr  但是 s 的 arr 值也被改变了。

2.由于子类实现的继承是靠其原型 prototype 对父类的实例化实现的,因此在创建父类的时候,是无法向父类传递参数的,因而在实例化子类的时候也无法对父类构造函数内的属性进行初始化。

2.构造函数继承

在解决原型中包含引用类型值所带来的问题的过程中,开发人员开始使用一种叫做 借助构造函数 的技术。这种技术的基本思想相当简单,即在子类型构造函数的内部调用超类型构造函数。别忘了,函数只不过是在特定环境中执行代码的对象,因此通过使用 apply() 和 call() 方法也可以在(将来)新创建的对象上执行构造函数。

//声明父类
function SuperClass(id){
    this.arr = ['a','b','c']
    this.id = id;
}
//子类
function SubClass(id){
    SuperClass.call(this,id)
}
var s = new SubClass(123);
console.log(s.id,s.arr);//123 (3) ["a", "b", "c"]
var s2 = new SubClass(234);
s2.arr.push('ddd');
console.log(s.id,s.arr);//123 (3) ["a", "b", "c"]
console.log(s2.id,s2.arr);//234 (4) ["a", "b", "c", "ddd"]

call 方法可以改变函数的执行环境,在子类中调用父类的 call 方法就是将子类中的变量在父类中执行一遍,由于父类中是给 this 绑定属性的,因此子类自然就继承了父类的共有属性。

缺点:由于这种继承没有涉及原型 prototype,所以 父类的原型方法自然不会被子类继承,而如果想要被子类继承就必须要放到构造函数中,这样创建出来的每个实例都会单独拥有一份而不能共用,违背了代码复用的原则。

3.组合继承

组合继承,有时候也叫做伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样既通过在原型上定义方法实现了复用,又能够保证每个实例都有它自己的属性。

//声明父类
function SuperClass(id){
    this.arr = ['a','b','c']
    this.id = id;
}
//父类原型共有方法
SuperClass.prototype.getArr = function(){
    return this.arr;
}
//子类
function SubClass(id){
    SuperClass.call(this,id)
    this.age = 18;
}
//子类继承父类原型共有方法
SubClass.prototype = new SuperClass();
//重新指定constructor属性
SubClass.prototype.constructor = SubClass;
//子类原型共有方法
SubClass.prototype.getAge = function(){
    return this.age;
}
var s = new SubClass(123);
console.log(s.id,s.arr); //123 (3) ["a", "b", "c"]
var s2 = new SubClass(234);
s2.arr.push('ddd');
console.log(s.id,s.getArr(),s.getAge()); //123 (3) ["a", "b", "c"] 18
console.log(s2.id,s2.getArr(),s2.getAge());//234 (4) ["a", "b", "c", "ddd"] 18

组合继承避免了原型链和构造函数的缺陷,融合了他们的优点,成为 JavaScript 中最常用的继承模式。而且,instanceof 和 isPrototypeOf() 也能够用于识别基于组合继承创建的对象。

缺点:组合继承最大的问题就是无论什么情况下,都会调用两次超类的构造函数:一次是在创建子类型的原型的时候,另一次实在子类型构造函数内部。没错,子类型会包含超类型独享的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。

4.原型式继承

道格拉斯·克罗克福德2006年发表,他的想法是借助原型可以基于已有的对象创建新的对象,同时还不必因此创建自定义类型。

function object(o){
    function F(){};
    F.prototype = o;
    return new F();
}

在 object 内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后反悔了这个临时类型的一个新实例。从本质上讲, object() 对传入其中的对象执行了一次浅复制。

var person = {
    name:'a',
    friends:['a','b','c']
}
var person2 = object(person);
person2.name = 'b';
person2.friends.push('d');
console.log(person.name,person.friends);//a (4) ["a", "b", "c", "d"]

克罗福德主张的这种原型式继承,要求你必须有一个对象可以作为另一个对象的基础。把他传给 object() ,然后再根据需求对得到的对象加以修改即可。在这个例子中,可以作为另一个对象基础的是 person 对象,传入 object 返回一个新的对象,这个新的对象将 person 作为原型,所以它的原型中就包含一个基本类型属性和一个引用类型属性。这意味着 person.friends 不仅属于 person 所有,而且也被所有创建的新对象所共享(如person2)。实际上这就相当于又创建了一个 person 对象的副本。

 ES5 中的 Object.create() 方法规范化了原型式继承。这个方法接收两个参数:一个用作新对象原型的对象和(可选的)一个为新对象定义额外属性的对象。在传入一个参数的情况下,Object.create() 与 object() 方法的行为相同。 

var person = {
    name:'a',
    friends:['a','b','c']
}
var person2 = Object.create(person);
person2.name = 'b';
person2.friends.push('d');
console.log(person.name,person.friends);//a (4) ["a", "b", "c", "d"]

5.寄生式继承

寄生式继承是与原型式继承紧密相关的一种思路,并且同样也是由克罗克福德推而广之的。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。 

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

在这个例子中,createAnother() 函数接收了一个参数,也就是将要作为新对象基础的对象。然后,把这个对象(original)传递给 object 函数,将返回的结果赋值给 clone 。再为 clone 对象添加一个新方法 sayHi(),最后返回 clone 对象。可以像下面这样来使用 createAnother() 函数:

var person = {
    name:'a',
    friends:['a','b']
};
var person2 =  createAnother(person);
person2.sayHi(); // 'hi'

这个例子中的代码基于 person 返回了一个新对象--person2。新对象不仅具有 person 的所有属性和方法,而且还有自己的 sayHi() 方法。

在主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。前面示范继承模式时使用的 object() 函数不是必需的;任何能够返回新对象的函数都适用于此模式。

使用寄生式继承来为对象添加函数,会由于不能做到函数复用而降低效率;这一点与构造函数模式类似。 

6.终极:寄生组合继承

前面使用的组合继承是 JavaScript 最常用的继承模式;不过,他也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次超类的构造函数:一次是在创建子类型的原型的时候,另一次实在子类型构造函数内部。没错,子类型会包含超类型独享的全部实例属性,但我们不得不在调用子类型构造函数时重写这些属性。

为了解决组合继承的问题,我们使用寄生组和继承。所谓寄生组合继承,即通过借用构造函数来继承属性,通过原型链的的混成形式来继承方法。其背后的基本思路是:不必为了指定子类型的原型而调用超类型的构造函数,我们所需要的无非就是超类型的原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型,基本模式如下:

function inheritPrototype(subType,superType){
    var prototype = object(superType.prototype);
    prototype.constructor = subType;
    subType.prototype = prototype;
}

这个示例中的 inheritPrototype() 函数实现了寄生组合式集成的最简单形式。这个函数接受两个参数:子类型构造函数和超类型构造函数。在函数内部,第一步是创建超类型原型的一个副本。第二步是为创建的副本添加 constructor 属性,从而弥补因重写原型而失去的默认的 constructor 属性。最后一步,将创建的对象(即副本)赋值给子类型的原型。这样,我们就可以调用 inheritPrototype() 函数的语句去替换前面例子中为子类型原型赋值的语句了:

//父类
//function SuperClass(id){
    this.arr = [1,2,3];
    this.id = id;
}
SuperClass.prototype.sayHi = function(){
    console.log(this.id);
}
//子类
function SubClass(age){
    //构造函数继承
    SuperClass.call(this,123);
    this.age = age;
};

//继承
inheritPrototype(SubClass,SuperClass);

//为子类添加方法
subClass.prototype.sayAge = function(){
    console.log(this.age);
}

如果参数只传一个父类进去: 

function inheritObject(superClass){
    //声明一个过渡函数对象
    function Proxy(){}
    //将目标的原型对象赋值给过渡对象的原型
    Proxy.prototype = superClass.prototype;
    //返回过渡对象的一个实例,该实例的原型继承了父对象原型上所有的属性方法,完成了父类原型的复制
    return new Proxy();
    
    //或者使用 Object.create() 方法
    // return Object.create(superClass.prototype)

}
//父类
//function SuperClass(id){
    this.arr = [1,2,3];
    this.id = id;
}
//子类
function SubClass(age){
    //构造函数继承
    SuperClass.call(this,123);
    this.age = age;
};

//继承
SubClass.prototype = inheritObject(SuperClass);
//完成 constructor 属性绑定
SubClass.prototype.constructor = SubClass;

寄生组合是结合了构造函数继承和寄生继承两种方式,汲取他们的优点从而规避掉其他继承方式的不足。它的高效率体现在它只调用了一次 父类的构造函数,并且因此避免了在 子类 的 prototype 上面创建不必要的,多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf() 。开发人员普遍认为寄生组合继承是引用类型最理想的集成范式。

当然还有浅拷贝继承和深拷贝继承详见:继承

 

内容概要:本文介绍了一个基于多传感器融合的定位系统设计方案,采用GPS、里程计和电子罗盘作为定位传感器,利用扩展卡尔曼滤波(EKF)算法对多源传感器数据进行融合处理,最终输出目标的滤波后位置信息,并提供了完整的Matlab代码实现。该方法有效提升了定位精度与稳定性,尤其适用于存在单一传感器误差或信号丢失的复杂环境,如自动驾驶、移动采用GPS、里程计和电子罗盘作为定位传感器,EKF作为多传感器的融合算法,最终输出目标的滤波位置(Matlab代码实现)机器人导航等领域。文中详细阐述了各传感器的数据建模方式、状态转移与观测方程构建,以及EKF算法的具体实现步骤,具有较强的工程实践价值。; 适合人群:具备一定Matlab编程基础,熟悉传感器原理和滤波算法的高校研究生、科研人员及从事自动驾驶、机器人导航等相关领域的工程技术人员。; 使用场景及目标:①学习和掌握多传感器融合的基本理论与实现方法;②应用于移动机器人、无人车、无人机等系统的高精度定位与导航开发;③作为EKF算法在实际工程中应用的教学案例或项目参考; 阅读建议:建议读者结合Matlab代码逐行理解算法实现过程,重点关注状态预测与观测更新模块的设计逻辑,可尝试引入真实传感器数据或仿真噪声环境以验证算法鲁棒性,并进一步拓展至UKF、PF等更高级滤波算法的研究与对比。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值