1 原型链
1)JavaScript 的原型,原型链?有什么特点?
(1)原型:
- JavaScript 的所有对象中都包含了一个 [proto] 内部属性,这个属性所对应的就是该对象的原型
- JavaScript 的函数对象,除了原型 [proto] 之外,还预置了 prototype 属性
当函数对象作为构造函数创建实例时,该 prototype 属性值将被作为实例对象的原型 [proto]。
原型特点:
JavaScript 对象是通过引用来传递的,当修改原型时,与之相关的对象也会继承这一改变
(2)原型链:
当一个对象调用的属性/方法自身不存在时,就会去自己 [proto] 关联的前辈 prototype 对象上去找
- 如果没找到,就会去该 prototype 原型 [proto] 关联的前辈 prototype 去找。依次类推,直到找到属性/方法或 undefined 为止。从而形成了所谓的“原型链”
js 原型 原型链图
- 每一个构造函数都有prototype指向当前构造函数的原型对象
- 每一个实例对象都有proto,指向当前实例对象构造函数的原型对象prototype
- 原型对象也是对象也有proto属性,指向原型对象构造函数的原型对象即Object.prototype
- 原型对象的constructor属性指向的是当前原型对象的构造函数
即Object.prototype.constructor = Object - 通过构造函数创建对象,构造函数本身也是一个对象
可以简单理解为:
函数有prototype属性,指向构造函数的原型对象
对象有proto属性,指向构造函数的原型对象
const foo = new Foo() //foo实例对象 Foo构造函数
const Foo = new Function() //Foo实例对象 Function构造函数
const Function = new Function() //Function实例对象 Function构造函数
const o = new Object() //o实例对象 Object构造函数
const Object = new Function() //Object实例对象 Function构造函数
形成的原型链图:
2.JavaScript里的proto和prototype到底有什么区别?
对比:
对比 | proto | constructor | prototype |
---|---|---|---|
归属 | 对象所独有的 | 对象所独有的 | 函数所独有的 |
写法 | proto属性的两边是各由两个下划线构成 | 由于JS中函数也是一种对象,所以函数也拥有proto和constructor属性,这点是致使我们产生困惑的很大原因之一。 | |
作用 | 当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的proto属性所指向的那个对象(父对象)里找,一直找,直到proto属性的终点null,然后返回undefined,通过proto属性将对象连接起来的这条链路即我们所谓的原型链。 | constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。 | prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.proto === Foo.prototype。 |
作用 | 连成原型链 | 让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.proto === Foo.prototype。 | |
说明 | JavaScript里所有的对象都有**proto**属性(对象,函数) | 只有函数function才具有prototype属性。 | |
对比 | 指向构造该对象的构造函数的原型。 | 这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。 |
首先声明:
1.在JS里,万物皆对象。
方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。
即:对象具有属性proto,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。
2.方法(Function)方法这个特殊的对象,除了和其他对象一样有上述proto属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。
原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。
验证:
1.1首先声明一个对象和一个函数,console.log一下对象和函数的proto
var A = function () {};
var B ={};
console.log(A.proto)
console.log(B.proto)
控制台输出结果如下图所示:
proto
刚才不是说函数是对象吗?它们的proto为啥不一样?往下看,别着急。
var A = function () {};
var B ={};
console.log(A.proto.proto)
console.log(B.proto)
console.log结果如下:
是不是一样了,,确实有点懵逼啊。。重点 隐式原型指向构造该对象的构造函数的原型。因为function是特殊的对象,A.proto就指向了构造该函数的一个函数(随意起个名字 C),C的proto就指向了和B对象一样的proto。。我自己都懵逼了。
1.2一个对象和一个函数,console.log一下对象和函数的prototype
var A = function () {};
var B ={};
console.log(A.prototype)
console.log(B.prototype)
console.log结果如下:
image.png
对象并不具有prototype属性,只有函数才有prototype属性。这就证明声明2的说法是正确的。
3.彻底搞懂JS中的prototype、proto与constructor
简单的例子展开讨论
function Foo() {…};
let f1 = new Foo();
以上代码表示创建一个构造函数Foo(),并用new关键字实例化该构造函数得到一个实例化对象f1。虽然是简简单单的两行代码,然而它们背后的关系却是错综复杂的,如下图所示:
图的说明:
右下角为图例
红色箭头表示proto属性指向
绿色箭头表示prototype属性的指向
棕色实线箭头表示本身具有的constructor属性的指向
棕色虚线箭头表示继承而来的constructor属性的指向;
蓝色方块表示对象
浅绿色方块表示函数(这里为了更好看清,Foo()仅代表是函数,并不是指执行函数Foo后得到的结果,图中的其他函数同理)。
图的中间部分即为它们之间的联系,图的最左边即为例子代码。
首先,我们需要牢记两点:
①proto和constructor属性是对象所独有的;
② prototype属性是函数所独有的。
但是由于JS中函数也是一种对象,所以函数也拥有proto和constructor属性,这点是致使我们产生困惑的很大原因之一。
图例拆分
proto属性
1.指向
第一,这里我们仅留下 proto 属性,它是对象所独有的,可以看到proto属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象)。
2.作用(构造了一个原型链)
那么这个属性的作用是什么呢?它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的proto属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的proto属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找…直到原型链顶端null(可以理解为原始人。。。),此时若还没找到,则返回undefined(可以理解为,再往上就已经不是“人”的范畴了,找不到了,到此结束),由以上这种通过proto属性来连接对象直到null的一条链即为我们所谓的原型链。
prototype 属性
1.指向
prototype属性,别忘了一点,就是我们前面提到要牢记的两点中的第二点,它是函数所独有的,它是从一个函数指向一个对象。
2.含义
它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象,由此可知:f1.proto === Foo.prototype,它们两个完全一样。
3.作用
那prototype属性的作用又是什么呢?它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。
constructor 属性
1..指向
constructor属性也是对象才拥有的,它是从一个对象指向一个函数,含义就是指向该对象的构造函数,每个对象都有构造函数(本身拥有或继承而来,继承而来的要结合proto属性查看会更清楚点,如下图所示),从上图中可以看出Function这个对象比较特殊,它的构造函数就是它自己(因为Function可以看成是一个函数,也可以是一个对象),所有函数和对象最终都是由Function构造函数得来,所以constructor属性的终点就是Function这个函数。
每个对象都有构造函数
这里的意思是每个对象都可以找到其对应的constructor,因为创建对象的前提是需要有constructor,而这个constructor可能是对象自己本身显式定义的或者通过proto在原型链中找到的。而单从constructor这个属性来讲,只有prototype对象才有。每个函数在创建的时候,JS会同时创建一个该函数对应的prototype对象,而函数创建的对象.proto === 该函数.prototype,该函数.prototype.constructor===该函数本身,故通过函数创建的对象即使自己没有constructor属性,它也能通过proto找到对应的constructor,所以任何对象最终都可以找到其构造函数(null如果当成对象的话,将null除外)。如下:
总结一下:
我们需要牢记两点:
①proto和constructor属性是对象所独有的;
② prototype属性是函数所独有的,因为函数也是一种对象,所以函数也拥有proto和constructor属性。
proto属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的proto属性所指向的那个对象(父对象)里找,一直找,直到proto属性的终点null,然后返回undefined,通过proto属性将对象连接起来的这条链路即我们所谓的原型链。
prototype属性的作用就是让该函数所实例化的对象们都可以找到公用的属性和方法,即f1.proto === Foo.prototype。
constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。