Javascript - prototype、__proto__、constructor

最近看了很多文章,想要更通透的搞懂JS中的prototype__proto__constructor属性,从各个博主的文章里摘取了我认为可以有助于理解的一些内容,希望自己能够掌握好这一重要知识点的同时也帮助到大家,具体内容请见下文。

(注意:文中__proto__属性的两边是各由两个下划线构成。 )


本文通过下面一个简单的例子展开讨论,并配以相关的图帮助理解:

function Foo() {
    this.name="小红";
};	//创建一个函数Foo
var f1 = new Foo();	
var f2 = new Foo();		//通过new关键字,实例化Foo这个构造函数得到一个实例化对象f1

console.log(f1 === f2); //false

// 打印起来,看看都是啥吧
console.log(fn.prototype);	//undefined,可见由构造函数创建出的对象没有prototype属性,但是有__proto__属性
console.log(Foo.prototype);	//{constructor: ƒ Foo(...), __proto__: Object}
console.log(fn.__proto__);	//{constructor: ƒ Foo(...), __proto__: Object} fn.__proto__指向构造函数的prototype
console.log(Foo.__proto__);	//ƒ () { [native code] } 指向了Foo的原型对象Function,大家可以尝试一下Function.prototype是什么结果
console.log(fn.constructor);	//ƒ Foo() {this.name="小红"}	对象的constructor用于返回创建这个对象的函数(即构造函数)
console.log(Foo.constructor);	//ƒ Function() { [native code] } Foo也是一个对象,它的构造函数是Function(),所以返回的也是Function(),而Function()较特殊,它的构造函数是他自己,下文会再次强调
console.log(Foo.prototype.constructor);	//ƒ Foo() {this.name="小红"}
console.log(Foo.prototype.__proto__);	//{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ,…}
复制代码

虽然是简简单单的两行代码,然而它们背后的关系却是错综复杂的,如下图所示:

别怕,让我们一步步剖析,彻底搞懂它们。

图的说明:右下角为图例,红色箭头表示__proto__属性指向、绿色箭头表示prototype属性的指向、棕色箭头表示constructor属性的指向;蓝色方块表示对象,浅绿色方块表示函数。图的中间部分即为它们之间的联系,图的最左边即为例子代码

首先,我们需要牢记两点:

​ ① __proto__constructor属性是对象所独有的;

​ ② prototype属性是函数所独有的。

但是由于JS中函数也是一种对象,所以函数也拥有__proto__constructor属性,这点是致使我们产生困惑的很大原因之一。

(例如 获取图中Foo函数的__proto__和constructor的方式:Foo.prototype.__proto__; Foo.prototype.constructor;)

上图有点复杂,我们把它按照属性分别拆开,然后进行分析:

__proto__属性

__proto__属性,它是 对象所独有 的,可以看到__proto__属性都是由 一个对象指向一个对象 ,即指向它们的原型对象(也可以理解为父对象)。

那么__proto__属性的作用是什么呢?

它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找….直到原型链顶端 null (可以理解为原始人。。。),此时若还没找到,则返回undefined(可以理解为,再往上就已经不是“人”的范畴了,找不到了,到此为止),由以上这种通过__proto__属性来连接对象直到null的一条链即为我们所谓的 原型链

prototype属性

prototype属性,别忘了一点,就是我们前面提到要牢记的两点中的第二点:

​ ① 函数所独有的

​ ② 它是从一个函数指向一个对象

它的 含义函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象,由此可知:f1.__proto__ === Foo.prototype,它们两个完全一样。

prototype属性的作用又是什么呢?

它的作用就是 包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法

constructor属性

constructor属性也是对象才拥有的,它是从一个对象指向一个函数含义 就是指向该对象的构造函数,每个对象都有构造函数,从图中可以看出 Function 这个对象比较特殊,它的构造函数就是它自己(因为Function可以看成是一个函数,也可以是一个对象),所有函数最终都是由Function()构造函数得来,所以constructor属性的终点就是 Function()

——————————————————————

总结一下:

  1. 我们需要牢记两点:

    __proto__constructor属性是对象所独有的;

    prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有__proto__constructor属性。

  2. __proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,一直找,直到__proto__属性的终点null,然后返回undefined,通过__proto__属性将对象连接起来的这条链路即我们所谓的 原型链

  3. prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.__proto__ === Foo.prototype

  4. constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向终点Function()

下面根据上述内容整理相关的表格如下:

__proto__constructorprototype
属性归属对象独有对象独有函数独有(由于函数也是对象,所以函数也拥有__proto__constructor属性)
含义指向它们的原型对象(也可以理解为父对象),最终指向null,实现了原型链指向该对象的构造函数,最终指向Function()函数的原型对象
指向一个对象指向一个对象一个对象指向一个函数一个函数指向一个对象
作用作为原型链的桥梁,帮助向上一层层找到被访问对象的属性,直到null返回对创建此对象的函数的引用包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。

帮助理解

帮助理解 __proto__ 和 prototype

