0%

javascript-interview-questions-01

JS中的箭头函数有哪些特点?

特性 优点 缺点
语法简洁 箭头函数语法更短、更简洁,适合简单的回调函数或匿名函数。 复杂逻辑中使用隐式返回或多层嵌套时,可能降低代码可读性和可维护性。
词法作用域绑定 this 箭头函数继承定义时的 this,避免了传统函数中 this 动态绑定的问题。 无法动态绑定 this,在需要动态绑定 this 的场景(如对象方法)中不适合使用。
没有 arguments 对象 避免与普通函数中的 arguments 冲突,可以使用剩余参数(...args)代替。 缺乏 arguments 对象,在需要访问动态参数列表的场景下可能带来不便。
不能用作构造函数 避免误用为构造函数的风险(箭头函数不能通过 new 调用)。 在需要定义构造函数的场景中无法使用箭头函数。
隐式返回 单行箭头函数支持隐式返回,无需使用 return 关键字。 隐式返回在复杂逻辑中可能导致代码难以理解。
调试困难 - 箭头函数的简洁性可能导致代码可读性下降,调试和维护可能会变得困难。
不能用作生成器函数 - 箭头函数不能用作生成器函数(无法与 function* 结合使用)。
适用场景 回调函数、不需要动态绑定 this 的场景、简短的函数逻辑。 需要动态绑定 this 的场景、需要访问 arguments 对象的场景、复杂的逻辑、生成器函数场景。

JS中apply, call, bind的作用和区别是什么?

特性 apply call bind
调用方式 立即调用函数 立即调用函数 返回一个新函数
参数传递 参数以数组或类数组形式传递 参数逐个传递 参数逐个传递,可预填部分参数
适用场景 参数数量不确定时(如动态参数) 参数数量固定时 需要延迟调用或复用绑定函数时
是否修改原函数 否(返回新函数,不修改原函数)

JS中mapweakMap的区别是什么?

特性 Map WeakMap
键的类型 可以是任何类型(包括对象、原始值) 只能是对象或者non-registered symbols
值的类型 可以是任何类型 可以是任何类型
键的引用方式 强引用 弱引用
垃圾回收 键对象不会被垃圾回收 如果键对象没有其他引用,则会被回收
可迭代性 支持(可通过 keys()values() 等方法遍历) 不支持(无法直接遍历或获取大小)
获取大小 支持(通过 size 属性) 不支持
典型应用场景 通用键值对存储 私有数据存储、避免内存泄漏

简单描述一下JS中的事件循环机制?

简单描述一下JS中的Promise

以下代码输出什么?

1
[1, 2, 3].map(parseInt);

答案:[1, NaN, NaN]

解析:先看一下MDN上关于Array.prototype.map的定义,这里我们没有传递第二个参数,所以使用的是下面第一种调用方式。

1
2
map(callbackFn)
map(callbackFn, thisArg)

callbackFn接受三个参数

  • element:当前正在处理的元素
  • index:当前正在处理的元素的索引
  • array:调用map方法的数组

parseInt函数有如下两种形式,由于map传递过来的参数有三个,所以这里会调用第二种形式。

1
2
parseInt(string)
parseInt(string, radix)

所以上面的代码就变成了如下形式

1
[1, 2, 3].map((element, index, array) => parseInt(element, index));

展开之后相当于三次parseInt调用

1
2
3
parseInt(1, 0); // 1
parseInt(2, 1); // NaN
parseInt(3, 2); // NaN

注意:parseInt函数的第二个参数是进制,这个参数有如下限制

  • 如果传递的进制不在2-36之间,那么parseInt返回NaN
  • 如果不传递该参数,或者传递0,那么parseInt会根据第一个参数推断:
    • 如果第一个参数以0x或者0X开头,那么会被解析为十六进制。
    • 否则会被解析为十进制。

所以:

  • parseInt(1, 0); radix为0,根据第一个参数判断,而第一个参数1并非以0x0X开头,所以会被解析为十进制,返回1。
  • parseInt(2, 1); radix为1,不在2-36之间,返回NaN
  • parseInt(3, 2); radix为2,但是3不是二进制数,返回NaN

需要注意的是:parseInt会将第一个参数转换为字符串,然后再解析。所以parseInt(3, 2);等价于parseInt('3', 2);。可是二进制数中不可能有3这个数字,所以返回NaN。一个合法的例子是parseInt('11', 2);,这个会返回3。

大数相加

实现一个函数,输入两个字符串,返回它们的和。这两个字符串表示的数字不会以0开头,且不会以空格开头。返回的结果也不会以0开头。
这个题考察的点有以下几个

  1. 字符串如何转换为整数?
  2. 进位处理
  3. 字符串对齐处理,如何填充前导0?
  4. 如何取整。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function bigNumberAdd(a, b) {
const maxLength = Math.max(a.length, b.length);
a = a.padStart(maxLength, '0');
b = b.padStart(maxLength, '0');

let result = '';
let carry = 0;

for (let i = maxLength - 1; i >= 0; i--) {
const sum = Number(a[i]) + Number(b[i]) + carry;
// This is string concatenation, not numeric addition.
result = (sum % 10) + result; // if sum > 10, we only need the last digit.
carry = Math.floor(sum / 10); // the carry is the first digit when sum > 10.
}

// Don't forget the last carry.
if (carry > 0) {
result = carry + result;
}

return result;
}
console.log(bigNumberAdd('999', '999')); // 1998

JS中如何判断Primitive类型?

可以使用Object()函数。

1
2
3
4
5
6
7
8
function isPrimitive(value) {
return value !== Object(value);
}

// or
function isPrimitive(value) {
return value === null || (typeof value !== 'object' && typeof value !== 'function');
}

JS中有哪些方法可以判断数组类型?

  1. Array.isArray([])
  2. [] instanceof Array
  3. Object.prototype.toString.call([]) === '[object Array]'
  4. Array.prototype.isPrototypeOf([])
  5. [].__proto__ === Array.prototype
  6. [].constructor === Array

深拷贝要注意哪些问题?

  1. 循环引用
  2. 特殊对象,比如Date, RegExp, Map, Set, Function等。

以下是一个简单的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function deepCopy(obj, hash = new WeakMap()) {
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Date) return new Date(obj);
if (obj === null || typeof obj !== 'object') return obj; // primitives or function
if (hash.has(obj)) return hash.get(obj); // circular reference

let newObj = Array.isArray(obj) ? [] : {};
hash.set(obj, newObj);

for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepCopy(obj[key], hash);
}
}

return newObj;
}

// 示例对象
const obj = {
a: 1,
b: [2, 3],
c: { d: 4 },
e: new Date(),
f: /abc/,
g() {
console.log('g');
},
};

// 深拷贝
const copy = deepCopy(obj);
console.log(copy);
copy.g(); // g

类型转换,以下代码输出什么?

详情请参考+的运算规则:

1
2
3
4
console.log([] + []);
console.log([] + {});
console.log({} + []);
console.log({} + {});

输出结果是什么?

1
2
3
4
const numbers = [1, 2, 3];
numbers[4] = 4;
console.log(numbers); // 1, 2, 3, <1 empty item>, 4
console.log(numbers[3]); // undefined

以下代码输出什么?

1
2
3
4
5
6
const a = {};
const b = { key: 'b' };
const c = { key: 'c' };
a[b] = 123;
a[c] = 456;
console.log(a[b]);

答案:456
本题考察的点是:对象的键名只能是字符串或者Symbol类型,如果不是,会被转换为字符串。所以a[b]a[c]都会把键名转换为[object Object],所以a[b]a[c]实际上是同一个键,最后赋值会覆盖前面的值。