1.概念
原型在JavaScript是一个很重要的概念,它是面向对象可以继承的基础。知道吗,JavaScript的设计模式就是原型模式(二十三个经典设计模式之一),正是因为它有这个模式,所以它才十分的灵活。可以基于原型模式实现更多的面向对象设计模式。
1.什么是原型链?
原型链是原型的查找机制,是一条寻址链。其实原型上的方法或属性查找,都是按照一定的顺序沿着原型链进行查找的。如果查找到最后仍然没有找到这个原型和方法,那么JS就会抛出没有这个属性或方法的异常。
2.什么是原型指针?
原型指针是连接原型对象之间的地址桥梁,看下面的图你就会理解了
原型对象包括两部分,原型数据和原型指针。原型数据用来存储属性或方法,原型指针是为了验证原型链表进行查找
2.原型链规则
一个new出来的实例对象它的原型指针指向这个类的原型对象,这个类的原型对象指针默认指向Object原型对象
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())
接下来就是原型链继承
我们画图理解原型链继承
修改之前的原型链
修改之后的原型链
我们在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实例
红色是原型对象,紫色是原型指针
以上就是如何使用es5实现类的继承,其实在es6中我们也可以实现继承,那就非常简单了。我们新增了extends关键字,简直和java一摸一样了
伙伴们记住,当你打印对象的时候,你看到的[[prototype]]就是原型指针,和它同级别的那些属性呀,方法呀是原型里的内容/属性+实例对象里的内容和属性。因为属性和内容有两个存储容器,一个是实例对象可以存他们,一个是原型对象可以存他们。
如果你清晰查看原型和原型链,请打印实例对象,一个个展开[[protype]],去看里面的constructor方法,就知道它们(原型指针)究竟指向谁了(就像上图一样)
我们都知道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() }
我们使用图解来理解这个过程
对应的代码是 fn.prototype=obj【修改fn原型,fn现在的原型是obj了】
记住实例化的对象,它的原型指针指向它的原型对象,也就是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关键字的底层是如何实现的吗?没错就是原型和原型链。我们让一个对象的原型指针指向指定对象的原型就好了
function new_(classify){ let obj={} classify.call(this) obj.__proto__=classify.prototype return obj }
使用它既能访问构造函数的属性和方法【也可以叫做对象实例中的属性/方法】,又能访问原型链上的属性/方法
这次就先分享这么多了,我们下次再见!