我对原型的理解

181 阅读5分钟

本文旨在用自己的理解形象化的解释原型,以强化自己的印象

同时提供给与自己脑回路相同的人参考

漫谈原型

说到原型,那先要谈谈为什么要去理解它。

然而事实上,在ES6中,去理解它已经变得不是那么重要,因为通过语法糖class,创建类的写法与Java等语言的写法已经非常相似了。

但是,毕竟作为程序员,还是有必要对其一探究竟的。

原型源于JS对于对象的实现方式是被称为基于原型的面向对象的方式。 即JS的对象都是基于原型创建的。

prototype与constructor与__proto__

在JS中创建类离不开的prototype与constructor 下面是创建一个类的简单例子

// constructor
function Cat(name) {
  this.name = name;
}
// prototype
Cat.prototype.shout = function() {
  console.log('喵');
}
// 实例化
var cat1 = new Cat('小花');

可以看到创建一个类用到了constrctorprototype,实例化用到了new,共三个关键字。

我不会尝试在这里去解释这些关键字的准确意义。

我更希望把constructor称为制造对象的机器,(这里解释和使用的不准确,但不需要深究,便于理解)

把prototype称为制造对象的模板

把__proto__称为对象的原材料

把new称为生产

Function与Object

学习原型的难点在于Function与Object的关系。 大家经常会看见的一张图是

原型关系图

这张图对于熟悉原型的人来说是一种知识的梳理,但对于初学者而言那就不一定了。


下面我会用我自己的理解来解释Funciton和Object

上面提到我将constructor称为机器,prototype称为模板,通过new生产出一个新的产品。

Q1: 原材料的原材料是什么?

A: 书的原材料是纸,纸的原材料是木头,木头原材料是无机物,这样一层层找下去,一定能找到个源头,在物理世界这个源头可能是夸克。

那在JS中这个源头是什么呢,答案是null,哈哈,正所谓,道生一,一生二,二生三,三生万物。从null中诞生出来的那个原子核就是Object.prototype

用代码表示即为Object.prototype.__proto__ === null, 同时得出cat1.__proto__.__proto__...__proto__ === null

Q2: prototype和__proto__的关系?

A: 模板就是产品的原型,虽然拗口,也好理解。

book.__proto__ === Book.prototype // 真实的写法,左边的表述为产品的原型,右边为机器的模板,说明模板就是产品的原型
// 相当于
book.__proto__ === BookPrototype // BookPrototype就是定义的一个普通对象,意在说明模板其实就是一个普通对象

我希望在这里停顿一下,在这里可以引申出来一个JS中非常重要的概念--原型链

原型链是什么?就是我们写的这个**proto.proto.proto**这个串。

原型链有个很重要的性质,即每个对象不仅可以访问自身的属性还可以访问原型链上的属性。

道理很简单,比如说我是书,书可以承载知识,书由木头制造,书就可以燃烧,书就有木头所有的一切物理属性。


Q1: 如何理解book.__proto__ === Book.prototype

A: 我们可以这么理解。

var prototype = {};
function Book() {}
Book.prototype = prototype;

var book = new Book();

Q2: prototype与constructor的关系?

A: prototype有用,constructor在日常使用中几乎没用。因此我不想探讨他们之间的关系。

我们需要知道的是以下几个例子,从中也可以看出机器的模板机器(创建者)之间的关系。

Object.prototype.constructor === Object
Function.prototype.constructor === Function
// 与之相对的
function Cat() {}
Cat.prototype.constructor === Function // 自己创建的对象是不存在指向自身的情况
// ES6
class Cat {}
Cat.prototype.constructor === Cat // ES6的语法糖帮助你做了一步操作: Cat.prototype.constructor = Cat

实际上,所有关于原型的基础知识就这些,重点是区分清楚__proto与prototype,区分prototype与XXX.prototype。

原型知识拼图的最后一块在于制造机器的机器Function,以及生产材料之母Object


var obj = new Object(); // 以Object.prototype为模板制造一个产品

obj.__proto__ === Object.prototype // 因此这个产品的原材料为基础原材料Object.prototype

obj.constructor === obj.__proto__.constructor === Object // 自己没有这个属性,所以去原型链上找,那么Object.prototype.constructor === Object

obj.constructor.__proto__ === Object.__proto__ === Function.prototype // 因为机器是机器制造出来的(function 关键字),所以机器的原材料就是制造机器的机器(Function)的模板

obj.constructor.__proto__.__proto__ === Function.prototype__.proto__ === Object.prototype === obj.__proto__ // 机器的模板的原材料是基础原材料
// 这个式子其实说明了一个很好玩的事,即生产(生产产品的机器)的机器的原材料和生产产品的原材料是同一个原材料,似乎也不是那么难理解,不管你是机器还是产品,只要是追溯源头,都是原子,不是嘛

// 所有有以下几个恒等式
Function.__proto__ === Function.prototype // 因为机器也是机器生产出来的,所以机器的原材料和机器的模板相同,即机器之母自我复制
Object.__proto__.__proto__.constructor === Object // 因为Object是材料之母,所以制造它的原材料(制作机器的原材料)的原材料,也就是基础原材料是供自己使用的
Object.__proto__.constructor === Function // 材料之母由机器之母诞生
Function.__proto__.__proto__.constructor === Object // 机器之母原材料由材料之母提供
Function.__proto__.__proto__.constructor.__proto__.constructor === Function // 由上面两个式子推出

// 自可以在chrome控制台尝试哦,这是循环的,永远点不完。

怎么使用这些知识?

  1. 最有用的自然是理解原型链,即自身找不到的属性会在原型链中一次寻找,直到null
  2. 其次是自己创建类时对于new关键字和class关键字的理解
  3. 加深对于constructor和prototype的理解,更好的使用,prototype是所有对象均可使用的,constructor只有对象自身可用,在实例化时动态创建。

因此你知道类的静态方法定义在哪了吗?

很显然,定义在constructor和prototype上的方法和属性只有实例化后的对象拥有,静态方法应该定义在function上。

function Foo() {}
Foo.eat = function() { console.log('eat') }

Foo.eat() // 静态方法