文章中英模式
布鲁斯前端JS面试题目 - JS的new、原型链与Class实现解析
深入讲解JavaScript中的new关键字、原型链机制、Class语法,以及私有变量的实现原理与最佳实践。
文章中英模式
懒得看文章?那就来看视频吧
构造函数 vs Class语法
JavaScript 可通过构造函数或 ES6 Class 语法创建对象,两者底层机制相同,但语法与使用方式有差异。
构造函数写法
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
// 原型方法 - 所有实例共享一份
Person.prototype.introduce = function() {
return `我是 ${this.name},今年 ${this.age} 岁`;
};
// 模拟静态方法
Person.createAnonymous = function () {
return new Person('Anonymous', 0);
};
// 建立实例
const john = new Person('小明', 30);
console.log(john.introduce()); // "我是 小明,今年 30 岁"
Class写法(ES6+)
// Class 语法(ES6+)
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 原型方法(共享)
introduce() {
return `I'm ${this.name}, ${this.age} years old`;
}
// 静态方法 - 直接挂在类别上,不需要实例化就能调用
static createAnonymous() {
return new Person('Anonymous', 0);
}
}
// 直接使用 new 建立实例
const jane = new Person('Jane', 25);
// 静态方法调用
const anonymous = Person.createAnonymous();
/*
直接new vs静态方法建立实例的差异:
1. 直接new:
- 优点:直观明确,完全控制参数
- 缺点:重复逻辑无法封装,每次都需要提供完整参数
2. 静态方法:
- 优点:封装复杂建构逻辑,提供预设值,实现工厂模式
- 优点:可实现单例模式或对象池等设计模式
- 缺点:间接性可能降低程序代码可读性
实务应用:
- 当对象建立逻辑复杂或需要重复使用特定配置时,静态方法更合适
- 常见于React.createElement()、Array.from()等API设计
*/
原型链解析
__proto__
与 prototype
JavaScript 对象系统是基于原型的,有两个关键概念:
- •
prototype
: 是函数的属性,包含将被共享给实例的方法与属性 - •
__proto__
: 是对象的属性,指向建立该对象的构造函数的 prototype
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() {
return `${this.name} says woof!`;
};
const fluffy = new Dog('Fluffy');
console.log(fluffy.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.constructor === Dog); // true
console.log(fluffy.bark()); // "Fluffy says woof!"
function Dog(name) {
this.name = name;
}
Dog.prototype.bark = function() {
return `${this.name} says woof!`;
};
const fluffy = new Dog('Fluffy');
console.log(fluffy.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.constructor === Dog); // true
console.log(fluffy.bark()); // "Fluffy says woof!"
原型链查找机制
当访问对象属性时,JavaScript 引擎会:
- 1.检查对象自身是否拥有该属性
- 2.若无,则查找对象的
__proto__
指向的原型 - 3.若仍无,则继续沿原型链向上查找
- 4.直到找到属性或到达原型链顶端 (
Object.prototype.__proto__ === null
)
// 原型链示例
function Animal() {}
Animal.prototype.eat = function() { return '吃东西...'; };
function Dog() {}
// 设置原型继承
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修正 constructor
Dog.prototype.bark = function() { return '汪汪!'; };
const doggy = new Dog();
console.log(doggy.bark()); // "汪汪!" - 在 Dog.prototype 找到
console.log(doggy.eat()); // "吃东西..." - 在 Animal.prototype 找到
替代 __proto__ 的方法
由于 __proto__
已被弃用,建议使用以下方法:
// 获取原型
Object.getPrototypeOf(obj); // 替代 obj.__proto__
// 设置原型
Object.setPrototypeOf(obj, prototype); // 替代 obj.__proto__ = prototype
// 创建带有指定原型的新对象
Object.create(prototype); // 比 new 更直接
私有变量实现原理
1. 闭包实现私有变量
function Counter() {
// 私有变量
let count = 0;
// 公开方法
this.increment = function() {
count++;
return count;
};
this.decrement = function() {
count--;
return count;
};
this.getValue = function() {
return count;
};
}
const counter = new Counter();
console.log(counter.getValue()); // 0
counter.increment();
console.log(counter.getValue()); // 1
console.log(counter.count); // undefined (无法直接访问)
2. ES2022 私有属性 (#)
class BankAccount {
#balance = 0; // 私有属性
constructor(initialBalance) {
if (initialBalance > 0) {
this.#balance = initialBalance;
}
}
deposit(amount) {
if (amount > 0) {
this.#balance += amount;
return true;
}
return false;
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount(100);
console.log(account.getBalance()); // 100
// console.log(account.#balance); // SyntaxError: Private field
🔥 常见面试题目
(一)new 关键字做了什么?请描述其执行过程。
解答:当使用 new 操作符调用函数时,会执行以下步骤:
- 1.创建一个新的空对象
- 2.将该对象的
__proto__
设置为函数的 prototype 属性 - 3.将函数内的 this 绑定到新建的对象
- 4.执行函数中的代码(通常会初始化对象属性)
- 5.如果函数没有返回对象,则返回新创建的对象
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;
}
// 使用示例
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`你好,我是 ${this.name}`);
};
const person = myNew(Person, '小明');
person.sayHello(); // 输出:你好,我是 小明
(二)prototype 和 __proto__ 有什么区别?
解答:
- •
prototype
是函数特有的属性,指向一个对象,该对象包含会被实例继承的属性和方法 - •
__proto__
是所有对象都有的属性,指向该对象的构造函数的 prototype - •当用
new Foo()
创建对象时,这个对象的__proto__
会被设为Foo.prototype
- •所以
instance.__proto__ === Constructor.prototype
恒成立
(三)ES6 的 Class 和传统构造函数有何不同?
解答:
ES6 Class 本质上仍是原型继承的语法糖,底层机制没有改变。
比较项目 | Class 写法 | Function 写法 |
---|---|---|
语法 | 更清晰直观,OOP风格 | 较为分散,需分别定义原型方法 |
提升行为 | 不会被提升 | 函数声明会被提升 |
继承实现 | 使用 extends 关键字简洁 | 需手动设置原型链,较复杂 |
静态方法 | 使用 static 关键字定义 | 直接赋值给构造函数 |
私有属性 | 支持 # 语法定义真正私有属性 | 需使用闭包或其他技巧模拟 |
兼容性 | ES6+,需考虑旧浏览器兼容 | 全面支持,兼容性好 |
代码比较:
Class 写法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
hello() {
return `你好,我是 ${this.name}`;
}
static create(name, age) {
return new Person(name, age);
}
#privateField = '私有';
getPrivate() {
return this.#privateField;
}
}
Function 写法
function Person(name, age) {
this.name = name;
this.age = age;
// Private variable using closure
var privateField = 'private';
this.getPrivate = function() {
return privateField;
};
}
// Prototype method
Person.prototype.hello = function() {
return `Hello, my name is ${this.name}`;
};
// Static method
Person.create = function(name, age) {
return new Person(name, age);
};