JavaScript原型和原型链(详细解读)

原型和原型链

一、原型概述

1.概念

原型在JavaScript是一个很重要的概念,它是面向对象可以继承的基础。知道吗,JavaScript的设计模式就是原型模式(二十三个经典设计模式之一),正是因为它有这个模式,所以它才十分的灵活。可以基于原型模式实现更多的面向对象设计模式。

二、原型链

1.什么是原型链?

原型链是原型的查找机制,是一条寻址链。其实原型上的方法或属性查找,都是按照一定的顺序沿着原型链进行查找的。如果查找到最后仍然没有找到这个原型和方法,那么JS就会抛出没有这个属性或方法的异常。

2.什么是原型指针?

原型指针是连接原型对象之间的地址桥梁,看下面的图你就会理解了

JavaScript原型和原型链(详细解读)

原型对象包括两部分,原型数据和原型指针。原型数据用来存储属性或方法,原型指针是为了验证原型链表进行查找

2.原型链规则

一个new出来的实例对象它的原型指针指向这个类的原型对象,这个类的原型对象指针默认指向Object原型对象

JavaScript原型和原型链(详细解读)

A是一个类,它new出来的实例a的原型指针指向A,而A的原型指针默认指向Object这个万物类。

换句话说这个原型链是从头到尾查找的,如果a原型上没有这个属性,那么就去找创造它的A,如果A原型上也没有,那么就去找Object这个万物类了,如果Object上的原型仍然没有,那么就会抛出异常

三、实现类的继承

实现类的继承分为两步:

  • 继承对象中的属性和方法(对象继承)
  • 继承对象原型中的属性和方法(原型链继承)

在研究他们之前,我们首先建立两个类

function father() {
    //这些属性在father实例对象上
    this.name = "cry"
    this.age = 50
}
//这些方法在father原型对象上
father.prototype.getName = function () {
    return "父亲叫" + this.name
}
father.prototype.getAge = function () {
    return "父亲他今年" + this.age + "岁了"
}

//子类(姓名,年龄)
function child() {

}

我们首先实现实例对象的继承

function child() {
    //继承第一步,实例对象的属性继承
    father.call(this)//call可以把child的上下文传进Father里进行属性赋值
}
//现在你已经可以访问father的name属性和age属性了
var c1 = new child()
console.log(c1.name, c1.age)
//但是我们还是没有办法访问father原型上的方法
console.error("error",c1.getAge())

接下来就是原型链继承

我们画图理解原型链继承

修改之前的原型链

JavaScript原型和原型链(详细解读)

修改之后的原型链

JavaScript原型和原型链(详细解读)

我们在object和child之间插入一个father原型对象节点,这样如果子类方法没有,就会沿着原型链向下查找到father原型下的方法

那么对应的代码就是

//修改child的原型指针让它指向father这个原型对象(之前默认指向object,如上上图所示)
child.prototype.__proto__ = father.prototype

完整继承代码

//父类(姓名,年龄)
function father() {
    this.name = "cry"
    this.age = 50
}

father.prototype.getName = function () {
    return "父亲叫" + this.name
}
father.prototype.getAge = function () {
    return "父亲他今年" + this.age + "岁了"
}

//子类(姓名,年龄)
function child() {
    //继承第一步,属性继承
    father.call(this)//call可以把child的上下文传进Father里进行属性赋值
    //如果你想重写override的话,就在下面覆盖child ex:this.想要覆盖的属性=xxx
}

//这个时候的child已经有name属性和age属性了
var c1 = new child()
console.log(c1.name, c1.age)
//但是现在还不能访问father原型链上的getName和getAge方法
// console.error("undefined",c1.getAge())  会报错

//第二步骤继承--原型链继承(__proto__是原型指针)

child.prototype.__proto__ = father.prototype
//如果你想重写override的话,就在下面覆盖child ex:child.prototype.想覆盖的方法=function()
var c2 = new child()
console.log(c2.getName(), c2.getAge())
//现在就能实现真正的继承了

以上就是实现类继承的方法

运行截图,打印child实例

JavaScript原型和原型链(详细解读)

红色是原型对象,紫色是原型指针

以上就是如何使用es5实现类的继承,其实在es6中我们也可以实现继承,那就非常简单了。我们新增了extends关键字,简直和java一摸一样了

伙伴们记住,当你打印对象的时候,你看到的[[prototype]]就是原型指针,和它同级别的那些属性呀,方法呀是原型里的内容/属性+实例对象里的内容和属性。因为属性和内容有两个存储容器,一个是实例对象可以存他们,一个是原型对象可以存他们。

如果你清晰查看原型和原型链,请打印实例对象,一个个展开[[protype]],去看里面的constructor方法,就知道它们(原型指针)究竟指向谁了(就像上图一样)

四、Object.create()

我们都知道Object.create()可以给我们创造一个新的对象实例,它与new不同的是,new只能传造出有contructor(构造器)的对象。而我们的Object.create()可以无门槛创建对象。

举个例子(演示区别)

let obj={
    a:1,
    func:function(){
        console.log("愿图灵工作室越办越好!")
    }
}
Object.create(obj)//不会报错
new obj()//会报错,因为obj没有构造函数,不属于构造函数类型的对象

上述说明的是Object.create()和new 的区别

现在我们要用原型链重新实现一下Object.create()这个方法,没错就是手写源码!

function create (obj){
    function fn(){}
    //把fn的原型对象替换成obj(原型对象包括原型指针【寻址部分】和原型属性/方法【数据部分】)
    fn.prototype=obj
    //这样new fn,这个实例对象的原型指针指向fn这个类,而fn这个原型就是obj,所以new fn继承了obj的原型,可以使用原型里面的所有东西
    return new fn()
}

我们使用图解来理解这个过程

JavaScript原型和原型链(详细解读)

对应的代码是 fn.prototype=obj【修改fn原型,fn现在的原型是obj了】

JavaScript原型和原型链(详细解读)

记住实例化的对象,它的原型指针指向它的原型对象,也就是new fn指向fn,因为fn的原型已经是obj的了,所以你new出来的东西可以认为就是obj了,它和new obj()已经没啥区别了。唯一的区别是直接new obj()可能会报错,因为上面我讲过了,没有constructor的东西是不能new的,可是我通过修改function fn原型的方法,让他的原型是obj,肯定是可以new的吧,因为function是存在构造函数的。

完整案例

function create (obj){
    function fn(){}
    //把fn的原型对象替换成obj(原型对象包括原型指针【寻址部分】和原型属性/方法【数据部分】)
    fn.prototype=obj
    //这样new fn,这个实例对象的原型指针指向fn这个类,而fn这个原型就是obj,所以new fn继承了obj的原型,可以使用原型里面的所有东西
    return new fn()
}
let b={
    a:'你好',
    sayhello:function(){
        return "hello"
    }
}
let cc=create(b)
console.log(cc.sayhello())

五、实现new关键字

你知道new关键字的底层是如何实现的吗?没错就是原型和原型链。我们让一个对象的原型指针指向指定对象的原型就好了

function new_(classify){
    let obj={}
    classify.call(this)
    obj.__proto__=classify.prototype
    return obj
}

使用它既能访问构造函数的属性和方法【也可以叫做对象实例中的属性/方法】,又能访问原型链上的属性/方法

这次就先分享这么多了,我们下次再见!

上一篇:找二叉树中两个结点的最低公共祖先C++实现


下一篇:kruskal算法生成最小生成树