浅谈javascript的原型及原型链

 

浅谈javascript的原型及原型链

 

这里,咱们列出原型的几个概念,以下:javascript

  • prototype属性
  • [[prototype]]
  • __proto__

prototype属性

只要建立了一个函数,就会为该函数建立一个prototype属性,指向该函数的原型对象。实例对象是不会拥有该属性的。
默认状况下,该原型对象也会得到一个constructor属性,该属性包含一个指针,指向prototype属性所在的函数。css

Person.prototype.constructor===Person 

[[prototype]]和__proto__

javascript中,不能够访问的内部属性都是用[[propertyName]]这种形式来表示的,好比还有枚举属性[[Enumberable]]。java

[[prototype]]属性只能是对象能够拥有的属性。好比实例化的对象以及原型对象,而不是构造函数。这个属性指向拥有其属性的对象的构造函数的原型对象。注意,此处的构造函数指的是使用new方式的构造函数。并不由于更改了原型对象上的constructor属性而改变。chrome

__proto__是个啥呢,就是对[[propertyName]]的实现。也就是说,你能够在支持该实现的浏览器下(FF,chrome,safari),去访问对象的构造函数的原型对象。好比:segmentfault


var Person=function(name){ this.name=name; }; var p1=new Person(); p1.__proto__===Person.prototype;//true Person.prototype={}; var p2=new Person(); p2.__proto__===Object.prototype;//false

固然,__proto__只是浏览器的私有实现,目前ECMAScript标准实现方法是Object.getPrototypeOf(object)浏览器


var Person=function(name){ this.name=name; }; var p1=new Person(); Object.getPrototypeOf(p1)===Person.prototype;//true Person.prototype={}; var p2=new Person(); Object.getPrototypeOf(p2)===Object.prototype;//false

另一种判断实例对象和其原型对象存在指向关系(由实例的[[prototype]]指向其构造函数的原型对象)的方法是:isPrototypeOf。好比:函数

Person.prototype.isPrototypeOf(p1);//true 

因为原型对象也是一个对象,因此,它天然而然也拥有[[prototype]]属性。性能

 

Javascript 并无类继承模型,而是使用原型对象 prototype 进行原型式继承。
尽管人们常常将此看作是 Javascript 的一个缺点,然而事实上,原型式继承比传统的类继承模型要更增强大。举个例子,在原型式继承顶端构建一个类模型很简单,然而反过来则是个困可贵多的任务。
Javascript 是惟一一个被普遍运用的原型式继承的语言,因此理解两种继承方式的差别是须要时间的。ui

第一个主要差别就是 Javascript 使用原型链来继承:this

function Foo() { this.value = 42; } Foo.prototype = { method: function() {} }; function Bar() {} 

设置 Bar 的 prototype 为 Foo 的对象实例:

Bar.prototype = new Foo(); Bar.prototype.foo = 'Hello World'; 

确保 Bar 的构造函数为自己,并新建一个 Bar 对象实例:

Bar.prototype.constructor = Bar; var test = new Bar(); 

下面咱们来看下整个原型链的组成:

test [instance of Bar] Bar.prototype [instance of Foo] { foo: 'Hello World' } Foo.prototype { method: ... } Object.prototype { toString: ... /* etc. */ } 

在上面的例子中,对象 test 将会同时继承 Bar.prototype 和 Foo.prototype。所以它能够访问定义在 Foo 中的函数 method。固然,它也能够访问属性 value。须要提到的是,当 new Bar() 时并不会建立一个新的 Foo 实例,而是重用它原型对象自带的 Foo 实例。一样,全部的 Bar 实例都共享同一个 value 属性。咱们举例说明:

test1 = new Bar(); test2 = new Bar(); Bar.prototype.value = 41; test1.value //41 test2.value//41 

原型链查找机制

当访问一个对象的属性时,Javascript 会从对象自己开始往上遍历整个原型链,直到找到对应属性为止。若是此时到达了原型链的顶部,也就是上例中的 Object.prototype,仍然未发现须要查找的属性,那么 Javascript 就会返回 undefined 值。

原型对象的属性

尽管原型对象的属性被 Javascript 用来构建原型链,咱们仍然能够值赋给它。可是原始值复制给 prototype 是无效的,如:

function Foo() {} Foo.prototype = 1; // no effect 

