背景知识
看完类型开始看原型,个人觉得原型,原型链和闭包是js的两大难点,其他的知识点都是比较容易掌握的。
什么是原型、什么是原型链
JS中,每个对象都有一个内部属性prototype,我们通常称之为原型。原型的值可以是一个对象,也可以是null。如果它的值是一个对象,则这个对象也一定有自己的原型。这样就形成了一条线性的链,我们称之为原型链。
构造函数
具体概念以及如何理解请参阅JS中数据类型剖析,咱们只拿js示例来说事。
1 | function Car(color) { |
console.log(car1.constructor === Car) //true
console.log(car2.constructor === Car) //true
car1和car2都是Car实例化出来的对象,所以实例的对象的constructor指向Car
原型对象
在JS中,每个函数对象都有一个prototype属性,这个属性指向函数的原型对象。
还是上面的例子,我们换一个写法
1 | function Car(color, brand, country) { |
这里我们得到第一个结论,Car实例化的时候有一些默认属性,如果我们不通过自定义的复写方法来修改的话,我们实例化的对象都会具有该属性。这些属性的集合叫做原型对象,如下:
1 | Car.prototype = { |
我们生成了一个造车工厂,该工厂会生产德国产默认颜色的大众汽车,当然也可以生产其他品牌国家以及不同颜色的汽车。除了color,brand,country以及product方法以外,我们的造车工厂还有一个默认的属性:constructor。所以我们的Car.prototyep也有一个constructor属性,我们上节讲过constructor是指向它父级的一个指针所以我们来尝试一下:
1 | console.log(Car.prototype.constructor === Car) //true |
该结果验证了我们的结论,同我们上节讲的对象类型一样,我们在生成新的对象的时候也会添加一个constructor(构造函数)属性,这个属性指向生成它的父对象即Car,那么我们再实例化一个vwCar:
1 | let vwCar = new Car() |
结论也是true,那么我们对比一下两行代码:
1 | Car.prototype.constructor === Car |
vwCar.constructor是Car方法实例化的,自然,vwCar.constructor === Car。那么通过Car.prototype.constructor === Car是不是验证了,Car.prototype也是Car实例化来的呢。即:
1 | var car = new Car() |
原型对象(Car.prototype)是构造函数(Car)的一个实例。
__proto__
JS在创建对象的时候,除Object.prototype对象以外,所有对象的__proto__都指向创建这个对象的constructor的prototype。
上篇文章js中的数据类型中分析过Object.prototype.__proto__指向了null。
1 | let vwCar = new Car() |
通过上面代码得出如下结构图:
es5中并不是所有浏览器都支持__proto__属性,__proto__已经添加至es6标准中。
Prototyep
Prototype是保存着它们所有实例方法的真正所在。
正如我们上节所分析Object实例所有共有方法都是存在在Object.prototype中。
本节我们通过一个简单的类型Number来分析prototype。
console.log(Number.prototype)
上图中Number的内置方法多多少少都不陌生。比如
1 | let a = 123.3213 |
这里的toFixed方法就是从Number继承来的,而toFixed方法写在Number.prototype。
(3).plus(5)如何让该语句执行结果输出一个8
该题是在面试中遇到的,当时面试官打开电脑问我(3).plus(5)是啥,我想了想没有这个方法,肯定是报错呀。毕竟没有这个方法。又问如果希望它能输出一个8该如何处理。你可以在浏览器里面输入任何代码。
首先我们来看。3是一个数字类型的数据。继承了Number下的所有方法,所以我们只需要在Number.prototype下定义一个plus方法即可。
1 | Number.prototype.plus = function(number) { |
当然在工作中我们不鼓励着么做,因为Number是系统内置对象,虽然我们可以修改它的内容,但是我们没有权利去修改任意系统内置对象,因为可能会导致其他人在使用该对象的时候出现一些莫名奇妙的bug。
比如我在说明==是一个坑的时候举过的一个例子:
1 | var x = 1 |
这里也是一样,我们复写了obj下的valueOf方法,结果是非常可怕的。如若我们复写了Object.prototype的内置方法,所有Object类型的数据内置方法都会出问题,结局可想而知。
总结升华
原型和原型链是JS实现继承的一种模型。
原型链的形成是真正是靠__proto__而非prototype
引用网上某兄台的总结
再写一个现阶段能理解的js类
1 | function Car(color, brand, country) { |
奔驰发明了汽车,大众学习了奔驰的造车方法,然后创建了MLB平台,而我买了一辆基于MLB平台叫做macan的suv,是大众的子品牌保时捷
可以看出,macan继承了MLB平台,MLB平台继承了Volkswagen,而Volkswagen继承了Car方法。示例有些偷懒,应该有保时捷出现在继承中。想想篇幅算了,后面会在js设计模式中好好写一下吧。
上图中的继承均是通过原型链来继承的。该继承方式正如上文引用:
原型和原型链是JS实现继承的一种模型
原型链的形成是真正是靠__proto__而非prototype
object其他的内置方法也可以通过该示例来进行检测,这里我就不贴代码了。各位自己试试可以加深对内置对象的理解。