面向对象编程(OOP)是一种重要的编程范式。它允许开发者创建可重用的代码块——对象,这些对象可以包含属性和方法。JavaScript中的OOP可以通过多种方式实现,包括使用对象字面量、构造函数以及ES6引入的class
关键字。本文将探讨这三种方式,并重点介绍构造函数、原型和类之间的关系。
思考一下,你会如何创建一个对象?
对象字面量
最简单直接的方式是使用对象字面量创建对象。例如:
const person = {
name: 'Alice',
age: 30,
greet: function() { console.log(`我是${this.name}`); }
};
这种方式虽然简洁,但在创建多个具有相同属性和方法的对象时显得不够灵活和高效。
ES6的class
关键字
随着ECMAScript 6 (ES6)的到来,JavaScript引入了class
关键字,使得定义类更加直观和符合传统面向对象语言的习惯。例如:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
eat() {
console.log(`${this.name}爱吃饭`);
}
}
let wei = new Person('阿威', 18);
wei.eat();//输出:阿威爱吃饭
class
提供了一种更为清晰的方式来组织代码,特别是在需要批量创建相似对象的情况下。在ES6的class
语法中,面向对象编程的语法被简化了,但底层的原理仍然是基于构造函数和原型的。
构造函数
构造函数是一种特殊的函数,用于创建并初始化对象。在ES6之前,JavaScript没有原生的class
关键字,因此开发者通常使用构造函数来模拟类的行为。构造函数本质上是一个普通的函数,但通过一些特定的规则和约定,如首字母大写,来表明其用途是创建和初始化对象实例。
使用new
关键字(运算符)调用构造函数时,会执行以下步骤:
- 创建一个新的空对象。
- 将这个新对象的
__proto__
属性设置为构造函数的prototype
属性。(prototype是什么后面会解释) - 将构造函数内的
this
绑定到这个新对象。 - 执行构造函数内的代码,为新对象添加属性和方法。
- 返回新对象。
const cy = {
name: '陈炎',
playBasketball: function(){
console.log('后仰跳投');
}
}
function Person(name, age){
this.name = name;
this.age = age;
console.log(this.name);
console.log(this.age);
}
Person.prototype = cy;
Person('郭',18)// 普通方式运行
const wu = new Person('武', 19);// 构造函数运行
wu.playBasketball();
console.log(wu.__proto__ === cy);// 输出:true
区分一个函数是不是构造函数关注这几点即可:
- 首字母大写:构造函数通常首字母大写(非必要但是为了形成良好的编程风格你需要这么做)。
- 使用
new
运算符:用new创建对象实例才能让构造函数被正确地使用。在上面这段代码中,Person('郭',18)
只会调用并把参数传给Person
方法,这个过程并没有创建对象实例,也就是说它只会打印name
和age
而不会打印后仰跳投
(没有实例继承原型对象cy
!) this
关键字:构造函数中的this
指向新创建的对象实例。
原型
主角登场!原型(Prototype) 是实现继承和共享属性及方法的核心机制。每个函数都有一个prototype
属性,该属性是一个对象,包含了可以被该函数的所有实例共享的属性和方法。通过原型,JavaScript实现了基于原型的继承模型,这是一种不同于经典面向对象语言(如Java、C++)的继承方式。
原型的基本概念
-
原型对象:
- 每个函数都有一个
prototype
属性,该属性是一个对象。 - 这个对象被称为原型对象,包含了所有实例共享的属性和方法。
- 每个函数都有一个
-
原型链:
- 当尝试访问一个对象的属性或方法时,如果该对象自身没有这个属性或方法,JavaScript引擎会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端(
null
)。
- 当尝试访问一个对象的属性或方法时,如果该对象自身没有这个属性或方法,JavaScript引擎会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的顶端(
-
__proto__
属性:- 每个对象都有一个内部属性
[[Prototype]]
,可以通过__proto__
属性访问。 __proto__
属性指向创建该对象的构造函数的prototype
对象。
- 每个对象都有一个内部属性
function Person(name, age){
this.name = name;
this.age = age;
}
// 每个函数都有一个原型对象
Person.prototype.eat = function(){
console.log(`${this.name}爱吃饭`);
}
const xck = new Person('肖总', 18);
xck.eat();//输出:肖总爱吃饭
通过修改Person.prototype
,我们可以向所有Person
的实例添加新的属性或方法,而无需重新定义每个实例。
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.name = '孔子'
Person.prototype.hometown = '山东'
let person1 = new Person('张总',18);
let person2 = new Person('郭总',18);
console.log(person1 === person2);
console.log(person1.name,person1.hometown,person2.name);
也就是说,你甚至可以根据需求更换原型对象,从而使你创建的对象实例获取到你想要的属性和方法。不得不说,JavaScript原型式面向对象的设计是非常灵活的。
总结构造函数、原型对象与实例的关系
- 构造函数是一种特殊的函数,主要用于创建特定类型的对象。构造函数通常首字母大写以区别于普通函数。
- 每个JavaScript函数都有一个
prototype
属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。当你通过构造函数创建一个对象实例时,该实例会继承构造函数原型上的所有属性和方法。 - 当你使用
new
关键字调用构造函数时,你实际上是在创建该构造函数的一个实例。每个实例都会继承构造函数原型上的属性和方法。这意味着不同的实例可以共享这些属性和方法,而不需要为每个实例都复制一份。
点个赞再走吧~