Skip to content

https://www.toptal.com/javascript/interview-questions

闭包

Array

Object

Vue

数据深拷贝

排序算法

树结构

Css

解构赋值是深拷贝还是浅拷贝?

深拷贝:修改新变量的值不会影响原有变量的值。默认情况下基本数据类型都是深拷贝。 浅拷贝:修改新变量的值会影响原有的变量的值。默认情况下引用类型都是浅拷贝。

js
const a = {
  name: "name",
  age: 18,
  marriage: false,
  addr: { province: "sichuan", city: "chengdu" },
};

let { name, age, marriage, addr } = a;

name = "myname";
age = 26;
marriage = true;
addr.province = "shanghai";
addr.city = "shanghai";

console.log(name, age, marriage, addr);
console.log(a);
js
// myname 26 true {province: "shanghai", city: "shanghai"}
// { name: "name", age: 18, marriage: false, addr: {province: "shanghai", city: "shanghai"} }

以上例子中,我们从对象 a 中解构赋值了 name、age、marriage、addr 四个变量,分别是 string、number、boolean 、object 类型。改变这四个变量的值后,再与 a 原来的值作对比,我们发现 a 的 name,age,marriage 属性没有改变,而 addr 属性发生了改变。由此可以得出结论,解构赋值对 object 类型只是浅拷贝。 实际上,无论是使用扩展运算符(...)还是解构赋值,对于引用类型都是浅拷贝。所以在使用 splice()、concat()、...对数组拷贝时,只有当数组内部属性值不是引用类型是,才能实现深拷贝。

为什么 JS 1 小于 x 小于 5 永远是 true

今天同事问了我一道题

问:为什么输出出来了

看见这张图后相信大多数人跟我一样反应都是谁会去这么写代码!!! 我也是这么给他说的,然后他说“面试官问的”。。。-_-||

首先,面试官很聪明,先用 1<=4<=1000 来迷惑你,让人很容易就掉进区间判断的坑,接着第二个给你来个怪异的,就把你放蒙了 当然,我也蒙了

于是我先在控制台试了一下 5<3<1 不出意料的还是 true

原有基础告诉我,判断区间不能这么写,但是,还真没有这样试过,只是感觉这肯定不是判断完区间的结果。 思路如下:

5<3<1 肯定不是区间判断 按顺序执行?赋值语句从右往左,他肯定是由左往右 难道是(5<3)<1? 于是想到了 js 隐式类型转换,双等==加减用的多了,都忘了大于小于也有隐式类型转换 抽出显示器下的红宝书,查了一番 以下在第三版 p50 页

与 ECMAScript 中的其他操作符一样,当关系操作符的操作数使用了非数值时,也要进行数据转换或完成某些奇怪的操作。以下就是相应的规则。  如果两个操作数都是数值,则执行数值比较。  如果两个操作数都是字符串,则比较两个字符串对应的字符编码值。  如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较。 如果一个操作数是对象,则调用这个对象的 valueOf()方法,用得到的结果按照前面的规则执行比较.如果对象没有 valueOf()方法,则调用 toString()方法,并用得到的结果根据前面的规则执行比较。  如果一个操作数是布尔值,则先将其转换为数值,然后再执行比较。

由此可见,5<3<1是经历了(5<3)<1->false<1->0<1->true的过程

综上,对于这种代码,只要最后以为大于 1,不管前面是啥,结果都是 true,所以前面的题自然会 console.log(‘2222222’)啦

.map(Number)所展开的

.map(Number)

字符数组转数字数组

js
["1", "2", "3"].map(Number);

上述代码等价于:

js
["1", "2", "3"].map((value, index, array) => Number(value, index, array));

尽管传递了额外的参数,但是 Number 忽略了除第一个参数之外的所有内容。 String 与 Boolean 也是如此:

js
[1, 2, 3].map(String) >
  ["1", "2", "3"][("1", "2", "3")].map(Boolean) >
  [true, true, true];

.map(Number.call, Number)

js
let array =
  Array.apply(null, { length: 10 }).map(Number.call, Number) >
  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

1. Array.apply(null, {length: 10})

Array.apply(null, {length: 10})创建了一个长度为 10 的数组。

func.apply(thisArg, [argsArray]) apply()方法接收两个参数,第一个为调用时指定的上下文(context),第二个为一个数组或者一个类数组对象。 {length: 10}是一个类数组对象,长度为 10,每个元素值是 undefined。 所以Array.apply(null, {length: 10})等价于 Array(undefined, undefined, ...)。

2. .map(Number.call, Number)

Array.prototype.map(callback, thisArg) Array.prototype.map第一个参数是回调函数,第二个参数是回调函数的 this 值。 .map(Number.call, Number)等价于:

js
.map((value, index, array) => {
    return Number.call(value, index, array)
}, Number)

第一个参数 value 被当做 this:

js
.map((value, index, array) => {
    return Number.call(Number, index, array)
})

最后得到:

js
.map((value, index) => Number(index))

.map(parseInt)

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

上述代码等价于

js
[1, 2, 3].map((item, index) => {
  return parseInt(item, index);
});

进一步:

js
[parseInt(1, 0), parseInt(2, 1), parseInt(3, 2)];

parseInt 文档:mdn parseInt

parseInt(string, radix);

radix,一个介于 2 和 36 之间的整数

在基数为 undefined,或者基数为 0 或者没有指定的情况下,JavaScript 作如下处理:

  • 如果字符串 string 以”0x”或者”0X”开头, 则基数是 16 (16 进制).
  • 如果字符串 string 以”0”开头, 基数是 8(八进制)或者 10(十进制),那么具体是哪个基数由实现环境决定。ECMAScript 5 规定使用 10,但是并不是所有的浏览器都遵循这个规定。因此,永远都要明确给出 radix 参数的值。 如果字符串 string 以其它任何值开头,则基数是 10 (十进制)。
  • 如果第一个字符不能被转换成数字,parseInt 返回 NaN。

parseInt(1, 0),相当于parseInt(1, 10),值为 1

parseInt(2, 1),基数 1 不合理,经测试值为 NaN

parseInt(3, 2),基数 2 合理,但是 3 不是二进制,值为 NaN

所以结果为[1, NaN, NaN]

抽象语法树 AST

TCP 为什么断开连接四次挥手

因为 TCP 是双工通信,客户端和服务端两个方向上都有数据传输,之所以要四次挥手,因为一次挥手加确认,只代表一方不再发送数据,但另一方可能数据还没发送完,处理完后再一次挥手完成断开