龙空技术网

JavaScript系列之原型与原型链

半只程序员 269

前言:

而今兄弟们对“js的原型和原型链接是什么”大致比较关切,我们都想要分析一些“js的原型和原型链接是什么”的相关内容。那么小编在网上收集了一些对于“js的原型和原型链接是什么””的相关文章,希望咱们能喜欢,看官们一起来学习一下吧!

喜欢的可以收藏转发加关注

前言

JavaScript 也是一门面向对象的语言,ES6之前并没有引入类(class)的概念,像c++ 这种典型的面向对象语言都是通过类来创建实例对象,而JavaScript是直接通过构造函数来创建实例。

所以理解两种继承模式的差异是需要一定时间的,今天我们就来了解一下原型和原型链,在介绍原型和原型链之前,我们有必要先了解一下构造函数的知识。

构造函数

构造函数模式的目的就是为了创建一个自定义类,并且创建这个类的实例。

构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写。另外就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。我们先使用构造函数创建一个对象:

function Dog() { this.name = '阿黄'}var dog = new Dog()console.log(dog.name) // 阿黄

上面例子中,Dog 就是一个构造函数,我们使用 new 创建了一个实例对象 dog。

原型

prototype

每个函数都有一个特殊的属性叫作原型(prototype),正如下面代码所展示的。

function Dog() { this.name = '阿黄'}console.log(Dog.prototype)

那这个构造函数的 prototype 属性指向的是什么呢?是这个函数的原型吗?

打开 chrome 浏览器的开发者工具,在 console 栏输入上面的代码,你可以看到 Dog.prototype 的值:

其实,函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型。

那什么是原型呢?你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

让我们用一张图来表示构造函数和实例原型之间的关系:

那么我们该怎么表示实例与实例原型,也就是 person 和 Person.prototype 之间的关系呢,接下来就应该讲到第二个属性:

__proto__

这是每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。

为了证明这一点,我们可以在chrome中输入:

function Dog() { this.name = '阿黄'}var dog = new Dog()console.log(Object.getPrototypeOf(dog) === dog.__proto__) // trueconsole.log(dog.__proto__ === Dog.prototype) // true

于是我们更新下关系图:

既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?

constructor

指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。

为了验证这一点,我们在chrome中输入:

function Dog() { this.name = '阿黄'}console.log(Dog.prototype.constructor === Dog) // true

所以再更新下关系图:

综上我们已经得出:

function Dog() { this.name = '阿黄'}var dog = new Dog()console.log(dog.__proto__ == Dog.prototype) // trueconsole.log(Dog.prototype.constructor == Dog) // true// 顺便学习一个ES5的方法,可以获得对象的原型console.log(Object.getPrototypeOf(dog) === Dog.prototype) // true

原型链

在上文我们理解了原型,原型链肯定是与原型有关了,是一个个原型链接起来的么?我们先通过下面的图来观察一下。

解析:

obj.prop1:假设我们现在有一个对象,就称作obj,而这个对象包含一个属性(property),我们称作prop1,现在我们可以使用obj.prop1来读取这个属性的值,就可以直接读取到prop1的属性值了。

obj.prop2:JavaScript中会有一些预设的属性和方法,所有的对象和函数都包含prototype这个属性,假设我们把prototype叫做proto,这时候如果我们使用obj.prop2的时候,JavaScript引擎会先在obj这个对象的属性里去寻找有没有叫作prop2的属性,如果它找不到,这时候它就会再进一步往该对象的proto里面去寻找。所以,虽然我们输入obj.prop2的时候会得到回传值,但实际上这不是obj里面直接的属性名称,而是在obj的proto里面找到的属性名称(即,obj.proto.prop2,但我们不需要这样打)。