这里讲个本篇的题外话,介绍下什么是原始值:
在 Javascript 中,变量能够存放两种类型的值,分别是原始值和引用值。

1.原始值(primitive value):
原始值是固定而简单的值,是存放在栈 stack 中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。
原始类型有如下五种型: Undefined, Null, Boolean, Number, String

2.引用值(reference value):
引用值则是比较大的对象,存放在堆 heap 中的对象,也就是说,存储在变量处的值是一个指针 pointer,指向存储对象的内存处。全部引用类型都集成自 Object

原型链性能问题

若是须要查找的属性位于原型链的上端,那么查找过程对于性能而言无疑会带来负面影响。当在性能要求必要严格的场景中这将是须要重点考虑得因素。此外,试图查找一个不存在属性时将会遍历整个原型链。
一样,当遍历一个对象的属性时,全部在原型链上的属性都将被访问。

总结

理解原型式继承是写较为复杂的 Javascript 代码的前提,同时要注意代码中原型链的高度。当面临性能瓶颈时要学会将原型链拆分开来。此外,要将原型对象 prototype 和原型 __proto__ 区分开来,这里主要讨论原型对象 prototype 就不阐述关于原型 __proto__ 的问题了。

 

图片来自基友 kzloser

图片说明

1.总共三类对象(蓝色大框)

2.实例对象(经过new XX() 所获得的实例),跟原型链相关的只有 __proto__ 属性,指向其对应的原型对象 *.prototype 。

3.构造函数对象分原生和自定义两类。跟原型链相关的有 __proto__ 属性,除此以外还有 prototype 属性。它们的 __proto__ 属性都是指向 Function.prototype 这个原型对象的。prototype 也是指向对应的原型对象。

4.原型对象除了同样拥有 __proto__ 外,也拥有独有的属性 constructor 。它的__proto__ 指向的都是 Object.prototype ,除了 Object.prototype 自己,它本身是指向 null 。而 constructor 属性指向它们对应的构造函数对象。

5.原型链是基于 __proto__ 的。实例只能经过其对应原型对象的 constructor 才能访问到对应的构造函数对象。构造函数只能经过其对应的 prototype 来访问相应的原型对象。

 

原型与原型链是javascript里面最最核心的内容,若是不能理解它们之间的存在关系的话,那么咱们是不能理解这门语言的。

在JS中,主要有两种建立对象的方法, 分别是对象字面量以及调用构造函数

//对象字面量 var obj1 = {} //调用构造函数 var obj2 = new Object()

其实上述两种建立对象的方法,本质上是同样的,都是JS引擎调用对象的构造函数来新建出一个对象。构造函数自己也是一个普通的JS函数

下面咱们来看一个例子

//建立构造函数 function Person(name){ this.name = name } //每一个构造函数JS引擎都会自动添加一个prototype属性,咱们称之为原型,这是一个对象 //每一个由构造函数建立的对象都会共享prototype上面的属性与方法 console.log(typeof Person.prototype) // 'object' //咱们为Person.prototype添加sayName方法 Person.prototype.sayName = function(){ console.log(this.name) } //建立实例 var person1 = new Person('Messi') var person2 = new Person('Suarez') person1.sayName() // 'Messi' person2.sayName() // 'Suarez' person1.sayName === person2.sayName //true

咱们借助上面的例子来理解构造函数-原型-实例,三者之间的关系,主要有几个基本概念

  • 构造函数默认会有一个protoype属性指向它的原型

  • 构造函数的原型会有一个consctructor的属性指向构造函数自己, 即

    Person.prototype.constructor === Person
  • 每个new出来的实例都有一个隐式的__proto__属性,指向它们的构造函数的原型,即

    person1.__proto__ === Person.prototype
    person1.__proto__.constructor === Person

了解了这些基本概念以后,咱们再来看看javascript的一些原生构造函数的关系网,看下列的图


引自stackoverflow

按照咱们上面的理解, Oject自己是一个构造函数,它也是一个对象,那么

Object.__proto__ === Function.prototype

为了方便咱们记住上图,还有几个须要咱们知道的特殊概念:

  • Function的原型属性与Function的原型指向同一个对象. 即

    Function.__proto__ == Function.prototype
  • Object.prototype.__proto__ === null

  • typeof Function.prototype === 'function'

相关文章
相关标签/搜索