2

【前端芝士树】Javascript的原型、原型链以及继承机制

前端的面试中经常会遇到这个问题,自己也是一直似懂非懂,趁这个机会整理一下

0. 为什么会出现原型和原型链的概念

1994年,网景公司(Netscape)发布了Navigator浏览器0.9版,但是刚开始的Js没有继承机制,更别提像同时期兴盛的C++和Java这样拥有面向对象的概念。在实际的开发过程中,工程师们发现没有继承机制很难解决一些问题,必须有一种机制能将所有的对象关联起来。

Brendan Eich鉴于以上情况,但不想把Js设计得过为复杂,于是引入了new关键词constructor构造函数来简化对象的设计,引入了prototype函数对象来包含所有实例对象的构造函数的属性和方法,引入了proto原型链的概念解决继承的问题。

1. Javscript 函数和函数对象

var o1 = {}; 
var o2 =new Object();
var o3 = new f1();

function f1(){}; 
var f2 = function(){};
var f3 = new Function('str','console.log(str)');
凡是直接或者间接通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。

在上面的程序中,o1、o2、o3都是普通对象,f1、f2、f3都是函数对象。

2. 构造函数

function Person(name, age, job) {
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = function() { alert(this.name) } 
}
var person1 = new Person('Zaxlct', 28, 'Software Engineer');
var person2 = new Person('Mick', 23, 'Doctor');
person1person2 都是 构造函数 Person 的实例。
实例的构造函数属性(constructor)指向构造函数。

3. 原型对象

Person.prototype = {
   name:  'Zaxlct',
   age: 28,
   job: 'Software Engineer',
   sayName: function() {
     alert(this.name);
   }
}

每个函数对象都有一个 prototype 属性,这个属性就是函数的原型对象

每个对象都有 proto 属性,但只有函数对象才有 prototype 属性

clipboard.png

在默认情况下,所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Person)

Person.prototype.constructor == Person

而在Person这个对象进行实例化的时候,实际上是创建了一个它的实例对象并赋值给它的 prototype,所以得出以下结论:

原型对象(Person.prototype)是 构造函数(Person)的一个实例。

4. _proto_

JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象,也就是prototype。

clipboard.png

Person.prototype.constructor == Person;
person1.__proto__ == Person.prototype;
person1.constructor == Person;

看下面一段代码

Person.prototype.__proto__ === Object.prototype;

Person.prototype 是一个普通对象,我们无需关注它有哪些属性,只要记住它是一个普通对象。
因为一个 普通对象的构造函数 === Object
所以 Person.prototype.__proto__ === Object.prototype

5. 原型链

原型链的形成是真正是靠__proto__ 而非prototype。
person1.__proto__ === Person.prototype;
Person.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;
//Object.prototype.__proto__ === null,保证原型链能够正常结束。

Person.__proto__ === Function.prototype;
Object.__proto__ === Function.prototype;
Function.prototype.__proto__ === Object.prototype;

前面三项已经形成了一个原型链,那么后面代码中Person和Object的__proto__都是Function.prototype呢?

所有函数对象的proto都指向Function.prototype,它是一个空函数(Empty function)
Number.__proto__ === Function.prototype  // true
Number.constructor == Function //true

Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true

String.__proto__ === Function.prototype  // true
String.constructor == Function //true

Object.__proto__ === Function.prototype  // true
Object.constructor == Function // true

Function.__proto__ === Function.prototype // true
Function.constructor == Function //true

Array.__proto__ === Function.prototype   // true
Array.constructor == Function //true

RegExp.__proto__ === Function.prototype  // true
RegExp.constructor == Function //true

Error.__proto__ === Function.prototype   // true
Error.constructor == Function //true

Date.__proto__ === Function.prototype    // true
Date.constructor == Function //true
所有的构造器都来自于 Function.prototype,甚至包括根构造器Object及Function自身。所有构造器都继承了·Function.prototype·的属性及方法。如length、call、apply、bind

所以我们再举一个原型链的例子

let num = new Number();
num.__proto__ === Number.prototype;
Number.prototype.__proto__ === Function.prototype;
Funtion.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;

也可看下图的实现,更直观。
clipboard.png

ok, 所以明白为什么Number、String、Array这样的对象实例能继承到Object的属性以及原型链是怎么一回事了吧。

参考文章
最详尽的 JS 原型与原型链终极详解,没有「可能是」。
三张图搞懂JavaScript的原型对象与原型链 - 水乙 - 博客园
Javascript继承机制的设计思想 - 阮一峰的网络日志

云中的猫
769 声望56 粉丝

生活是一个BUG。