原型和原型链

这几天一直在学习js的继承。感觉有了一个比较清晰的认识。不过要想深刻理解,最好的方式就是输出出来。 其实对于这篇来说,懂的人不用看。不懂的人,看了也不会立即理解。不过,仍希望看了之后对于理解有一定的帮助。

原型模式

尽管es6提出了class,似乎看起来是类。但是仅仅只是语法糖。实质任然是函数。这一点,会在后面进行说明解释。

首先我们需要知道一点,对于js的函数而言。它们都有一个**prototype(原型)**属性。这是一个指针,指向一个对象,这个对象包含实例共享的 属性和方法。默认情况下,所有的原型对象,都会获得一个constructor(构造函数)属性,然而这个属性,它右包含一个指向prototype属性所在函数的指针。大致就是你指向我,我指向你。

function Person(name){
    this.name = name;
}
let p1 = new Person("hang");
console.log(p1);//环境为chrome 
复制代码

实例,原型对象,构造函数,之间的关系图示(再也不用画图去画图了)

这里补充说明一下,__proto__属性,这并不是js自带的,而是各大浏览器所实现的一个可访问对象原型的属性。在es5中可以使用Object.getprototypeOf(p1)来获取p1的对象原型。js中自带的应该是[[prototype]]属性 讲到这里,应该大概就可以知道instanceof的原理。

p1 instanceof Person
//等同于
Object.getPrototypeOf(p1) === Person.prototype
复制代码

组合使用构造函数和原型模式

这是认可度比较高的一种做法,即解决引用属性共享,也没有切断原本的构造循环链(我创造的一个词吧...hahaha),

function Person(name){
    this.name = name;
    this.friends = ["Bob","Tony"];
}
Person.prototype = {
    constructor : Person,
    sayName     :  function(){
        console.log(this.name);
    }
}
复制代码

不过这样的做法有一个缺点,就是constructor属性变成了可枚举的,可以这样做,如果需要的话,可以加上这些

//重设构造函数,只适用于ES5兼容的浏览器
Object.definePrototype(Person.prototype,"constructor",{
    enumerable: false,
    value: Person
});
复制代码
顺便提下new

执行new关键字时,大致是以下的步骤

let p1 = new Person("hang");
//等同于
let p1 = {};
p1.__proto__ = Person.prototype;
Person.call(p1,"hang");
复制代码

原型链

前面我提到过一个属性_proto_,一个指向原型对象的指针。还记得,刚接触js时,就听过的一句话,一切皆对象。那么它的所有的对象都有一个__proto__属性。这样一来,就像一个链条一样,将所有的东西串了起来。这就是原型链。 以上文的实例p1为例

p1.__proto__ ==
Object.getPrototypeOf(p1) //Person.prototype
p1.__proto__.proto__ ==
Object.getPrototypeOf(Object.getPrototypeOf(p1)) //Object.prototype
p1.__proto__.proto__.__proto__ ==
Object.getPrototypeOf(Object.getPrototypeOf(Object.getPrototypeOf(p1))) //null
//p1的原型链大致是这个样子
// p1 --> Person.prototype --> Object.prototype --> null
复制代码

回到null类型的定义上来看。红宝书第三版是这样说的从逻辑的角度来看,null表示一个空对象指针,这也就是为什么对象的顶层是一个null了。

借用构造函数继承

这种很少使用,只是为后面做铺垫

function SuperType(name){
    this. name = name;
    this.colors = ["red","blue","green"];
}
function SubType(name,age){
    //继承SuperType,同时传递参数
    SuperType.call(this,name);
    this.age = age;
}
复制代码

借用构造函数的问题,方法都在构造函数中定义,函数无法复用。所以就产生了组合继承

组合继承*

组合继承,有时候也叫做伪经典继承,将原型链和借用构造函数技术整合到一块。

function SuperType(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
}
function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
    console.log(this.age);
}
复制代码

组合继承避免原型链(实例共享引用对象)和借用和构造函数的缺陷,融合了它们的优点。但是它也有一个缺点,无论什么情况下,都会调用两次超类型构造函数。好在出现了寄生组合继承

寄生组合继承**

通过借用构造函数来来继承属性,通过原型链的混成形式形式来继承方法

//寄生组合式继承
//构造循环链不能断,也不知道这样理解对不对
function inheritPrototype(subType,superType){
    let 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(){
    console.log(this.name);
}
function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}
//继承方法
inheritprototype(SubType,SuperType);
//如果不用封装的继承函数,可以这样使用
// SubType.prototype = Object.create(SuperType.prototype);
// SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    console.log(this.age);
}
复制代码
class

es6产生了class来定义类(原型),实际上依旧是函数

class SuperType{
    constructor(name){
        this.name = name;
    }
    sayName(){
        console.log(this.name);
    }
}
let s1 = new SuperType("Bob");
typeof SuperType //function 

class SubType extends SuperType{
    constructor(name,age){
        super(name);
        this.age = age;
    }
}
let s2 = new SubType("Tony",52);
复制代码

将以上的代码,通过Babel进行转化,你会发现es5的实现,也是使用的寄生组合继承。

小结

js支持面向对象编程,但不使用类或者接口。对象可以在代码执行过程中创建和增强,具有动态性,而非严格定义的实体。 原型模式:使用构造函数的prototype属性来指定那些应该共享的属性和方法。 组合使用构造函数模式和原型模式。使用构造函数定义实例属性,使用prototype来定义共享属性和方法。 大致的继承方法: 原型继承,无法自定义属性。本质是对给定对象的浅复制。 寄生式继承,基于某个对象或信息创建一个对象,然后增强对象,最后返回对象。 组合继承:原型继承+构造继承。多次调用超类型和构造函数而导致低效率。 寄生组合式继承:组合继承上的优化。实现基于类型继承的最有效方式。es6的extends就是这种的语法糖。

参考文献:

javascript高级程序设计(第三版)

MDN|继承和原型链

转载于:https://juejin.im/post/5cc6cf406fb9a031ed20c578

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值