JavaScript面试题集合

数据类型

null 和 undefined 的区别

浮点数精度

请问对表达式 0.1 + 0.2 === 0.3 求值,结果是什么?

0.1 加上 0.2 ,数学上结果应该是 0.3 ,因此表达式的值应该是 true 。如果你这样回答,就错啦!正确答案应该是 false 。这是为什么呢?

众所周知,计算机内部以二进制处理数据,浮点数也不例外。准确来说,计算机内部采用二进制版的科学计数法来表示浮点数。因为在二进制中,1 不能被 10 整除,十进制的 0.1 无法用二进制小数精确表示。

类比 $\frac{1}{3}$ 在十进制中,只能用无穷无尽的小数 0.3333…… 不断逼近;$\frac{1}{10}$ 在二进制中也是一个无限循环的小数,也只能不断逼近。但由于计算机表示浮点数小数部分的位数是有限的,这样就会有误差。

误差在计算的过程中会不断积累,以致出现 0.1 + 0.2 不等于 0.3 的现象,使用不当就会酿成大祸。为了避免浮点数精度不足的坑,我们可以采用高精度的十进制类库( decimal )。

更多关于浮点数精度问题,可以参考这篇文章:IEEE-754浮点数那些坑

数组去重有哪些方法

  • 双重循环(低效)
  • 利用indexOf include findIndex等方法判断(低效)
  • 利用Set进行去重(需要注意顺序)
  • 先排序,在遍历

数组的常用方法有哪些

  • forEach
  • map
  • filter
  • reduce
  • every
  • some
  • shift
  • unshift
  • push
  • pop
  • splice

Set的常用方法有哪些

  • add
  • has
  • delete
  • clear
  • entries
  • forEach
  • values

如何深拷贝对象

  • JSON.parse(JSON.stringify(xxx))
  • Object.assign(target, source1, source2)
  • 递归拷贝(环形应用)

如何将对象属性设置为不可修改

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const obj = {
  foo: 1,
};

Object.defineProperty(obj, 'foo', {
  writable: false, // 不可写
});

console.log(obj.foo);

obj.foo = 2;
console.log(obj.foo);

delete obj.foo;
console.log(obj.foo);
  • writable:是否可以写,false 表示不能修改,但可以删除
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const obj = {
  foo: 1,
};

Object.defineProperty(obj, 'foo', {
  writable: false,     // 不可写
  configurable: false, // 不可配置
});

console.log(obj.foo);

obj.foo = 2;
console.log(obj.foo);

delete obj.foo;
console.log(obj.foo);

// 报错
Object.defineProperty(obj, 'foo', {
  writable: true,     // 不可写
});
  • configurable:是否可以配置,false表示不能调整属性,也不能删除
  • enumerable:是否可以被遍历到

语法

for in VS for of

请问 for in 循环和 for of 循环有什么区别?

  • for in 循环用于遍历对象属性;
  • for of 循环用于遍历可迭代对象,即遍历容器对象中保存的元素;

for in 循环以任意顺序遍历一个对象的可枚举属性,包括继承的可枚举属性,Symbol除外:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const me = {
  name: 'fasionchan',
  org: '小菜学编程',
}

for (let field in me) {
  console.log(`${field}=${me[field]}`)
}

// name=fasionchan
// org=小菜学编程

for of 循环用于遍历可迭代对象,包括 ArrayMapSetStringTypedArrayarguments 对象等等。

1
2
3
4
5
6
7
8
9
const numbers = [10, 20, 30];

for (let number of numbers) {
  console.log(number)
}

// 10
// 20
// 30

let var const 区别

  • let: 局部作用域
  • var: 全局作用域
  • const: 常量

这个例子,变量 i 在循环外申明,生命周期覆盖整个循环。因此,改动对闭包函数可见:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const buttons = [];
let i = 0;
while (i<5) {
  buttons.push(<Button
    onClick={() => {
      console.log(`点击按钮${i}`)
    }}
  >{`按钮${i}`}</Button>)
  i++
}

// 不管点哪个按钮都是输出5

而这个例子,变量 i 在循环中申明,具有块级作用域。它的生命周期仅限于当前循环体,在后续循环中的改变,对当前循环体中的闭包函数不可见:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const buttons = [];
for (let i=0; i<5; i++) {
  buttons.push(<Button
    onClick={() => {
      console.log(`点击按钮${i}`)
    }}
  >{`按钮${i}`}</Button>)
}

// 点某个按钮输出它的序号
1
2
3
4
5
const buttons = new Array(5).fill(0).map((_, i) => <Button
  onClick={() => {
    console.log(`点击按钮${i}`)
  }}
>{`按钮${i}`}</Button>)

箭头函数和普通函数的区别

  1. this指向不同:箭头函数 this 指向外层代码块的 this ;而普通函数的 this 指向函数调用者;callapplybind 可以修改普通函数的 this 指向,但不能修改箭头函数的 this 指向;
  2. 箭头函数没有原型,不能用作构造函数,而普通函数可以;

代码题

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function foo() {
  let id = 10;
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 20;

foo();
foo.call({ id: 30 });
  • foo 第一次调用,this 指向的是 windows 对象,输出 20
  • foo 第二次调用,this 指向的是对象 {id : 30 } ,输出 30

Promise

以下两个程序分别输出什么?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function later(ms) {
  return new Promise(r => setTimeout(r, ms));
}

async function demo() {
  const start = new Date();
  const t1 = later(1000);
  const t2 = later(1000);

  await t1;
  await t2;

  // 这里会输出多少耗时?
  console.log(new Date() - start);
}

demo();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function later(ms) {
  return new Promise(r => setTimeout(r, ms));
}

async function demo() {
  const start = new Date();

  await later(1000);
  await later(1000);

  // 这里会输出多少耗时?
  console.log(new Date() - start);
}

demo();

小菜笔记】系列文章首发于公众号【小菜学编程】,敬请关注:

【小菜笔记】系列文章首发于公众号【小菜学编程】,敬请关注: