0%

javascript-prototype

Introduction

本来用英语写了一篇,结果看起来乱糟糟的,还是用中文写一下吧。

原型是JavaScript中非常重要的概念,面试中也常常会问到这个问题。那么,原型到底是什么?
原型的英文是prototype,他是函数的一个属性,是一个对象。

下面来看一张图,这个图乍一看非常复杂,但是理解之后,就会发现原型其实非常简单。
js-prototype

在理解这张图之前,我们先明确几个概念:

  1. 对象具有__proto__constructor属性。
  2. 函数具有prototype属性。
  3. 因为JS中函数也是对象,所以函数也有__proto__constructor属性。

所以,我们可以得出以下结论:

  1. 对象具有__proto__constructor属性。
  2. 函数具有prototype, __proto__constructor属性。

这两条结论反映在上面的图中就是:

  1. 所有蓝色节点都向外发出两条线,分别对应__proto__constructor属性。
  2. 所有绿色节点都向外发出三条线,分别对应prototype, __proto__constructor属性。

我们以一个简单的Person函数阐述上面这张图。

1
2
3
4
5
6
7
8
9
10
function Person(name) {
this.name = name;
}

Person.prototype.printName = function () {
console.log(this.name);
};

const p = new Person('Philip');
console.log(p);

__proto__属性

在这个例子中,Person函数是一个构造函数,pPerson的一个实例。为了方便,我们将上图简化一下,只保留__proto__属性。
js-prototype-proto

先看左边纵向的这条线。

  1. 因为对象pPerson函数的一个实例,所以p__proto__属性指向Personprototype属性。
  2. Person继承自Object,所以Person__proto__属性指向Objectprototype属性。
  3. Object__proto__属性指向null

p, Person, Object, null之间通过__proto__属性形成了一条链,这就是所谓的原型链。当我们在某个对象上查找一个属性时,如果该对象没有这个属性,那么JS会通过原型链向上查找,直到找到这个属性或者到达null。举几个例子:

  • p.printName() - p对象上没有printName属性,所以JS会通过原型链向上查找,找到Personprototype属性,从而找到printName方法。
  • p.hasOwnProperty('name') - p对象上没有hasOwnProperty属性,向上查找到PersonPerson的原型对象上也没有hasOwnProperty属性,继续向上查找到ObjectObject原型上有hasOwnProperty属性,于是调用之,所以返回true
  • p.foo() - p对象上没有foo属性,向上查找到PersonPerson的原型对象上也没有foo属性,继续向上查找到ObjectObject的原型上还是没有foo属性,继续向上查找到null,而null上也没有foo属性,所以会报错。— TypeError: p.foo is not a function

再看右侧部分,Person, Function, Object这三者皆是函数,而JS中函数也是对象,故而这三者亦有__proto__属性,而且都指向Functionprototype属性。而Function__proto__属性指向Objectprototype属性。

由此我们可以得出结论:

  1. 所有对象的__proto__属性指向其构造函数的prototype属性。
  2. 所有函数的__proto__属性指向Functionprototype属性。
  3. Function__proto__属性指向Objectprototype属性。
  4. Object__proto__属性指向null

所有__prototype__的终点都是null.

prototype属性

照例现简化一下上图,只保留prototype属性。
js-prototype-prototype

因为prototype是函数的一个属性(对象没有prototype),所以图中只有绿色节点有prototype属性。对于一个函数Foo来说,他的prototype属性是一个对象,指向Foo.prototype

constructor

最后来看constructor属性。
js-prototype-constructor

constructor就是某个对象的构造函数,因为JS中函数也是对象,所以函数也有constructor属性。

  1. 对于普通对象,其constructor属性指向其构造函数。
  2. 对于函数,其constructor属性指向Function
  3. Functionconstructor属性指向其自身。

根据以上结论,不难得出如下结论:

  1. 对象pconstructor属性指向Person函数, 因为pPerson的一个实例。
  2. Person.prototypeconstructor指向Person函数,这个是为啥?- 硬性规定
  3. PersonObjectconstructor都指向Function,因为PersonObject都是函数。
  4. Functionconstructor属性指向Function

由以上结论可以推导出:Function是所有constructor的终点。

总结

我们把上面的结论公式化一下,对于如下代码:

1
2
function Person() {}
const p = new Person();

我们有如下结论成立:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
console.log(p.__proto__ === Person.prototype); // true
console.log(p.constructor === Person); // true

console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Person.prototype.constructor === Person); // true

console.log(Object.prototype.__proto__ === null); // true

console.log(Person.__proto__ === Function.prototype); // true
console.log(Person.constructor === Function); // true

console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Function.prototype.constructor === Function); // true

console.log(Object.__proto__ === Function.prototype); // true
console.log(Object.constructor === Function); // true

console.log(Function.constructor === Function); // true

其他杂项

  1. 箭头函数没有prototype属性,因为箭头函数不可以做构造函数。
  2. Bound函数(使用bind创建的函数)也没有prototype属性。why?

Introduction

prototype is an very important concept in JavaScript. It’s used to implement inheritance in JavaScript.

prototype is a property of a function, it’s an object. When you create a function, JavaScript engine will automatically create a prototype object for you. This object has a property called constructor, which points back to the function.

The prototype data property of a Function instance is used when the function is used as a constructor with the new operator. It will become the new object’s prototype.

Take the following code as an example, its prototype is an empty object {}.

1
2
3
4
function Person(name) {
}
console.log(Person.prototype.constructor === Person); // true
console.log(Person.prototype); // {}

Let’s create a functions on its prototype, and it’s prototype now has a property(function) called printName.

1
2
3
4
5
6
7
8
9
function Person(name) {
this.name = name;
}

Person.prototype.printName = function () {
console.log(this.name);
};

console.log(Person.prototype); // { printName: [Function (anonymous)] }

Create an instance of Person

When you create an instance of Person, the instance will have a property called __proto__, which points to the prototype of the constructor function(the Person function).

1
2
3
4
5
6
7
8
9
10
11
function Person(name) {
this.name = name;
}

Person.prototype.printName = function () {
console.log(this.name);
};

const person = new Person('Philip');
console.log(person.__proto__ === Person.prototype); // true
console.log(person.printName()); // Philip

Functions defined on the prototype are shared by all instances of the constructor function.

1
2
3
4
5
6
7
8
9
10
11
function Person(name) {
this.name = name;
}

Person.prototype.printName = function () {
console.log(this.name);
};

const p1 = new Person('zdd');
const p2 = new Person('Philip');
console.log(p1.printName === p2.printName); // true

But, functions defined in the constructor function are not shared by all instances.

1
2
3
4
5
6
7
8
9
10
function Person(name) {
this.name = name;
this.printName = function () {
console.log(this.name);
};
}

const p1 = new Person('zdd');
const p2 = new Person('Philip');
console.log(p1.printName === p2.printName); // false

References

  1. https://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html
  2. https://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html
  3. https://zhuanlan.zhihu.com/p/65858062