资讯专栏INFORMATION COLUMN

js继承的理解

BlackFlagBin / 1898人阅读

摘要:创建自定义的构造函数之后,其原型对象只会取得属性,其他方法都是从继承来的。优缺点寄生式继承在主要考虑对象而不是创建自定义类型和构造函数时,是十分有用的。

原文链接:https://kongchenglc.coding.me...

1.原型链

  js的继承机制不同于传统的面向对象语言,采用原型链实现继承,基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。理解原型链必须先理解原型,以下是对于原型的一些解释:

无论什么时候,只要创建了一个新函数,就会根据一组特定规则为该函数创建一个prototype属性。这个属性指向函数的原型对象,所有原型对象都会自动获得一个constructor属性,这个属性是一个指向prototype属性所在函数的指针。创建自定义的构造函数之后,其原型对象只会取得constructor属性,其他方法都是从Object继承来的。当调用构造函数创建一个新实例之后,该实例的内部包含一个指针,指向构造函数的原型对象,即[[Prototype]],在浏览器中为_proto_

  也就是说,构造函数和实例实际上都是存在一个指向原型的指针,构造函数指向原型的指针为其prototype属性。实例也包含一个不可访问的指针[[Prototype]](实际在浏览器中可以用_proto_访问),而原型链的形成真正依赖的是_proto_而非[[Prototype]]

举个例子

下边是一个最简单的继承方式的例子:用父类实例充当子类原型对象。

