JS原型与原型链

吐槽君 分类:javascript

本篇主要从以下几点进行展开说明:

  1. 什么是原型
  2. 为什么需要原型
  3. 什么是原型链
  4. 鸡生蛋,蛋生鸡问题

什么是原型

在JS中原型分为显式原型隐式原型

显示原型:每个函数都有一个prototype属性,在定义函数的时候自动添加,在不做修改的时候,它默认指向一个对象,这个对象只有一个constructor对象,这个constructor对象又指向函数本身。

隐式原型:每个对象都有一个__proto__属性,在对象创建的时候自动添加,他默认指向的是其构造函数的显示原型prototype

示例:

function test() {};
const obj = new test();
console.log(test.prototype); //{constructor: ƒ}
console.log(obj.__proto__);//{constructor: ƒ}
console.log(obj.__proto__ === test.prototype); // true
console.log(test.prototype.constructor === test); // true

 

为什么需要原型

JS是面向(基于)对象编程,那么面向对象的三大原则是封装,继承,和多态。在Java语言中,通过class关键字进行定义一个类,通过extend关键字进行继承实现复用。举一个简单的例子:

人和狗都是哺乳动物,有一些相同的行为,比如我们都要睡觉,都要吃饭,都要繁衍后代等行为。那么在Java中就会将这些相同的行为添加到一个公共的类中去,这里暂定类名是哺乳动物类,然后在创建一个与一个的类。因为我们都属于哺乳动物,刚好有一个哺乳动物类,我们只需要继承它,我们内部就不需要写吃饭睡觉等公共方法,进行了复用。也进行了数据共享。

但是在JS中,我们假如没有原型的话,当我生成一个的实例和的实例就必须给这两个实例的构造函数中添加吃饭睡觉等方法,不能进行复用。这也是JS设计之父在设计的时候想到的,但是他不想JAVA那么麻烦专门去搞一个extend关键字去继承实现复用,因为当时的JS只是用作简单交互,并不会有太多复杂的逻辑,所以选择了间接的通过prototype以及来实现复用。

举例:

function animal() {};
const commMethods = {
  eat: () => {
    console.log('我每天都要干饭');
  },
  sleep: () => {
    console.log('我每天都要睡觉');
  },
};
animal.prototype = commMethods;
const people = new animal();
const dog = new animal();
console.log(people, dog);
 

WechatIMG93

什么是原型链

我们从上面已经知道,JS的继承复用是通过prototype实现的,在什么是原型小节我们说了隐式引用,当对象被创建的时候,有个__proto__指向他的构造函数的原型,这样相当于我对象也可以从这个原型上继承和复用属性或方法,那么当我这个对象的__proto__上没有我所找的属性的时候,我就一直找我这个对象的__proto____proto__,直到找到顶层,要是还未找到,就终止查找并返回undefined

鸡生蛋,蛋生鸡问题

在阐述鸡蛋问题前,需要一些前置知识,

  1. 原型链的顶端Object.prototype,所有的对象都从Object.prototype上继承属性或方法。
  2. Function.prototype Function.__proto__指向同一个内置对象。
console.log(Object.prototype.__proto__) // 在这里Object指的是构造函数。构造函数的原型是一个对象,对象可以通过__proto__进行查找他的构造函数的原型 所以这个返回的是null。
console.log(Function.prototype === Function.__proto__); // true 
 

在JS中万物皆对象,包括Functon。对象,又是通过构造函数实例化出来的。函数呢,又是一个对象(万物皆对象)。所以就有以下关系:

// Array, Object等函数对象是通过Function实例化出来的
console.log(Array.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true
console.log(String.__proto__ === Function.prototype); // true,
// 函数又是一个对象
/*
	ES规范中是这么阐述的:
		Function本身就是函数,Function.__proto__是标准的内置对象Function.prototype。

		Function.prototype.__proto__是标准的内置对象Object.prototype。
*/
console.log(Function.prototype.__proto___ === Object.prototype);
 

根据以上的关系,我们可以得到下面这个公式:

console.log(Object instanceof Function) // true
console.log(Function instanceof Object) // true
 

上面的公式就是著名的鸡生蛋,蛋生鸡问题。其实仔细捋一捋会发现,

  1. Object.prototype是所有原型链的顶端,所以Object.prototype是所有对象的爷爷,
  2. Function继承于他,也就是Function.prototype.__proto__ === Object.prototype,
  3. 对象又是通过构造函数生成的Object.__proto__ === Function.prototype
  4. 因此不存在鸡生蛋,蛋生鸡的问题,他的原型链应该是Object ---> Function.prototype ---> Object.prototype

回复

我来回复
  • 暂无回复内容