引言
在当今的Web开发世界里,JavaScript已经从一个简单的脚本语言进化成了一个功能强大的编程工具。无论是前端开发、后端服务,还是移动应用,JavaScript无处不在。而掌握JavaScript中的面向对象编程概念,无疑是每一个开发者必备的技能之一。今天将带你从零开始,逐步探索JavaScript中的OOP,通过丰富的示例和实用技巧,让你在编程的道路上更进一步。
1.创建对象的方法有哪些?
1.1 对象字面量
最简单的创建对象的方式就是使用对象字面量,这种方式直观易懂,适合快速定义少量的数据结构,但如果需要创建多个相似的对象,这种方式会显得冗余且难以维护。
const person = {
name: '张三',
age: 25,
greet: function() {
console.log(`Hello, my name is ${this.name}`);
}
};
person.greet(); // 输出: Hello, my name is 张三
1.2 Class关键字
随着ES6 的发布,JavaScript引入了class关键字,使得面向对象编程更加接近传统语言如Java或C++的语法,使用class关键字可以使代码更加清晰和易于维护,特别是在处理复杂对象和继承关系时。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
const person1 = new Person('李四', 30);
person1.greet(); // 输出: Hello, my name is 李四
1.3 构造函数
在ES6之前的版本中,通常使用构造函数来模拟类的行为,这也是我们今天介绍的重点地方,接下来将重点围绕构造函数这一块,分享我的理解。
下面我定义了一个构造函数与一些实例:
function Person(name, age) {
this.name = name;
this.age = age;
}
//实例
const z = new Person('张三', 18);
console.log(z.name,l.age);
const l = new Person('李四', 18);
console.log(z.name,l.age);
在这里面,我们可以看到这里运用this指针和new关键字,那这两个东西在这里有什么作用,充当着什么角色呢?
function Person(name, age) {
console.log(this);
this.name = name;
this.age = age;
}
Person('小丽', 19)
const xl = new Person('小李', 19)
const we = new Person('王二', 20)
}
试一试在代码栏中打出这一段代码,你会发现出现了这样的一段代码结果:
出现这个结果当然和this指针和new关键字脱不了关系,在这里this指针指向新创建的实例对象,由于小丽是以普通函数的方式执行,所以this会指向全局对象global,而小李和王二是使用new关键字创建的新对象,它会将构造函数内的this绑定到新创建的对象,是以构造函数的方式运行,所以会产生两个Person{}对象,这里先浅浅的了解一下,下面会有更详细的过程。
2. 接下来讲讲原型与原型链
2.1 原型(Prototype)
在JS中,每个函数都有一个prototype属性,这个属性是一个对象,可以用来定义所有实例共享的属性和方法。当通过new关键字调用构造函数创建实例时,这些实例会继承原型对象上的属性和方法。
function Person(name, age) {
console.log(this);
this.name = name;
this.age = age;
}
//每个函数都有一个原型对象 原型对象是一个对象
//原型对象的作用:给实例对象共享方法 实例对象可以访问原型对象的方法
Person.prototype = {
eat: function () {
console.log(`${this.name}爱吃饭`);
}
}
const we = new Person('王二', 18);
we.eat();
const xl = new Person('小李', 19)
xl.eat();
结果展示如下:
通过这一段代码我们可以原型对象的作用:
- 共享属性和方法:通过原型对象,多个实例可以共享相同的属性和方法,节省内存。
- 动态扩展:可以在运行时动态地向原型对象添加属性和方法,所有实例都会立即获得这些新的属性和方法。
2.2 原型链
1.原型链的基本概念
在JavaScript中,每个对象都有一个内部属性Prototype,指向另一个对象。这个被指向的对象称为原型对象。当访问一个对象的属性时,JavaScript引擎会首先检查该对象自身是否有这个属性。如果没有,引擎会沿着原型链向上查找,直到找到该属性或到达原型链的末端(即null)。
2. 原型链的工作原理
-
属性查找:当访问一个对象的属性时,JavaScript引擎会按照以下步骤进行查找:
- 首先检查对象自身是否有该属性。
- 如果对象自身没有该属性,引擎会检查对象的
Prototype对象是否有该属性。 - 如果
Prototype对象也没有该属性,引擎会继续检查Prototype对象的Prototype对象,依此类推,直到找到该属性或到达原型链的末端(即null)。
-
方法查找:方法的查找过程与属性查找类似,也是沿着原型链向上查找。
3. 构造函数、原型对象与实例的关系
我们可以通过下面这段代码来梳理它们三者之间的关系:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.name = '孔子';
Person.prototype.hometown = '山东';
let person1 = new Person('张', 19);
let person2 = new Person('郭', 19);
console.log(person1 === person2); // false 不相同
console.log(person1.name, person1.hometown, person2.hometown, person2.name);
// 输出: 张 山东 山东 郭
3.1 person1 和 person2 是否相同?
console.log(person1 === person2); // false 不相同
- 解释:
person1和person2是两个不同的实例对象,即使它们都由同一个构造函数Person创建。因此,person1和person2是不相同的对象,person1 === person2的结果是false。
3.2 访问属性的结果是什么?
console.log(person1.name, person1.hometown, person2.hometown, person2.name); // 输出: 张 山东 山东 郭
-
person1.name和person2.name:- 在构造函数
Person中,this.name被赋值为传入的参数name。因此,person1.name是张,person2.name是郭。
- 在构造函数
-
person1.hometown和person2.hometown:hometown属性是在Person.prototype上定义的。当我们在实例对象上访问hometown属性时,如果实例对象本身没有这个属性,JavaScript引擎会沿着原型链向上查找,找到Person.prototype上的hometown属性。因此,person1.hometown和person2.hometown都输出山东。
3.3 原型链是怎么样的?
-
person1和person2的原型链:person1和person2的[[Prototype]]属性都指向Person.prototype。Person.prototype的[[Prototype]]属性指向Object.prototype。Object.prototype的[[Prototype]]属性指向null。
原型链可以表示为:
person1 -> Person.prototype -> Object.prototype -> null person2 -> Person.prototype -> Object.prototype -> null
3.4 总结
- 构造函数:用于创建和初始化对象。通过
new关键字调用构造函数时,this指针指向新创建的实例对象。 - 原型对象:每个构造函数都有一个
prototype属性,可以用来定义所有实例共享的属性和方法。 - 实例:通过
new关键字调用构造函数创建,每个实例都继承了原型对象上的属性和方法。
4. 实例化过程详解
当我们理解了以上内容后,我们就可以得出使用new关键字调用构造函数时,JS引擎会执行的步骤:
- 创建一个新对象 => 将新对象的
[[Prototype]]属性设置为构造函数的prototype属性 => 将构造函数内的this绑定到新对象 => 执行构造函数内的代码 => 返回新对象
5. 结语
JavaScript的面向对象编程模型与其他语言有所不同,它基于原型而非经典的类继承机制,这种模型提供了极大的灵活性,同时也要求开发者对其内部机制有深刻的理解。希望本文能够帮助你更好地掌握JavaScript中的OOP,从而在实际项目中更加游刃有余。