文章中英模式
布魯斯前端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);
};