bigdots / blog

个人博客

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

构造函数、原型与原型链

bigdots opened this issue · comments

commented

image

构造函数

ECMAScript 中提供了构造函数来创建新对象。但构造函数本身就是一个函数,与普通函数没有任何区别,只不过为了区分,一般将其首字母大写,但这并不是必须的。

函数被 new 关键字调用时就是构造函数。

function f(name) {
    console.log("execute");
    this.name = name;
}

var k = new f("k"); // execute
console.log(k); // {name: "k"}
var h = f("h"); // execute
console.log(h); // undefined

从上面代码可以看出:

  • 首字母是否大写并不影响函数 f 作为构造函数使用,
  • 不使用 new 调用函数就是普通函数,直接执行内部代码,使用 new,函数的角色就成为了构造函数,创建一个对象并返回。

对象由构造函数通过 new 关键字创造,那么是如何创造的呢?

new 关键字的内部实现机制:

  • 创建一个新对象;
  • 将构造函数的作用域赋值给新对象;
  • 执行构造函数中的代码;
  • 返回新对象
var obj = {}; // 创建一个空对象
obj.__proto__ = constructor.prototype; // 添加__proto__属性,并指向构造函数的 prototype 属性。
constructor.call(this); // 绑定this
return obj;

prototype

每一个函数都有一个 prototype 属性。

function Foo() {}

Foo.prototype; // {constructor,__proto__}

无论什么时候,只要创建了一个新函数,根据一组特定的规则为该函数创建一个 prototype 属性,这个属性指向函数的原型对象。

那么这个创建的原型对象是什么呢?

{
    constructor: ƒ Foo(),
    __proto__: Object
}

constructor 属性

每一个原型对象都有一个 constructor 属性

创建了自定义的构造函数后,其原型对象只会默认取得 constructor 属性。这个属性解决了对象识别问题,即可以通过该属性判断出实例是由哪个构造函数创建的。

Foo.prototype.constructor === Foo; //  true

前面说了,原型对象只会默认取得 constructor 属性,那么原型对象的其他属性(比如:__proto__ )是这么来的呢,这就要说到 __proto__ 指针了。

__proto__

每一个实例都有一个 __proto__ 指针,指向构造函数的原型对象。

var foo = new Foo();
foo.__proto__ === Foo.prototype; //true

上面提到的构造函数的原型对象它本身也是一个实例,所以在它内部会有一个 __proto__ 指针。

想要知道构造函数的原型对象是由什么创建的吗?

活学活用,这里我们可以使用刚刚学到对象指向问题判断的知识。我们想要知道 constructor,那么可以通过实例的原型对象(因为每一个原型对象都有一个 constructor 属性),所以我们可以这样操作:

Foo.prototype.__proto__.constructor // ƒ Object() { [native code] }

这说明,构造函数的原型的原型是由 Object 生成的!!所以,构造函数原型对象的其他方法,则是从 Object 上继承来的。

Foo.prototype.__proto__ === Object.prototype; // true

原型链

原型链的理论主要基于上述提到的构造函数、实例和原型的关系:

  • 每一个构造函数都有一个原型对象
  • 原型对象都包含一个指向构造函数的 constructor 属性
  • 每一个实例都包含一个指向原型对象的 __proto__ 指针

其中最最重要的是第三条,依赖这条关系,层层递进,就形成了实例与原型的链条。

我接着上面的探索,构造函数的原型的原型是由 Object 生成的,那么 Object 的原型是由什么生成?而原型链的终点又是在哪?

Object.prototype.__proto__ // null
null.__proto__; // Uncaught TypeError: Cannot read property '__proto__' of null
// game over

原型的终点是 null,因为 null 没有 proto 属性。

哇,感觉这很符合创世纪的故事啊—— “初始,一切且不存在(null)”。

tips: 其实 null 是基本数据类型,typeof null 返回 object 是 ECMAScript 设计上的一个错误

最后,一图胜千言:
image