您现在的位置是:网站首页 > 构造函数与new操作符文章详情
构造函数与new操作符
陈川
【
JavaScript
】
45499人已围观
7775字
构造函数与new操作符
构造函数是JavaScript中用于创建对象的特殊函数,通常与new
操作符配合使用。通过构造函数可以批量创建具有相同属性和方法的对象实例,这是实现面向对象编程的重要方式之一。
构造函数的基本概念
构造函数本质上就是普通函数,但按照约定,构造函数的名称通常以大写字母开头。当使用new
操作符调用函数时,该函数就作为构造函数执行:
function Person(name, age) {
this.name = name
this.age = age
this.sayHello = function() {
console.log(`Hello, my name is ${this.name}`)
}
}
const person1 = new Person('Alice', 25)
person1.sayHello() // 输出: Hello, my name is Alice
构造函数内部使用this
关键字来引用新创建的对象实例。当使用new
调用时,JavaScript引擎会执行以下步骤:
- 创建一个新的空对象
- 将这个新对象的原型指向构造函数的
prototype
属性 - 将构造函数的
this
绑定到这个新对象 - 执行构造函数内部的代码
- 如果构造函数没有显式返回对象,则返回这个新对象
new操作符的工作原理
new
操作符在JavaScript中扮演着关键角色。理解它的内部机制有助于更好地使用构造函数:
function myNew(constructor, ...args) {
// 1. 创建一个新对象,并将其原型指向构造函数的prototype
const obj = Object.create(constructor.prototype)
// 2. 调用构造函数,将this绑定到新对象
const result = constructor.apply(obj, args)
// 3. 如果构造函数返回了一个对象,则返回该对象;否则返回新创建的对象
return result instanceof Object ? result : obj
}
// 使用自定义的new实现
const person2 = myNew(Person, 'Bob', 30)
console.log(person2.name) // 输出: Bob
这个自定义的myNew
函数模拟了原生new
操作符的行为。在实际开发中,我们通常直接使用new
,但了解其原理非常重要。
构造函数的返回值
构造函数通常不需要显式返回值,但如果返回了值,会影响new
操作的结果:
function Car(model) {
this.model = model
// 返回原始值会被忽略
return 'This will be ignored'
}
const car1 = new Car('Tesla')
console.log(car1.model) // 输出: Tesla
function Bike(model) {
this.model = model
// 返回对象会替代新创建的对象
return { custom: 'object' }
}
const bike1 = new Bike('Yamaha')
console.log(bike1.model) // undefined
console.log(bike1.custom) // 输出: object
构造函数与原型
构造函数与原型对象紧密相关。通过原型,我们可以实现方法的共享,避免每个实例都创建自己的方法副本:
function Animal(name) {
this.name = name
}
// 在原型上添加方法
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise`)
}
const animal1 = new Animal('Dog')
const animal2 = new Animal('Cat')
// 两个实例共享同一个speak方法
console.log(animal1.speak === animal2.speak) // true
构造函数继承
JavaScript使用原型链实现继承。子构造函数可以通过调用父构造函数并设置正确的原型链来实现继承:
function Vehicle(type) {
this.type = type
}
Vehicle.prototype.drive = function() {
console.log(`Driving a ${this.type}`)
}
function Car(type, brand) {
Vehicle.call(this, type) // 调用父构造函数
this.brand = brand
}
// 设置原型链
Car.prototype = Object.create(Vehicle.prototype)
Car.prototype.constructor = Car
Car.prototype.honk = function() {
console.log(`${this.brand} honks!`)
}
const myCar = new Car('sedan', 'Toyota')
myCar.drive() // 输出: Driving a sedan
myCar.honk() // 输出: Toyota honks!
ES6类与构造函数
ES6引入了class
语法糖,它底层仍然是基于构造函数和原型的:
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
greet() {
console.log(`Hello, I'm ${this.name}`)
}
}
const person = new Person('Charlie', 28)
person.greet() // 输出: Hello, I'm Charlie
// 类本质上仍然是函数
console.log(typeof Person) // function
构造函数的安全检查
为了防止构造函数被错误调用(没有使用new
),可以在构造函数内部添加检查:
function User(name) {
// 确保通过new调用
if (!(this instanceof User)) {
return new User(name)
}
this.name = name
}
const user1 = User('Alice') // 即使忘记new也能正常工作
const user2 = new User('Bob')
console.log(user1.name) // Alice
console.log(user2.name) // Bob
在ES6中,可以使用new.target
来检测:
function Product(name) {
if (!new.target) {
throw new Error('Product must be called with new')
}
this.name = name
}
try {
const p = Product('Widget') // 抛出错误
} catch(e) {
console.error(e.message) // Product must be called with new
}
构造函数的应用场景
构造函数常用于创建特定类型的多个对象。例如,在游戏开发中创建多个敌人实例:
function Enemy(type, health, attack) {
this.type = type
this.health = health
this.attack = attack
this.isAlive = true
}
Enemy.prototype.takeDamage = function(damage) {
this.health -= damage
if (this.health <= 0) {
this.isAlive = false
console.log(`${this.type} has been defeated!`)
} else {
console.log(`${this.type} has ${this.health} health remaining`)
}
}
const orc = new Enemy('Orc', 100, 15)
const goblin = new Enemy('Goblin', 40, 8)
orc.takeDamage(30) // 输出: Orc has 70 health remaining
goblin.takeDamage(50) // 输出: Goblin has been defeated!
构造函数与工厂函数对比
除了构造函数,工厂函数也是创建对象的常用方式。两者各有优缺点:
// 构造函数方式
function Circle(radius) {
this.radius = radius
}
Circle.prototype.area = function() {
return Math.PI * this.radius ** 2
}
// 工厂函数方式
function createCircle(radius) {
return {
radius,
area() {
return Math.PI * radius ** 2
}
}
}
const c1 = new Circle(5)
const c2 = createCircle(5)
console.log(c1.area()) // 78.53981633974483
console.log(c2.area()) // 78.53981633974483
// 主要区别
console.log(c1 instanceof Circle) // true
console.log(c2 instanceof Object) // true
构造函数的性能考虑
由于构造函数会在每个实例上创建方法副本,将方法定义在原型上更高效:
// 不推荐 - 每个实例都有自己的方法副本
function InefficientWidget(name) {
this.name = name
this.logName = function() {
console.log(this.name)
}
}
// 推荐 - 方法在原型上共享
function EfficientWidget(name) {
this.name = name
}
EfficientWidget.prototype.logName = function() {
console.log(this.name)
}
const widgets1 = []
const widgets2 = []
// 测试创建10000个实例
console.time('Inefficient')
for (let i = 0; i < 10000; i++) {
widgets1.push(new InefficientWidget('Widget' + i))
}
console.timeEnd('Inefficient') // 耗时更长
console.time('Efficient')
for (let i = 0; i < 10000; i++) {
widgets2.push(new EfficientWidget('Widget' + i))
}
console.timeEnd('Efficient') // 耗时更短
构造函数与对象字面量
对于简单对象,直接使用对象字面量可能更合适:
// 使用构造函数
function Book(title, author) {
this.title = title
this.author = author
}
const book1 = new Book('JavaScript', 'John Doe')
// 使用对象字面量
const book2 = {
title: 'JavaScript',
author: 'John Doe'
}
// 当需要创建多个相似对象时,构造函数更有优势
const books = []
for (let i = 0; i < 10; i++) {
books.push(new Book(`Book ${i}`, `Author ${i}`))
}
构造函数的进阶应用
构造函数可以与其他模式结合使用,如模块模式:
const Counter = (function() {
// 私有变量
let privateCounter = 0
// 构造函数
function Counter() {
this.increment = function() {
privateCounter++
console.log(privateCounter)
}
this.decrement = function() {
privateCounter--
console.log(privateCounter)
}
}
return Counter
})()
const counter1 = new Counter()
const counter2 = new Counter()
counter1.increment() // 1
counter1.increment() // 2
counter2.decrement() // 1 - 共享同一个privateCounter
构造函数与this绑定
构造函数中的this
绑定有时会导致意外行为,特别是在回调函数中:
function Timer() {
this.seconds = 0
setInterval(function() {
// 这里的this指向全局对象或undefined(严格模式)
this.seconds++ // 不会按预期工作
console.log(this.seconds) // NaN
}, 1000)
}
const timer = new Timer()
// 解决方案1: 使用箭头函数
function FixedTimer1() {
this.seconds = 0
setInterval(() => {
this.seconds++ // 正确绑定
console.log(this.seconds)
}, 1000)
}
// 解决方案2: 保存this引用
function FixedTimer2() {
this.seconds = 0
const self = this
setInterval(function() {
self.seconds++ // 通过闭包访问
console.log(self.seconds)
}, 1000)
}
构造函数的静态方法
可以在构造函数上直接定义静态方法,这些方法属于构造函数本身而非实例:
function MathUtils() {}
// 静态方法
MathUtils.add = function(a, b) {
return a + b
}
// 实例方法
MathUtils.prototype.multiply = function(a, b) {
return a * b
}
console.log(MathUtils.add(2, 3)) // 5 - 直接通过构造函数调用
const util = new MathUtils()
console.log(util.multiply(2, 3)) // 6 - 通过实例调用
// console.log(util.add(2, 3)) // 错误 - 实例不能访问静态方法
在ES6类中,静态方法使用static
关键字定义:
class StringUtils {
static reverse(str) {
return str.split('').reverse().join('')
}
toUpperCase(str) {
return str.toUpperCase()
}
}
console.log(StringUtils.reverse('hello')) // olleh
const utils = new StringUtils()
console.log(utils.toUpperCase('hello')) // HELLO
上一篇: 函数调用方式与this指向
下一篇: 回调函数模式