【前端基础】一文搞懂JS原型与原型链

381 阅读6分钟

一、对象

话说在Javascript中一切皆对象,对象是引用类型的一个实例,在JS中引用类型包括:Object、Array、Date、RegExp和Function,除此之外,ECMAScript还提供3个特殊的引用类型:Boolean、Number、String。

如前所述,对象是某个特定引用类型的实例,新对象是通过new操作符后跟一个构造函数来创建的,构造函数本身就是一个函数,只不过该函数是出于创建对象的目的而出现的。

举个栗子:

console.log(typeof Object);//function
console.log(typeof Array);//function
console.log(typeof Date);// function
console.log(typeof RegExp);//function

var obj = new Object();
var array = new Array();
var date = new Date();
var reg = new RegExp();

console.log(typeof obj);//Object
console.log(typeof array);//Object
console.log(typeof date);//Object
console.log(typeof reg);//Object

二、Function类型

要说ECMAScript中什么最有意思,那莫过于函数了,而有意思的根源在于——函数实际上也是对象,可称为函数对象。 每个函数都是Function类型的实例,而且与其他引用类型一样具有属性和方法。由于函数是对象,因此函数名实际上也是一个指向函数对象的指针

function fn(params){console.log(params)}
//等价于
var fn = new Function('params','console.log(params)')

与普通对象不同的是:

console.log(typeof Function);//function
console.log(typeof fn); //function

三、原型对象

在第一节说过,构造函数可以用来创建特定类型的对象,像Object、Array这样的原生函数,运行时会自动出现在执行环境中。我们也可以创建自定义的构造函数,从而自定义对象:

function Person(){
  this.name = name;
  this.age = age;
  this.sayName = function(){
   console.log(this.name)
 }
}
var person = new Person('Jim',30);

构造函数创建对象必须通过new操作符来进行,用这种方式创建对象会经历以下四个过程:

(1)创建一个新对象;

(2)将构造函数的作用域赋给新对象(让this指向新对象);

(3)执行构造函数中的代码(为新对象添加属性);

(4)返回新对象;

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数(构造函数本身)的指针:

Person.prototype.constructor == Person

创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性;至于其他方法,则都是从Object继承而来的。当调用构造函数new一个实例(对象)后,该实例内部将包含一个指针(内部属性_proto_),指向构造函数的原型对象, 这个连接存在于实例和构造函数的原型对象之间,而不是实例和构造函数之间:

person.__proto__ == Person.prototype

之前在一篇博文中发现这样的写法:

person.constructor == Person.prototype

其实实例本身并没有constructor属性,而是实例通过__proto__指向了Person.prototype,而constructor属性是Person.prototype上的属性,指向构造函数本身。

原型对象其实就是一个普通的对象,但是Function.prototype除外,它是一个函数对象,但它很特殊,没有prototype属性前面说道函数对象都有prototype属性))

function Person(){};
 console.log(Person.prototype) //{constructor:f}具有constructor属性的对象
 console.log(typeof Person.prototype) //Object
 console.log(typeof Function.prototype) // Function,这个特殊
 console.log(typeof Object.prototype) // Object
 console.log(typeof Function.prototype.prototype) //undefined

四、原型链

原型链是实现继承的主要方法,其基本思想是:利用原型(prototype)让一个引用类型继承另一个引用类型的属性和方法。

回顾一下构造函数、原型和实例的关系:

(1)每个构造函数都有一个原型对象;

(2)原型对象都包含一个指向构造函数的指针(constructor);

(3)实例都包含一个指向原型对象的内部指针(proto

如第三节所说,JS 在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__ 的内置属性,用于指向创建它的构造函数的原型对象。
对象 person 有一个 __proto__属性,创建它的构造函数是 Person,构造函数的原型对象是 Person.prototype ,所以:
person.proto == Person.prototype;

如果我们让原型对象等于另一个对象的实例,结果会怎样呢?此时的原型对象将包含一个指向另一个原型对象的指针,相应的另一个原型对象中也包含着另一个构造函数的指针。

即就是:

function Person1(){};
function Person2(){};
var person1 = new Person1();
var person2 = new Person2();

person1.__proto__ == Person1.prototype;
Person1.prototype.constructor == Person1;

person2.__proto__ == Person2.prototype;
Person2.prototype.constructor == Person2;

//如果
Person2.prototype = new Person1();
//那么
Person2.prototype.__proto__ == Person1.prototype
Person1.prototype.constructor == Person1

假如另一个原型又是另一个类型的实例那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。

然鹅,我们都知道,所有的引用类型默认都继承了Object,这个继承也是通过原型链实现的。大家要记住,所有函数的默认原型都是Object的实例,原型对象就是一个普通对象,一个普通对象的构造函数就是Object。因此默认原型都会包含一个内部指针,指向Object.prototype, 所以最终的原型链

person.__proto__ == Person.prototype;
Perosn.prototype.__proto__ == Object.prototype;
Object.prototype.__proto__ === null

五、再来说说Function

回头看看第一节, 对象是使用 new 操作符后跟一个构造函数来创建的,构造器不仅仅有 Object,也可以是 Array,Date,Function等,既然他们都是函数,辣么它们的__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

// 所有的构造器都来自于Function.prototype,包括根构造器Object及Function自身
Object.__proto__ === Function.prototype  // true
Object.constructor == Function // true

// 所有的构造器都来自于Function.prototype,包括根构造器Object及Function自身
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true

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

插一句: JavaScript中有内置(build-in)构造器/对象共计12个(ES5中新加了JSON),剩下如Global不能直接访问,Arguments仅在函数调用时由JS引擎创建,Math,JSON是以对象形式存在的,无需new。它们的proto是Object.prototype。

上面的情况说明了神马?

所有的构造器都来自于Function.prototype,甚至包括根构造器ObjectFunction自身。故所有构造器都继承了·Function.prototype·的属性及方法。如length、call、apply、bind**

Function.prototype也是唯一一个typeoffunctionprototype。其它的构造器的prototype都是一个对象。

下载.jfif

知道了所有构造器(含内置及自定义)的`__proto__`都是Function.prototype,那Function.prototype的__proto__是谁呢?

相信都听说过JavaScript中一切皆对象,函数也是对象,从哪能体现呢:

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

这说明所有的构造器也都是一个普通 JS 对象,可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等。

最后Object.prototype的proto是谁?

Object.prototype.__proto__ === null // true 

相信到此大家对于对象、构造函数、原型、原型链、继承都有了比较明确的了解了,再也不用怕各种原型刁难了!!!