当前位置: 首页 > news >正文

【JavaScript】构造函数与 new 运算符

什么是构造函数

构造函数 (Constructor Function) 是用于创建和初始化对象的特殊函数.

通过 new 关键字调用构造函数, 可以创建具有相同属性和方法的多个对象实例.


构造函数的类型

JavaScript 中的构造函数分为两类:

1. 内置构造函数

JavaScript 提供的系统构造函数, 用于创建基本类型的包装对象和内置对象:

// 基本类型包装对象
const num = new Number(42);
const str = new String("hello");
const bool = new Boolean(true);// 内置对象
const obj = new Object();
const arr = new Array(1, 2, 3);
const fn = new Function("a", "b", "return a + b");
const date = new Date();
const regex = new RegExp("\\d+");// 注意: Symbol 和 BigInt 不能用 new 调用
// const sym = new Symbol(); // TypeError: Symbol is not a constructor
// const bigInt = new BigInt(123); // TypeError: BigInt is not a constructor

2. 自定义构造函数

开发者定义的构造函数, 用于创建自定义类型的对象:

// 构造函数使用大驼峰命名 (PascalCase)
function Person(name, age) {this.name = name;this.age = age;
}// 普通函数使用小驼峰命名 (camelCase)
function calculateAge(birthYear) {return new Date().getFullYear() - birthYear;
}

命名规范

  • 构造函数: 使用大驼峰命名 (PascalCase), 如 Person, UserAccount, OrderManager
  • 普通函数: 使用小驼峰命名 (camelCase), 如 getUserInfo, calculateTotal, handleClick

这种命名约定帮助开发者快速识别函数的用途.



new 运算符的工作原理

使用 new 关键字调用构造函数时, JavaScript 引擎会执行以下步骤:


执行步骤

function Person(name) {// 步骤 1: 创建空对象 (引擎自动执行)// const newObj = {};// 步骤 2: 将空对象的 [[Prototype]] 指向构造函数的 prototype (引擎自动执行)// Object.setPrototypeOf(newObj, Person.prototype);// 步骤 3: 将 this 绑定到新对象 (引擎自动执行)// 从这里开始, this 指向新创建的对象// 步骤 4: 执行构造函数代码this.name = name;this.greet = function () {console.log(`Hello, I'm ${this.name}`);};// 步骤 5: 返回新对象 (引擎自动执行, 除非显式返回对象)// return newObj;
}const person = new Person("Alice");
console.log(person.name); // Alice
person.greet(); // Hello, I'm Alice

不使用 new 的后果

如果不使用 new 关键字, 构造函数会作为普通函数执行:

function Person(name) {this.name = name;console.log("this", this); // 在浏览器中指向 window, 在严格模式下是 undefined
}// ❌ 错误用法: 不使用 new
const person1 = Person("Charlie");
console.log(person1); // undefined (函数没有返回值)
console.log(window.name); // 'Charlie' (污染了全局对象!)// ✅ 正确用法: 使用 new
const person2 = new Person("David");
console.log(person2); // Person { name: 'David' }

严格模式下的保护:

"use strict";function Person(name) {// 严格模式下, this 为 undefinedthis.name = name; // TypeError: Cannot set property 'name' of undefined
}const person = Person("Eve"); // 抛出错误



构造函数的基本使用

定义与调用

// 定义构造函数
function User(username, email) {// 实例属性this.username = username;this.email = email;this.createdAt = new Date();
}// 使用 new 创建实例
const user1 = new User("alice", "alice@example.com");
const user2 = new User("bob", "bob@example.com");console.log(user1.username); // alice
console.log(user2.username); // bob// 验证实例类型
console.log(user1 instanceof User); // true
console.log(user1.constructor === User); // true

参数传递与默认值

ES6 参数默认值:

function Person(name = "Anonymous", age = 0) {this.name = name;this.age = age;
}const p1 = new Person("Alice", 25);
const p2 = new Person("Bob");
const p3 = new Person();console.log(p1); // Person { name: 'Alice', age: 25 }
console.log(p2); // Person { name: 'Bob', age: 0 }
console.log(p3); // Person { name: 'Anonymous', age: 0 }

