浅析JavaScript中原型及constructor、__proto__、prototype的关系

前言

先说一说为何要搞清楚JavaScript的原型,由于这就是JS的根。JavaScript虽然不是一门传统的面向对象语言,但它有本身的类和继承机制,最重要的就是它采用了原型的概念。与其说JS是面向对象,不如叫面向原型。JS这门语言从开发之初就是基于原型去作事情的,它是面向对象的思想,但归根结底是面向原型的原理,从操做上来讲也是这样的。程序员

咱们老师之前说过,好多工做几年的人,在这个问题上都模棱两可。基础才会是决定一个程序员上限的最终指标。由于对一门语言的基础掌握得越好,就越可能经过原生的语言去开发新的东西,框架也好、插件也好。但若是基础很差,顶多也就能用用别人开发的东西,你本身是没能力去开发的。编程

那么要搞懂原型,涉及到的知识点就比较多了,构造函数、指针、对象、继承这些概念都会是门槛,但不要着急,一口是无法吃成大胖子的。我在翻阅研究《JavaScript高编》和各类资料后,总结出了本身对这部分的理解,尽可能用连贯性和通俗易懂的方法去解释,这样方便本身的记忆,相信你们看了也不会懵逼。数组

原型模式

原型的概念

那么到底什么是原型呢?原型的英文就是“prototype”。记住这个朋友,它会伴随咱们的一辈子,不离不弃。浏览器

维基百科的官方解释
原型模式是是面向对象编程的子系统和一种方式,特色在于经过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是咱们所称的“原型”,这个原型是可定制的。
维基百科:https://zh.wikipedia.org/wiki...框架

通俗地讲,原型就是咱们复印件的原件。函数

那么JS里的原型是什么呢?“咱们建立的每一个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法。”原型就是这句话中的prototype,说白了它就是一个对象{},叫原型对象。学习

抓重点:
1.每一个函数才有prototype属性,即function abc(){},abc.prototype是存在的!
2.prototype是一个指针,指向一个对象。(指针什么意思?下面解答)
3.prototype指向的对象可让其余实例共享其属性和方法。this

这里的第二点,指针究竟是个什么意思呢?好比var a = [1],指针就是指向等号右边这个数组在计算机内存中的存储地址。因此当咱们使用a的时候,a就是一个指针,它指向[1]存储的地方。说得通俗点,就是经过a这个变量,咱们能够找到等号右边这个值。spa

prototype就是指向一个对象存储的地方,能够理解为咱们去找到prototype = {}中等号右边这个值,因此最终返回的就是一个对象。prototype

原型模式和构造函数模式的区别

可是咱们在JS中建立新的对象会有两种模式,一种是构造函数模式,一种就是原型模式。这个构造函数也是new一个实例,他们有什么区别呢?先看下面这段代码:

/*
原型模式
*/

function Person(){};  //新建一个空函数

Person.prototype.name = 'Gomi';  //为它的原型添加属性
Person.prototype.myName = function(){
    console.log(this.name)
};

var gomi = new Person();  //复制一个Person实例
var gomi2 = new Person();  //再复制一个Person实例

gomi.myName === gomi2.myName  //true,说明他们的this都是指向同一个原型,即Person.prototype。

/*
构造函数模式
*/
function Animal(){
    this.name = 'cat',
    this.myName = function(){
        console.log(this.name)
    }
};  //新建一个函数,并直接在里面添加属性和值

var jack = new Animal();  //一样实例化了一遍,但这时每一个实例中的属性和值都是独立存在的。
var jack2 = new Animal(); 

jack.myName === jack2.myName //false,说明他们的this指向不一样,都是单独生成的新的实例,而不是依赖于同一个原型。

原型和构造函数的区别就像影分身和克隆的区别。咱们把原型模式看做影分身,复制原型的过程看做本体产生分身的过程,影分身的任何动做都是基于那个惟一本体的,他作什么,影分身就会作什么。而构造函数模式就是克隆,虽然克隆的时候是基于惟一本体的基因,但其实克隆出来的每一个人都是一个新的独立的人了,他们虽然长得如出一辙,但互相之间没有任何关联。若是本体整容了,其他的克隆人也不会变。但在本体整容后再进行克隆的人,确定就会跟整容后同样咯。而影分身是,一旦本体整容,那么全部分身都会跟着变样。

搞清了构造函数和原型的区别后,就能够继续了。

constructor、prototype、__proto__的关系

光是搞清楚构造函数和原型的区别还远远不够,咱们常常会在控制台看到下面这种结构:

clipboard.png

这是一个绝对可以搞晕你的结构,我圈出的constructor、prototype、__proto__这三者老是在出现,老是在互相嵌套。他们究竟是什么关系?又表明什么意思呢?

首先搞清楚他们所表明的含义:
1.constructor:指向构造这个对象的函数
2.prototype:函数的原型对象(上面提到,只有函数才有)
3.__proto__:指向构造函数的原型对象,存在于实例与构造函数的原型对象之间。(只要是对象就有这个属性,因此函数也会有。注意这是一个非标准的属性,因此大多数人叫隐式属性,可是现代全部浏览器都支持访问)
若是很差理解,一个一个来。

constructor

constructor的官方定义是“返回建立该对象的函数的引用”
说白了就是找到这个对象是经过什么构造函数来生成它的,经过constructor就能找到这个函数。

直接声明一个对象时(广义的对象),它的constructor

//定义对象
var obj = {a:1};
obj.constructor;//输出Object(){},说明生成obj的上层函数是Object(){}这个函数
obj.constructor.constructor; /*输出Function(){},由于在JS中函数也是对象,因此Object()这个函数对象上层构造它的函数是Function(){}*/
obj.constructor.constructor.constructor;//仍是输出Function(){},说明Function(){}就是最顶层的构造函数了

