JS 原型与原型链 学习笔记和归纳

js 原型与原型链: 原型:
? “原型是一 种对象,被用在中实现继承结构、状态和行为 。当构造函数创建对象时 , 那个对象隐含引用构造函数的关联原型,以此分解属性引用 。通过程序中的表达式 . 可以引用到构造函数的关联原型 , 通过继承,添加给对象的属性 会被所有共享此原型的对象共享 。”----------《规范-第三版_中文版》
首先我们根据规范可以知道,原型是一个具体的对象 。
几条总结:
相关函数与对象的关系图:

JS 原型与原型链 学习笔记和归纳

文章插图
上面的关系图清楚的显示了相关的对象联系 。
原型链:
原型链是一种链式的结构 , 我们都知道在数据结构中,有一种数据结构叫做链表,可以通过指针来寻找到该对象的前面后后面是什么数据 。和链表类似,在js当中,我们也使用了类似于链表的结构来实现相关数据的访问 。
“当谈到继承时 ,  只有一种结构:对象 。每个实例对象()都有一个私有属性(称之为 proto )指向它的构造函数的原型对象( ) 。该原型对象也有一个自己的原型对象( proto ),层层向上直到一个对象的原型对象为 null 。根据定义,null 没有原型,并作为这个原型链中的最后一个环节 。几乎所有中的对象都是位于原型链顶端的的实例 。” ----------《MDN:继承与原型链》
当我们去访问一个对象的属性的时候 , 假如本对象中没有该属性,则会向更高层去寻找,更高层没有继续向更高层去寻找,以此类推 。层层寻找的关系,我们把它称之为原型链 。
原型链查找原理:
当我们去访问一个对象的属性的时候,假如本对象中没有该属性,此时并不会报错或者返回,js会进入到该对象的所指向的对象中找,如果还没有找到,继续进入到现在这个对象所指向的对象中去找,直到找到然后停止在这个链上的查找 。如果没有找到就会抛出一个错误 。
通常情况下我们遇到的是这样的情况:
实例:
JS 原型与原型链 学习笔记和归纳

文章插图
function Animo(name){this.name = name}Animo.prototype.eat = function(){console.log("this is a action for " + this.name)}var tiger = new Animo("tiger")tiger.eat()//this is a action for tiger
我们从这个实例中我们可以看到在tiger实例当中并没有eat这个方法,但是js会去向tiger.所指向的对象中查找 , 发现有eat这个函数,于是就对调用隐式原型里的eat函数 。
实例:
function Animo(name){this.name = name}Animo.prototype = {eat:function(){console.log("this is a action for " + this.name)}}var tiger = new Animo("tiger")tiger.eat()//this is a action for tiger
上面的实例,我们把Animo.存了一个新的对象的地址引用 , 我们在前面说过,当我们声明了一个函数后它就有自身的原型,通过函数的去指向它,虽然是一个对象 。凡是当我们进行上面的赋值操作后,就和原来的对象失去了联系 , 原本我们默认的查找顺序是:tiger-> Animo.->.->null , 这是我们默认的原型链 。但是当我们重新赋值的时候 , 查找的顺序变了:tiger->obj->.->null 。其实这不是我们想要的结果 。假如我们在原型改变之前就有一个实例:
function Animo(name){this.name = name}var tigerA = new Animo("tigerA")Animo.prototype = {eat:function(){console.log("this is a action for " + this.name)}}var tiger = new Animo("tiger")
此时的两个变量是有着完全不同的原型链的 。
的原型链:->Animo.->.->null.
tiger的原型链:tiger->obj->.->null
为什么会出现这样的情况,是因为你在没有改变原型的情况下就已经实例化了一个对象 。那么 , 该对象的存储的就是Animo.里的地址值,也就是默认原型 。当你改变原型的时候,也就是说 , 将Animo.的值改变成了另一个地址值,当你实例化的时候,js会把构造函数中村的原型的地址值给 。但是此时你的的值已经改变了,所以实例化对象的的值就是改变了的值 。
还有一点需要注意,当你重新给构造函数.赋新的对象的时候,一定要带上这个属性,
:
? “返回创建实例对象的构造函数的引用 。注意 , 此属性的值是对函数本身的引用 , 而不是一个包含函数名称的字符串 。” -------《MDN:属性》 。
所有的对象都会从他的原型上继承一个属性 。
var o = {};o.constructor === Object; // truevar a = [];a.constructor === Array; // true
我们手动实例化的对象本身没有这个属性,但是在对象的中有这个属性,该属性指向的是构建实例对象的构造函数 。
以下内容来自MDN文档,他们写的很好 。可以用来做笔记 。
改变函数的:
大多数情况下,此属性用于定义一个构造函数,并使用new和继承原型链进一步调用它 。
function Parent() {}Parent.prototype.parentMethod = function parentMethod() {};function Child() {}//Object.create的第一个参数是创建该函数的原型 。Child.prototype = Object.create(Parent.prototype); // re-define child prototype to Parent prototypeChild.prototype.constructor = Child; // return original constructor to Child
但为什么我们需要在这里执行最后一行?很不幸正确答案是 - 看情况而定 。让我们来尝试定义在哪些情况下,重新分配原始构造函数会发挥重要作用,以及在什么时候它就是额外的未使用的(无效的)代码行 。
试想下一种情况:该对象具有创建自身的方法 。
function Parent() {};function CreatedConstructor() {}CreatedConstructor.prototype = Object.create(Parent.prototype);CreatedConstructor.prototype.create = function create() {return new this.constructor();}new CreatedConstructor().create().create();// error undefined is not a function since constructor === Parent
我们可以来分析一下这段代码的原型链构成:
实例对象->obj->.->.->null 。这里的obj是.创建出来的函数 。现在,函数存在于obj对象中 。当我们new ()时,生成一个实例对象 。当我们执行.()时我们会:
return new this.constructor();//this.constructor()会被执行 , 这个this就是我们生成的实例对象 。//这个实例对象的原型指向的是Prarent.prototype 。我们在上面可以知道 。//constructor的指向的是其原型所对应的构造函数,也就是我们这里的Parent构造函数 。//return new this.constructor() <===> return new Parent()//但是我们很清楚的知道,Parent的prototype里并没有create()这个函数,所以会报错 。最根本原因是因为//construtor指向的是Parent而不是CreatedConstructor,所以说会报错 。
我们来改一下代码:
function Parent() {};function CreatedConstructor() {}CreatedConstructor.prototype = Object.create(Parent.prototype);CreatedConstructor.prototype.constructor = CreatedConstructorCreatedConstructor.prototype.create = function create() {return new this.constructor();}new CreatedConstructor().create().create();
在这里我们将..指向了,而在的原型中有我们想要的函数 。所以我运行该代码的时候不会报错 。
【JS 原型与原型链 学习笔记和归纳】总结:手动设置或更新构造函数可能会导致不同且有时令人困惑的后果 。为了防止它,只需在每个特定情况下定义构造函数的角色 。在大多数情况下,不使用构造函数,并且不需要重新分配构造函数 。