0%

有以下 3 个判断数组的方法,请分别介绍它们之间的区别和优劣Object.prototype.toString.call() 、 instanceof 以及 Array.isArray()

个人题解

1、Object.prototype.toString.call(),是使用原型上的toString()方法,输出为[Object xxxxx],通过xxxxx的内容判断当前的类型.目前大部分判断类型都是基于此方法,比较准确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log(Object.prototype.toString.call([1, 2, 3])); //[object Array]
console.log(Object.prototype.toString.call(1)); //[object Number]
console.log(Object.prototype.toString.call(NaN)); //[object String]
console.log(Object.prototype.toString.call("1")); //[object Function]
console.log(Object.prototype.toString.call(() => {})); //[object Number]
console.log(Object.prototype.toString.call(undefined)); //[object Undefined]
console.log(Object.prototype.toString.call(null)); //[object Null]
console.log(Object.prototype.toString.call({})); //[object Object]
console.log(Object.prototype.toString.call(/reg/)); //[object RegExp]
console.log(Object.prototype.toString.call(true)); //[object Boolean]
console.log(Object.prototype.toString.call(Symbol)); //[object Function]
console.log(Object.prototype.toString.call(new Set())); //[object Set]
console.log(Object.prototype.toString.call(new Map())); //[object Map]
console.log(Object.prototype.toString.call(new Date())); //[object Date]
console.log(Object.prototype.toString.call(new WeakSet())); //[object WeakSet]
console.log(Object.prototype.toString.call(new WeakMap())); //[object WeakMap]
// ....

2、instanceof是基于原型链的判断方法,一个数组会找到他的原型是不是属于array,如果是返回true

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
let fsimpleStr = "This is a simple string"; 
let fmyString = new String();
let fnewStr = new String("String created with LETructor");
let fmyDate = new Date();
let fmyObj = {};
let fmyNonObj = Object.create(null);
simpleStr instanceof String; // 返回 false, 检查原型链会找到 undefined
myString instanceof String; // 返回 true
newStr instanceof String; // 返回 true
myString instanceof Object; // 返回 true

myObj instanceof Object; // 返回 true, 尽管原型没有定义
({}) instanceof Object; // 返回 true, 同上
myNonObj instanceof Object; // 返回 false, 一种创建非 Object 实例的对象的方法

myString instanceof Date; //返回 false

myDate instanceof Date; // 返回 true
myDate instanceof Object; // 返回 true
myDate instanceof String; // 返回 false

class Parent {}
class Child extends Parent {}
let demo = new Child();
console.log(demo instanceof Child); // true
console.log(demo instanceof Parent); // true
console.log(demo instanceof Object); // true

3、Array.isArray(),Array对象上提供的判断是否是数组的方法,比较准确

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
Array.isArray([1, 2, 3]);  
// true
Array.isArray({foo: 123});
// false
Array.isArray("foobar");
// false
Array.isArray(undefined);
// false

// 下面的函数调用都返回 true
Array.isArray([]);
Array.isArray([1]);
Array.isArray(new Array());
Array.isArray(new Array('a', 'b', 'c', 'd'))
// 鲜为人知的事实:其实 Array.prototype 也是一个数组。
Array.isArray(Array.prototype);

// 下面的函数调用都返回 false
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(17);
Array.isArray('Array');
Array.isArray(true);
Array.isArray(false);
Array.isArray(new Uint8Array(32))
Array.isArray({ __proto__: Array.prototype });

相关链接

第 21 题:有以下 3 个判断数组的方法,请分别介绍它们之间的区别和优劣Object.prototype.toString.call() 、 instanceof 以及 Array.isArray()
MDN isArray
MDN instanceof

如何实现a=1a=2a=3

个人题解

1
2
3
4
5
6
7
8
9
10
11
// node
let b = 1;
Object.defineProperty(global, "a", {
get() {
return b++;
},
});

console.log(a===1); // true
console.log(a===2); // true
console.log(a===3); // true

谈谈你对TCP三次握手和四次挥手的理解

个人题解

tcp 三次握手

1、客户端:发送报文到服务器,表示我要发送请求了
2、服务端:确认客户端请求,返回确认报文,表示可以接收数据
3、客户端:确认报文,建立tcp链接

tcp 三次挥手

1、客户端:发送断开链接报文,表示我要断开链接了
2、服务端:收到断开链接报文,确认信息包是否发送完毕
3、服务端:信息发送完毕,返回确认报文
4、客户端:收到服务端到确认消息,断开链接.服务端会等待一定时间,确认没有新的消息,然后断开链接.

高赞题解

20200907103418

相关链接

第 16 题:谈谈你对 TCP 三次握手和四次挥手的理解 #15
最高赞作者github

简单讲解一下http2的多路复用

个人理解

不会

最高赞题解

在 HTTP/1 中,每次请求都会建立一次HTTP连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,即使开启了 Keep-Alive ,解决了多次连接的问题,但是依然有两个效率上的问题:

第一个:串行的文件传输。当请求a文件时,b文件只能等待,等待a连接到服务器、服务器处理文件、服务器返回文件,这三个步骤。我们假设这三步用时都是1秒,那么a文件用时为3秒,b文件传输完成用时为6秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输)
第二个:连接数过多。我们假设Apache设置了最大并发数为300,因为浏览器限制,浏览器发起的最大请求数为6,也就是服务器能承载的最高并发为50,当第51个人访问时,就需要等待前面某个请求处理完成。
HTTP/2的多路复用就是为了解决上述的两个性能问题。
在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。
帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。
多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。

结论

不会

参考

第 15 题:简单讲解一下 http2 的多路复用 #14
http1.1 与 http2 对比
HTTP/2 相比 1.0 有哪些重大改进?
HTTP/2 资料汇总

如何实现一个new

个人题解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Demo(a, b) {
this.a = a;
this.b = b;
}
Demo.prototype.test = function () {
console.log(this.a);
};

function newConstructor(cons) {
return function () {
let result = {};
cons.apply(result, arguments);
result.__proto__ = cons.prototype;
return result;
};
}

let newDemo = newConstructor(Demo);
let a = newDemo(1, 2);
a.test();
  • 试了好一会试出来的
  • 一直用prototype方法不行,挠头….
  • proto 这个方法记得不是标准写法
  • 标准写法是contructor prototype?看答案吧

最高赞题解

1
2
3
4
5
function _new(fn, ...arg) {
const obj = Object.create(fn.prototype);
const ret = fn.apply(obj, arg);
return ret instanceof Object ? ret : obj;
}

先理清楚 new 关键字调用函数都的具体过程,那么写出来就很清楚了

  • 首先创建一个空的对象,空对象的proto属性指向构造函数的原型对象
  • 把上面创建的空对象赋值构造函数内部的this,用构造函数内部的方法修改空对象
  • 如果构造函数返回一个非基本类型的值,则返回这个值,否则上面创建的对象

结论

  • proto 不是标准对象,使用Object.create(fn.prototye)创建原型对象
  • 需要判断传入的对象是不是null或者undefined来判断是否返回实例对象
  • 不需要使用必包的方式进行返回(单例模式或许可以)

相关链接

如何实现一个new
最高赞作者github

斐波那契额数列第n位 算法题

个人题解

递归的方式

1
2
3
4
5
6
7
function fibonacci(n) {
if (n === 0 || n === 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
fibonacci(40)

结论:

  • 运行时间为1360ms左右,性能比较差
  • 到45位的时候计算时间已经是15s

递归的方式,加一层缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function fibonacci(n, cache) {
if (n === 0 || n === 1) {
return n;
}
if (!cache) {
cache = {};
}
if (cache[n]) {
return cache[n];
}
cache[n] = fibonacci(n - 1, cache) + fibonacci(n - 2, cache);
return cache[n];
}
fibonacci(40)

结论:

  • 第5000位的时候是9ms,此时数值已经是超过js最大数值,为Infinity
  • 9000位左右递归栈会爆掉

循环的方式

1
2
3
4
5
6
7
8
function fibonacci(n) {
let arr = [0, 1];
for (let i = 0; i < n; i++) {
arr.push(arr[arr.length - 1] + arr[arr.length - 2]);
}
return arr[n - 1];
}
fibonacci(50)

结论:

  • 性能最佳,5000位约7毫秒

总结

  • 递归一般情况下性能比普通循环要低
  • 递归可能会爆栈
  • 除非必须要递归的方式、其他方案成本过高、能确定递归次数较少,不然不建议用递归
  • 某些文档提到尾递归可以提高性能,但是这个斐波那契额数列,没有找到使用尾递归的方式,单独开一篇尾递归的内容

Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?

个人题解

构造函数是同步执行,then的时候是异步

1
2
3
4
5
6
7
8
9
10
11
12
13
const demo = function () {
return new Promise((reslove, reject) => {
console.log(1);
reslove(2);
console.log(3);
});
};

demo().then((res) => {
console.log(res);
});

//1 3 2

相关链接

一开始底牌尽处的人,注定是输家.

(滴滴、挖财、微医、海康)JS 异步解决方案的发展历程以及优缺点

个人题解

  • callback->promise->generator/yield->async/await

    callback (最初的方案)

    最开始使用的是callback,比如浏览器端的dom事件,ajax.node上的io事件等等,但是有定位问题困难,callback hell,异常处理等问题

1
2
3
4
5
6
7
8
9
10
11
12
// 浏览器端
document.getElementById('btn').addEventListener('click',function(e){
// do something
})

// nodejs
fs.readFile('path',function(err,res)=>{
if(err){
return
}
// do something
})

promise

相比callback的回调写法,链式调用的写法更优雅,异常处理变得友好.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const demo = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("something");
}, 1000);
});
};

