前言
上一篇文章我们讲到JavaScript的数据类型实际最终都是显示为函数,比如数字,数组,字符串,函数,Object,分别来自Number(),Array(),String(),Function(),Object()。而上一篇我们也看到函数它们都有一个Constructor()构造函数,另外函数都有prototype对象属性。但是如果我们定义的实例是一个对象,它会有__proto__属性,而没有prototype对象属性。示例如下
Let a= {}//let a= Object()也可以 console.dir(Object.getPrototypeOf(a))
如果是函数,代码如下
let a=()=>{} //let a=3 let a="3333" let a=false都可以 console.dir(Object.getPrototypeOf(a))
函数返回Prototype,对象返回__proto__。我们可以通过它的构造函数constructor就可以看到,为Object()的就没有Prototype。我们今天就详细的讲解一下constructor prototype proto 这3个东西把。
什么是constructor
JavaScript中,具有原型的函数都会有一个constructor 函数,叫做构造函数(构造器函数),它的作用是用来初始化函数的。假设我们现在创建一个函数function tmp(){},那么首先javascript会做2件事情,一件就是创建prototype 然后再prototype 创建constructor
我们再把上诉的代码改变一下把!
function Tmp (sex,name){ this.sex="男" this.name="李四" } let a=new Tmp("女","小王") console.dir(a)
我们看到constructor引用与Tmp(sex,name),而再Tmp()代码内部,虽然我们有2个参数,但都通过this写死了sex和name属性。所以不管我们实例化的时候赋予这2个参数什么值,都没有作用。如果要让它起作用需要把this.sex=sex指向与参数
function Tmp (sex,name){ this.sex=sex this.name=name }
构造器函数中不允许直接赋值,如Tmp.add=3,这样是不允许的。如果需要添加方法第一,再函数内部添加。第二,通过prototype添加,比如Tmp.prototype.add=3
constructor会包含一个指针,这个指针将指向所在函数(Tmp(sex,name))
function Tmp (sex,name){ this.sex=sex this.name=name this.school="一中" } Tmp.prototype.info=function (){ return `学校:${this.school},性别:${this.sex} 姓名:${this.name}` } let a=new Tmp("女","小王") console.log(a.info())
最终将打印学校:一中,性别:女 姓名:小王。info()方法可以再所有的Tmp实例里共享。其实我们这个info可以直接再Tmp里写,为什么要写再Tmp.prototype里呢?假设上面这个代码我们要开发一个插件调用方法为plug,如果写再Tmp里,是不是需要直接修改Tmp的源码?但是我们通过原型就不需要修改源码了,直接Tmp.prototype.plug="我的插件内容"是不是就扩展了一个功能开放给所有人使用?
什么是prototype
我们现在已知的是函数都有一个prototype,当然一些特殊的对象没有,如arguments、Enumerator、Error、Global、Math、RegExp、Regular Expression。prototype会有一个指针,指向它的引用对象(原型对象)。比如上述示例中的引用对象为Tmp(sex,name)。prototype里包含了构造函数和一系列的方法都可以被new出来的实例继承。举个例子let a=3。它的prototype如下图
因为a继承了Number(),所以可以使用Number的Prototype里的toString()方法,我们就可以直接a.toString()把数字转换为字符串了。包括上图中的所有方法如toFixed都可以被数字变量使用。所以prototype作用为当创建一个原型对象实例后,实例将继承原型对象的所有属性和方法。而且我们可以发现,这些方法通常也为一个函数。利用这种特性,我们甚至可以重写javascript的内置函数方法,比如我们现在把toString()方法改变它的功能为数字自增1。
Number.prototype.toString=function (){ return this.valueOf()+1 } let b=3 console.dir(b.toString())
最终显示结果为4。valueOf方法为返回b的值,也就是3。当然有细心的同学会想,这个let b=3并没有new 一个Number()啊?其实这个是javascript自动帮我们new好了,我们这些写也是没问题的。
let b=new Number() b=3
这里再重新讲一下prototype的原理,javascript会从原型对象prototype继承属性和方法。比如let b=3.它的继承顺序为
Number->Function->Object。比如toString()方法,首先会查找自身及自身的prototype及prototype的prototype,如果有toString()方法,就执行,没有就向Number查找,如果没找到,再往上,一直到Object,如果还没找到这个方法将返回null,并结束查找。Object是原型的最终点,一切的行为到它这里就结束了。
什么是__proto__
再文章开头已经讲过__proto__只存在于对象Object,其实对象这个玩意它原理上也是由函数生成的。再生成对象后自然会有一个__proto__属性,而该属性指向函数的prototyped,当然你也可以手动的修改__proto__的指向。否则的话它将遵守我上面说的默认行为。当然这里要强调一点最终的Object的_proto__的值为null,它没有指向,这是为了好让程序可以得到终止,否则比如Object.prototype .__proto__指向Object 然后Object.__proto__又指向Object.prototype 这样不就陷入一个死循环了吗?我们可以看一个示例
let b=function (){} console.dir(b.__proto__)
最终的Object. __proto__,这是原型的终点,它整个流程是这样的。Function.prototype->Object->Object.__proto__->Object->Object.__proto__==null。当然我们还可以用一个更简单的方法来证明。上面说过了,对象也是由函数生成的。所以函数也是一个对象,既然是对象就有__proto__属性。所以我们可以直接打印
console.dir(Function.prototype.__proto__)
显示指向了Object.prototype,而Object.prototype已经是终点了,所以它的__proto__也显示如null,根据文章开头说明。对象没有prototype,只有__proto__,所以结构图如下
最后总结
JavaScript的原型大家一定要掌握好,这个是js的基础,充分理解了它,那么JavaScript宣称的一切皆对象就会明白这是怎么一回事。比如我们平常定义的一个字符串let s="你好,世界"。这表明看起来不是一个函数,但实际上它确实是一个函数,String()然后通过层层转换它最终的面目还就是Object。用C#的语法来说Object是一切的基类,Object最终演变成了JS形形色色的函数,函数又演变为各种数据类型。