//定义函数对象
var func = function(){};
func.constructor;//输出Function(){}

//定义数字对象
var num = 1;
num.constructor;//输出Number(){},说明上层生成这个数字对象的函数是Number(){}
num.constructor.constructor;//输出Function(){},最顶层的函数

当这个对象是prototype原型对象时,它的constructor

function Person(){};
Person.prototype.name = 'Gomi';
Person.prototype.constructor;//输出Person(){}
Person.prototype.constructor.constructor;/*输出Function(){},最上层的函数。*/

记住一点就行,constructor是找到咱们JS中生成这个对象上层的构造函数是什么。万物皆对象,若是定义一个字符串,那么它的上层函数就是String(){},若是定义一个数组,它的上层函数就是Array(){}......一直找到最上层就是Function(){}

pototype

prototype就是咱们说的原型对象,只有函数才有这个属性。
因此当咱们随便定义一个函数时,都会自带这个属性。

function Person(name,age){
    console.log('hello')
}

Person.prototype输出以下图:
clipboard.png

返回的是一个对象,因为咱们没有给prototype添加任何属性,因此除了constructor这个属性,咱们找不到其余属性了(其余属性继承于Object())。
如今咱们给它添加属性:

Person.prototype.name = 'Gomi';
Person.prototype.age = 18;
Person.prototype.myName = function(){
    console.log(this.name)
}

这时Person.prototype的输出结果以下:
clipboard.png

如今咱们new一个实例看看:

var gomi = new Person();
gomi.name;//输出'Gomi';
gomi.name = 'hi';
gomi.name;//输出'hi'

从上面的结果能够看出来,这个机制是会先去找实例自己的属性,若是存在就直接返回实例的属性,若是不存在再去原型里找。因此当你给实例添加了一个和原型相同的属性时,从表面上看就是覆盖了原型的属性,由于咱们不能直接经过实例这个属性去返回原型的属性了,但实际上实例和原型的这两个属性都是单独存在的。

好了,那么咱们为何要用这个prototype呢?由于咱们开头说到的,能够供其余实例共享这些属性。好比有这样一个场景,咱们如今的Person实例存的是公司的人员信息模板,如今归类,将公司名称和员工类型做为属性,符合这一类的员工咱们就生成一个实例。那么咱们就能够像下面这样生成所有公司的员工类型,每一个人一个实例:

function Person(){};
Person.prototype.company = 'sefon';
Person.prototype.type = 'inter';
var Anna = new Person();
var Gomi = new Person();
var Lily = new Person();
...
//生成的这些实例就都具有Person的属性了,这些人就都是咱们的inter实习生

那要是咱们如今要给每一个员工新增一个职位的属性怎么办呢?难道咱们要给每一个实例添加一遍吗?

Anna.job = 'front-end';
Gomi.job = 'front-end';
Lily.job = 'front-end';
...

这样太麻烦了,因而咱们prototype的做用就来了,只须要Person.prototype.job = 'front-end'就好了,全部实例会自动继承咱们的Person全部的属性和值。固然这样确定也是有弊端的,就很少说了。

要注意的是当咱们使用下面这种将整个prototype重写的状况时,会切断构造函数和原型之间的联系,也就是说constructor再也不指向Person(){}了,而是指向Object(){}

function Person(){};
Person.prototype.constructor; //输出Person(){}
Person.prototype = {
    name:'Gomi'
}
Person.prototype.constructor; //输出Object() { [native code] }

那么prototypeconstructor是个什么关系呢?
上面已经提到一些,constructor是找咱们对象的构造函数是什么,返回的是一个函数。prototype是函数才有的原型对象。因而乎,constructor是否是有prototype呢?答案固然是有的。那prototype是个对象,里面确定也有constructor咯。

他们的关系我画了一个简洁版的关系图以下:
clipboard.png

proto
__proto__是个非标准属性,可是很重要。它就像一根链条,将咱们JS对象链接起来。那么它究竟是个什么东西呢?
首先,__proto__是链接于实例与构造函数原型之间,而不是实例与构造函数之间的。什么意思呢?举个例子:

function Person(){};
Person.prototype.name = 'Gomi';
var gomi = new Person();
gomi.constructor; //输出Person(),gomi实例的构造函数是Person()
gomi.__proto__ ; //输出{name:'Gomi',constructor:f}
gomi.constructor.prototype; //输出{name:'Gomi',constructor:f}
gomi.constructor.prototype === gomi.__proto__ ; //返回true,说明gomi.__proto__指向的是gomi构造函数的原型

__proto__一层一层最终指向的是Object()这个构造函数的原型时,__proto__就是null。因此你们常说的原型链最顶端是null就是这么来的。好比下面这样:

function Person(){};
Person.__proto__;//输出ƒ () { [native code] }
Person.__proto__.__proto__//输出{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
Person.__proto__.__proto__.__proto__;//输出null

上面的代码就说明了__proto__指向的是实例的构造函数的原型,记住是xxx.constructor.prototype === xxx.__proto__就好了。因此他们的关系用图来表示就是下面这样:
clipboard.png

说明:图可能画得不是很好,这里我单独把指向的对象写了出来,好比constructor返回的是一个构造函数,也就是说其实constructor就是一个构造函数,只是为了更加清晰,我便单独把返回的东西用指向来讲明。

本文仅做为本身的学习和总结,若有错误请直接评论,我会修改的哈哈。

若是以为还不够明白或讲得很差,能够看看更加权威的:
https://developer.mozilla.org...

相关文章
相关标签/搜索