obj.prop3:同样地,每一个对象里面都包含一个prototype,包括物件proto本身也不例外,所以,如果输入obj.prop3时,JavaScript会先在obj这个对象里去寻找有没有prop3这个属性名称,找不到时会再往obj的proto去寻找,如果还是找不到时,就再往proto这个对象里面的proto找下去,最后找到后回传属性值给我们(obj.proto.proto.prop3)。

虽然乍看之下,prop3很像是在对象obj里面的属性,但实际上它是在obj → prop → prop的对象里面,而这样从对象本身往proto寻找下去的链我们就称作「原型链(prototype chain)」。这样一直往下找会找到什么时候呢?它会直到某个对象的原型为null为止(也就是不再有原型指向)。

举个例子来帮助理解原型链

让我们实际来看个例子帮助我们了解prototype chain这个概念,千万这个例子只是单纯为了用来说明prototype chain的概念,实际上千万不要使用这样的方式编程!

首先,我们先建立一个对象person 和一个对象john:

var person = {  firstName : 'Default' ,  lastName : 'Default' ,  getFullName : function ( ) {  return this . firstName + ' ' + this . lastName ;  } , } ;var john = {  firstName : 'John' ,  lastName : 'Doe' , } ;

接着,我们知道所有的对象里面都会包含原型(prototype)这个对象,在JavaScript中这个对象的名称为__proto__。如同上述原型链(prototype chain)的概念,如果在原本的对象中找不到指定的属性名称或方法时,就会进一步到__proto__这里面来找。

为了示范,我们来对__proto__做一些事:

//千万不要照着下面这样做,这么做只是为了示范 john . __proto__ = person ;

如此,john这个对象就继承了person对象。在这种情况下,如果我们想要呼叫某个属性或方法,但在原本john这个对象中找不到这个属性名称或方法时,JavaScript引擎就会到__proto__里面去找,所以当接着执行如下的代码时,并不会报错:

console . log ( john . getFullName ( ) ) // John Doe;

我们可以得到"John Doe"的结果。原本在john的这个对象中,是没有getFullName()这个方法的,但由于我让__proto__里面继承了person这个对象,所以当JavaScript引擎在john对象里面找不到getFullName()这个方法时,它便会到__proto__里面去找,最后它找到了,于是它回传"John Doe"的结果。

如果我是执行:

console . log ( john . firstName ) ; // John

我们会得到的是John而不是'Default',因为JavaScript引擎在寻找john.firstName这个属性时,在john这个对象里就可以找到了,因此它不会在往__proto__里面找。这也就是刚刚在上面所的原型链(prototype chain)的概念,一旦它在上层的部分找到该属性或方法时,就不会在往下层的prototype去寻找。

在了解了prototype chain这样的概念后,让我们接着看下面这段代码:

var jane = {  firstName : 'Jane' }jane . __proto__ = person ; console . log ( jane . getFullName ( ) ) ;

现在,你可以理解到会输出什么结果吗?

答案是"Jane Default" 。

因为在jane这个对象里只有firstName这个属性,所以当JavaScript引擎要寻找getFullName()这个方法和lastName这个属性时,它都会去找__proto__里面,而这里面找到的就是一开始建立的person这个对象的内容。

全代码如下:

var person = {  firstName : 'Default' ,  lastName : 'Default' ,  getFullName : function ( ) {  return this . firstName + ' ' + this . lastName ;  } }var john = {  firstName : 'John' ,  lastName : 'Doe' }//千万不要照着下面这样做,这么做只是为了示范 john . __proto__ = person ; console . log ( john . getFullName ( ) ) ; // John Doe console . log ( john . firstName ) ; // Johnvar jane = {  firstName : 'Jane' }jane . __proto__ = person ; console . log ( jane . getFullName ( ) ) ;

以上就是目前能总结的全部了,肯定还是有缺陷的地方,后续还会修改完善的。

学习前端的伙伴可以私信回复小编“前端”领取全套免费前端学习资料、

标签: #js的原型和原型链接是什么 #javascript原型链 #原型链