function SuperType(){                        
    this.property = true;
    this.arr = [1];
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
SubType.prototype = new SuperType();              
//在此继承,SubType的prototype为SuperType的一个实例
SubType.prototype.getSubValue = function(){
    return this.subproperty;
};
var instance = new SubType();
var instance2 = new SubType();                                   
c(instance.getSuperValue());                      //true
c(instance.getSubValue());                        //false
c(instance.__proto__.prototype);                  //undefined
//SubType继承了SuperType,SuperType继承了Object。
//instance的_proto_是SubType的原型对象,即SubType.prototype。
//而SubType.prototype又是SuperType的一个实例。
//则instance._proto_.prototype为undefined,
//因为SuperType的实例对象不包含prototype属性。
instance.arr.push(2);
c(instance.arr);                                  //[1,2]
c(instance2.arr);                                 //[1,2]
//子类们共享引用属性

需要注意的一点:无论以什么方式继承,请谨慎使用将对象字面量赋值给原型的方法,这样会重写原型链。

优缺点

  原型链继承方式的优点在于简单,而缺点也十分致命:

子类之间会共享引用类型属性

创建子类时,无法向父类构造函数传参

2.借用构造函数

  又叫经典继承,借用构造函数继承的主要思想:在子类型构造函数的内部调用超类型构造函数,即用call()apply()方法给子类中的this执行父类的构造函数,使其拥有父类拥有的属性实现继承,这种继承方法完全没有用到原型。下边是借用构造函数的实现:

function SuperType(){                                 
    this.colors = ["red","blue","green"];
}
function SubType(){
    SuperType.call(this);     //借用构造函数
}
var instance1 = new SubType();
instance1.colors.push("black");
c(instance1.colors);          //["red","blue","green","black"]
var instance2 = new SubType();
c(instance2.colors);          //["red","blue","green"]
举个例子

  借用构造函数,相当于将父类拥有的属性在子类的构造函数也写了一遍,使子类拥有父类拥有的属性,这种方法在创建子类实例时,可以向父类构造函数传递参数 。

function SuperType(name){           
    this.name = name;
}
function SubType(name){
    SuperType.call(this,name);          //借用构造函数模式传递参数
    this.age = 29;
}
var instance = new SubType("something");
c(instance.name);                       //something
c(instance.age);                        //29
优缺点

  借用构造函数模式,不同于原型式继承和原型模式,它不会共享引用类型属性,而且也可以向超类型构造函数传递参数。但是相对的,由于不会共享属性,也无法实现代码复用,相同的函数在每个实例中都有一份。为了实现代码复用,提示效率,大神们又想出了下边的继承方法。

3.组合继承

组合继承有时也叫伪经典继承,是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。

  即用原型链实现对原型属性和方法的继承(需要共享的),通过借用构造函数实现对实例属性的继承(不共享的)。这样的方法实现了函数复用,而且每个实例拥有自己的属性。

举个例子
function SuperType(name) {                       //父类的实例属性
    this.name = name;
    this.colors = ["red", "blue", "green"];      
}
SuperType.prototype.sayName = function() {       //父类的原型属性
    c(this.name);
};

function SubType(name, age) {                    //借用构造函数继承实例属性
    SuperType.call(this, name);
    this.age = age;
}
SubType.prototype = new SuperType();             //原型链继承原型属性
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function() {
    c(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
c(instance1.colors);        //"red,blue,green,black"
delete instance1.colors;    
//删除从实例属性继承来的colors,读取colors会成为从原型继承来的实例属性
c(instance1.colors);        //"red,blue,green"
instance1.sayName();        //Nicholas
instance1.sayAge();         //29
var instance2 = new SubType("Greg", 27);
c(instance2.colors);        //"red,blue,green"
instance2.sayName();        //Greg
instance2.sayAge();         //27
优缺点

这是所有继承方式中最常用的,它的优点也十分明显:

可以在创建子类实例时向父类构造函数传参。

引用类型属性的值可以不共享。

可以实现代码复用,即可以共享相同的方法。

但是这种方法依然有一点不足,调用了两次父类的构造函数,最后会讲到一种理论上接近完美的继承方式,即寄生组合式继承。

4.原型式继承

  原型式继承借助原型基于已有对象创建新对象,需要一个对象作为另一个对象的基础:

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

  上边段代码就是原型式继承的核心代码,先创建一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例。

举个例子
var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);      //在此继承
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
c(person.friends);              //["Shelby","Court","Van","Rob","Barbie"]
c(person.name);                 //Nicholas
c(anotherPerson.name);          //Greg
c(yetAnotherPerson.name);       //Linda
delete yetAnotherPerson.name;   
//删除子类的属性,就会解除对父类属性的屏蔽,暴露出父类的name属性
c(yetAnotherPerson.name);       //Nicholas

  从上边的代码显示,由object(注意首字母小写,不是对象的构造函数)产生的两个子类会共享父类的引用属性,其中friends数组是共享的,anotherPerson和yetAnotherPerson都是继承自person。实际上相当于创建了两个person对象的副本,但可以在产生之后拥有各自的实例属性。

ECMAScript5新增了Object.create()方法规范化了原型式继承,这个方法接受两个参数:

一个作为新对象原型的对象(可以是对象或者null)

另一个为新对象定义额外属性的对象(可选,这个参数的格式和Object.defineProperties()方法的第二个参数格式相同,每个属性都是通过自己的描述符定义的)

  下边是一个例子:

var person = {                      //原型式继承规范化为create()函数
    name: "Nicholas",
    friends: ["Shelby","Court","Van"]
};
var anotherPerson = Object.create(person, {
    name: {
        value: "Greg"
    }
});
c(anotherPerson.name);             //"Greg"
优缺点

  如果想让一个对象与另一个对象保持类似,原型式继承是很贴切的,但是与原型模式一样,包含引用类型的值得属性会共享相应的值。

5.寄生式继承

寄生式继承与原型式继承紧密相关的一种思路,与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,函数内部以某种方式来增强对象,最后再像真的做了所有工作一样返回对象。

举个例子
function createAnother(original) { 
    var clone = object(original);          //此处用到了原型式继承
    clone.sayHi = function() {
        c("Hi");
    };
    return clone;
}
var person = {                             //父类实例
    name: "Nicholas",
    friends: ["Shelby","Court","Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi();

  上边的寄生式继承用到了原型式继承,向实现继承的函数传入一个父类对象实例,再用原型式继承得到一个父类对象实例的副本,再给这个副本添加属性,即增强这个对象,最后返回这个副本对象。由于用到了原型式继承,这个对象的原型指向传入的父类对象实例。上边例子用到的object()函数(原型式继承)并不是必须的,任何能够返回新对象的函数都适用于寄生式继承模式

优缺点

  寄生式继承在主要考虑对象而不是创建自定义类型和构造函数时,是十分有用的。但是如果考虑到用寄生式继承为对象添加函数等,由于没有用到原型,做不到函数复用,会导致效率降低。

6.寄生组合式继承

  这个名字并不是很贴切,虽然叫寄生组合式继承,但是和寄生式继承关系不是很大,主要是用原型式继承来实现原型属性的继承,用借用构造函数模式继承实例属性。寄生组合式继承和组合继承的区别在于:

在继承原型属性时,组合继承用原型链继承了整个父类(通过将父类实例赋值给子类构造函数的原型对象来实现),这使子类中多了一份父类的实例属性。而寄生组合式继承用原型式继承只继承了父类的原型属性(把父类构造函数的原型对象用原型式继承复制给子类的构造函数的原型对象)。

组合继承调用了两次超类型构造函数,寄生组合式继承调用了一次。

举个例子
function inheritPrototype(subType, superType) {             //寄生式继承
    var prototype = Object.create(superType.prototype);     //创建对象
    prototype.constructor = subType;                        //增强对象
    subType.prototype = prototype;                          //指定对象
}
function SuperType(name) {
    this.name = name;
    this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
    c(this.name);
};
function SubType(name, age) {
    SuperType.call(this, name);                 //借用构造函数
    this.age = age;                             //添加子类独有的属性
}
inheritPrototype(SubType, SuperType);           //此处调用实现寄生组合继承的函数
SubType.prototype.sayAge = function() {         //添加子类独有原型属性
    c(this.age);
};
var son = new SubType("erzi",16);
var father = new SuperType("baba");
c(typeof father.sayName);                       //function
c(typeof father.sayAge);                        //SubType独有的方法,返回undefined
SubType.prototype.sayName = function() {        
    c("This function has be changed");          
}   
//更改子类的方法只会影响子类,prototype是对象,添加新属性和更改属性不会影响父类的prototype
father.sayName();                               //baba
son.sayName();                                  //This function has be changed
SuperType.prototype.sayName = function() {      //更改父类的原型属性
    c("This function has be changed");
}
father.sayName();                               //This function has be changed
son.sayName();                                  //This function has be changed
优缺点

  这种继承方式理论上是完美的,但是由于出现的较晚,人们大多数使用的是组合继承模式。

  以上就是我对于js继承的一些理解,如有不足欢迎指出。

参考资料:《JavaScript高级程序设计》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/83014.html

相关文章

  • JS理解继承

    摘要:父类子类原理就是改变中的指向,指向的实例,子类会获得父类的私有属性和方法原型链继承在中通过继承到了父类的私有属性和私有方法。子类私有继承父类私有原理目前比较常用的是混合继承和混合继承,子类能很清晰的继承父类的公有和私有。 前言:JS之理解原型和原型链,几种常见的继承方式介绍 1.call继承,也叫借用构造函数、伪造对象或是经典继承。call继承回把父类的私有属性和方法继承给子类私有;...

    caiyongji 评论0 收藏0
  • 理解js原型与继承

    摘要:相当于在用原型继承编写复杂代码前理解原型继承模型十分重要。同时,还要清楚代码中原型链的长度,并在必要时结束原型链,以避免可能存在的性能问题。 js是一门动态语言,js没有类的概念,ES6 新增了class 关键字,但只是语法糖,JavaScript 仍旧是基于原型。 至于继承,js的继承与java这种传统的继承不一样.js是基于原型链的继承. 在javascript里面,每个对象都有一...

    wthee 评论0 收藏0
  • JS理解ES6 继承extends

    摘要:理解继承在中对继承有了更友好的方式。总的来说的的实质和以前的继承方式是一致的,但是有了更好的,更清晰的表现形式。 理解ES6继承extends 1.在es6中对继承有了更友好的方式。在下面的继承中那到底在extends的时候做了什么,super()又是代表什么意思。 class People{ constructor(name, age) { this.name = name; ...

    starsfun 评论0 收藏0
  • JS中原型理解

    摘要:我们都知道在的世界中,几乎所有东西都是对象,而对象又是通过继承来层层获得属性和方法,首先我们要区分对象和构造函数的区别,中对象继承的是对象,函数继承的是函数虽然函数也是对象,只有函数才有原型属性供它实例的对象继承,也就是说在中显示如下字符串 我们都知道在JS的世界中,几乎所有东西都是对象,而对象又是通过继承来层层获得属性和方法, var str = new String(mario);...

    fxp 评论0 收藏0
  • javascript基础篇:关于js面向对象理解

    摘要:关于中面向对象的理解面向对象编程它是一种编程思想我们的编程或者学习其实是按照类实例来完成的学习类的继承封装多态封装把实现一个功能的代码封装到一个函数中一个类中以后再想实现这个功能,只需要执行这个函数方法即可,不需要再重复的编写代码。 关于js中面向对象的理解 面向对象编程(oop) 它是一种编程思想 (object-oriented programming ), 我们的编程或者学习其...

    roadtogeek 评论0 收藏0
  • javascript基础篇:关于js面向对象理解

    摘要:关于中面向对象的理解面向对象编程它是一种编程思想我们的编程或者学习其实是按照类实例来完成的学习类的继承封装多态封装把实现一个功能的代码封装到一个函数中一个类中以后再想实现这个功能,只需要执行这个函数方法即可,不需要再重复的编写代码。 关于js中面向对象的理解 面向对象编程(oop) 它是一种编程思想 (object-oriented programming ), 我们的编程或者学习其...

    newtrek 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<