对象参数解构:

function Person({ name = "Anonymous", age = 0, email = "no-email" } = {}) {this.name = name;this.age = age;this.email = email;
}// 灵活的调用方式
const p1 = new Person({ name: "Alice", age: 25 });
const p2 = new Person({ email: "bob@example.com" });
const p3 = new Person();console.log(p1); // Person { name: 'Alice', age: 25, email: 'no-email' }
console.log(p2); // Person { name: 'Anonymous', age: 0, email: 'bob@example.com' }
console.log(p3); // Person { name: 'Anonymous', age: 0, email: 'no-email' }

this 的指向

function Person(name) {this.name = name;// this 指向新创建的实例对象console.log("Inside constructor:", this);this.regularMethod = function () {// 普通函数中的 this 指向调用者console.log("Regular method:", this);};this.arrowMethod = () => {// 箭头函数中的 this 继承自构造函数的 thisconsole.log("Arrow method:", this);};
}const person = new Person("Alice");
// Inside constructor: Person { name: 'Alice' }person.regularMethod();
// Regular method: Person { name: 'Alice', ... }person.arrowMethod();
// Arrow method: Person { name: 'Alice', ... }// 注意: 普通函数的 this 可以被改变
const borrowed = person.regularMethod;
borrowed(); // Regular method: undefined (严格模式) 或 window (非严格模式)// 箭头函数的 this 不会改变
const borrowedArrow = person.arrowMethod;
borrowedArrow(); // Arrow method: Person { name: 'Alice', ... }



构造函数的返回值

默认返回行为

构造函数默认返回新创建的实例对象 (this), 即使没有显式写 return 语句.

function Person(name) {this.name = name;// 隐式返回 this
}const person = new Person("Alice");
console.log(person); // Person { name: 'Alice' }
console.log(person instanceof Person); // true

显式返回的影响

构造函数的返回值行为遵循以下规则:

规则 1: 返回原始值 (Primitive) - 被忽略

function PersonWithNumber(name) {this.name = name;return 42; // 返回数字 (原始值)
}function PersonWithString(name) {this.name = name;return "hello"; // 返回字符串 (原始值)
}function PersonWithNull(name) {this.name = name;return null; // 返回 null (原始值)
}// 所有原始值返回都会被忽略, 依然返回实例对象
const p1 = new PersonWithNumber("Alice");
const p2 = new PersonWithString("Bob");
const p3 = new PersonWithNull("David");console.log(p1); // PersonWithNumber { name: 'Alice' }
console.log(p2); // PersonWithString { name: 'Bob' }
console.log(p3); // PersonWithNull { name: 'David' }

规则 2: 返回对象 (Object) - 覆盖默认返回值

function PersonWithArray(name) {this.name = name;return [1, 2, 3]; // 返回数组 (对象)
}function PersonWithObject(name) {this.name = name;return { customName: "Custom", value: 100 }; // 返回对象
}function PersonWithFunction(name) {this.name = name;return function () {return "I am a function";}; // 返回函数 (对象)
}// 对象类型的返回值会覆盖默认的实例对象
const p1 = new PersonWithArray("Alice");
const p2 = new PersonWithObject("Bob");
const p3 = new PersonWithFunction("Charlie");console.log(p1); // [1, 2, 3]
console.log(p1 instanceof PersonWithArray); // false (不再是实例)console.log(p2); // { customName: 'Custom', value: 100 }
console.log(p2 instanceof PersonWithObject); // falseconsole.log(p3); // [Function (anonymous)]
console.log(p3()); // I am a function
console.log(p3 instanceof PersonWithFunction); // false

实际应用场景

场景 1: 单例模式

function Singleton(name) {// 如果已经存在实例, 返回已有实例if (Singleton.instance) {return Singleton.instance;}this.name = name;this.timestamp = Date.now();// 保存实例引用Singleton.instance = this;
}const s1 = new Singleton("First");
const s2 = new Singleton("Second");console.log(s1 === s2); // true (同一个实例)
console.log(s1.name); // First
console.log(s2.name); // First (不是 Second)

