【前端芝士树】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');
person1
和person2
都是 构造函数 Person 的实例。
实例的构造函数属性(constructor)指向构造函数。
3. 原型对象
Person.prototype = {name: 'Zaxlct',age: 28,job: 'Software Engineer',sayName: function() {alert(this.name);}
}
每个函数对象都有一个 prototype
属性,这个属性就是函数的原型对象。
每个对象都有 proto 属性,但只有函数对象才有 prototype 属性
在默认情况下,所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Person)
Person.prototype.constructor == Person
而在Person这个对象进行实例化的时候,实际上是创建了一个它的实例对象并赋值给它的 prototype,所以得出以下结论:
原型对象(Person.prototype)是 构造函数(Person)的一个实例。
4. _proto_
JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象,也就是prototype。
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 //trueBoolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //trueString.__proto__ === Function.prototype // true
String.constructor == Function //trueObject.__proto__ === Function.prototype // true
Object.constructor == Function // trueFunction.__proto__ === Function.prototype // true
Function.constructor == Function //trueArray.__proto__ === Function.prototype // true
Array.constructor == Function //trueRegExp.__proto__ === Function.prototype // true
RegExp.constructor == Function //trueError.__proto__ === Function.prototype // true
Error.constructor == Function //trueDate.__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;
也可看下图的实现,更直观。
ok, 所以明白为什么Number、String、Array这样的对象实例能继承到Object的属性以及原型链是怎么一回事了吧。
参考文章
最详尽的 JS 原型与原型链终极详解,没有「可能是」。
三张图搞懂JavaScript的原型对象与原型链 - 水乙 - 博客园
Javascript继承机制的设计思想 - 阮一峰的网络日志