一、出现的原因
JavaScript是一个面向对象语言,即一切皆对象。
那么怎么生成对象?在Java的世界里,对象是由类(Class)实例出来的,通俗地说,就是将事物抽象成一个模具,用这个模具(类)生产出一个个具体的实物(对象)。
可是JS中没有类这个概念,有的是“原型”,对象是由原型衍生出来的。通俗地说,在JS的世界里,“原型”并不是一个模具,而是一个具体的实物(对象)。所有对象都是由另一个对象衍生出来的,而这个被衍生的对象就是所谓的“原型对象”。
JavaScript 中除了基础类型外的数据类型,都是对象(引用类型)。正是因为其没有 类(class,ES6 引入了 class,但其只是语法糖)的概念,如何将所有对象联系起来就成立一个问题,于是就有了原型和原型链的概念。
二、基本概念
1.基本概念
- js分为函数对象和普通对象,每个对象都有
__proto__属性(隐式原型),但是只有函数对象才有prototype属性(显式原型)。 - Object、Function都是js内置的函数, 类似的还有我们常用到的Array、RegExp、Date、Boolean、Number、String
- 属性
__proto__是一个对象,它有两个属性,constructor和__proto__; - 原型对象
prototype有一个默认的constructor属性,用于记录实例是由哪个构造函数创建,因为是对象自然也有__proto__属性
原型对象:每创建一个函数,该函数会自动带有一个prototype属性,该属性是一个指针,指向了一个对象,我们称之为原型对象。
补充理解:
- 实例对象a只有__proto__(隐式原型),构造函数既有
__proto__(隐式原型)也有prototype(显式原型) __proto__和 prototype 都是一个对象,既然是对象,就表示他们也有一个__proto__- 实例对象a的隐式原型指向它构造函数的显式原型,指向的意思是恒等于
- 当调用某种方法或查找某种属性时,首先会在自身调用和查找,如果自身并没有该属性或方法,则会去它的
__proto__属性中调用查找,也就是它构造函数的prototype中调用查找。
原型链:通过__proto__向上进行查找,最终到null结束。
补充理解:
- 查找属性,如果本身没有,则会去
__proto__中查找,也就是构造函数的显式原型中查找,如果构造函数的显式原型中也没有该属性,因为构造函数的显式原型也是对象,也有__proto__,那么会去它的显式原型中查找,一直到null,如果没有则返回undefined - 通过
__proto__形成原型链而非protrotype
2.准则
//有以下构造函数Person,他的原型上有所属国属性motherland='China'
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.motherland = 'China'
//通过new Person()创建的person01实例
let person01 = new Person('小明', 18);
js之父在设计js原型、原型链的时候遵从以下两个准则
- 准则1:原型对象(即Person.prototype)的
constructor指向构造函数本身,即Person.prototype.constructor == Person - 准则2:实例对象(即person01)的
__proto__指向它的构造函数的原型对象(prototype ),即person01.__proto__ == Person.prototype
3.原型/原型链的关系
(1)整体图
每个实例对象( object )都有一个私有属性(称之为
__proto__ )指向它的构造函数的原型对象(prototyp:e )。该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。
(2)经典的原型链相等图
// 从上方 function Foo() 开始分析这一张经典之图
function Foo()
let f1 = new Foo();
let f2 = new Foo();
f1.__proto__ = Foo.prototype; // 准则2
f2.__proto__ = Foo.prototype; // 准则2
Foo.prototype.__proto__ = Object.prototype; // 准则2 (Foo.prototype本质也是普通对象,可适用准则2)
Object.prototype.__proto__ = null; // 原型链到此停止
Foo.prototype.constructor = Foo; // 准则1
Foo.__proto__ = Function.prototype; // 准则2
Function.prototype.__proto__ = Object.prototype; // 准则2 (Function.prototype本质也是普通对象,可适用准则2)
Object.prototype.__proto__ = null; // 原型链到此停止
// **此处注意Foo 和 Function的区别, Foo是 Function的实例**
// 从中间 Function Object()开始分析这一张经典之图
function Object()
let o1 = new Object();
let o2 = new Object();
o1.__proto__ = Object.prototype; // 准则2
o2.__proto__ = Object.prototype; // 准则2
Object.prototype.__proto__ = null; // 原型链到此停止
Object.prototype.constructor = Object; // 准则1
Object.__proto__ = Function.prototype // 准则2 (Object本质也是函数);
// 此处有点绕,Object本质是函数,Function本质是对象
Function.prototype.__proto__ = Object.prototype; // 准则2 (Function.prototype本质也是普通对象,可适用准则2)
Object.prototype.__proto__ = null; // 原型链到此停止
// 从下方 Function Function()开始分析这一张经典之图
function Function()
Function.__proto__ = Function.prototype // 准则2
Function.prototype.constructor = Function; // 准则1
以上一整个原型与原型层层相链接的过程即为原型链
此可以得出结论: 除了Object的原型对象(Object.prototype)的__proto__指向null,其他内置函数对象的原型对象(例如:Array.prototype)和自定义构造函数的 __proto__都指向Object.prototype, 因为原型对象本身是普通对象。 即:
Object.prototype.__proto__ = null;
Array.prototype.__proto__ = Object.prototype;
Foo.prototype.__proto__ = Object.prototype;
4.自引用设计
Function 构造函数的原型关系是 JavaScript 原型体系中一个非常特殊的「自引用」设计。
Function.__proto__ = Function.prototype
Function.prototype.constructor = Function
先回顾原型链的两个基础规则:
- 规则 1:任何原型对象(
prototype)的constructor属性,默认指向其对应的构造函数。例如:Person.prototype.constructor === Person(Person是自定义构造函数)。 - 规则 2:任何对象(包括函数对象)的
__proto__属性,指向其「原型对象」(即创建它的构造函数的prototype)。例如:const p = new Person(),则p.__proto__ === Person.prototype。
原因
为什么 Function 会有这种「自引用」?
Function 是 JavaScript 中所有函数的「根构造函数」—— 所有函数(包括普通函数、箭头函数、内置函数如 Object、Array 等)本质上都是通过 Function 构造函数创建的(或等价于其创建的实例)。
这种特殊性导致了 Function 的原型链出现了「自循环」:
-
Function.prototype.constructor = Function(符合规则 1)Function.prototype是Function构造函数的原型对象,根据规则 1,它的constructor属性自然指向其对应的构造函数Function。这和普通构造函数的逻辑一致,例如Person.prototype.constructor = Person。 -
Function.__proto__ = Function.prototype(看似打破规则,实则特殊设计) 根据规则 2,一个对象的__proto__应指向创建它的构造函数的prototype。但Function本身也是一个函数(函数是特殊的对象),那么谁是创建Function的构造函数?答案是:Function自己。
因为 Function 是「根构造函数」,没有更上层的构造函数来创建它,所以 JavaScript 设计为:Function 是由自身创建的实例。因此:
Function 的 __proto__ 指向其构造函数(自身)的 prototype,即 Function.__proto__ = Function.prototype。
用通俗的话解释:
Function是一个函数,同时也是一个构造函数。- 作为构造函数,它的原型对象是
Function.prototype,所以Function.prototype.constructor指向Function(规则 1)。 - 作为一个函数对象,它必须有一个原型(
__proto__),但没有其他构造函数能创建它,所以它的原型只能指向自己的prototype(Function.prototype),形成自引用。
验证与扩展
所有函数的 __proto__ 都指向 Function.prototype(因为所有函数都是 Function 的实例):
function fn() {}
console.log(fn.__proto__ === Function.prototype); // true
console.log(Object.__proto__ === Function.prototype); // true(Object 也是函数)
而 Function 自身的 __proto__ 也指向 Function.prototype,形成了一个闭环:
console.log(Function.__proto__ === Function.prototype); // true
这种设计是 JavaScript 原型体系的底层特殊逻辑,目的是保证所有函数的原型链最终能统一指向 Function.prototype,同时避免「谁创造了根构造函数」的无限循环问题。
三、意义
原型对象的作用,是用来存放实例中共有的那部份属性、方法,可以大大减少内存消耗。
eg:
//有以下构造函数Person,他的原型上有所属国属性motherland='China'
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.motherland = 'China'
//通过new Person()创建的person01,person02实例
let person01 = new Person('小明', 18);
let person02 = new Person('小花', 20);
console.log(person01)
console.log(person02)
打印person01, 他有自己属性 name = '小明',age = 18; 同时通过原型链关系,他有属性motherland = 'China';
打印person02, 他有自己属性 name = '小花',age = 20; 同时通过原型链关系,他有属性motherland = 'China';
原型对象存放了person01、person02共有的属性所属国motherland = 'China'. 我们不用在每个实例上添加motherland 属性,而是将这一属性存在他们的构造函数原型对象上。
当相同的属性、方法越多,原型、原型链的意义越大。 eg:
Person.prototype.hairColor = 'black';
Person.prototype.eat = function(){
console.log('We usually eat three meals a day.')
}
console.log(person01)
console.log(person02)
此时我们再打印person01、person02,我们惊喜的发现,他们有了属性hairColor和eat方法;实例们动态的获得了Person构造函数之后添加的属性、方法,这是就是原型、原型链的意义所在!可以动态获取,可以节省内存。
注意:如果person01将头发染成了黄色,那么hairColor会是什么呢?
person01,hairColor = 'yellow';
console.log(person01)
console.log(person02)
复制代码
可以看到,person01的hairColor = 'yellow', 而person02的hairColor = 'black'; 实例对象重写原型上继承的属相、方法,相当于“属性覆盖、属性屏蔽”,这一操作不会改变原型上的属性、方法,自然也不会改变由统一构造函数创建的其他实例,只有修改原型对象上的属性、方法,才能改变其他实例通过原型链获得的属性、方法。
总结
构造函数、原型对象、实例对象三者分别是什么?
- 构造函数:是用来创建对象的函数,通过
new关键字来声明。 - 原型对象:每一个函数在创建的时候,系统都会给分配一个对象,这个对象就是原型对象。
- 实例对象:构造函数中通过
new关键字返回的对象就是实例对象。
打个比方:三者就相当于一家三口的关系,构造函数相当于爸爸,原型对象相当于妈妈,实例对象相当于儿子,爸爸只能有一个妻子,妈妈只能有一个丈夫,所以它们是相互指向的,而儿子必须需要通过妈妈才能找到自己的亲爸爸(因为妈妈一定是亲的,爸爸就不一定咯),哈哈哈。通过这么一比喻是不是就更容易理解了呢!
普通对象、实例对象、原型对象三者分别是什么?
在 JavaScript 中,普通对象、实例对象、原型对象 是三个紧密关联但含义不同的概念,它们的核心区别体现在创建方式、作用和在原型链中的位置上。我们可以通过以下方式清晰区分:
(1)普通对象(Plain Object)
定义:最基础的对象类型,通常通过对象字面量 {} 或 new Object() 创建,不与特定的自定义构造函数绑定,是 “无类型标识” 的通用数据容器。
特点:
- 创建方式:
{ key: value }或new Object()。 - 原型链:直接继承自
Object.prototype(__proto__ === Object.prototype)。 - 作用:用于存储无序键值对(如配置、状态等),无特定业务逻辑。
- 无 “类型归属” :不属于任何自定义构造函数,仅属于
Object类型。
const plainObj = { name: '普通对象' };
console.log(plainObj.__proto__ === Object.prototype); // true(原型是 Object 原型) console.log(plainObj instanceof Object); // true(是 Object 的实例)
console.log(plainObj instanceof Person); // false(与自定义构造函数无关)
(2)实例对象(Instance Object)
定义:通过构造函数(new 构造函数()) 或 class 实例化创建的对象,是某个特定构造函数 / 类的 “具体实例”,继承了该构造函数原型上的属性和方法。
特点:
- 创建方式:
new 构造函数()(如new Person()、new Array())。 - 原型链:
__proto__指向对应构造函数的prototype(原型对象)。 - 作用:承载特定业务逻辑(如用户、商品等实体),多个实例共享构造函数原型上的方法。
- 有明确 “类型归属” :可通过
instanceof检测所属构造函数。
// 自定义构造函数
function Person(name) {
this.name = name;
}
// 原型上的方法(所有实例共享)
Person.prototype.sayHi = function() {};
// 实例对象(Person 的实例)
const person = new Person('张三');
console.log(person.__proto__ === Person.prototype); // true(原型是 Person 的原型对象)
console.log(person instanceof Person); // true(明确属于 Person 类型)
(3)原型对象(Prototype Object)
定义:每个函数(构造函数) 自带的 prototype 属性所指向的对象,是该构造函数所有实例的 “原型”,用于存储实例共享的属性和方法。
特点:
- 创建方式:函数定义时自动创建(
function F() {}会自动生成F.prototype)。 - 核心作用:实现实例间的属性 / 方法共享(避免重复创建,节省内存)。
- 自带
constructor属性:默认指向对应的构造函数(F.prototype.constructor === F)。 - 原型链位置:是实例对象的直接原型(
实例.__proto__ === 构造函数.prototype)。
function Person() {}
// 原型对象(Person.prototype)
console.log(Person.prototype); // { constructor: ƒ Person(), __proto__: Object.prototype }
// 原型对象的 constructor 指向构造函数
console.log(Person.prototype.constructor === Person); // true
// 实例的原型就是原型对象
const p = new Person();
console.log(p.__proto__ === Person.prototype); // true
三者关系与原型链示意图
null(原型链终点)
↑ __proto__
普通对象的原型(Object.prototype)
↑ __proto__
原型对象(Person.prototype)
↑ __proto__
实例对象(p)
- 实例对象的
__proto__指向原型对象(构造函数的prototype)。 - 原型对象本身也是一个 “普通对象”(继承自
Object.prototype)。 - 所有对象最终都间接继承自
Object.prototype,除了Object.prototype自身(其__proto__是null)。
Object 和 Function 区别?
在 JavaScript 中,Object 和 Function 是两个核心的内置构造函数,它们既是函数(可用于创建实例),也是对象(可拥有属性和方法),但二者的定位和作用有本质区别。
定义:
Function:是所有函数的 “根构造函数”,负责创建和定义函数的行为。所有函数(包括普通函数、箭头函数、内置构造函数如Object、Array等)本质上都是Function的实例。Object:是所有对象的 “根构造函数”,负责定义对象的基础行为(如toString()、hasOwnProperty()等)。所有对象(包括普通对象、数组、函数、原型对象等)最终都继承自Object.prototype。
核心作用:
-
Function的核心作用:- 创建函数实例:通过
new Function(...)可以动态创建函数(虽然实际开发中很少用,但理论上所有函数都等价于Function的实例)。示例:const fn = new Function('a', 'b', 'return a + b');(等价于function fn(a, b) { return a + b })。 - 定义函数的共享行为:
Function.prototype上的方法(如apply()、call()、bind())是所有函数都能共享的方法,用于控制函数的执行方式。
- 创建函数实例:通过
-
Object的核心作用:- 创建普通对象:通过
new Object()或对象字面量{}创建普通对象({}是new Object()的语法糖)。 - 定义对象的共享行为:
Object.prototype上的方法(如toString()、hasOwnProperty()、isPrototypeOf())是所有对象(包括函数对象)都能共享的基础方法。
- 创建普通对象:通过
原型链中的关系
Function 和 Object 之间存在特殊的 “互相依赖” 关系,这是 JavaScript 原型体系的底层设计:
Function是Object的实例: 因为Function本身是一个对象(函数是特殊的对象),而所有对象最终都继承自Object.prototype。Function的本质:Function是构造函数,同时也是一个「对象」(JavaScript 中函数是对象的子集)。- 所有对象的最终父级:任何对象(包括函数对象)的原型链最终都会指向
Object.prototype(对象的根原型)。 - 原型链证据:
Function的原型链经过Function.prototype后,最终指向Object.prototype。
// Function 的 __proto__ 是 Function.prototype(第一步已讲)
// 而 Function.prototype 的 __proto__ 指向 Object.prototype
console.log(Function.prototype.__proto__ === Object.prototype); // true
// 因此 Function 的原型链包含 Object.prototype,符合 instanceof 判定规则
console.log(Function instanceof Object); // true → 因此 Function 是 Object 的实例
-
Object是Function的实例: 因为Object本身是一个函数(构造函数),而所有函数都是Function的实例。Object的本质:Object是 JavaScript 内置的构造函数(用于创建对象),它首先是一个「函数对象」。- 所有函数的创建者:在 JavaScript 中,任何函数(包括内置构造函数
Object、Array、Function自身)都是Function构造函数的实例。 - 原型链证据:函数的
__proto__指向Function.prototype(创建它的构造函数的原型)。
// Object 是函数,因此它的 __proto__ 指向 Function.prototype
console.log(Object.__proto__ === Function.prototype); // true
// 根据 instanceof 的判定规则:若 A.__proto__ 指向 B.prototype,则 A instanceof B 为 true
console.log(Object instanceof Function); // true → 因此 Object 是 Function 的
简单说:Function 创建了 Object 这个函数,而 Object 定义了 Function 这个对象的基础行为。
一句话总结:
Function是 “函数的源头”,负责生成所有函数并定义函数的行为;Object是 “对象的源头”,负责定义所有对象(包括函数)的基础行为。
用原型链闭环图理解
Function → __proto__ → Function.prototype → __proto__ → Object.prototype → __proto__ → null
↑ ↑
| |
是 Function 的实例(因为 __proto__ 指向 Function.prototype) |
|
Object → __proto__ → Function.prototype → __proto__ → Object.prototype |
↑ |
| |
是 Function 的实例 |
|
(所有函数都是 Function 的实例) (所有对象最终继承自 Object.prototype,因此都是 Object 的实例)