//如果你能耐心看完下列打印内容和注释,相信你会对 prototype、__proto__ 属性理解的更通透
function Fun(){};	//创造了一个函数Fun,这个函数由Function生成(Function作为构造函数)
var fn=new Fun();	//创建了一个函数fn,这个函数由Fn生成(Fn作为构造函数) 
console.log(fn.__proto__===Fun.prototype)    //true
// fn的__proto__指向其构造函数Fun的prototype
console.log(Fun.__proto__===Function.prototype)        //true
// Fun的__proto__指向其构造函数Function的prototype
console.log(Function.__proto__===Function.prototype)    //true
// Function的__proto__指向其构造函数Function的prototype
// 构造函数自身是一个函数,他是被自身构造的
console.log(Function.prototype.__proto__===Object.prototype)    //true
// Function.prototype的__proto__指向其构造函数Object的prototype
// Function.prototype是一个对象,同样是一个方法,方法是函数,所以它必须有自己的构造函数也就是Object
console.log(Fun.prototype.__proto__===Object.prototype)         //true
// 与上条相同
// 此处可以知道一点,所有构造函数的的prototype方法的__都指向__Object.prototype(除了....Object.prototype自身)
console.log(Object.__proto__===Function.prototype)        //true
// Object作为一个构造函数(是一个函数对象!!函数对象!!),所以他的__proto__指向Function.prototype
console.log(Object.prototype.__proto__===null)        //true
// Object.prototype作为一切的源头,他的__proto__是null

// 下面是一个新的,额外的例子
var obj={}
// 创建了一个obj
console.log(obj.__proto__===Object.prototype)        //true
// obj作为一个直接以字面量创建的对象,所以obj__proto__直接指向了Object.prototype,而不需要经过Function了!!

// 下面是根据原型链延伸的内容
// 还有一个上文并未提到的constructor,  constructor在原型链中,是作为对象prototypr的一个属性存在的,它指向构造函数(由于主要讲原型链,这个就没在意、);
console.log(obj.__proto__.__proto__ === null)        //true
console.log(obj.__proto__.constructor === Object)        //true
console.log(obj.__proto__.constructor.__proto__===Function.prototype)        //true
console.log(obj.__proto__.constructor.__proto__.__proto__===Object.prototype)    //true    
console.log(obj.__proto__.constructor.__proto__.__proto__.__proto__===null)        //true
console.log(obj.__proto__.constructor.__proto__.__proto__.constructor.__proto__===Function.prototype)    //true
复制代码

帮助理解 constructor和prototype

刚刚有说,constructor属性是对象才拥有的,它是从一个对象指向一个函数含义 就是指向该对象的构造函数

prototype属性是函数所独有的,是函数的原型对象,是由一个函数指向一个对象。

按照javascript的说法,function定义的方法就是一个Object(对象),而且还是一个很特殊的对象,这个使用function定义的对象与使用new操作符生成的对象之间有一个重要的区别。就是function定义的对象有一个prototype属性,使用new生成的对象就没有这个prototype属性

prototype属性又指向了一个prototype对象,注意prototype属性与prototype对象是两个不同的东西,要注意区别。在prototype对象中又有一个constructor属性,这个constructor属性同样指向一个constructor对象,而这个constructor对象恰恰就是这个function函数本身。

有点晕么?请看下图:

function Person(name){  
   this.name=name;  
   this.showMe=function(){  
       alert(this.name);  
   }  
};  
var one=new Person('js');  

console.log(one.prototype);	//undefined  证明了one这个对象没有prototype属性
console.log(typeof Person.prototype);	//object  
console.log(Person.prototype.constructor);	//function Person(name) {...}; 
console.log(one.constructor);	//function Person(name) {...}; 
复制代码

巩固

练习:下面自己动手画一画这段代码的原型图:

function People(name) {
    this.name = name;
    this.sayName = function() {
        console.log('my name is:' + this.name);
    }
}

People.prototype.walk = function() {
    console.log(this.name + 'is walking');
}

var p1 = new People('小明');
var p2 = new People('小红');
复制代码

练习:创建一个 Car 对象,拥有属性name、color、status;拥有方法run,stop,getStatus

function Car(name, color, status) {
    this.name = name;
    this.color = color;
    this.status = status;
}
Car.prototype = {
    constructor : Car,
    run: function() {
        this.status = 'run';
    },
    stop: function() {
        this.status = 'stop';
    },
    getStatus: function() {
        console.log(this.status);
    }
}

var car1 = new Car('BMW', 'red', 'stop');

复制代码

参考

本文内容均参考自以下文章(以便追根溯源):

www.cnblogs.com/xiaohuochai…

blog.csdn.net/cc188688768…

www.cnblogs.com/libin-1/p/6…

developer.mozilla.org/zh-CN/docs/…

www.cnblogs.com/zjunet/p/45…

www.ibm.com/developerwo…


如若发现文中纰漏请留言,欢迎大家纠错,我们一起成长。

转载于:https://juejin.im/post/5c765cdce51d453f4142d25a

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值