浅谈javascript的原型及原型链
浅谈javascript的原型及原型链
这里,我们列出原型的几个概念,如下:
- prototype属性
- [[prototype]]
__proto__
prototype属性
只要创建了一个函数,就会为该函数创建一个
prototype属性,指向该函数的原型对象。实例对象是不会拥有该属性的。
默认情况下,该
原型对象也会获得一个
constructor属性,该属性包含一个指针,指向
prototype属性所在的函数。
Person.prototype.constructor===Person
[[prototype]]和__proto__
javascript中,不可以访问的内部属性都是用
[[propertyName]]这种形式来表示的,比如还有枚举属性[[Enumberable]]。
[[prototype]]属性只能是对象可以拥有的属性。比如实例化的对象以及
原型对象,而不是构造函数。这个属性指向拥有其属性的对象的构造函数的原型对象。注意,此处的
构造函数指的是使用
new方式的构造函数。并不因为更改了原型对象上的
constructor属性而改变。
__proto__是个啥呢,就是对
[[propertyName]]的实现。也就是说,你可以在支持该实现的浏览器下(FF,chrome,safari),去访问对象的构造函数的原型对象。比如:
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是唯一一个被广泛运用的原型式继承的语言,所以理解两种继承方式的差异是需要时间的。
第一个主要差异就是
Javascript使用原型链来继承:
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的一些原生构造函数的关系网,看下列的图
按照我们上面的理解, Oject本身是一个构造函数,它也是一个对象,那么
Object.__proto__ === Function.prototype
为了方便我们记住上图,还有几个需要我们知道的特殊概念:
-
Function
的原型属性与Function
的原型指向同一个对象. 即Function.__proto__ == Function.prototype
-
Object.prototype.__proto__ === null
-
typeof Function.prototype === 'function'
- [转]浅谈 JavaScript的原型对象与原型链
- 浅谈javascript的原型及原型链
- Javascript原型和原型链
- 学习javascript面向对象 理解javascript原型和原型链
- 一步一步掌握Javascript中的原型与原型链
- 浅谈JavaScript中的原型模式
- 深入理解JavaScript系列(5):强大的原型和原型链
- 理解javascript中的原型和原型链
- JavaScript中的原型和原型链
- JavaScript探秘:强大的原型和原型链
- 深入理解JavaScript系列(5):强大的原型和原型链
- javascript的原型与原型链
- JavaScript原型对象、原型属性、原型链
- JavaScript的原型与原型链
- Javascript 作用域 闭包 原型和原型链
- 学习javascript面向对象 理解javascript原型和原型链
- 深入理解JavaScript系列(5):强大的原型和原型链
- 深入理解JavaScript系列(5):强大的原型和原型链
- Javascript 原型和原型链
- 深入理解JavaScript系列(5):强大的原型和原型链