算法
递归求和
const recursiveSum=function(arr,index){
return index < 0 ? 0 : arr[index] + recursiveSum(arr,index - 1);
}
技巧
数组
数组求交集:filter() + includes()
const intersection = (arr1, arr2) => { arr1.filter(value => arr2.includes(value)) }
数组求和:reduce()
const sum=array.reduce((preNum,currentNum)=>preNum+currentNum,0)
清空数组
arr=[];
arr.splice(0);
arr.length=0;
ES6
const obj={
name:'zhangsan',
sayHi1:()=>{
console.log(this.name);
},
sayHi2(){
(()=>{
console.log(this.name);
})()
}
}
obj.sayHi1(); // 指向全局对象,浏览器中则显示空字符串,node中则显示undefined
obj.sayHi2(); // zhangsan
代码题
考查【在for循环里0被看作false】
let count = 0;
const nums = [0, 1, 2, 3];
nums.forEach(num => {
if (num) count += 1;
});
console.log(count); // 输出 3,因为只有 1, 2, 3 是真值
考查【参数传递】、【数据类型】
function fn(obj, val) {
// 在函数内部,直接修改传入的对象obj的name属性
obj.name = 1;
// val是一个基本类型值,这里只是复制了一份值,并不会影响外部的num变量
val = 1;
}
var obj = { name: 0 }; // 创建一个对象,name属性初始值为0
var num = 0; // 创建一个基本类型数值,初始值为0
fn(obj, num); // 调用fn函数,将obj和num作为参数传递进去
console.log(obj, num); // 输出修改后的obj对象和原始的num值, { name: 1 } 0
考查【对象引用】
var a = { num: 1, count: 2 }; // 创建一个对象a,它有两个属性:num和count
b = a; // 将对象a的引用赋给变量b,此时a和b都指向同一个对象
b = { num: 3, count: 4 }; // 开辟了新对象,b指向新对象,与a不再指向同一个对象
console.log(a.num, a.count, b.num, b.count); // 输出结果为:1 2 3 4
var c = { num: 1, count: 2 }; // 创建一个对象c,它有两个属性:num和count
d = c; // 将对象c的引用赋给变量d,此时c和d都指向同一个对象
d.num = 3; // 修改了同一个对象的属性,将对象c的num属性改为3
d.count = 4; // 修改了同一个对象的属性,将对象c的count属性改为4
console.log(c.num, c.count, d.num, d.count); // 输出结果为:3 4 3 4
考查【对象引用复制】
let person = { name: "Lydia" };
const members = [person];
person = null;
console.log(members); // [{ name: "Lydia" }],因为 person 是一个对象,将引用复制给了 members。当将 person 设为 null 时,members 仍然引用这个对象
考查【JSON】
const obj = {
a: 3,
b: 4,
c: null,
d: undefined,
get e() {
return 5; // 提供一个返回值
}
};
console.log(JSON.stringify(obj));
// {"a":3,"b":4,"c":null,"e":5}
考查【作用域】、【变量提升】
var a = 0;
function demo1() {
console.log(a); // 0,打印全局作用域中的a,由于非严格模式下this指向全局对象
console.log(this.a); // 0 (如果是严格模式下,this为undefined,会执行到这里报错Uncaught TypeError: Cannot read properties of undefined (reading 'a'))
a = 1; // 修改全局作用域中的a
console.log(a); // 1
console.log(this.a); // 1
}
function demo2() {
console.log(a); // undefined,因为var a=2的声明提升
console.log(this.a); // 1
var a = 2;
console.log(a); // 2
console.log(this.a); // 1
}
demo1();
demo2();
考查【调用链】
// 创建一个调用链,使用多次 call 方法将 console.log 函数的上下文设置为全局对象
const demo = console.log.call.call.call.call.call.call.apply(
(a) => a, // 一个简单的箭头函数,接受一个参数并返回它
[1, 2] // apply 方法将这个数组作为参数传递给箭头函数,但箭头函数并未使用这些参数
);
console.log(demo); // 2
考查【this指向】
let length = 1;
function fn() {
console.log(this.length);
}
let arr = [fun, 'a', 'b'];
arr[0](); // 3 (this指向arr)
let fun2 = arr[0];
fun2(); // 1 (this指向window)
let name='hello world'
let obj={
name:'zhangSan',
say:function(){
console.log(this.name)
}
}
obj.say() // zhangSan
setTimeout(obj.say,1000) // hello world(obj.say 是一个单独的函数调用,它的 this 指向会被设置为全局对象(在浏览器环境下通常是 window))
setTimeout(() => obj.say(), 1000); // 这样会输出 zhangSan
setTimeout(obj.say.bind(obj), 1000); // 这样也会输出 zhangSan
考查【this指向】、【箭头函数】
const shape = {
radius: 10,
diameter() { return this.radius * 2 },
perimeter: () => 2 * Math.PI * this.radius
}
shape.diameter(); // 20
shape.perimeter(); // NaN,因为 this 指向的是全局对象,而全局对象没有 radius 属性
考查【属性名的类型】
const arr = [1, 2];
arr[0]++;
arr["1"]++;
console.log(arr[0], arr["1"]); // 2 3
// arr:{"0":1,"a":2,length:2}
考查【类型转换】、【操作符优先级】
console.log(3>2>1); // false (3>2)=>true=>0
console.log(3<2<1); // true (3<2)=>false=>0
考查【闭包】
考查【事件循环】、【作用域】、【异步】、【闭包】
for (var i = 0; i < 3; i++) {
console.log(i);
setTimeout(() => console.log(i), i * 1000);
}
// 直接输出 0 1 2 ,然后在 0 1 2 秒后输出 3 3 3
// 因为 setTimeout 是异步的,所以在执行 setTimeout 的时候,for 循环已经执行完毕,此时 i 的值为 3
考查【异步函数】
通用版数组分类方法
const people = [
{ name: 'Alice', age: 30, sex: 'female' },
{ name: 'Bob', age: 25, sex: 'male' },
{ name: 'Char', age: 30, sex: 'male' },
{ name: 'Diana', age: 25, sex: 'female' },
{ name: 'Eva', age: 25, sex: ' female' },
{ name: 'Frank', age: 25, sex: 'male' },
{ name: 'Grace', age: 20, sex: ' female' }
]
function groupBy(arr, generateKey) {
if (typeof generateKey === 'string') {
const propName = generateKey;
generateKey = (item) => item[propName]
}
const result = {};
for (const item of arr) {
const key = generateKey(item)
if (!result[key]) {
result[key] = [];
}
result[key].push(item);
}
return result
}
console.debug(groupBy(people, "age"));
console.debug(groupBy(people, (item) => `${item.age}-${item.sex}`));
动态执行JS
变量作用域 | 同/异步 | |
---|---|---|
eval() | 当前 | 同步 |
setTimeout() | 全局 | 异步 |
Function | 全局 | 同步 |
script | 全局 | 同步 |
let a = 1;
function exec(code) {
let a =ieCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
eval(code) // a:2 sync (当前作用域 同步执行)
setTimeout(code, 0) // sync a:1 (全局作用域 异步执行)
const fn = new Function(code)
fn() // a:1 sync (全局作用域 同步执行)
const script = document.createElement("script")
script.innerHTML = code
document.body.appendChild(script) // a:1 sync (全局作用域 同步执行)
}
exec('console.log("a:",a)')
console.log(`%csync`, 'font-weight: bold; color: white; background: black;');
解构赋值
const obj = {
// name: 'demo',
age: 20,
// sex: "男"
}
let { name = "hello", age: ageObj, sex: sexObj = "未知" } = obj;
console.debug(name); // hello
console.debug(ageObj); // 20
console.debug(sexObj); // 未知
页面滚动到指定元素
使用原生html的锚点跳转<a href="#id名" />
有2个缺点:1. 影响到地址栏,会和Router起冲突 2. 指定元素只能置顶不能指定位置
因此可以使用以下JS:
// 平滑滚动到页面中间
$0.scrollIntoView({behavior:'smooth',block:'center'})
手动解析DOM树
DOM解析器DOMParser
function removeTag(fragment) {
return (
new DOMParser().parseFromString(fragment, "text/html").body.textContent ||
""
);
}
console.log(
removeTag(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div>
<ul>
<li>苹果</li>
<li>雪梨</li>
<li>香蕉</li>
</ul>
</div>
</body>
</html>
`)
);
/* 苹果
雪梨
香蕉 */
面试题
大整数相加
/**
* 大整数相加
* @param {string} a
* @param {string} b
* @return {string} 结果
* @example
* add('99', '1') => '100'
*/
function sum(a, b) {
// 确定两个数中较长的数字长度
const len = Math.max(a.length, b.length);
// 将较短的数用 0 补齐到与较长数相同的长度
a = a.padStart(len, '0');
b = b.padStart(len, '0');
// 初始化进位为 0
let carry = 0;
// 存储相加的结果
let result = '';
// 从最后一位开始逐位相加
for (let i = len - 1; i >= 0; i--) {
// 将当前位的数相加并加上上一次的进位
const sum = +a[i] + +b[i] + carry;
// 计算当前位的数字
const r = sum % 10;
// 更新进位
carry = Math.floor(sum / 10);
// 将当前位的数字加入到结果的开头
result = r + result;
}
// 如果最高位还有进位,需要将进位加到结果的开头
if (carry) {
result = carry + result;
}
// 返回最终结果
return result;
}
一些面试题
["1","2","3"],map(parse)
因为map()只要传入function 就会默认传递3个参数;而parse()则会根据第二个参数来把第一个参数转成不同的进制(只能传入0或者2到36之间,0则转成十进制)
代码题
考核Promise的封装
已知有一个异步接口函数为:
function getData(a, b, callback) {
setTimeout(function () {
callback(a + b);
}, 1000);
}
请用此定义一个新函数getData2实现:
getData2(1, 2).then((result) => {
console.log(result);
});
答案:
function getData(a, b, callback) {
setTimeout(function () {
callback(a + b);
}, 1000);
}
function getData2(a, b) {
return new Promise(function (resolve, reject) {
getData(a, b, function (result) {
resolve(result);
});
});
}
// 使用 getData2
getData2(1, 2).then((result) => {
console.log(result);
});
考核类型转换
知识点:
以下值可以自动转换为布尔值false
- 数字0
- NaN
- 空字符串""
- null / undefined
console.log([] == 0); // true——空数组 [] 被转换为空字符串 "",然后转换为数字 0,最终比较为 0 == 0
console.log([] == false); // true——隐式类型转换,它将空数组 [] 转换为一个空字符串 "",然后将空字符串转换为数字 0,最后与 false 进行比较,因此结果是 0 == false
console.log(![] == false); // true——[] 是一个空数组,在布尔上下文中被视为真值,![] 将真值取反,变成 false。
console.log(null == undefined); // true
console.log(null == 0); // false——null、undefined与其他任何值都不相等,包括 0
console.log(undefined == 0); // false——null、undefined与其他任何值都不相等,包括 0
console.log("[object Object]" == {}); // true——"[object Object]" 是一个字符串,{} 是一个空对象。当将对象与字符串进行比较时,JavaScript 会尝试将对象转换为字符串。
console.log(Number("abc") == Number("aaa")); // false——比较 NaN == NaN
考核解析url
提取 url 变量中的数据,生成 data 对象,并能够根据url 的变化得到相应的数据
// 地址
const url = "https://www.baidu.com?keyword=lzy&page=1&size=10";
// 需要生成的对象
const data = {
protocol: "https",
host: "baidu.com",
origin: "https://www.baidu.com",
query: {
keyword: "lzy",
page: 1,
size: 10,
},
};
const url = "https://www.baidu.com?keyword=lzy&page=1&size=10";
// 解析 URL
const urlObj = new URL(url);
// 提取数据
const data = {
protocol: urlObj.protocol.replace(":", ""),
host: urlObj.hostname,
origin: urlObj.origin,
query: {},
};
// 将查询参数添加到 query 对象中
urlObj.searchParams.forEach((value, key) => {
if (!isNaN(value)) {
value = parseInt(value);
}
data.query[key] = value;
});
console.log("原url:");
console.table(data);
// 模拟 URL 变化并更新数据
function updateDataFromUrl(newUrl) {
const newUrlObj = new URL(newUrl);
data.protocol = newUrlObj.protocol.replace(":", "");
data.host = newUrlObj.hostname;
data.origin = newUrlObj.origin;
data.query = {};
newUrlObj.searchParams.forEach((value, key) => {
if (!isNaN(value)) {
value = parseInt(value);
}
data.query[key] = value;
});
console.log("新url:");
console.table(data);
}
// 模拟 URL 变化
updateDataFromUrl("https://www.example.com?keyword=test&page=2&size=20");
怎么判断变量a 是否为空对象{}
(typeof a === 'object') && (a !== null) && (Object.keys(a).length === 0)
考核形参
let user = {
name: "小A",
};
function changeName(user) {
user.name = "小B";
user = {
name: "小C",
};
console.log("内部user.name:", user.name);
}
changeName(user);
console.log("外部user.name:", user.name);
/* 内部user.name: 小C
外部user.name: 小B */
考核事件循环+闭包
for (var i = 0; i < 5000; i+=1000) {
setTimeout(function () {
console.log(i);
}, i);
}
// 主线程结束后分别在第0、1、2、3、4秒后打印“5”
考核事件循环:主线程、微任务、宏任务
// 1 4 10 12 14 16 暂停5s 11 5 15 6 13 7 2 3 9 8
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => {
console.log(3);
});
});
new Promise((resolve, reject) => {
console.log(4);
resolve(5);
}).then((data) => {
console.log(data);
Promise.resolve()
.then(() => {
console.log(6);
})
.then(() => {
console.log(7);
setTimeout(() => {
console.log(8);
}, 0);
});
});
setTimeout(() => {
console.log(9);
});
console.log(10);
process.nextTick(function () {
console.log(11);
});
async function async1() {
console.log(12);
await async2();
console.log(13);
}
async function async2() {
console.log(14);
await async3();
console.log(15);
}
async function async3() {
console.log(16);
}
async1();
function delay(duration) {
let start = Date.now();
console.log("暂停5s");
while (Date.now() - start < duration) {}
}
delay(5000);
setTimeout(
() => console.log(1) // 宏仁务
, 0
);
(function () {
new Promise((resolve, reject) => resolve(2, 3))
.then((arg1, arg2, arg3) => {
console.log(arg1, arg2, arg3); // 微任务; 2 undefined undefined;因为resolve只能接受一个参数
return arg1;
})
.finally(() => {
const args = Array.prototype.slice.apply(arguments);
console.log(args.reverse()); // 微任务; [];因为finally不能接受参数
});
console.log(arguments[0]); // 主线程
})(4);
console.log(5); // 主线程
// 所以输出顺序是:4 5 2 undefined undefined [] 1
console.log("1");
const p = new Promise((resolve) => {
console.log("2");
resolve();
});
setTimeout(() => {
p.then(() => console.log("3"));
console.log("4");
}, 0);
Promise.resolve().then(() => console.log("5"));
console.log("6");
// 1, 2, 6, 5, 4, 3
考核函数作用域+undefined拼接数字/字符串
var foo = "Hello";
var num = 1;
var demo;
(function () {
var bar = "World";
console.log(foo + bar);
})();
console.log(foo + demo);
console.log(num + demo);
console.log(foo + bar);
// HelloWorld
// Helloundefined(字符串拼接undefined)
// NaN(数字拼接undefined)
// Uncaught ReferenceError: bar is not defined(函数作用域)
考核闭包
已知页面中有 5个button 元素,现执行以下代码,按照预期,当点击某个button 时,应该弹出一个对话框显示其序号(从0开始)。请问此时点击第 2个button,弹出什么内容?为什么?怎么解决?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button>Click</button>
<button>Click</button>
<button>Click</button>
<button>Click</button>
<button>Click</button>
<script>
var add_the_handlers = function (nodes) {
var i;
for (i = 0; i < nodes.length; i++) {
nodes[i].onclick = function (e) {
alert(i);
};
}
};
var buttons = document.getElementsByTagName("button");
add_the_handlers(buttons);
</script>
</body>
</html>
解决办法:使用闭包,将每个循环迭代的值保存在一个闭包作用域中。可以通过将循环的索引作为参数传递给一个自执行的函数,从而在每个迭代中创建一个新的闭包作用域。
for (i = 0; i < nodes.length; i++) {
(function (i) {
nodes[i].onclick = function (e) {
alert(i);
};
})(i);
}
考核嵌套函数作用域
var foo = function () {
var a = 3,
b = 5;
var bar = function () {
var b = 7,
c = 11;
console.log(`一:a:${a} b:${b} c:${c}`);
a += b + c;
console.log(`二:a:${a} b:${b} c:${c}`);
};
console.log(`三:a:${a} b:${b} c:${c}`);
bar();
console.log(`四:a:${a} b:${b} c:${c}`);
};
foo();
// 三:a:3 b:5 c:undefined
// 一:a:3 b:7 c:11
// 二:a:21 b:7 c:11
// 四:a:21 b:5 c:undefined
考核变量作用域和 this 指向
var name = "张三"; // 全局变量
var person = (function () {
var name = "李四"; // 函数作用域内的局部变量
return {
name: "王五",
sayName: function () {
var name = "赵六"; // 函数作用域内的局部变量
console.log(this.name);
}
}
})();
console.log(name); // 张三
console.log(person.name); // 访问 person 对象的 name 属性,结果为:王五
person.sayName(); // 调用 person 对象的 sayName 方法,此时 this 指向 person 对象,输出为:王五
person.sayName.call(window); // 显式地调用 person 对象的 sayName 方法,并将 this 绑定到 window 对象,输出为:张三
window.addEventListener("DOMContentLoaded", person.sayName); // 添加 DOMContentLoaded 事件监听,当事件触发时调用 person.sayName 方法,此时 this 指向触发事件的对象(window),输出为:undefined
考核变量提升+事件循环
console.log(a); // 这时 a 是一个函数,它已经被声明了,但是还未被赋值,所以输出 [Function: a]。
console.log(a()); // 函数 a 中并没有返回值,所以输出 undefined
var a = 1; // 将 a 重新赋值为 1
function a() {
setTimeout(function () {
console.log(2); // 微任务
}, 0);
Promise.resolve().then(function () {
console.log(3); // 宏任务
});
console.log(4);
}
console.log(a); // 1
a = 5;
console.log(a); // 5
// [Function: a] 4 undefined 1 5 3 2
考核深浅拷贝
const obj1 = {
a: 1,
b: {
c: 2,
},
};
const obj2 = Object.assign({}, obj1); // 浅拷贝
const obj3 = JSON.parse(JSON.stringify(obj1)); // 深拷贝
obj1.a = 10;
obj1.b.c = 20;
console.log(obj1); // {a:10,b:{c:20}}
console.log(obj2); // {a:1,b:{c:20}}
console.log(obj3); // {a:1,b:{c:2}}
颜色排序
按“黑色白色其他颜色”的先后顺序排序,同时遇到同样颜色则根据“size”从小到大排序
let arr = [
{ color: "black", size: 10 },
{ color: "black", size: 5 },
{ color: "white", size: 5 },
{ color: "white", size: 10 },
{ color: "red", size: 12 },
{ color: "red", size: 9 },
{ color: "blue", size: 9 },
{ color: "green", size: 6 },
{ color: "orange", size: 55 },
{ color: "orange", size: 5 },
{ color: "orange", size: 5 },
];
const colorOrder = ["black", "white", "red", "blue", "green", "orange"];
arr.sort((a, b) => {
const colorIndexA = colorOrder.indexOf(a.color);
const colorIndexB = colorOrder.indexOf(b.color);
if (colorIndexA !== colorIndexB) {
return colorIndexA - colorIndexB;
} else {
return a.size - b.size;
}
});
console.log(arr);
算法题
考核递归:将包含层级信息的平面数组转换为树状结构
function arrayToTree(arr, parentId = null) {
const tree = [];
for (const item of arr) {
if (item.parentId === parentId) {
const children = arrayToTree(arr, item.id);
if (children.length > 0) {
item.children = children;
}
tree.push(item);
}
}
return tree;
}
// 示例数据
const nestedArray = [
{ id: 1, name: "Node 1", parentId: null },
{ id: 2, name: "Node 2", parentId: null },
{ id: 3, name: "Node 1.1", parentId: 1 },
{ id: 4, name: "Node 1.2", parentId: 1 },
{ id: 5, name: "Node 2.1", parentId: 2 },
{ id: 6, name: "Node 1.1.1", parentId: 3 },
];
const tree = arrayToTree(nestedArray);
console.log(JSON.stringify(tree, null, 2));
反转字符串
编写一个JavaScript 函数,接受一个字符串作为输入,然后返回该字符串的反转版本。例如,如果输入为"Hello,World!“,则函数应该返回"IdlroW .olleH”
请注意:
1)不要使用JavaScript 内置的 reverse函数
2)考虑处理字符串中的空格和特殊字符
function reverseString(str) {
var reversed = '';
for (var i = str.length - 1; i >= 0; i--) {
reversed += str.charAt(i);
}
return reversed;
}
// 测试
var inputString = "Hello, World!";
var reversedString = reverseString(inputString);
console.log(reversedString); // 输出 "dlroW ,olleH"
贪心算法
给出一组区间,将所有重叠的区间合并为一个区间。
输入:intervals =[1.3],[2,6],[8,10],[15,18]
输出:[1,6],[8.10],[15,18]
(解释:在上面的示例中,区间 [1,3] 和 [2,6] 重叠了,因此它们被合并为 [1,6])
function mergeIntervals(intervals) {
if (intervals.length <= 1) {
return intervals;
}
// 首先按照区间的起点进行排序
intervals.sort((a, b) => a[0] - b[0]);
const mergedIntervals = [intervals[0]];
for (let i = 1; i < intervals.length; i++) {
const currentInterval = intervals[i];
const lastMergedInterval = mergedIntervals[mergedIntervals.length - 1];
// 如果当前区间的起点小于等于上一个合并区间的终点,则它们重叠
if (currentInterval[0] <= lastMergedInterval[1]) {
// 合并两个区间,取最大的终点
lastMergedInterval[1] = Math.max(lastMergedInterval[1], currentInterval[1]);
} else {
// 否则,当前区间不重叠,将其加入结果数组
mergedIntervals.push(currentInterval);
}
}
return mergedIntervals;
}
const intervals = [[1, 3], [2, 6], [8, 10], [15, 18]];
const merged = mergeIntervals(intervals);
console.log(merged);