Skip to content
索引

原型与原型链

创建对象的方法

在了解原型链之前,先了解一下创建对象的几种方式,介绍以下三种

  • 字面量
  • 构造函数
  • Object.create
js
// 第一种方式:字面量
var o1 = { name: "o1" };
var o2 = new Object({ name: "o2" });
// 第二种方式:构造函数
var M = function (name) {
  this.name = name;
};
var o3 = new M("o3");
// 第三种方式:Object.create
var p = { name: "p" };
var o4 = Object.create(p);

console.log(o1);
console.log(o2);
console.log(o3);
console.log(o4)
// 第一种方式:字面量
var o1 = { name: "o1" };
var o2 = new Object({ name: "o2" });
// 第二种方式:构造函数
var M = function (name) {
  this.name = name;
};
var o3 = new M("o3");
// 第三种方式:Object.create
var p = { name: "p" };
var o4 = Object.create(p);

console.log(o1);
console.log(o2);
console.log(o3);
console.log(o4)

什么是实例?原型对象?

js
var M = function (name) { this.name = name; }
var o3 = new M('o3')
var M = function (name) { this.name = name; }
var o3 = new M('o3')
  • 实例就是对象,在本例中o3就是实例,M就是构造函数
  • 实例通过new一个构造函数生成的
  • 原型对象的construor指向的是构造函数

什么是构造函数?

通过 new 函数名 来实例化对象的函数叫构造函数。任何的函数都可以作为构造函数存在。构造函数首字母一般大写

