原型
什么是原型?
原型是用来实现面向对象的
原型的概念要早于计算机技术的出现,它指代一个事物最初的模样。我们通过复制原型可以创造出一个新的对象,这个新对象具有和原型相同的特点,但是对其进行的任何修改都不会影响到原型。
在传统的面向对象语言,例如 Java 中,类之间通过继承关系来创建层次结构,子类通过继承从而可以访问父类的属性和方法,于是父类和子类创建的对象可以通过它们的类型得到关联。而 JS 则采用了原型来实现这种类型之间的关联。
在 JS 中,函数虽然也被当做是对象,但它的地位远远高于一般的对象,简单地说,所有的对象都是通过new
一个函数创建的,因此可以简单地将这个函数当做这个对象的类型。为了将这个对象(例如obj
)和函数(例如fn
)关联起来,JS 分别给fn
添加了一个对象属性prototype
,给obj
添加了一个对象属性__proto__
,且obj.__proto__ === fn.prototype
。
虽然现代浏览器都支持使用`__proto__`来访问对象的原型,但实际上这并非官方标准
当我们访问obj.foo
时,如果obj
本身不具有foo
方法,则会从其原型对象obj.__proto__
上找,如果依然没有,则会进一步从obj.__proto__.__proto__
上找,以此类推,直到找到foo
方法或某一个__proto__
为null
时终止,这个就是原型链。这种行为类似 Java 中的继承,访问子类上没有定义的方法时,会一级一级向上查找直到Object
为止。
模拟 new
上面提到我们可以通过复制原型得到一个新对象,在 JS 也提供了这样的 API,即Object.create
。我们可以传入一个对象,JS 会以该对象为原型创造一个新的对象,即将新对象的原型指向传入的对象。借此,我们可以模拟new
的行为,以下是一个示例代码:
function newInst<T extends any[], R>(fn: (...args: T) => R, ...args: T)
: R extends void ? any : R {
const obj = Object.create(fn.prototype);
const r: R = fn.apply(obj, args);
return r ?? obj; // 优先返回构造函数的返回值
}
2
3
4
5
6
Object 与 Function
函数是对象,对象由函数创建
上一节提到,原型链会在__proto__
为null
时终止,为了避免无限递归地查找,诞生了Object
这一特例(Object.prototype.__proto__
为null
)。
当我们创建一个对象时,必然是使用了函数来进行创建(var a = {}
等价于var a = new Object()
),即使这个函数不是Object
,由于函数的prototype
是一个对象,它也一定是通过一个函数创建的,如果我们不手动修改原型链,那么最终一定能找到一个原型对象是由Object
创建的,原型链到此终止。
上面我们提到函数也是对象,所以函数们也需要一个“父类”,那就是Function
。所有的函数都可以理解为是通过Function
创建的,以下三种写法都可以定义一个名为fn
的函数:
function fn() { console.log("hello") }
fn(); // "hello"
var fn = function () { console.log("hello") };
fn(); // "hello"
var fn = new Function("console.log('hello')");
fn(); // "hello"
2
3
4
5
6
7
8
上面提到了“所有的函数都可以理解为是通过Function
创建的”,而Function
也是一个函数,如此便会无限递归下去,所以这里也有特例:
/* 1. Function */
console.log(Function.constructor === Function); // true
console.log(Function.__proto__ === Function.prototype); // true
/* 2. Object */
console.log(Object.constructor === Function); // true
console.log(Object.__proto__ === Function.prototype); // true
/* 3. prototype */
console.log(typeof Function.prototype); // "function"
console.log(Function.prototype.prototype); // undefined
2
3
4
5
6
7
8
9
10
11
至此,在不进行手动修改的情况下,所有的对象(包括函数),它们的原型链最终都会指向Object.prototype
,而所有对象的constructor
链最终都会指向Function
。
原型示例
图示
- 红色方块是对象
- 蓝色方块是函数
fn
的定义:function fn() {}
- 函数和对象的空心实线箭头指向其
__proto__
- 函数的实心实线箭头指向其
prototype
- 函数和对象的空心虚线箭头指向其
constructor