144期
1. Vue模板是如何编译的?
2. TS中never类型是用来干嘛的?
3. 请手写冒泡排序?
上面问题的答案会在第二天的公众号(程序员每日三问)推文中公布
也可以小程序刷题,已收录500+面试题及答案
143期问题及答案
1. 手写一个apply函数?
apply
函数是 JavaScript 中的 Function 原型上的一个方法,它的功能是调用一个函数,同时可以将一个上下文(即this)和单个数组(或类数组对象)作为参数传递给这个函数。以下是一个用来模拟原生 apply
方法功能的自定义实现:
function customApply(context = window, args) {
if (typeof this !== 'function') {
throw new TypeError('Error');
}
// 给context创建一个唯一属性以避免覆盖原有属性
const fnSymbol = Symbol('fn');
// this指向调用customApply的函数
context[fnSymbol] = this;
// 处理边界情况,即没有第二个参数args的情况
let result;
if (args == null) {
result = context[fnSymbol]();
} else if (!Array.isArray(args)) {
throw new TypeError('CreateListFromArrayLike called on non-object');
} else {
result = context[fnSymbol](...args); // 展开参数数组
}
delete context[fnSymbol]; // 移除临时属性
return result; // 返回函数执行结果
}
// 通过Function.prototype添加该自定义函数
Function.prototype.customApply = customApply;
// 下面是测试代码
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const context = {
name: 'John'
};
// 正常使用apply
console.log(greet.apply(context, ['Hello', '!'])); // "Hello, John!"
// 使用自定义的customApply
console.log(greet.customApply(context, ['Hello', '!'])); // "Hello, John!"
在这个 customApply
函数实现中,我们首先检查了调用 customApply
的对象是否为一个函数类型,如果不是则抛出 TypeError
。接着,我们为 context
对象创建了一个唯一标识的属性,并且将这个属性指向了调用 customApply
的函数。然后,我们根据传递的参数 args
调用这个函数,并在调用后删除我们之前添加到 context
上的属性,并返回函数调用的结果。如果 args
不传递或者为 null
,则直接调用函数;如果传递的 args
不是数组类型,则抛出 TypeError
。
这就是一个 apply
函数的简单模拟实现,它允许你在任何函数上调用 customApply
方法并且指定 this
上下文和参数。
2. 删除链表中的某个节点?
在数据结构中的单向链表中删除一个节点需要以下步骤:
定位到待删除节点的前一个节点。
将前一个节点的 next 指针指向待删除节点的 next 节点。
这里是删除链表节点的一个基本示例,使用JavaScript来实现:
function ListNode(val) {
this.val = val;
this.next = null;
}
function removeNode(head, node) {
if (head === null) return null;
if (head === node) { // 如果要删除的是头节点
return head.next; // 直接返回头节点的下一节点
}
let current = head;
// 遍历链表,找到待删除节点的前一个节点
while (current.next !== null && current.next !== node) {
current = current.next;
}
if (current.next === node) {
// 找到节点,进行删除
current.next = current.next.next;
} else {
// 没找到节点,可能是因为传入了链表中不存在的节点
console.log("Node not found in the list");
}
return head; // 返回链表头节点
}
在这段代码中,我们首先检查 head
是否为空或者就是要删除的节点,接着遍历链表直到找到待删除节点的前一个节点,并对其进行删除操作。若待删除的节点在链表中不存在,我们将打印一条错误信息。
这是用 JavaScript 实现的链表节点删除操作的伪代码,其他的编程语言实现会有所不同,但是理念是一致的。需要注意的是,删除链表节点时,如果外部环境还持有对该节点的引用,则需要额外注意内存管理。在使用如C++这样手动管理内存的编程语言时,还需要确保删除后释放节点占用的内存空间。
3. new过程具体执行了什么?能否自己实现一个?
在 JavaScript 中,new
操作符用于创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型的实例。使用 new
进行对象创建的过程大致可以分为以下几个步骤:
创建一个新对象。
将这个新对象的内部属性 [[Prototype]] (proto) 链接到构造函数的原型对象(prototype)上。
将新对象作为
this
上下文传递给构造函数执行。如果构造函数返回的是一个对象,则返回这个对象。如果构造函数没有显式返回一个对象,则默认返回创建的新对象。
下面我们通过伪代码来模拟 new
操作符的工作流程:
function customNew(constructor, ...args) {
// 1. 创建一个新对象
const obj = {};
// 2. 将新创建的对象的__proto__链接到构造函数的prototype对象上
Object.setPrototypeOf(obj, constructor.prototype);
// 3. 将新对象作为this上下文调用构造函数
const result = constructor.apply(obj, args);
// 4. 根据构造函数返回值确定返回的对象
return (result !== null && (typeof result === 'object' typeof result === 'function')) ? result : obj;
}
// 测试用例
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};
}
// 使用customNew模拟new操作
const john = customNew(Person, 'John', 30);
john.sayHello(); // Hello, my name is John
console.log(john); // Person { name: 'John', age: 30, sayHello: [Function] }
在 customNew
函数中,我们首先创建了一个空对象 obj
。然后我们将这个空对象的 __proto__
属性设置为构造函数 constructor
的 prototype
属性,从而实现继承原型。接着,我们使用 apply
方法以新对象作为 this
上下文调用构造函数,传递剩余参数 ...args
。最后,我们检查构造函数是否返回了一个对象。如果构造函数返回了一个对象,我们就使用这个对象;如果没有返回,或者返回的不是对象类型,我们就返回最开始创建的新对象 obj
。
customNew
函数能够模拟 new
操作符的大部分功能,但由于它是一个函数而不是操作符,所以在细节上可能会与 new
操作符的行为存在一些差异(比如对构造函数的错误处理)。
需要注意的是,在现代JavaScript引擎中,new
的实现会更加复杂并且进行了优化,上述代码压思才是用来帮助理解 new
的基本工作原理。在实际项目开发中,应当直接使用 new
而不是自己实现。
我要提问
如果你遇到有趣的面试题,或者有想知道的前端面试题,可以在下面的小程序提问,收到问题后会在第一时间为你解答。
我要出题
学习不打烊,充电加油只为遇到更好的自己,每天早上9点纯手工发布面试题,每天坚持花20分钟来学习与思考,在千变万化,类库层出不穷的今天,不要等到找工作时才狂刷题,提倡每日学习。