Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

构造函数、原型与原型链 #1

Open
bigdots opened this issue Feb 6, 2018 · 0 comments
Open

构造函数、原型与原型链 #1

bigdots opened this issue Feb 6, 2018 · 0 comments
Projects

Comments

@bigdots
Copy link
Owner

bigdots commented Feb 6, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
前端
ECMAScript基础进阶
Development

No branches or pull requests

1 participant