demo()
.then((res) => {
console.log(res); // something
return res + "then";
})
.then((res) => {
console.log(res);
throw new Error("err");
})
.catch((err) => {
console.log(err.message); // err
});

generator/yield

用的不多,不做评价.emmm 打扰了

async/await

异步终极解决方案,generator/yield语法糖,基于promise,同步的写法,同步代码的异常捕获能力.强!无敌!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const demo = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("something");
}, 1000);
});
};

async function test() {
try {
let res = await demo();
console.log(res); // some
throw new Error("error");
} catch (e) {
console.log(e); // error
}
}
test();

最高赞题解

JS 异步已经告一段落了,这里来一波小总结

1. 回调函数(callback)

setTimeout(() => {
// callback 函数体
}, 1000)
缺点:回调地狱,不能用 try catch 捕获错误,不能 return

回调地狱的根本问题在于:

  • 缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符
  • 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转)
  • 嵌套函数过多的多话,很难处理错误
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ajax('XXX1', () => {
    // callback 函数体
    ajax('XXX2', () => {
    // callback 函数体
    ajax('XXX3', () => {
    // callback 函数体
    })
    })
    })
    优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)

2. Promise

Promise就是为了解决callback的问题而产生的。

Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装

优点:解决了回调地狱的问题

1
2
3
4
5
6
7
8
9
10
ajax('XXX1')
.then(res => {
// 操作逻辑
return ajax('XXX2')
}).then(res => {
// 操作逻辑
return ajax('XXX3')
}).then(res => {
// 操作逻辑
})

缺点:无法取消 Promise ,错误需要通过回调函数来捕获

3. Generator

特点:可以控制函数的执行,可以配合 co 函数库使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function *fetch() {
yield ajax('XXX1', () => {})
yield ajax('XXX2', () => {})
yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
```
### 4. Async/await
async、await 是异步的终极解决方案

优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题

缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
```javascript
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}

下面来看一个使用 await 的例子:

let a = 0
let b = async () => {
  a = a + await 10
  console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1

对于以上代码你可能会有疑惑,让我来解释下原因

首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来
因为 await 是异步操作,后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码
同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10
上述解释中提到了 await 内部实现了 generator,其实 await 就是 generator 加上 Promise的语法糖,且内部实现了自动执行 generator。如果你熟悉 co 的话,其实自己就可以实现这样的语法糖。

本文首发于我的博客:JS异步解决方案的发展历程以及优缺点

相关链接

文章链接
最高赞作者github
最高赞作者地址 JS异步解决方案的发展历程以及优缺点
阮一峰 Javascript异步编程的4种方法

题目

已知如下数组:
var arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];
编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

个人题解

  • 递归;

    不想写

  • while循环遍历

    不想写

  • Array.prototype.flat

    1
    2
    3
    4
    5
    var arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];
    function arrayFlat(arr) {
    return Array.from(new Set(arr.flat(Infinity))).sort((a,b)=>{ return a-b});
    }
    arrayFlat(arr) //
  • 骚操作

    1
    2
    3
    4
    5
    var arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];
    function saocaozuo(arr) {
    return [...new Set(arr.toString().split(","))].sort((a, b) => a - b);
    }
    saocaozuo(arr) //

相关链接

第 11 题:将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组
Array.prototype.flat() MDN