前言
js作为一门的编程语言 ,但是并没有"子类"和"父类"的概念,也没有"类"(class)和"实例"(instance)的区分。 对此,我一直很疑惑。 所以这一次下定决心打算好好总结一下相关的知识点,为自己解惑 。
Javascript中面向对象的设计
面向对象的语言有一个标志(如java,Object-C等),那就是它们都有类(class)的概念,通过类可以创建任意多个具有相同属性和方法的对象。并且类是对象的抽象,而对象则是类的实例。
但是在Javascript中, 却没有类这种抽象, 对象可以直接定义它自己的行为。即Javascript中没有类(class)这种东西 ,Javascript里仅有对象。
它摒弃了'类'这个概念,转而专注于对象本身。 对于一个对象来说重要的是它能做什么,而不是它来自哪里 。
只要理解了其面向对象的设计的核心,知道对象的生成再也不需要经过类这个间接层。那么我想对于Javascript中许多'怪异'的行为和问题,我们也就迎刃而解了。
原型
如上所述,在Javascript中对象可以直接定义它自己的行为。这样的话,就会造成如下问题 :
- 每个对象都是相对独立的个体。对象之间没有可共享的属性和方法
- 没有类这一概念,也就不能基于类来实现继承。 需要另一种新的模式来实现继承。
所以需要一种机制来把对象联系起来。 于是引入了原型这个概念。
Javascript中每一个对象都有有且仅有一个与之相关联的对象,我们称其为原型. 并且对象可以从其原型对象中继承属性和方法。
注:所以原型的本质只是一个对象而已
对象在进行属性查询时,s会先查看其自有属性(own property
, 如果自有属性中没有则会从其原型的属性进行查询。
__proto __
在Javascript中每一对象都有一个 __proto __属性,它包含对指定对象的内部原型的引用。即 __proto __就是对象原型的引用。
虽然很多浏览器都为每个对象提供了这个proto属性, 但是这个属性并不属于标准,因此一般不提倡使用。
不过 在ES5之后,提供了一个getPrototypeOf()
方法,使用该方法我们可以获取对象的原型对象 。 而且在ES6中还提供了setPrototypeOf()
方法可以直接修改一个对象的原型对象。 (推荐使用以上方法来获取或者修改原型。)
prototype
在 JavaScript 中,prototype 是函数的一个属性。它主要在函数用作构造函数时使用。并且它有一个默认属性constructor
指向构造函数本身。
prototype的默认结构如下:
// Fun代表任意一个(构造)函数。
Fun.prototype ={constructor:Fun}
下图也比较详细的说明了prototype与构造函数的关系。
那么给函数对象添加这个prototype属性的目的是什么呢? 个人认为可以从以下三方面解释:
- 方便统一设置(所有)实例的原型对象。
- 建立原型与构造函数之间的联系 。
- 实现了基于原型的继承和属性共享。
下面我用伪代码来模拟一下prototype和实例的关系。
// Fun 是一个构造函数。f是它的一个实例
let f = new Fun()
=> // 等价于
{
var obj = {} // 创建一个新对象
Fun.call(obj) // 调用构造函数Fun来初始化对象的状态
// 设置对象的原型对象
obj.__proto__= Fun.prototype
// 或 obj.setPrototypeOf( Fun.prototype )
return obj // 返回新的对象
}
由此可知,构造函数的prototype(对象)便是其实例的原型。通过修改 prototype的属性的值便可以为每个实例设置一个指定的原型。
如图:
关于__proto__
的指向
对象的__proto__
的指向取决于对象的创建方式。
- 如若对象是由对象直接量创建的, 那么其原型便是Object.prototype
var obj = {name:'xiaoli'}
obj.__proto__=== Object.prototype // true
- 如若对象是由构造函数创建的, 那么其原型指向构造函数的prototype(对象)。
function () Fun () {}
var f = new Fun()
f.__proto__=== Fun.prototype // true
- 如若对象是由Object.create()方法创建的, 那么其原型则取决于传递的参数。
var prototype = {name:'li'}
var obj = Object.create(prototype)
obj.__proto__=== prototype //true
我们可以用一张图来表示:
原型链与继承
在基于类的语言中,对象是类的实例,并且类可以从另一个类继承。 Javascript是一门基于原型的语言,这意味着对象可以直接从对象继承 。
如上文所提到的,原型也是一个对象,所以它也有__proto__
这个属性指向它自身的原型, 即原型也有自己的原型。 这并不难于理解。
如果我们让一个对象的原型指向一个实例。 那么这个实例的原型便是该对象的原型的原型。 假设该对象的原型的原型中有一个该对象没有的属性, 我们可以通过原型链来访问到这个属性。
// 构造函数
function Student (name){
this.name = name
}
// 修改默认的prototype
var newPrototype = {class: '1303班'}
Student.prototype= newPrototype
Student.prototype.constructor= Student
// 创建一个实例
var s1= new Student('李明')
s1.class // 1303班
其中class并不是s1的“自有属性”(own property), 但是我们依然能访问到class 属性的值。
概念: 假如一个实例的原型是另一个实例,并且另一个实例的原型也有一个包含对原型的引用指向另一个构造函数的实例。 如此,层层递进(直到原型为null为止), 就构成了实例和原型的链条,这就是所谓的原型链的基本概念。
其结构如下图:
利用原型链,我们可以实现基于原型的继承。 对象可以从它的原型(或原型链上的原型对象)继承属性。
注:基于原型的继承只是JavaScript中实现继承机制的方式之一
总结
对于以上叙述我总结几个关键点:
-
__proto__
是每个对象都有的属性。 -
prototype
是函数对象才有的属性(一般只用于构造函数中),同时也是其实例的原型。 - 原型也是一个对象,它也有自己的原型对象。
- Object.prototype的原型是null, 同时也是原型链的终点。
花了几天时间才写完这篇博客, 也许再某些方面写的不是太好。 但也只能如此了。 希望大家多多包涵。