JavaScript:原型链和原型对象
1. 前言
原型链和原型对象一直是JavaScript
中的一个重要的概念,因为涉及到面向对象。
在现在的高级语言中,比如Java
,C#
,C++
都采用了面向对象的设计方式,正因如此,JavaScript
并没有类的概念,但是JavaScript
却大量的使用了对象,而为了保持对象之间的联系,JavaScript
引入了原型和原型链的概念。
即使在ES6
后添加了class
关键字,但它其实是一种语法糖,它的实现原理依然是通过原型链和原型对象。
在现在的React
和Vue
中,已经大量的使用ES6
的语法,所以ES6
语法必须要进行学习。
2. 原型对象
在初步学习JavaScript
原型和原型链的概念时,很容易不知所云,这个时候只有反复研究,再去查阅相关的资料,因为原型和原型链是JavaScript
中非常重要的一个概念。
在JavaScript
中,对象本质上就是一个函数,在一般的书写中,对象的声明一般首字母要使用大写,例如:function Person()
。而对象的实例化则需要使用到关键字new
,例如:let person = new Person();
。
至于new
关键字到底做了什么,我们下文中会着重讲解。
2.1 __proto__
对象.__proto__
:实例的原型对象。
对象.__proto__ === 函数.prototype
。
例子:
function Person() {
}
let person = new Person(); // 实例化对象
console.log(person.__proto__ === Person.prototype); // true
2.2 原型prototype
每一个构造函数都拥有一个
prototype
属性,这个属性指向一个对象,也就是原型对象。当使用这个构造函数创建实例的时候,prototype
属性指向的原型对象就成为实例的原型对象。function Parsen() { } Parsen.prototype.run = function () { console.log('在运动'); }; let p = new Parsen(); console.log(Parsen.prototype === p.__proto__); //true
原型对象默认拥有一个
constructor
属性,指向指向它的那个构造函数(也就是说构造函数和原型对象是互相指向的关系)。function Parsen() { } Parsen.prototype.run = function () { console.log('在运动'); }; let p = new Parsen(); console.log(Parsen.prototype.constructor === Parsen); //true
每个对象都拥有一个隐藏的属性
[[prototype]]
,指向它的原型对象,这个属性可以通过Object.getPrototypeOf(obj)
或obj.__proto__
来访问。function Parsen() { } Parsen.prototype.run = function () { console.log('在运动'); }; let p = new Parsen(); console.log(Object.getPrototypeOf(p) === p.__proto__); //true console.log(Object.getPrototypeOf(p) === Parsen.prototype); //true
实际上,构造函数的
prototype
属性与它创建的实例对象的[[prototype]]
属性指向的是同一个对象,即对象.__proto__ === 函数.prototype
。如上文所述,原型对象就是用来存放实例中共有的那部分属性。
在
JavaScript
中,所有的对象都是由它的原型对象继承而来,反之,所有的对象都可以作为原型对象存在。访问对象的属性时,
JavaScript
会首先在对象自身的属性内查找,若没有找到,则会跳转到该对象的原型对象中查找。
3. 原型链
了解了什么是原型对象后,我们就可以进一步学习原型链。
因为原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链(prototype chain)。
通过下面的图中,我们可以很清晰的观测到整个原型链。
注意:原型链的终点都是Object
函数的prototype
属性,因为在JavaScript
中的对象都默认由Object()
构造。Objec.prototype
指向的原型对象同样拥有原型,不过它的原型是null
,而null
则没有原型。
例子:
function Parsen() {}
Parsen.prototype.run = function() {
console.log("在运动");
};
console.log(Parsen.prototype.__proto__ === Object.prototype); //true
4. new关键字
可以看到,在实例化对象的时候我们使用了new
关键字,即:let person = new Person();
,那么这个new
关键字到底起了什么样的作用呢?
其实new
关键字做了下面的几件事情:
- 创建一个临时对象。
- 给临时对象绑定原型。
- 给临时对象对应的属性赋值。
prototype
对象的方法的this
指向实例对象。- 将临时对象
return
。
function Fun() {
//new做的事情
var obj = {};
obj.__proto__ = Fun.prototype; // Base为构造函数
obj.name = 'Damonare';
... // 一系列赋值以及更多的事
return obj;
}
//例子
function Fun1() {
this.name = 'Damonre';
this.age = 21;
this.sex = 'man';
this.run = function () {
return this.name + this.age + this.sex + '正在跑步';
};
}
//可以改写为
function Fun2() {
var obj = {}; // 创建一个临时对象。
obj.__proto__ = Fun2.prototype; // 给临时对象绑定原型。
obj.name = 'Damonre'; // 给临时对象绑定原型。
obj.age = 21;
obj.sex = 'man';
return obj; // 将临时对象return。
}
Fun2.prototype.run = function () {
return this.name + this.age + this.sex + '正在跑步'; // prototype对象的方法的this指向实例对象。
};
console.log(Fun2().run());
注意:**new
关键字本质上是语法糖**。
5. 最后
了解了原型和原型链后,我们接下来就可以正式进入JavaScript
面向对象的学习。
面向对象的三个基本特征:封装、继承、多态。
而关于JavaScript
的继承,有很多种方式,这些就留到下一篇文章再进行归纳整理。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!