场景 2: 条件性返回不同对象

function UserFactory(type, name) {this.name = name;// 根据类型返回不同的对象结构if (type === "admin") {return {name: name,role: "admin",permissions: ["read", "write", "delete"],};}if (type === "guest") {return {name: name,role: "guest",permissions: ["read"],};}// 默认返回普通用户实例
}const admin = new UserFactory("admin", "Alice");
const guest = new UserFactory("guest", "Bob");
const user = new UserFactory("user", "Charlie");console.log(admin); // { name: 'Alice', role: 'admin', permissions: [...] }
console.log(guest); // { name: 'Bob', role: 'guest', permissions: [...] }
console.log(user); // UserFactory { name: 'Charlie' }



实例属性与方法

实例独有属性

每次使用 new 调用构造函数时, 都会创建一个新的独立对象:

function Counter() {this.count = 0;this.increment = function () {this.count++;console.log(`Count: ${this.count}`);};
}const counter1 = new Counter();
const counter2 = new Counter();counter1.increment(); // Count: 1
counter1.increment(); // Count: 2counter2.increment(); // Count: 1
counter2.increment(); // Count: 2console.log(counter1.count); // 2
console.log(counter2.count); // 2// 两个实例互不影响
console.log(counter1 === counter2); // false
console.log(counter1.increment === counter2.increment); // false (每个实例都有自己的方法)

方法定义的性能问题

⚠️ 性能警告: 在构造函数中直接定义方法会导致每个实例都创建新的函数对象, 浪费内存.

function Person(name) {this.name = name;// ❌ 不推荐: 每次创建实例都会创建新的函数对象this.greet = function () {console.log(`Hello, I'm ${this.name}`);};
}const p1 = new Person("Alice");
const p2 = new Person("Bob");// 两个实例的 greet 方法是不同的函数对象
console.log(p1.greet === p2.greet); // false// 每个实例都占用独立的内存空间
console.log(p1.greet.toString() === p2.greet.toString()); // true (代码相同)
// 但它们是不同的对象, 占用不同的内存

✅ 解决方案: 使用原型

function Person(name) {this.name = name;
}// 将共享方法定义在原型上
Person.prototype.greet = function () {console.log(`Hello, I'm ${this.name}`);
};const p1 = new Person("Alice");
const p2 = new Person("Bob");// 所有实例共享同一个方法
console.log(p1.greet === p2.greet); // true (节省内存)p1.greet(); // Hello, I'm Alice
p2.greet(); // Hello, I'm Bob



new.target 元属性

new.target 是 ES2015 (ES6) 引入的元属性, 用于检测函数是否通过 new 运算符调用.


基本使用

function Person(name) {// 检测是否使用 new 调用if (!new.target) {throw new Error("Person must be called with new");}this.name = name;
}// ✅ 正确使用
const person1 = new Person("Alice");
console.log(person1); // Person { name: 'Alice' }// ❌ 错误使用
try {const person2 = Person("Bob"); // 抛出错误
} catch (e) {console.error(e.message); // Person must be called with new
}

自动修正

function Person(name) {// 如果忘记使用 new, 自动补救if (!new.target) {return new Person(name);}this.name = name;
}const person1 = new Person("Alice"); // 使用 new
const person2 = Person("Bob"); // 忘记使用 new, 自动补救console.log(person1); // Person { name: 'Alice' }
console.log(person2); // Person { name: 'Bob' }
console.log(person2 instanceof Person); // true

检测继承调用

new.target 在继承场景中指向实际被 new 调用的子类构造函数:

function Person(name) {this.name = name;console.log("new.target:", new.target.name);
}function Employee(name, job) {Person.call(this, name);this.job = job;
}const person = new Person("Alice");
// new.target: Personconst employee = new Employee("Bob", "Engineer");
// new.target: Employee



手动实现 new 运算符

通过手动实现 new 运算符的逻辑, 可以深入理解其工作原理.


完整实现

