奕玖科技 > 新闻中心 > 技术文章

JavaScript原型链constructor prototype __proto__讲解

来源: 奕玖科技 瘦死的猪 | 2023/1/5 12:36:47

前言

上一篇文章我们讲到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形形色色的函数,函数又演变为各种数据类型。

栏目导航
相关文章
文章标签
关于我们
公司简介
企业文化
资质荣誉
服务项目
高端网站定制
微信小程序开发
SEO排名推广
新闻动态
行业新闻
技术学院
常见问题
联系我们
联系我们
人才招聘
联系方式
Q Q:24722
微信:24722
电话:13207941926
地址:江西省抚州市赣东大道融旺国际3栋
Copyright©2008-2022 抚州市奕玖科技有限公司 备案号:赣ICP备2022010182号-1