原型跟原型链,原型和原型链的理解
面试八股文,可以说是必问的一道题,也是很多前端包括之前的我,也是稀里糊涂的,不知所以然。经过我多日的研究,终于肝明白了,废话不多说,直接上图。
现在看到这张图是不是头大,因为网上很多这种图,我刚开始的时候也是走马观花,直接看麻了。主要是需要自己动手体验一下这种过程,你才能明白这图意义何在。Constructor 构造函数构造函数应该不陌生吧(构造函数一般为了区别普通函数要求首字母大写),我们先来下一个构造函数function Person(){}p
相信很多的前端对这个概念都不陌生吧。
面试八股文,可以说是必问的一道题,也是很多前端包括之前的我,也是稀里糊涂的,不知所以然。经过我多日的研究,终于肝明白了,废话不多说,直接上图。
现在看到这张图是不是头大,因为网上很多这种图,我刚开始的时候也是走马观花,直接看麻了。主要是需要自己动手体验一下这种过程,你才能明白这图意义何在。
Constructor 构造函数构造函数应该不陌生吧(构造函数一般为了区别普通函数要求首字母大写),我们先来下一个构造函数
function Person(){}prototype 原型什么是原型?原型其实就是一个对象,实例“继承”那个对象的属性,通过“继承”的这种方式,new出来的实例也有了这个属性。(“继承”这个过程是在new内部实现的)
先来说一下原型和构造函数的关系,从下图可以看到构造函数内部有一个prototype的属性,通过这个属性就能访问到原型。
let obj = {}obj.__proto__.haha = 'gogo'console.log(obj.haha) // "gogo"
实例对象通过_proto_就能访问到原型的属性(等会会讲到实例访问原型)。
也就是Person是构造函数,Person.prototype就是原型,那么由此可见,就得到了下面的关系图。
instance 实例
有了构造函数,我们就可以直接在原型上创建可以“继承”的属性,并通过new来操作它。
由此,我们又得到了下图↓
proto 隐式原型
上面也说了通过_proto_可以直接访问到原型,所以如果是实例,那么就可以直接通过这个属性访问到原型,由此,我们又可以得到下面的关系图。
既然构造函数通过 prototype 来访问到原型,那么原型也应该能够通过某种途径访问到构造函数,于是乎又得到了下图。
原型链
原型同样也可以通过 __proto__ 访问到原型的原型,比方说这里有个构造函数 Person 然后“继承”前者的有一个构造函数 People,然后 new People 得到实例 p
当访问 p 中的一个非自有属性的时候,就会通过 __proto__ 作为桥梁连接起来的一系列原型、原型的原型、原型的原型的原型直到 Object 构造函数为止。
这个搜索的过程形成的链状关系就是原型链
看到 null 了吗,原型链搜索搜到 null 为止,搜不到那访问的这个属性就是不存在的:
以上就是原型与原型链、构造函数、实例之间的联系,其实并不复杂。如果你理解了这一步,你就可以去了解prototype为何会出现?真正的构造函数属性藏在哪里?
原型和原型链是什么?
原型和原型链是用线条图形描绘出的产品框架,也称线框图。原型链通俗易懂的理解就是可以把它想象成一个链条,互相连接构成一整串链子,而原型链中就是实例对象和原型对象之间的链接,每个函数都有一个prototype属性,这个prototype属性就是我们的原型对象。
原型链的特点
原型链通过隐式原型把一些构造函数层层的串起来,因为所有的对象都是继承自Object,原型是一个对象,并且只有函数有prototype,prototype对象中有一个constructor属性,指向了这个函数本身。
当访问一个对象属性或方法的时候,首先会在它本身属性上查找,如果没有找到就在它的proto隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的proto中查找,这样一层一层向上查找会形成一个链式结构称为原型链。
原型与原型链详解
与大部分面向对象语言不同,ES6之前中并没有引入类(class)的概念,JavaScript并非通过类而是直接通过构造函数来创建实例。在介绍原型和原型链之前,我们有必要先复习一下构造函数的知识。
构造函数模式的目的就是为了创建一个自定义类,并且创建这个类的实例。构造函数模式中拥有了类和实例的概念,并且实例和实例之间是相互独立的,即实例识别。
构造函数就是一个普通的函数,创建方式和普通函数没有区别, 不同的是构造函数习惯上首字母大写 。另外就是调用方式的不同,普通函数是直接调用, 而构造函数需要使用new关键字来调用 。
每创建一个Person构造函数,在Person构造函数中,为每一个对象都添加了一个sayName方法,也就是说构造函数每执行一次就会创建一个新的sayName方法。这样就导致了构造函数执行一次就会创建一个新的方法,执行10000次就会创建10000个新的方法,而10000个方法都是一摸一样的,为什么不把这个方法单独放到一个地方,并让所有的实例都可以访问到呢?这就需要原型( prototype )
在JavaScript中,每当定义一个函数数据类型(普通函数、类)时候,都会天生自带一个 prototype 属性,这个属性指向函数的原型对象,并且这个属性是一个对象数据类型的值。
让我们用一张图表示构造函数和实例原型之间的关系:
原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
每一个对象数据类型(普通的对象、实例、 prototype ......)也天生自带一个属性 __proto__ ,属性值是当前实例所属类的原型( prototype )。原型对象中有一个属性 constructor , 它指向函数对象。
在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链 。
举例说明:person → Person → Object ,普通人继承人类,人类继承对象类
当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined。
我们可以使用对象的 hasOwnProperty() 来检查对象自身中是否含有该属性;使用 in 检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
person实例中没有a这个属性,从 person 对象中找不到 a 属性就会从 person 的原型也就是 person.__proto__ ,也就是 Person.prototype中查找,很幸运地得到a的值为123。那假如 person.__proto__ 中也没有该属性,又该如何查找?
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层Object为止。 Object是JS中所有对象数据类型的基类(最顶层的类)在Object.prototype上没有 __proto__ 这个属性。
文章评论