function myNew(constructor, ...args) {// 参数验证: 确保 constructor 是函数if (typeof constructor !== "function") {throw new TypeError("First argument must be a constructor function");}// 步骤 1 & 2: 创建对象并设置原型const obj = Object.create(constructor.prototype);// 步骤 3 & 4: 绑定 this 并执行构造函数const result = constructor.apply(obj, args);// 步骤 5: 判断返回值// 如果构造函数返回对象, 则返回该对象; 否则返回新创建的对象const isObject = result !== null && typeof result === "object";const isFunction = typeof result === "function";return isObject || isFunction ? result : obj;
}



常见陷阱与注意事项

陷阱 1: 忘记使用 new

function Person(name) {this.name = name;
}// ❌ 错误: 忘记 new
const person = Person("Alice");
console.log(person); // undefined
console.log(window.name); // 'Alice' (污染全局对象)

解决方案:

  • 使用 new.target 检测
  • 使用严格模式
  • 使用 ES6 Class (强制使用 new)

陷阱 2: 构造函数返回对象覆盖实例

function Person(name) {this.name = name;return { custom: "object" }; // 覆盖默认返回值
}const person = new Person("Alice");
console.log(person); // { custom: 'object' }
console.log(person.name); // undefined
console.log(person instanceof Person); // false

陷阱 3: 箭头函数不能作为构造函数

const Person = (name) => {this.name = name;
};try {const person = new Person("Alice"); // TypeError: Person is not a constructor
} catch (e) {console.error(e.message);
}

原因:

  • 箭头函数没有自己的 this
  • 箭头函数没有 prototype 属性
  • 箭头函数没有 [[Construct]] 内部方法

陷阱 4: 在构造函数中定义方法导致内存浪费

// ❌ 不推荐: 每个实例都创建新函数
function Person(name) {this.name = name;this.greet = function () {console.log(`Hello, I'm ${this.name}`);};
}const p1 = new Person("Alice");
const p2 = new Person("Bob");console.log(p1.greet === p2.greet); // false (浪费内存)

✅ 解决方案: 使用原型方法

function Person(name) {this.name = name;
}Person.prototype.greet = function () {console.log(`Hello, I'm ${this.name}`);
};const p1 = new Person("Alice");
const p2 = new Person("Bob");console.log(p1.greet === p2.greet); // true (共享方法)



http://www.dtcms.com/a/549989.html

相关文章:

  • OpenEuler 22.03 安装 snmptt 1.5
  • 摇杆控制View
  • 常州网站建设段新浩网站推广公司有哪些
  • TCP三次握手和四次断开
  • 黑马JAVA+AI 加强08 File-IO流
  • (XMODEM协议)自旋锁异常报错
  • 关于光照探针的实验和疑问
  • 校园网站建设的优点wordpress最新文章链接插件
  • 南城网站建设公司策划山东省建设执业资格注册中心网站
  • 【机器学习】模型持久化与部署
  • 「用Python来学微积分」21. 玩转高阶导数
  • 不谈AI模型,只谈系统:SmartMediaKit低延迟音视频技术现实主义路线
  • 哪些证书对学历没硬性要求?高职生必看
  • 公司网站做推广做商城型网站
  • PyQt5 QSet完全指南:深入理解Qt的高性能集合容器
  • 乡村旅游电子商务网站建设有网站怎么做淘宝客
  • 狭小空间难嵌入?这款寻北仪重新定义新标准!
  • 成华区网站建设公司软件工程最好的出路
  • 网站的关键词怎么选择工信部网站登陆
  • Rust 复合类型深度解析:从元组与数组看内存安全与抽象设计
  • ASTMD4169对于医疗冷链包装在空陆联运中的测试验证
  • g++/gcc编译器与自动化构建make/Makefile
  • 高性能人工智能目标检测开山篇----YOLO v1算法详解(上篇)
  • 【文字库】新华字典部分年份出版汇总
  • 个体工商户备案网站备案wordpress推广
  • 设计师网站推荐wordpress换域名安装
  • 搭建 k8s
  • 【MCU控制 初级手札】1.5 化学键(离子键、共价键、金属键)与化合价 【化学基础】
  • Rust与Python完全指南:从零开始理解两门语言的区别与关系
  • 服务器硬盘的作用都有哪些?