1. 如何在JS中实现继承(用尽可能多的方式)?
- 原型链继承
ini
function Parent() {
this.name = "parent";
this.like = ["play", "run"];
}
Parent.prototype.doSomething = function () {
console.log("do something");
};
function Child() {
this.name = "child";
}
Child.prototype = new Parent();
console.log(Child.prototype.constructor); // [Function: Parent]
/**
* 当你用构造函数创建对象时(比如 function Child() {}),
* JavaScript 会默认给 Child 生成一个 prototype 对象,
* 这个对象自带 constructor 属性,
* 指向构造函数本身(即 Child.prototype.constructor === Child 为 true)。
因为 Child.prototype 被整个替换成了 Parent 的实例,原本指向 Child 的 constructor 就丢失了,
转而指向 Parent------ 这不符合逻辑(Child 的实例的构造器应该是 Child 本身)。
*/
Child.prototype.constructor = Child;
console.log(Child.prototype.constructor); // [Function: Child]
const child = new Child();
const child2 = new Child();
child.like.push("jump");
console.log(child.like); // [ 'play', 'run', 'jump' ]
console.log(child2.like); // [ 'play', 'run', 'jump' ]
关键解析:
主要通过将子类的原型 (prototype)指向父类的实例来实现。
优点是实现简单:
缺点:
-
引用类型属性共享,导致实例间相互影响;
-
无法在创建子类实例时灵活向父类构造函数传参,扩展性差;
-
会导致子类 constructor 指向异常,需要手动修复。
2. 构造函数继承
ini
function Parent() {
this.type = "parent";
this.like = ["play", "run"];
}
Parent.prototype.doSomething = function () {
console.log("do something");
};
function Child(name) {
Parent.call(this);
this.name = name;
this.age = 18;
}
const child = new Child("child");
console.log(child.type); // parent
// child.doSomething(); // child.doSomething is not a function
child.like.push("jump");
console.log(child.like); // [ 'play', 'run', 'jump' ]
const child2 = new Child("child2");
console.log(child2.like); // [ 'play', 'run' ]
关键解析:
构造函数继承是通过在子类的构造函数中调用父类的构造函数来实现的。 这样,子类的实例就可以继承父类的属性。
优点是每个实例都有自己的属性,不会共享父类的属性,但不能继承父类的原型方法。
- 组合继承
ini
function Parent() {
this.type = "parent";
this.like = ["play", "run"];
}
Parent.prototype.doSomething = function () {
console.log("do something");
};
function Child(name) {
Parent.call(this); // 被调用一次
this.name = name;
this.age = 18;
}
Child.prototype = new Parent(); // 被调用一次
Child.prototype.constructor = Child;
const child = new Child("child");
const child2 = new Child("child2");
child.like.push("jump");
console.log(child.like); // [ 'play', 'run', 'jump' ]
console.log(child2.like); // [ 'play', 'run' ]
child.doSomething(); // do something
关键解析:
组合继承结合了原型链继承和构造函数继承的优点。
它通过调用父类构造函数继承属性,通过将子类的原型指向父类的实例继承方法。
4. 寄生组合继承
ini
function Parent() {
this.type = "parent";
this.like = ["play", "run"];
console.log("parent 被调用", this);
}
Parent.prototype.doSomething = function () {
console.log("do something");
};
function Child(name) {
Parent.call(this);
this.name = name;
this.age = 18;
}
Child.prototype = Object.create(Parent.prototype); // 被调用一次
Child.prototype.constructor = Child;
const child = new Child("child");
child.doSomething(); // do something
child.like.push("jump");
console.log(child.like); // [ 'play', 'run', 'jump' ]
关键解析:
寄生组合继承是对组合继承的一种优化。
它避免了组合继承中父类构造函数被调用两次的问题。
是目前使用最多、最推荐的方法。
5. ES6 class 语法
scala
class Parent {
constructor() {
this.type = "parent";
this.like = ["play", "run"];
}
doSomething() {
console.log("do something");
}
}
class Child extends Parent {
constructor(name) {
super();
this.name = name;
this.age = 18;
}
}
const child = new Child("child");
child.like.push("jump");
console.log(child.like); // [ 'play', 'run', 'jump' ]
child.doSomething(); // do something
关键解析:
ES6 引入了 class 语法更加简洁语法糖,使得继承更容易读写,而且天然支持组合继承。
比传统的原型继承和组合继承方法更直观,有利于代码维护和理解。
拓展知识
原型链是 js 实现继承的基础。每个对象都有一个原型对象,通过原型对象可以访问到父类的属性和方法。原型链的终点是 Object.prototype, 它的原型是null。
构造函数是用于创建对象的特殊函数,通过 new 关键字调用,可以创建一个新的对象实例,并将构造函数中的 this 绑定到新创建的对象上。
class 对原型链和构造函数的语法糖,使得继承变得更加简洁和直观。通过 extends 关键字可以实现继承,通过 super 关键字可以调用父类的构造函数和方法。
2. 如何使用 JS 实现发布订阅模式?
javascript
class eventBus {
constructor() {
this.eventCenter = [];
}
on(event, listener) {
if (!this.eventCenter[event]) {
this.eventCenter[event] = [];
}
this.eventCenter[event].push(listener);
}
emit(event, ...args) {
if (!this.eventCenter[event]) {
return;
}
this.eventCenter[event].forEach((listener) => {
listener(...args);
});
}
off(event, listener) {
if (!this.eventCenter[event]) {
return;
}
this.eventCenter[event] = this.eventCenter[event].filter(
(item) => item !== listener,
);
}
}
// 定于回调函数
const user1 = (message) => {
console.log("user1 收到消息:", message);
};
const user2 = (message, aaa) => {
console.log("user2 收到消息:", message, aaa);
};
const eventBus1 = new eventBus();
eventBus1.on("message", user1);
eventBus1.on("message", user2);
eventBus1.emit("message", "hello world", "hahahah");
eventBus1.off("message", user2);
eventBus1.emit("message", "hello world111", "111");
关键解析:
发布订阅模式是一种代码的设计模式,它允许对象间进行松散耦合的通信。
发布者不会直接调用订阅者,相反,它们通过时间通道发布消息;
订阅者通过注册监听事件通道上的消息来做出响应。
这种模式在事件驱动编程和异步编程中非常有用。
要实现发布订阅模式,需要以下几个关键功能:
-
一个用于存储事件及其对应回调的事件中心。
-
一个 on 方法,用于订阅某个事件,并将回调函数注册到事件中心中。
-
一个 emit 方法, 用于发布某个事件,并调用所有订阅该事件的回调函数。
-
一个 off 方法,用于解除订阅
扩展知识:
应用场景: 聊天室
假如我们要实现一个简单的聊天室应用,当某个用户发送消息时,所有在线用户都能接受到消息。
javascript
class ChatRoom {
constructor() {
this.eventEmitter = { events: {} };
}
on(event, listener) {
if(!this.eventEmitter.events[event] ) {
this.eventEmitter.events[event] = [];
}
this.eventEmitter.events[event].push(listener);
}
emit(event, ...args) {
if(!this.eventEmitter.events[event]) {
return;
}
this.eventEmitter.events[event].forEach((listener) => listener(...args));
}
off(event, listener) {
if(!this.eventEmitter.events[event]) {
return;
}
this.eventEmitter.events[event] = this.eventEmitter.events[event].filter((item) => item !== listener);
}
join(user) {
this.on('message', user.receiveMessage.bind(user));
}
sendMessage(message) {
this.emit('message', message);
}
}
class User {
constructor(name) {
this.name = name;
}
receiveMessage(message) {
console.log(`${this.name} 收到消息: ${message}`);
}
}
const chatRoom = new ChatRoom();
const user1 = new User('user1');
const user2 = new User('user2');
chatRoom.join(user1);
chatRoom.join(user2);
chatRoom.sendMessage('hello');
- 如何使用 JS 实现斐波那契数列?
bash
// 递归实现function fibonacci(n) { if (n === 0) return 0; if (n === 1) return 1; return fibonacci(n - 1) + fibonacci(n - 2);}// 递归实现方式简单直观,但性能较差,重复计算//迭代function fi(n) { if (n === 0) return 0; if (n === 1) return 1; let a = 0; let b = 1; let temp; for (let i = 2; i <= n; i++) { temp = a + b; a = b; b = temp; } return b;}console.log('1')fibonacci(1);console.log('2')console.log(fi(21)); // 10946
关键解析:
斐波那契数列 是一个经典的数学问题,其中每个数都是前两个数的和。
数列以 0 和 1 开始 ,例如:0, 1, 1, 2, 3, 5, 8, 13, 21, ...
后续每一个数都是它前两个数之和。
其定义可以用递归公式表示为:
f(0) = 0
f(1) = 1
f(n) = f(n-1) + f(n-2) n >2 且 n 是自然数
在 js 中我们可以用包括递归、迭代、动态规划等方式进行实现。
在这道题中,我们将分别探讨递归和迭代的实现方式,并最终给出在实际开发性能优化的迭代解决方案。