JS错题集
记忆函数
思路
使用map记录曾经输入过的入参以及入参对应的结果。
后续相同入参能够在map中找到并直接返回结果,不需要再次调用fn,达到记忆的效果。
Code
function memoize(fn) {
const map = new Map()
return function(...args) {
const item = args.join(',')
if(!map.has(item)){
map.set(item,fn(...args))
}
return map.get(item)
}
}
执行可取消的延迟函数
知识点
闭包
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
setTimeout
全局的 setTimeout() 方法设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。
总的来说,闭包和 setTimeout
在这个问题中共同工作,以创建一个可取消的延迟函数执行机制。
Code
var cancellable = function(fn, args, t) {
const timeout = setTimeout(()=>fn(...args),t);
return ()=>clearTimeout(timeout);
};
间隔取消
知识点
闭包
闭包是指一个函数可以“记住”并访问它定义时的作用域,即使在这个函数外部被调用。这意味着内部函数可以访问其外部函数的变量。
setInterval
setInterval
是 JavaScript 中用于定期执行指定函数的一个内置方法。它允许开发者设置一个时间间隔,在这个间隔内重复调用某个函数,直到手动取消。
let intervalID = setInterval(function, delay, arg1, arg2, ...);
Code
var cancellable = function(fn, args, t) {
fn(...args); // 立即调用传入的函数 fn
const time = setInterval(() => fn(...args), t);
return () => clearInterval(time)
};
question
如何结合 cancelT
参数实现定时器的取消:
完整的调用过程如下:
var cancellable = function(fn, args, t) {
fn(...args); // 立即调用传入的函数 fn
const time = setInterval(() => fn(...args), t);
return () => clearInterval(time);
};
// 示例
let fn = (x) => x * 2;
let args = [4];
let t = 35;
let cancelT = 190;
let cancelFn = cancellable(fn, args, t); // 立即调用 fn,并开始定时器
// 190 毫秒后取消定时器
setTimeout(cancelFn, cancelT);
有时间限制的 Promise 对象
思路
用 promise.race 实现
知识点
Promise
对象是 JavaScript 中用于处理异步操作的一种机制。它代表一个未来可能会完成或者失败的操作及其结果。`Promise` 允许我们写出更清晰的异步代码,避免嵌套的回调函数(也就是所谓的 "回调地狱")。
1. Promise 的状态
一个 Promise
对象有三种可能的状态:
Pending(待定):初始状态,既没有完成也没有失败。
Fulfilled(已完成):操作成功完成。
Rejected(已拒绝):操作失败。
2. Promise 的基本用法
Promise
对象的构造函数接收一个执行函数,该函数包含两个参数:resolve
和 reject
。这两个参数是用于将 Promise
的状态从 pending
改为 fulfilled
或 rejected
。
const myPromise = new Promise((resolve, reject) => {
const success = true; // 模拟一个异步操作
if (success) {
resolve("操作成功");
} else {
reject("操作失败");
}
});
在上面的代码中,myPromise
是一个 Promise
对象,resolve("操作成功")
表示操作成功,reject("操作失败")
表示操作失败。
3. Promise 链式调用
then()
:用于处理Promise
完成时的结果(`fulfilled` 状态)。catch()
:用于处理Promise
被拒绝时的情况(`rejected` 状态)。finally()
:无论Promise
成功还是失败,都会执行的代码块。
// 定义一个异步操作函数,返回一个Promise
const asyncOperation = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true; // 模拟一个异步操作
if (success) {
resolve("操作成功");
} else {
reject("操作失败");
}
}, 1000);
});
};
// 使用Promise并链式调用then、catch和finally方法
asyncOperation()
.then((result) => {
console.log(result); // 如果成功输出: "操作成功"
})
.catch((error) => {
console.log(error); // 如果失败输出: "操作失败"
})
.finally(() => {
console.log("无论如何都会执行");
});
在上面的示例中,setTimeout
模拟了一个异步操作,then
处理成功的结果,catch
处理失败的情况,`finally` 无论结果如何都会执行。
4. Promise 的常见方法
Promise.all()
Promise.all()
接收一个 Promise
数组,只有当所有的 Promise
都完成时,Promise.all()
才会执行。否则,如果有任何一个 Promise
失败,则整个 Promise.all()
失败。
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'foo'));
const promise3 = 42;
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // [3, "foo", 42]
});
Promise.race()
Promise.race()
也接收一个 Promise
数组,但只返回第一个完成的 Promise
的结果。
const promise1 = new Promise((resolve) => setTimeout(resolve, 500, 'one'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'two'));
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // "two"
});
Promise.allSettled()
Promise.allSettled()
接收一组 Promise
,并等待所有 Promise
都结束,无论它们是成功还是失败。返回一个对象数组,描述每个 Promise
的结果。
const promise1 = Promise.resolve('成功');
const promise2 = Promise.reject('失败');
Promise.allSettled([promise1, promise2]).then((results) =>
results.forEach((result) => console.log(result.status))
);
Promise.any()
Promise.any()
只要任意一个 Promise
成功就返回该结果,如果所有 Promise
都失败则返回失败。
const promise1 = Promise.reject('失败');
const promise2 = Promise.resolve('成功');
Promise.any([promise1, promise2]).then((value) => console.log(value)); // "成功"
5. 异步编程与 Promise
Promise
主要用于处理异步任务,比如网络请求、定时器等。通过 Promise
,我们可以避免深层嵌套的回调函数,使代码更清晰、易于维护。
示例:模拟异步网络请求
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve('数据加载成功');
} else {
reject('数据加载失败');
}
}, 2000);
});
}
fetchData()
.then(data => {
console.log(data); // 输出:'数据加载成功'
})
.catch(error => {
console.error(error);
});
在这个例子中,fetchData
模拟了一个异步操作,通过 Promise
处理网络请求成功或失败的情况。
总结
Promise 用于处理异步操作,是回调函数的替代方案。
一个
Promise
有三种状态:pending
(进行中)、fulfilled
(已完成)和rejected
(已拒绝)。常用方法:
then()
处理成功,catch()
处理失败,finally()
在操作结束时总是执行。提供了组合多个
Promise
的方法,如Promise.all()
、Promise.race()
等。
Code
var timeLimit = function(fn, t) {
return async function(...args) {
return new Promise((resolve,reject) => {
setTimeout(()=>{
reject("Time Limit Exceeded")
},t);
fn(...args).then(resolve).catch(reject);
})
}
};
判断对象是否为空
思路
第一种方法 是使用 JSON.stringify 将输入数组/对象转换为字符串。如果数组或对象为空,它将返回一个具有开头和结尾的括号或花括号的字符串。
第二种方法 是使用如上建议的 Object.keys() 来获取长度,然后验证它是否为空。
第三种方法 是仅使用 for 循环迭代器来检查是否有内容可迭代,如果有,表示对象不为空,如果没有可迭代的内容,表示对象为空。
Code
var isEmpty = function(obj) {
//return JSON.stringify(obj).length <= 2 ? true : false
//return Object.keys(obj).length === 0
for(const _ in obj) return false
return true
};
数组原型对象的最后一个元素
知识点(this关键字)
理解这个任务涉及到理解 JavaScript
中的 this 关键字。在这里,JavaScript
中的 this
关键字的行为与其他编程语言略有不同。this
的值取决于函数调用时的上下文。在这个问题中,this
将引用当前调用last()
方法的对象,它将是一个数组。
全局上下文
在全局执行上下文中(即,在任何函数之外),this 无论在严格模式还是非严格模式下,都引用全局对象。
在 web 浏览器中,全局对象是 window
,所以 this
将引用 window
对象:
console.log(this); // 在浏览器上下文中会记录 "[object Window]"
在 Node.js
环境中,全局对象不是window
而是 global
。因此,如果在 Node.js
上下文中运行相同的代码,this
将引用全局对象:
console.log(this); // 在 Node.js 上下文中会记录 "[object global]"函数上下文
函数上下文
在普通函数内部,this
的值取决于函数的调用方式。如果函数在全局上下文中调用,this
在严格模式下将为 undefined
,在非严格模式下将引用全局对象。
function func() {
console.log(this);
}
func(); // 在非严格模式的浏览器上下文中记录 "[object Window]",在严格模式下会记录 "undefined"
但是,当函数充当对象的方法时,this
将引用调用该方法的对象。这展示了 this
的值不绑定于函数本身,而是由函数被调用的方式和位置决定,这个概念称为执行上下文:
let obj = {
prop: "Hello",
func: function
() {
console.log(this.prop);
}
}
obj.func(); // 记录 "Hello"
然而,箭头函数不具有自己的 this
。相反,它们从创建时的父作用域继承 this
。换句话说,箭头函数内部的 this
值不由它的调用方式决定,而是由它的定义时的外部词法上下文决定:
let obj = {
prop: "Hello",
func: () => {
console.log(this.prop);
}
}
obj.func();
// 记录 "undefined"
,因为箭头函数内部的 this
不绑定到 obj
,而是绑定到其外部词法上下文
这在某些情况下可能很有用,但它也使得箭头函数不适合需要访问它们被调用的对象的其他属性的方法。
事件处理函数
在事件处理程序的上下文中,this
引用附加了事件监听器的元素,与event.currentTarget
相同。
button.addEventListener('click', function() {
console.log(this); // 记录按钮的整个 HTML 内容
});
重要的是注意,它不引用常用的event.target
属性。让我们澄清event.currentTarget
和 event.target
之间的区别。
event.currentTarget
:该属性引用附加了事件处理程序(如 addEventListener
)的元素。这是在事件处理程序函数的上下文中 this 引用的内容。
event.target
:该属性引用引发事件的实际 DOM 元素。对于会冒泡的事件特别重要。如果你点击内部元素,事件将冒泡到外部元素,触发它们的事件监听器。对于这些外部元素,event.target 将是实际被点击的最内层元素,而 event.currentTarget
(或 this
)将是当前处理程序附加到的元素。
<div id="outer">点击我
<div id="inner">或者点击我</div>
</div>
<script>
document.getElementById('outer').addEventListener('click', function(event) {
console.log("currentTarget: ", event.currentTarget.id);
console.log("this: ", this.id);
console.log("target: ", event.target.id);
});
</script>
在这种情况下,如果你点击外部 div
,所有三个日志都将打印 "outer"
,因为点击的元素(target)
和处理程序附加的元素(currentTarget
或 this
)是相同的。但是,如果你点击内部 div
中的 "或者点击我" 文本,event.target
将是 "inner"
(因为这是你点击的元素),而 event.currentTarget
(或 this
)仍将是 "outer"
(因为这是事件处理程序附加的元素)。
构造函数上下文
在构造函数内部,this
引用新创建的对象。但是,这里的“新创建”是什么意思呢?要理解这一点,我们需要探讨 JavaScript
中的 new
关键字。当你在函数调用之前使用 new
时,它告诉 JavaScript 进行四个操作:
创建一个新的空对象。这不是一个函数、数组或 null
,只是一个空对象。
使函数内部的 this 引用这个新对象。新对象与构造函数内的 this 关联起来。这就是为什么 Person(name)
内的 this.name
实际上修改了新对象。
正常执行函数。它像通常情况下执行函数代码一样执行。
如果函数没有返回自己的对象,则返回新对象。如果构造函数返回一个对象,那个对象将被返回,而不是新对象。如果返回其他任何内容,将返回新对象。
new
关键字允许 JavaScript
开发者以面向对象的方式使用语言,从构造函数中创建实例,就像其他语言中的类一样。这也意味着构造函数内部的 this
关键字将像从基于类的语言中转换的开发者所期望的那样引用对象的新实例。
function Person(name) {
// 当使用 `new` 调用时,这是一个新的、空的对象
this.name = name; // `this` 现在有一个 `name` 属性
// 函数结束后,将返回 `this`,因为没有其他对象被函数返回
}
let john = new Person('John'); // `john` 现在是函数 `Person` 返回的对象,包含一个值为 'John' 的 `name` 属性
console.log(john.name); // 记录 "John"
类上下文
在类中,方法内部的 this
引用类的实例:
class ExampleClass {
constructor(value) {
this.value = value;
}
logValue() {
console.log(this.value);
}
}
const exampleInstance = new ExampleClass('Hello');
exampleInstance.logValue(); // 记录 "Hello"
显式 / 隐式绑定
你还可以使用函数上的 .call()
、.apply()
或 .bind()
方法来明确设置 this
的上下文:
function logThis() {
console
.log(this);
}
const obj1 = { number: 1 };
const obj2 = { number: 2 };
logThis.call(obj1); // 记录 obj1
logThis.call(obj2); // 记录 obj2
const boundLogThis = logThis.bind(obj1);
boundLogThis(); // 记录 obj1
绑定方法和永久this
上下文
JavaScript
提供了一个名为 bind
的内置方法,允许我们设置方法中的 this
值。这个方法创建一个新函数,当调用时,将其 this
关键字设置为提供的值,以及在调用新函数时提供的一系列参数。
bind
方法的独特之处在于它创建了一个永久绑定的 this
值,无论后来如何调用该函数,都不会更改 this
的值。下面的示例演示了 bind
如何提供一种锁定函数中的 this
值的方法,在各种情况下都很有帮助,例如在设置事件处理程序时,希望 this
值始终引用特定对象,或者在使用调用回调函数的库或框架时,希望在回调中控制 this
引用的对象。
function greet() {
return `你好,我是 ${this.name}`;
}
let person1 = { name: 'Alice' };
let person2 = { name: 'Bob' };
// 创建一个与 `person1` 绑定的函数
let greetPerson1 = greet.bind(person1);
console.log(greetPerson1()); // 你好,我是 Alice
// 尝试使用 `call` 方法更改上下文;但是,它仍然使用 `person1` 作为 `this` 上下文
console.log(greetPerson1.call(person2)); // 你好,我是 Alice
// 相比之下,正常函数调用允许使用 `call` 方法设置 `this` 上下文
console.log(greet.call(person2)); // 你好,我是 Bob
在 JavaScript
中,了解 this
关键字的上下文对于操作和与对象交互非常重要,特别是在处理面向对象编程、事件处理程序和函数调用的某些方面。了解 this
的行为有助于改善代码的结构,并使其更可预测和更容易调试。此外,某些设计模式,如工厂模式和装饰器模式,大量使用 this
,因此了解其行为对于有效实现这些模式至关重要。
JavaScript
中的一个关键概念是函数对象中的 this
值通常不是固定的 - 它通常是根据函数的执行上下文而确定的,而不是根据其定义的时刻。然而,也有例外情况。使用函数上的 bind()
、call()
或 apply()
方法时,这些方法允许你显式设置函数调用的 this
值,从而覆盖其默认行为。此外,JavaScript
中的箭头函数行为不同。它们不绑定自己的 this
值。相反,它们从定义它们的外部词法环境中捕获 this
的值,并且这个值在函数的整个生命周期内保持不变。
有时间限制的缓存
思路
方法 1:setTimeout + clearTimeout + 类语法
知识点
常见的 Map
方法和属性
set(key, value)
将指定键与值关联,添加或更新键值对。
get(key)
返回与指定键关联的值,如果键不存在则返回
undefined
。
has(key)
检查
Map
是否包含指定键,返回true
或false
。
delete(key)
删除指定键及其关联的值,成功时返回
true
,失败时返回false
。
clear()
删除
Map
中的所有键值对。
size
返回
Map
中键值对的数量(属性)。
keys()
返回一个新的
Iterator
对象,包含Map
中每个元素的键。
values()
返回一个新的
Iterator
对象,包含Map
中每个元素的值。
entries()
返回一个新的
Iterator
对象,包含Map
中每个元素的[key, value]
数组。
forEach(callback)
对
Map
中的每个元素执行提供的函数,按插入顺序执行。
Code
function TimeLimitedCache() {
this.cache = new Map();
}
TimeLimitedCache.prototype.set = function(key, value, duration) {
const valueInCache = this.cache.get(key);
if (valueInCache) {
clearTimeout(valueInCache.timeout);
}
const timeout = setTimeout(() => this.cache.delete(key), duration);
this.cache.set(key, { value, timeout });
return Boolean(valueInCache);
}
TimeLimitedCache.prototype.get = function(key) {
return this.cache.has(key) ? this.cache.get(key).value : -1;
}
TimeLimitedCache.prototype.count = function() {
return this.cache.size;
}
在线资源
MDN网络文档(非常权威的 JavaScript 参考资料)。