走进JS,走近讶语
在JavaScript的世界里,对象和面向对象编程(OOP)是不可或缺的一部分。本文将带你深入了解JavaScript中对象的创建方式,特别是ES6中引入的class
语法,以及传统的构造函数和原型的概念。希望通过这篇文章,你能对JavaScript的面向对象编程有一个更深入的理解。
1. 造对象
在JavaScript中,创建对象有多种方式,每种方式都有其特点和适用场景。
1.1 对象字面量
对象字面量是最简单、最直接的方式,适合创建简单的对象。
javascript
代码解读
复制代码
const person = { name: 'Alice', age: 25, greet: function() { console.log(`Hello, my name is ${this.name}`); } }; person.greet(); // 输出: Hello, my name is Alice
虽然这种方式简单易用,但它缺乏灵活性,不适合创建多个具有相同属性和方法的对象。
1.2 ES6 Class 语法
ES6引入了class
关键字,使得JavaScript的面向对象编程更加直观和易于理解。
javascript
代码解读
复制代码
class Person { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log(`Hello, my name is ${this.name}`); } } const alice = new Person('Alice', 25); alice.greet(); // 输出: Hello, my name is Alice
class
语法不仅封装了属性和方法,还提供了构造函数、继承等高级特性,是目前最常用的面向对象编程方式。
1.3 类的本质
类是抽象的概念,用于封装属性和方法。类实际上是模板,定义了一组对象的共同特征和行为。与对象字面量相比,类提供了更好的组织和复用性。
2. 构造函数
在ES5及之前,JavaScript并没有class
关键字,而是通过构造函数来实现面向对象编程。
2.1 构造函数的基本概念
构造函数是一种特殊的函数,用于创建和初始化对象。构造函数的首字母通常大写,以区别于普通函数。
javascript
代码解读
复制代码
function Person(name, age) { this.name = name; this.age = age; } const bob = new Person('Bob', 30); console.log(bob); // 输出: Person { name: 'Bob', age: 30 }
在构造函数中,this
关键字指向新创建的实例对象。通过new
运算符调用构造函数,可以完成对象的实例化过程。
2.2 构造函数 vs 普通函数
函数是否为构造函数,不是由首字母大写决定的,而是由new
运算符决定的。首字母大写只是编程风格上的约定,有助于提高代码的可读性。
javascript
代码解读
复制代码
function Person(name, age) { this.name = name; this.age = age; console.log(this); // 输出当前实例化对象 } // 使用new运算符调用构造函数 const alice = new Person('Alice', 25); // 不使用new运算符,直接调用函数 Person('Charlie', 40); // 这样调用不会创建新的实例对象
输出示例:
分析:
- 使用
new
运算符:构造函数会创建一个新的实例对象,并将this
绑定到这个新对象上。 - 不使用
new
运算符:构造函数会将this
绑定到全局对象上,导致属性被添加到全局对象上,而不是创建一个新的实例对象。
3. 原型
在JavaScript中,函数也是对象,每个函数对象都有一个prototype
属性,用于存储所有实例共享的属性和方法。
3.1 原型的基本概念
通过原型,可以实现方法的共享,减少内存占用,因为如果把方法也书写在构造函数体内,随着创建的实例对象越来越多, 会造成额外的资源浪费。
javascript
代码解读
复制代码
function Person(name, age) { this.name = name; this.age = age; } // 在原型上定义方法 Person.prototype.greet = function() { console.log(`Hello, my name is ${this.name}`); }; const alice = new Person('Alice', 25); alice.greet(); // 输出: Hello, my name is Alice
3.2 类的方法部分由原型完成
在ES6的class
语法中,类的方法实际上也是定义在原型上的,
javascript
代码解读
复制代码
class Person { constructor(name, age) { this.name = name; this.age = age; } greet() { console.log(`Hello, my name is ${this.name}`); } } const alice = new Person('Alice', 25); alice.greet(); // 输出: Hello, my name is Alice
4. 三者关系
在JavaScript中,构造函数、原型对象和实例对象之间有着密切的关系。
- 构造函数:用于创建和初始化对象。
- 原型对象:存储所有实例共享的属性和方法。
- 实例对象:通过
new
运算符创建,拥有自己的属性,同时可以访问原型对象上的方法。
javascript
代码解读
复制代码
function Person(name, age) { this.name = name; this.age = age; } Person.prototype.greet = function() { console.log(`Hello, my name is ${this.name}`); }; const alice = new Person('Alice', 25); console.log(alice); // 输出: Person { name: 'Alice', age: 25 } // 实例对象先在自己身上查找属性,找不到再去原型对象上查找 alice.greet(); // 输出: Hello, my name is Alice
5. JavaScript的面向对象设计哲学
JavaScript的面向对象编程是基于原型的,而不是传统的类继承。这种设计哲学类似于中国人以孔子为原型,强调的是对象之间的关联和共享,而不是严格的类层次结构。
javascript
代码解读
复制代码
const kobe = { name: 'Kobe Bryant', playBasketball: function() { console.log(`${this.name} is playing basketball`); } }; function Player(name) { this.name = name; } Player.prototype = kobe; const leBron = new Player('LeBron James'); leBron.playBasketball(); // 输出: LeBron James is playing basketball
为什么输出的是 LeBron James
而不是 Kobe Bryant
?
-
实例对象的属性优先级:
- 当你调用
leBron.playBasketball()
时,JavaScript 引擎会首先在leBron
对象本身上查找playBasketball
方法。 - 如果在
leBron
对象上没有找到playBasketball
方法,它会沿着原型链向上查找。
- 当你调用
-
原型链的作用:
leBron
的原型对象是kobe
,因为我们在Player
构造函数中设置了Player.prototype = kobe
。- 因此,当在
leBron
上找不到playBasketball
方法时,JavaScript 引擎会在kobe
对象上查找该方法。
-
this
的绑定:- 在
playBasketball
方法中,this
指向的是调用该方法的对象,即leBron
。 - 因此,
this.name
实际上是leBron.name
,而不是kobe.name
。
- 在
总结
通过本文的介绍,相信你对JavaScript中对象的创建方式、构造函数、原型以及面向对象的设计哲学有了更深入的理解。无论是使用对象字面量、构造函数还是ES6的class
语法,都能根据具体需求选择合适的方式来实现面向对象编程。