对象、函数、原型之间的关系
在 JavaScript 中,对象、函数 和 原型 是三者紧密联系的核心概念。它们共同构成了 JavaScript 中面向对象编程的基石,并通过原型链实现了继承与代码复用。本文将从对象、函数、原型的基础概念到它们之间的关系进行详细的讲解,帮助你理解 JavaScript 的底层机制。
1. 什么是对象?
在 JavaScript 中,一切皆对象。对象是包含属性和方法的容器。属性可以是基本数据类型,如字符串、数字等;方法则是一个可以执行的函数。
var person = {
name: "浮游",
age: 20,
greet: function() {
console.log("Hello, I am " + this.name);
}
};
console.log(person.name); // 浮游
person.greet(); // Hello, I am 浮游
上面的 person
是一个对象,它包含了两个属性 name
和 age
,以及一个方法 greet
。
2. 什么是函数?
JavaScript 中的函数实际上也是对象。与其他对象不同的是,函数对象可以被调用。此外,每个函数在创建时都会自动拥有两个属性:prototype
和 __proto__
。
function Foo() {}
console.log(typeof Foo); // function
console.log(Foo instanceof Object); // true
可以看出,函数本质上是对象,具有对象的一些特性。同时,它们也有特殊的属性,方便实现继承与原型链机制。
函数的两大属性:
prototype
:每个函数都有的属性,指向函数的原型对象,用于继承。__proto__
:指向创建该对象的构造函数的原型。
3. 什么是原型?
原型是实现继承的核心概念。每个 JavaScript 对象都有一个隐藏的属性 [[Prototype]]
,可以通过 __proto__
来访问。原型是一个对象,它用于实现对象之间的属性和方法的共享。
3.1 原型对象 (prototype
)
每个函数对象在创建时都会有一个 prototype
属性,该属性指向原型对象,而这个原型对象中包含了通过该函数创建的实例所共享的属性和方法。
function Person(name, age) {
this.name = name;
this.age = age;
}
// 向原型对象中添加方法
Person.prototype.greet = function() {
console.log("Hello, I am " + this.name);
};
var person1 = new Person("浮游", 20);
var person2 = new Person("熠星", 24);
person1.greet(); // Hello, I am 浮游
person2.greet(); // Hello, I am 熠星
在上面的例子中,Person
函数有一个 prototype
属性,所有通过 Person
创建的对象 (person1
和 person2
) 都共享 prototype
中的 greet
方法。
3.2 隐式原型 (__proto__
)
每个 JavaScript 对象都有一个 __proto__
属性,指向其构造函数的原型。通过这个属性,对象可以继承构造函数原型上的属性和方法。__proto__
是对象与原型之间的链接,形成了原型链。
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null, 原型链的顶端
person1
通过 __proto__
链接到 Person.prototype
,Person.prototype
又通过 __proto__
链接到 Object.prototype
,这就是 JavaScript 原型链的基础。
4. 对象、函数与原型的关系
JavaScript 是基于原型的语言,这意味着每个对象都从一个“原型”对象继承属性和方法。
- 函数是对象:函数本身是一个对象,有
__proto__
属性,它指向Function.prototype
。 - 对象有原型:每个对象都有一个
__proto__
属性,它指向它的构造函数的prototype
。 - 原型链:通过
__proto__
属性,对象可以访问其原型对象上的属性和方法。如果在自身对象中找不到某个属性,它会沿着原型链向上查找。
4.1 构造函数与实例对象的关系
function Car(model) {
this.model = model;
}
Car.prototype.drive = function() {
console.log(this.model + " is driving");
};
var car1 = new Car("Toyota");
var car2 = new Car("Honda");
car1.drive(); // Toyota is driving
car2.drive(); // Honda is driving
console.log(car1.__proto__ === Car.prototype); // true
console.log(Car.prototype.constructor === Car); // true
car1
和car2
是由构造函数Car
创建的实例对象。- 它们的
__proto__
属性都指向Car.prototype
。 Car.prototype.constructor
指向Car
构造函数本身。
4.2 原型链的查找机制
当你访问一个对象的属性时,JavaScript 引擎首先会在对象本身的属性中查找。如果找不到,它会沿着原型链在其原型对象上查找。这个过程会持续到原型链的顶端——Object.prototype
。
console.log(car1.toString()); // [object Object]
car1
并没有 toString
方法,但是它沿着原型链找到了 Object.prototype.toString
,因此可以调用它。
5. 原型链
原型链是指对象通过 __proto__
一直向上查找,直到找到 Object.prototype
为止。如果在整个原型链中没有找到该属性,则返回 undefined
。
console.log(car1.__proto__); // Car.prototype
console.log(car1.__proto__.__proto__); // Object.prototype
console.log(car1.__proto__.__proto__.__proto__); // null, 原型链的顶端
6. 总结
- 对象:JavaScript 中的任何实体都是对象,它们可以有属性和方法。
- 函数:JavaScript 中的函数也是对象,它们有额外的
prototype
属性,用于支持原型链和继承。 - 原型:原型是对象的共享属性和方法的容器,通过原型链实现继承。每个对象都有一个
__proto__
属性,指向它的构造函数的prototype
。
对象、函数与原型的关系:
对象 --通过 __proto__ --> 构造函数的 prototype --通过 constructor--> 构造函数
通过这个关系,JavaScript 实现了灵活的继承机制,使得我们能够通过原型链实现对象的扩展和代码的复用。
7. 关键要点
- 原型链是 JavaScript 继承机制的核心,通过它可以实现属性和方法的查找。
- 每个函数都有一个
prototype
属性,指向它的原型对象,原型对象上的属性和方法可以被该构造函数的实例对象共享。 - 每个对象都有一个
__proto__
属性,指向它的构造函数的原型对象。 - 原型链的终点是
Object.prototype
,它是所有对象的最终原型。