js
function Person(name , age , sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
var person = new Person("Tony" , 18 , "男");
console.log(person.name);  // Tony
function Person(name , age , sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
}
var person = new Person("Tony" , 18 , "男");
console.log(person.name);  // Tony

上面这段代码就是创建一个了Person 的构造函数,在Person 构造函数中,为每一个对象都添加了三个属性(name,age,sex),也就是说构造函数每执行一次就会创建一个新的Person对象

构造函数的__proto__

构造函数本身也是一个函数对象,是一个js对象,是通过 Function构造器产生的

js
Person.__proto__ === Function.prototype
Person.__proto__ === Function.prototype

什么是原型对象?

原型对象本身是一个普通对象,而普通对象的构造函数都是Object

js
// 构造函数的原型对象
Person.prototype.__proto__ === Object.prototype
// 构造函数的原型对象
Person.prototype.__proto__ === Object.prototype

prototype

在JS中,每当定义一个函数时候,都会默认自带一个prototype属性,这个属性指向的是该构造函数创建的原型对象,并且这个属性是一个对象数据类型的值

原型:每一个JS对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。

举个原型的Demo:

js
function Person() { }
Person.prototype.name = 'Tony';  // 注意:prototype是函数才会有的属性
var person1 = new Person();
var person2 = new Person();
console.log(person1.name);  // Tony
console.log(person2.name);  // Tony
function Person() { }
Person.prototype.name = 'Tony';  // 注意:prototype是函数才会有的属性
var person1 = new Person();
var person2 = new Person();
console.log(person1.name);  // Tony
console.log(person2.name);  // Tony

Person.prototype表示实例原型。原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中

什么是原型链?

简单理解就是原型组成的链,对象的__proto__属性指向它的原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto__向上找,这就是原型链,当向上找找到Object的原型的时候,这条原型链就算到头了

原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法

为什么原型链的终点是null,而不是Object.prototype

首先要明确一点,原型链上的所有节点都是对象,所以终点不能是字符串、数字、布尔值等原始类型

另外,规范要求原型链必须是有限长度的(从任一节点出发,经过有限步骤后必须到达一个终点,显然也不能有环)

那么,应该用什么对象作为终点呢?很显然应该用一个特殊的对象

Object.prototype确实是个特殊对象,我们先假设用它做终点。那么考虑一下,当你取它的原型时应该怎么办?

js
Object.prototype.__proto__
Object.prototype.__proto__

应该返回什么?

取一个对象的属性时,可能发生三种情况:

  1. 如果属性存在,那么返回属性的值
  2. 如果属性不存在,那么返回undefined
  3. 不管属性存在还是不存在,有可能抛异常

我们已经假设Object.prototype是终点了,所以看起来不能是情况1。另外,抛出异常也不是好的设计,所以也不是情况3。那么情况2呢,它不存在原型属性,返回undefined怎么样?也不好,因为返回undefined一种解释是原型不存在,但是也相当于原型就是undefined。这样,在原型链上就会存在一个非对象的值

所以,最佳选择就是null。一方面,你没法访问null的属性,所以起到了终止原型链的作用;另一方面,null在某种意义上也是一种对象,即空对象,因为null一开始就是为表示一个“空”的对象存在的。这样一来,就不会违反“原型链上只能有对象”的约定。

所以,“原型链的终点是null”虽然不是必须不可的,但是却是最合理的

__proto__

每个JS对象都具有__proto__属性,这个属性指向该对象的原型对象

该对象的构造函数的prototype属性也指向该对象的原型对象

所以也可以这么说,每个对象的__proto__指向它构造函数的prototype

js
person1.__proto__ === Person.prototype
person1.__proto__ === Person.prototype

构造函数是一个函数对象,也是一个js对象,是通过 Function构造器产生的

js
Person.__proto__ === Function.prototype
Person.__proto__ === Function.prototype

__proto__ 绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于Object.prototype,与其说是一个属性,不如说是一个getter/setter,当使用obj.__proto__ 时,可以理解成返回了Object.getPrototypeOf(obj)

prototype

只有函数对象有prototype属性,对象是没有prototype属性的,因为函数对象也是对象,函数也有__proto__属性

constructor

每个原型对象都有一个constructor属性指向关联的构造函数

js
Person === Person.prototype.constructor  // true
Person === Person.prototype.constructor  // true

当获取person.constructor时,其实person中并没有constructor属性,当不能读取到constructor属性时,会从 person的原型也就是Person.prototype中读取,正好原型中有该属性,所以:

js
person.constructor === Person.prototype.constructor // true
person.constructor === Person.prototype.constructor // true

原型链:在JS中,万物皆对象,对象和对象之间也是有关系的,并不是孤立存在的。对象之间的继承关系,在JS 中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,专业术语称之为原型链

实例和原型

当我们读取实例的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,一直找到最顶层Object为止,Object对象的原型没有原型(Object是JS中所有对象数据类型的基类(最顶层的类)在Object.prototype上没有__proto__这个属性),如果在Object原型中依然没有找到,则返回undefined。

function Person() {} Person.prototype.name = 'Tony'; var person = new Person();

// 当我们给实例对象person添加了name属性,打印 person.name 时,结果为name的值Ken。 person.name = 'Ken'; console.log(person.name) // Ken

// 当我们删除了person的name属性时,再次读取person.name,从person 对象中找不到name属性就会从person的原型也就是person.__proto__和Person.prototype中查找name属性,因为之前我们给他添加了,所以找到了 name属性为 Tony。 delete person.name; console.log(person.name) // Tony 我们可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性,如果自身属性存在,则返回 true,否则为false;使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,则会返回true,如果都没有则返回false。

function Person() { this.name = 'Tony' }

Person.prototype.age = 18;

var person = new Person();

console.log(person.hasOwnProperty('name')); // true console.log(person.hasOwnProperty('age')); // false

console.log('name' in person); // true console.log('age' in person); // true console.log('a' in person); // false

最后,相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线:

用心读完上面的总结,应该会对 JS 的原型与原型链有了一个深层次的认识吧,其实这部分还是需要细心琢磨的,毕竟是比较底层的原理

实例、构造函数、原型对象之间的关系

  • 实例是对象
  • 实例通过new一个构造函数生成的
  • 实例的__proto__属性指向原型对象(又称是实例原型)
  • 构造函数是函数,也是函数对象,也是对象
  • 构造函数的prototype属性指向原型对象
  • 原型对象的construor指向的是构造函数

instanceof原理

instanceof是判断实例对象的__proto__和生成该实例的构造函数的prototype是不是引用的同一个地址,即比较原型对象的地址是否相同

是返回true,否返回false

new运算符

原理:

  • 一个新对象被创建。它继承自foo.prototype
  • 构造函数返回一个对象。在执行的时候,相应的传参会被传入,同时上下文(this)会被指定为这个新的实例。
  • new foo等同于new foo(), 只能用在不传递任何参数的情况
  • 如果构造函数反悔了一个对象,那个这个对象会取代整个new出来的结果。如果构造函数没有返回对象,那个new出来的结果为步骤1创建的对象

实现:

js
var new2 = function (func) {
    var o = Object.create(func.prototype);    //创建对象
    var k = func.call(o);             //改变this指向,把结果赋给k
    if (typeof k === 'object') {         //判断k的类型是不是对象
        return k;                  //是,返回k
    } else {
        return o;                  //不是返回返回构造函数的执行结果
    }
}   
var new2 = function (func) {
    var o = Object.create(func.prototype);    //创建对象
    var k = func.call(o);             //改变this指向,把结果赋给k
    if (typeof k === 'object') {         //判断k的类型是不是对象
        return k;                  //是,返回k
    } else {
        return o;                  //不是返回返回构造函数的执行结果
    }
}   

Function与Object

js
console.log(Function.__proto__ === Object.prototype); // false
console.log(Object.__proto__ === Object.prototype); // false

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


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

console.log(Function.prototype.__proto__.__proto__); // null
console.log(Function.__proto__.__proto__.__proto__); // null
console.log(Object.prototype.__proto__); // null
console.log(Object.__proto__.__proto__.__proto__); // null

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

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


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

console.log(Function.prototype.__proto__.__proto__); // null
console.log(Function.__proto__.__proto__.__proto__); // null
console.log(Object.prototype.__proto__); // null
console.log(Object.__proto__.__proto__.__proto__); // null

console.log(Object.constructor === Function.__proto__.constructor); // true
console.log(Function.__proto__.constructor === Function); // true
  1. Object.__proto__ === Function.prototype说明FunctionObject的构造函数
  2. Function.__proto__ === Function.prototype说明Function既是原型对象,同时自己是自己的构造函数( Function.constructor === Function),既是鸡也是蛋,就是这么牛掰
  3. ObjectFunction.__proto__Function.prototype指向的原型对象的构造函数

根源对象null

原型链最上层(第一层)的根源对象是null,并且它们彼此间也是完全相等的,说明根源对象null是同一个

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

第二层原型对象

顾名思义,即原型链最上层(第一层)的根源对象null下面一层的原型对象

第二层的原型对象的__proto__指向最上层(第一层)的根源对象null

表示方法

  • Function.prototype.__proto__
  • Function.__proto__.__proto__
  • Object.prototype
  • Object.__proto__.__proto__

因为Function.prototype.prototypeObject.prototype.prototype是undefined,说明第二层的原型对象不是构造函数

因为Function.__proto__.__proto__Object.prototype Object.prototype完全相等,说明第二层的原型对象是同一个

因为Object.__proto__.__proto__ === Object.prototype,说明Object是第二层原型对象的构造函数

总结

Person.__proto__ === Function.prototype

函数也是对象,所以构造函数也是对象,所以构造函数也有__proto__

函数的构造函数是 Function

它的__proto__属性指向的是Function.prototype

Person.prototype.__proto__ === Object.prototype

js
// 因为函数也是对象,即构造函数也是对象,所以构造函数也有__proto__属性
// 函数(构造函数)的构造函数是Function
// 它的__proto__属性指向的是Function.prototype
// 那么这就导致了,与Object是实例对象的构造函数,这条规律冲突了
console.log(Person.__proto__ === Function.prototype); // true
// 因为函数也是对象,即构造函数也是对象,所以构造函数也有__proto__属性
// 函数(构造函数)的构造函数是Function
// 它的__proto__属性指向的是Function.prototype
// 那么这就导致了,与Object是实例对象的构造函数,这条规律冲突了
console.log(Person.__proto__ === Function.prototype); // true

下面作出总结:

  • 一切的函数对象和 Object 对象,继承自 Function 对象

  • 一切对象都是继承自Object对象,Object 对象直接继承根源对象null

  • Object 对象直接继承自 Function 对象

  • Function对象的__proto__会指向自己的原型对象,最终还是继承自Object对象

既然 Function instanceof Object === true,为什么Function.__proto__ !== Object.prototype ?

ƒ ()

Function和Object原型对象相同

js
console.log(Function.__proto__ === Object.__proto__); // true
console.log(Function.__proto__); // 控制台输出ƒ () { [native code] }
console.log(Object.__proto__); // 控制台输出ƒ () { [native code] }
console.log(Function.__proto__ === Object.__proto__); // true
console.log(Function.__proto__); // 控制台输出ƒ () { [native code] }
console.log(Object.__proto__); // 控制台输出ƒ () { [native code] }

ƒ () { [native code] }是V8内部C++代码返回的结果,而不是真实的JS代码结果

第 1710 行附近的 FunctionSourceString 函数:

js
return 'function () { [native code] }';
return 'function () { [native code] }';

Released under the MIT License.