JavaScript:原型链和原型对象

1. 前言

原型链和原型对象一直是JavaScript中的一个重要的概念,因为涉及到面向对象。

在现在的高级语言中,比如JavaC#C++都采用了面向对象的设计方式,正因如此,JavaScript并没有类的概念,但是JavaScript却大量的使用了对象,而为了保持对象之间的联系,JavaScript引入了原型和原型链的概念。

即使在ES6后添加了class关键字,但它其实是一种语法糖,它的实现原理依然是通过原型链和原型对象。

在现在的ReactVue中,已经大量的使用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

  1. 每一个构造函数都拥有一个prototype属性,这个属性指向一个对象,也就是原型对象。当使用这个构造函数创建实例的时候,prototype属性指向的原型对象就成为实例的原型对象。

    function Parsen() {
    }
    
    Parsen.prototype.run = function () {
      console.log('在运动');
    };
    let p = new Parsen();
    console.log(Parsen.prototype === p.__proto__); 	//true
  2. 原型对象默认拥有一个constructor属性,指向指向它的那个构造函数(也就是说构造函数和原型对象是互相指向的关系)。

    function Parsen() {
    }
    
    Parsen.prototype.run = function () {
      console.log('在运动');
    };
    let p = new Parsen();
    console.log(Parsen.prototype.constructor === Parsen); //true
  3. 每个对象都拥有一个隐藏的属性[[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
  4. 实际上,构造函数的prototype属性与它创建的实例对象的[[prototype]]属性指向的是同一个对象,即 对象.__proto__ === 函数.prototype

  5. 如上文所述,原型对象就是用来存放实例中共有的那部分属性

  6. JavaScript中,所有的对象都是由它的原型对象继承而来,反之,所有的对象都可以作为原型对象存在。

  7. 访问对象的属性时,JavaScript会首先在对象自身的属性内查找,若没有找到,则会跳转到该对象的原型对象中查找。

3. 原型链

了解了什么是原型对象后,我们就可以进一步学习原型链。

因为原型对象自身也是一个对象,它也有自己的原型对象,这样层层上溯,就形成了一个类似链表的结构,这就是原型链(prototype chain)

通过下面的图中,我们可以很清晰的观测到整个原型链。

20200303132302223

注意:原型链的终点都是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关键字做了下面的几件事情:

  1. 创建一个临时对象。
  2. 给临时对象绑定原型。
  3. 给临时对象对应的属性赋值。
  4. prototype对象的方法的this指向实例对象。
  5. 将临时对象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 协议 ,转载请注明出处!