自用前端面试题集锦(JS)

402 阅读30分钟

1.JavaScript有哪些数据类型,它们区别是什么?

juejin.cn/post/694094…

2.数据类型检测的方式有哪些?

juejin.cn/post/694094…

3.判断数组的方式有哪些?

juejin.cn/post/694094…

4.null和undefined有什么区别?

juejin.cn/post/694094…

5.typeof null 的结果是什么?为什么?(null是对象么?)

juejin.cn/post/694094…

6.讲一下intanceof 操作符的实现原理?并自行实现

juejin.cn/post/694094…

typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?

35-10页-typeof vs instanceof

instanceof的原理是什么?

35-54页-instanceof的原理

7.为什么0.1+0.2 ! == 0.3?如何让其相等?(JS浮点数计算的精度问题)

juejin.cn/post/694094…

8.如何获取安全的undefined值?(讲一下void 0是什么?)

juejin.cn/post/694094…
void 0详解:blog.csdn.net/weixin_4570…

9.NaN是什么类型?(typeof NaN结果是什么?)

juejin.cn/post/694094…

10.isNaN 和 Number.isNaN 函数的区别是什么?

juejin.cn/post/694094…

11.讲一下其他各类型值转为字符串类型的规则

juejin.cn/post/694094…

12.讲一下其他各类型值转为数字类型的规则

juejin.cn/post/694094…

13.讲一下其他值到布尔类型的值的转换规则

juejin.cn/post/694094…

14.Object.is()、===、== 有什么区别?

juejin.cn/post/694094…

15.讲一下|| 和 && 操作符的执行逻辑

juejin.cn/post/694094…

16.什么是 JavaScript 中的包装类型?

juejin.cn/post/694094…

17.JS中如何进行隐式类型转换?(1+"2"运算过程中发生了什么?)

juejin.cn/post/694094…

类型转换规则

JS的类型转换有三种情况:

  • 转换为布尔值
  • 转换为数字
  • 转换为字符串

规则表:

image.png

转布尔

在条件判断时,除了 undefined , null , false , NaN , '' , 0 , -0 ,其他所有值都转为 true ,包括所有对象

对象转原始类型

对象在转换类型的时候,会调用内置的 [[ToPrimitive]] 函数,对于该函数来说,算法逻辑一般来说如下:

  1. 如果已经是原始类型了,那就不需要转换了
  2. 调用x.valueOf() ,如果转换为基础类型,就返回转换的值;
  3. 调用x.toString() ,如果转换为基础类型,就返回转换的值;
  4. 如果都没有返回原始类型,就会报错

当然你也可以重写 Symbol.toPrimitive ,该方法在转原始类型时调用优先级最高。

四则运算符

加法运算符

加法运算符不同于其他几个运算符,它有以下几个特点:

  • 运算中其中一方为字符串,那么就会把另一方也转换为字符串
  • 如果一方不是字符串或者数字,那么会将它转换为数字或者字符串

注意这种表达式:'a'++'b' 这会输出"aNaN",是字符串。
因为+'b'等于NaN,所以'a'++'b'=>'a'+NaN=>"aNaN"

其他运算符

对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字

比较运算符

  1. 如果是对象,就通过toPrimitive 转换对象
  2. 如果是字符串,就通过unicode字符索引来比较

18.+ 操作符什么时候用于字符串的拼接

juejin.cn/post/694094…

19.为什么设计了BigInt?

juejin.cn/post/694094…

20.BigInt和number有什么区别?

7-2页-BigInt和number的区别

20.Object.assign和扩展运算法是深拷贝还是浅拷贝?两者有什么区别?

juejin.cn/post/694094…

21.讲一下new操作符的实现原理(new一个对象时发生了什么?)

juejin.cn/post/694094…

22.map和Object有什么区别?为什么要有map?

区别:juejin.cn/post/694094…
需要map的原因:

  1. 对于Object,可能通过原型链访问到未定义的属性,例如constructor属性;
  2. Object的key只能是string或者symbol,不能满足很多情况在以其他类型为key进行存储的需求;
  3. Object并没有针对数据存储进行属性和接口的优化,对数据增删改查、迭代、复制以及合并等操作并不方便;
  4. Obejct不能记录键值对的插入顺序,而Map则可以确保键值对的遍历的顺序和插入顺序一致
  5. 给定同样内存大小的情况下,Map可以比Object多存储约50%的键值对;
  6. 对键值对进行大量增删查操作,Object性能表现不佳,Map表现更好。

23.讲一下map和weakMap的区别

juejin.cn/post/694094…

24.JavaScript有哪些内置对象?

juejin.cn/post/694094…

25.讲一下对JSON的理解

juejin.cn/post/694094…

26.JavaScript脚本延迟加载的方式有哪些?

juejin.cn/post/694094…

27.什么叫类数组对象?

juejin.cn/post/694094…

28.JS数组有哪些原生方法?

juejin.cn/post/694094…

29.Unicode、UTF-8、UTF-16、UTF-32有什么区别?

juejin.cn/post/694094…

30.常见运算符有哪些?计算规则是什么样的?

juejin.cn/post/694094…

31.为什么函数的arguments参数是类数组而不是数组?如何遍历类数组?

juejin.cn/post/694094…

32.什么是 DOM 和 BOM?

juejin.cn/post/694094…

33.讲一下对类数组对象的理解,类数组对象如何转化为数组?

juejin.cn/post/694094…

34.讲一下escape、encodeURI、encodeURIComponent的区别

juejin.cn/post/694094…

35.讲一下对AJAX的理解,并实现一个AJAX请求

juejin.cn/post/694094…

36.JavaScript为什么要进行变量提升?它导致了什么问题?

juejin.cn/post/694094…

37.什么是尾调用?使用尾调用有什么好处?

juejin.cn/post/694094…

38.ES6模块与CommonJS模块有什么异同?

juejin.cn/post/694094…

39.常见的dom操作有哪些?

juejin.cn/post/694094…

40.use strict是什么意思?有什么作用?

juejin.cn/post/694094…

41.如何判断一个对象的类型?

juejin.cn/post/694094…

42.讲一下强类型语言和弱类型语言的区别

juejin.cn/post/694094…

43.for...in和for...of的区别

juejin.cn/post/694094…

44.如何使用for...of遍历对象?

juejin.cn/post/694094…

45.解释性语言和编译型语言的区别

juejin.cn/post/694094…

46.ajax、axios、fetch有什么区别?

juejin.cn/post/694094…

47.数组的遍历方法有哪些?

juejin.cn/post/694094…

48.forEach和map方法有哪些区别?

juejin.cn/post/694094…

49.讲一下对原型、原型链的理解(什么是原型和原型链?)

juejin.cn/post/694094…
详解:11-1页-1.3原型/继承
详解:35-21页-原型

50.原型修改、重写(可能会看代码说结果)

juejin.cn/post/694094…

51.原型链指向(可能会看代码说结果)

juejin.cn/post/694094…

52.原型链的终点是什么?如何打印出原型链的终点?

juejin.cn/post/694094…

53.内部函数是如何访问到起外部函数的变量的?(JS的变量查找机制)

当在内部函数b中访问一个变量的时候,搜索顺序是先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依次查找,直到找到为止。如果整个作用域链上都无法找到,则返回undefined。如果函数b存在原型对象(prototype),则在查找完自身的活动对象后,先查找自身的原型对象,再继续查找。这就是JS中的变量查找机制。

53.如何获得对象非原型链上的属性?

juejin.cn/post/694094…

54.讲一下对闭包的理解(闭包是什么?闭包有什么作用?闭包有什么缺点?写一个闭包)(重点难点)

什么是闭包、作用、代码示例:juejin.cn/post/694094…
闭包的作用原理、缺点:juejin.cn/post/718706…
闭包作用详细:1-17-闭包的两大作用
机制详解:blog.leapoahead.com/2015/09/15/…

55.讲一下对作用域、作用域链的理解(作用域是什么?有哪些作用域?什么是作用域链?)

juejin.cn/post/694094…
拓展 词法作用域和动态作用域:github.com/mqyqingfeng…

56.执行上下文有哪些类型?

juejin.cn/post/694094…

57.什么是执行上下文栈?

juejin.cn/post/694094…

58.讲一下创建执行上下文的过程

juejin.cn/post/694094…

59.讲一下对this的理解(讲一下this在不同情况下指向是什么)

juejin.cn/post/694119…
其他:www.jianshu.com/p/d647aa6d1…
其他:35-13页-this

60.call() 和 apply() 的有什么区别?

juejin.cn/post/694119…

61.实现call、apply 及 bind 函数(可能会拆开考手写代码)

juejin.cn/post/694119…
参考:35-52页-手写 call、apply及bind 函数

62.有哪些异步编程的实现方式?

juejin.cn/post/694119…

63.setTimeout、Promise、Async/Await有哪些区别?()

juejin.cn/post/694119…

64.讲一下对Promise的理解

juejin.cn/post/694119…

Promise有哪几种状态?

juejin.cn/post/694119…

65.讲一下Promise的基本用法

juejin.cn/post/694119…

66.Promise解决了什么问题?

juejin.cn/post/694119…

67.Promise.all和Promise.race的区别是什么?各自使用场景是什么?

juejin.cn/post/694119…

68.手写Promise.all

blog.csdn.net/weixin_5611…

Promise 的特点是什么,分别有什么优缺点?什么是 Promise 链?Promise 构造函数执行和 then 函数执行有什么区别?

35-34页-Promise

68.讲一下对async/await 的理解

juejin.cn/post/694119…

async 及 await 的特点,它们的优点和缺点分别是什么?await 原理是什么?

35-35页-async 及 await

69.await 到底在等啥?

juejin.cn/post/694119…

70.async/await有什么优势?

juejin.cn/post/694119…

71.async/await对比Promise有什么优势?

juejin.cn/post/694119…

72.async/await 如何捕获异

juejin.cn/post/694119…

73.对象创建的方式有哪些?

juejin.cn/post/694119…

74.Object.create(obj,propertiesObject)、new Obejct()和{}有什么区别?

创建流程

  • {}创建对象时,不会调用构造函数,js引擎会创建一个空对象,然后改变this指向新创建的对象,
  • new则是需要经历以下步骤
    • 先创建空对象
    • 接着设置原型链,即设置新对象的 constructor 属性为构造函数的名称,设置新对象的__proto__属性指向构造函数的prototype对象
    • 接着使用新对象的调用函数,函数中的 this 被指向新对象,
    • 最后如果无返回值或者返回一个非对象值,则将新对象返回;如果返回值是一个新对象的话那么直接返回该对象。
  • Object.create(obj,propertiesObject)方法支持接受两个参数,obj是一个对象,是必传的,会作为新创建的对象的原型,propertiesObject是可选参数,propertiesObject的属性名称将是新创建的对象的属性名称,propertiesObject属性的值应该是属性描述符,可见Object.create()不会和new和{}一样默认继承 Object原型链的属性和方法,而是将入参指定对象继承到原型链上

{}创建对象的性能比new和 Object.create要好。

创建结果

  • {}是和new所创建出的对象在本质上没有区别,都继承了Object原型链(Obejct.prototype)的属性和方法;
  • Object.create(null)创建的对象没有任何属性和方法
  • Object.create(Object.prototype)则是指定了Object原型链作为新创建对象的原型,效果和new Obejct()和{}就是一样的了

75.对象继承的方式有哪些?

juejin.cn/post/694119…
有示例代码:blog.csdn.net/wuliyouMaoz…

76.简述下JS的继承,并举例

6-24页-简述下JS的继承,并举例

76.什么是垃圾回收?

juejin.cn/post/694119…

77.垃圾回收的方式有哪些?

juejin.cn/post/694119…

78.如何减少垃圾回收?

juejin.cn/post/694119…

V8 下的垃圾回收机制是怎么样的?

35-55页-垃圾回收机制

79.哪些情况会导致内存泄漏?

juejin.cn/post/694119…

80.说一下var let const的区别

1-15页-var&&let&&const

80.说一下var let const的区别

1-15页-var&&let&&const
14-64页-第83题

81.介绍一下节流、防抖的原理、区别和应用

1-21-介绍节流防抖原理、区别及应用

82.什么是基本类型和引用类型?基本类型和引用类型各有哪些?

juejin.cn/post/718706…

83.介绍下JS的四种设计模式,并写示例代码

6-20页-JS的四种设计模式

84.什么是函数柯里化(Currying)?函数柯里化作用?优缺点有哪些?

函数式编程详解:11-1页-1.7函数式编程

定义
leetcode.cn/leetbook/re…
实现柯里化的核心是闭包。
作用
leetcode.cn/leetbook/re…
缺点

  1. 函数嵌套多;
  2. 占内存,有可能导致内存泄漏(因为本质是配合闭包实现的);
  3. 效率差(因为使用递归);
  4. 变量存取慢,访问性很差(因为使用了arguments);

86.JavaScript和TypeScript有何区别?

7-10页-JavaScript和TypeScript的区别

87.什么是深拷贝?什么是浅拷贝?怎么实现深拷贝?

juejin.cn/post/718706…
详解:11-1页-深浅拷贝
详解:35-18页-深浅拷贝

88.模拟实现一个深拷贝,并考虑对象相互引用以及 Symbol 拷贝的情况

14-7页5-第95题

88.JS实现模板引擎变量替换(不知道题干啥意思)

13-1页-1.JS实现模板引擎变量替换

89.封装一个JS的HTTP请求库(TODO)

90.判断执行顺序

13-2页-2.判断执行顺序
13-2页-3

91.JS实现一个带并发限制的异步调度器

作者答案:13-8页-JS实现一个带并发限制的异步调度器
答案+详解:blog.csdn.net/qq_41370833…

92.介绍下 Set、Map、WeakSet 和 WeakMap 的区别

14-7页-第4题:介绍下 Set、Map、WeakSet 和 WeakMap 的区别

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

14-13页-第13题

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

14-18页-第20题

95.['1', '2', '3'].map(parseInt) 结果是什么?为什么?

14-6页-第2题

96.JS 异步解决方案的发展历程以及优缺点

14-13页-第12题

97.实现一个sleep函数

14-36页-第42题

98.实现 (5).add(3).minus(2) 功能,例: 5+3-2,结果为 6(原型相关)

14-39页-第50题

99.输出以下代码的执行结果并解释为什么

14-41页-第53题

100. 原数据{1:222, 2:123, 5:888}请把数据处理为如下结构:[222, 123, null, null, 888, null, null, null, null, null, null, null]

14-47页-第55题

101.要求设计 LazyMan 类,实现以下功能

LazyMan('Tony');  
// Hi I am Tony 
LazyMan('Tony').sleep(10).eat('lunch'); 
// Hi I am Tony  
// 等待了10 秒...  
// I am eating
lunchLazyMan('Tony').eat('lunch').sleep(10).eat('dinner'); 
// Hi I am Tony 
// I am eating lunch
// 等待了 10 秒...
// I am eating

dinerLazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food');
// Hi I am  Tony
// I am  eating lunch
// I am  eating dinner
// 等待了10s
// I am eating junk food

14-47页-第56题

102.模拟实现一个 Promise.finally

14-52页-第64题

实现一个简易版 Promise

35-39页-实现一个简易版 Promise

实现一个符合 Promise/A+ 规范的 Promise

35-40页-实现一个符合 Promise/A+ 规范的 Promise

103. a.b.c.d 和 a['b']['c']['d'],哪个性能更高?

应该是 a.b.c.d 比 a['b']['c']['d'] 性能高点,后者还要考虑 [ ] 中是变量的情况, 再者,从两种形式的结构来看,显然编译器解析前者要比后者容易些,自然也 就快一点。

104.为什么普通 for 循环的性能远远高于 forEach 的 性能?

14-56页-72题

105.输出以下代码运行结果(JS对象的创建方式)

// example 1  
var a={}, b= '123', c=123
a[b]='b';
a[c]='c';
console.log(a[b])

//example 2
var a={}, b= Symbol('123'), c=Symbol('123')
a[b]='b';
a[c]='c';
console.log(a[b])

//example 3
var a={}, b= {key:'123'}, c= {key:'456'}
a[b]='b';
a[c]='c';
console.log(a[b])

14-61页-76题

106.请实现一个 add 函数,满足以下功能(函数柯里化)(条件有点错乱,熟悉题型即可)

add(1);  
// 1add(1)(2);  
// 3add(1)(2)(3);
// 6add(1)(2, 3);
// 6add(1, 2)(3);
// 6add(1, 2, 3);
// 6

14-65页-第84题

107.实现 convert 方法,把原始list转换成树形结构,要求尽可能降低时间复杂度 (看着是算法,其实也是reduce函数的运用)

原始list如下

let list =[
{id:1,name:'部门 A',parentId:0},
{id:2,name:'部门 B',parentId:0},
{id:3,name:'部门 C',parentId:1},
{id:4,name:'部门 D',parentId:1},
{id:5,name:'部门 E',parentId:2},
{id:6,name:'部门 F',parentId:3},
{id:7,name:'部门 G',parentId:2},
{id:8,name:'部门 G',parentId:4}
]

14-69页-第88题

108.设计并实现Promise.race()

14-69页-第89题

109.写出如下代码的打印结果

function changeObjProperty(o)
o.siteUrl = "http://www.baidu.com"
o = new Object() // 这一行是迷惑点
o.siteUrl ="http://www.google.com"
}

let webSite = new Object;
changeObjProperty(webSite);
console.log(webSite.siteUrl)

输出"www.baidu.com"
webSite 属于复合数据类型,函数参数中以地址传递,修改值会影响到原始值, 但如果将其完全替换成另一个值,则原来的值不会受到影响

110.请写出如下代码的打印结果()

function Foo(){
 Foo.a = function(){
 console.log(1)
 }
 this.a = function(){
 console.log(2)
 }
}

Foo.prototype.a = function(){
  console.log(3)
}
Foo.a = function(){
  console.log(4)
}
Foo.a(); //输出4
let obj = new Foo();
obj.a(); //输出2
Foo.a(); //输出1

输出4 2 1
14-79页-第100题

111.let和const重复声明一个变量会怎么样?

会报错,重复声明

112.看代码输出(this指向问题)

image.png

113.看代码输出

for (var i = 0; i < 5; i++) {
      setTimeout(() => {
          console.log(i);
      });
} 
//输出 5 5 5 5

114.看代码输出(this问题)

function o() {
      this.name = '222'
      return {
          a() {
          console.log(this);
          },
          b: () => {
              console.log(this);
          }

} };
  o().a(); // 输出 { a: f, b: f }
  o().b(); // 输出 Window
  const c = o.a;
  const d = o.b;
  c(); //输出 Window
  d(); // 输出 Window

115.看代码输出(Promise问题)

new Promise((re, rj) => {
      re(1);
      rj(2);
      console.log(3);
  })
  .then((res) => {
          console.log(res);
      })
      .catch((err) => {
console.log(err);  
}); // 3 1 rj代码不会执行,因为promise的状态只能变更一次

116.看代码输出

const log = console.log;
log(1);
setTimeout(() => {
      log(2);
});
new Promise((r) => {
      log(3);
      r(4);
      log(5); 
}).then((res) => {
  log(res);
}); 
log(6);

(async () => {
   log(7);
   await new Promise((r) => {
       log(8);
       r(); 
     });
   log(9);
})(); // 1 3 5 6 7 8 4 9 2

117.讲一下JS bridge的通信原理

blog.csdn.net/sinat_17775…

118.手写数组flat函数实现

blog.csdn.net/weixin_4264…

119.讲一下Object.definedProPerty用法,如何设置一个不可更改对象?

17-1页-Object.definedProPerty用法

120.Event bus如果自己实现会怎么写?写一个EventEmitter

blog.csdn.net/roamingcode…

121.讲一讲js严格模式

c.biancheng.net/view/9374.h…

122.讲一下js的Iterator

blog.csdn.net/imagine_tio…

123.看代码输出结果合集(JS基础知识综合应用)

juejin.cn/post/695904…

124.列举一下JS的常见设计模式

juejin.cn/post/684490…

125.Math.min()比Math.max()大,为什么?

因为Math.min() 返回 Infinity, 而 Math.max()返回 -Infinity。

126.ES5有几种方式可以实现继承?

blog.csdn.net/weixin_3910…

127.讲一下for in && for of的区别

blog.csdn.net/qq_43796489…

128.看代码讲输出(微任务、宏任务执行顺序)

button.addEventListener('click', function() {

Promise.resolve().then(function() {

console.log('Microtask 1');

});

console.log('Task 1');

});

button.addEventListener('click', function() {

Promise.resolve().then(function() {

console.log('Microtask 2');

});

console.log('Task 2');

});

// 点击按钮时,输出 Task 1, Microtask 1, Task 2, Microtask 2

// 使用 button.click() 触发时,输出 Task 1, Task 2, Microtask 1, Microtask 2

129.如何实现私有变量?(闭包、proxy)

blog.csdn.net/sinat_17775…

130.观察者模式和发布订阅模式有什么区别?

juejin.cn/post/684490…

131.并发(concurrency)和并行(parallelism)区别

35-33页-并发与并行的区别

132.什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?

35-33页-回调函数(Callback)

133.setTimeout、setInterval、requestAnimationFrame 各有什么特点?

35-36页-常用定时器函数

134.进程与线程区别?JS单线程带来的好处?

35-44页-进程与线程

135.什么是执行栈?

35-44页-执行栈

136.为什么 0.1+0.2!=0.3?如何解决这个问题?(JS浮点数运算精度问题)

35-54页-为什么 0.1 + 0.2 != 0.3

137.实现对象的Map函数,类似Array.prototype.map

41-8页-实现对象的Map函数类似Array.prototype.map

138.实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多有两个

41-9页-实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多有两个

139.输出以下代码运行结果,为什么?如果希望每隔1s输出一个结果,应该如何改造?注意不可改动square方法

41-13页-输出以下代码运行结果,为什么?如果希望每隔1s输出一个结果,应该如何改造?注意不可改动square方法

140.介绍下Set Map WeakMap和WeakSet的区别

42-2页-介绍下Set Map WeakMap和WeakSet的区别

141.以下代买,执行会输出什么?(Promise调用顺序问题)

Promise.resolve().then(() => {
    console.log(0)
    return Promise.resolve(4)
}).then((res) => {
    console.log(res)
})

Promise.resolve().then(() => {
    console.log(1)
}).then(() => {
    console.log(2)
}).then(() => {
    console.log(3)
}).then(() => {
    console.log(5)
}).then(() =>{
    console.log(6)
})

输出结果:0 1 2 3 4 5 6

141.实现抽奖函数

47-1页-抽奖函数的实现

142.typeof的返回值有哪些?

blog.csdn.net/m0_67948827…
number,string,boolean,object,function,undefined

  1. 对于数字类型的操作数而言, typeof 返回的值是 number。比如说:typeof(1),返回的值就是number。 上面是举的常规数字,对于非常规的数字类型而言,其结果返回的也是number。比如typeof(NaN),NaN在 JavaScript中代表的是特殊非数字值,虽然它本身是一个数字类型。 在JavaScript中,特殊的数字类型还有几种:
    Infinity 表示无穷大特殊值
    NaN            特殊的非数字值
    Number.MAX_VALUE     可表示的最大数字
    Number.MIN_VALUE     可表示的最小数字(与零最接近)
    Number.NaN         特殊的非数字值
    Number.POSITIVE_INFINITY 表示正无穷大的特殊值
    Number.NEGATIVE_INFINITY 表 示负无穷大的特殊值
    以上特殊类型,在用typeof进行运算时,其结果都将是number。

  2. 对于字符串类型, typeof 返回的值是 string。比如typeof("123")返回的值是string。

  3. 对于布尔类型, typeof 返回的值是 boolean 。比如typeof(true)返回的值是boolean。

  4. 对于对象、数组、null 返回的值是 object 。比如typeof(window),typeof(document),typeof(null)返回的值都是object。

  5. 对于函数类型,返回的值是 function。比如:typeof(eval),typeof(Date)返回的值都是function。

  6. 如果运算数是没有定义的(比如说不存在的变量、函数或者undefined),将返回undefined。比如:typeof(sss)、typeof(undefined)都返回undefined。

142.请实现A函数使new A() instanceof A() == true

题干不通顺,可能有误,但考察的肯定是这部分内容www.cnblogs.com/snandy/arch…

143.'1'-['2','3']的结果是什么?(类型转换)

输出结果:NaN

144.介绍一下Service Worker

segmentfault.com/a/119000004…

145.讲一下prototype与_proto_的关系和区别

zhuanlan.zhihu.com/p/196810324

146.Array.sort()方法及实现原理

zhuanlan.zhihu.com/p/37510564

147.addEventListener和onClick的区别

  1. addEventListener可以对同一个元素绑定多个事件,执行顺序从上到下依次执行。而onclick同一个元素只能绑定一个事件,如有多个,后面的事件会覆盖前面的事件;
  2. addEventListener的第三个参数为布尔类型,默认为false,也就是执行冒泡机制,如为true,则执行捕获机制,以此控制listener的触发阶段
  3. addEventListener它对任何 DOM 元素都是有效的,而不仅仅只对 HTML 元素有效;
  4. 注册addEventListener事件时不需要写on,而onclick方式则必须加on;
  5. 在移除事件上,onclick使用的是指针指向null,例如document.onclick = null,而addEventListener则使用的是独有的移除方法removeListener;
  6. addEventListener为DOM2级事件绑定,onclick为DOM0级事件绑定;
  7. IE6 7 8只能使用attachEvent,无addEventListener

148.如何不用循环实现一个长度为80的数组,数组的每个值为数组的index

blog.csdn.net/arthurwangg…

149.分别介绍for...of,for...in,forEach及区别

www.cnblogs.com/theblogs/p/…

150.介绍数组的API:map和reduce的区别

map()方法:把回调函数中返回的值,作为一个新数组返回;
reduce()方法:对数组中的每一个元素执行一个自定义函数reducer,将其结果汇总为单个值返回。 reduce详解:blog.csdn.net/u013448372/…

151.typeof null和typeof undefined分别返回什么?

typeof null返回 'object',typeof undefined返回'undefined'

152.parseInt(071)和parseInt("071")分别输出什么?

parseInt(071)输出57,识别为八进制,执行了八进制转十进制操作;
parseInt("071")输出71,识别为十进制。
原理:www.cnblogs.com/xuan52rock/…

153.JS实现大数相加

blog.csdn.net/weixin_4572…

154.写代码实现异步按顺序输出0-9,写出能想到的解法

blog.csdn.net/ShiningDays…

155.利用闭包实现数组排序

blog.csdn.net/m0_60121436…

156.实现一个方法碾平一个数组 [[1,[2,3],4]]=>[1,2,3,4]

blog.csdn.net/xgangzai/ar…

157.实现mergePromise函数,把传进去的数组顺序先后执行,并且把返回的数据先后放到数组data中

blog.csdn.net/qq_41709082…

158.看代码说输出(原型相关)(TODO)

function Foo(){ 
this.getName = function(){
console.log('1')}; 
return this;
}

Foo.getName = function() { 
console.log('2'); 
};

Foo.prototype.getName=function(){
console.log('3');
};

var getName=function(){
console.log('4')
};

function getName(){
console.log('5');
};

// 打印
Foo.getName();
getName();
Foo().getName();
getName();
new (Foo.getName)();
(new Foo()).getName();

159.这几种继承的方式有哪些问题?(TODO)

const Shape = function() {};
const Area = function() {};

//1)
Area.prototype = Shape.prototype;
//2) 
Area.prototype = new Shape();
//3)
Area.prototype = Object.create(Shape.prototype);

160.以下代码存在问题,请指出并修复

const coder = {
  skills: ['js', 'css'],
  run: function() {
    for (var i = 0; i < skills.length - 1; ++i) {
      setTimeout(function() {
        console.log(this.skills[i] + i);
      }, 1000);
    }
  }
}

161.[]==false的结果为什么?为什么?(隐式类型转换)

结果为true,原理:blog.csdn.net/m0_57135756…

162.有 abcde 5个请求,如何顺序地输出请求结果(Promise.all)?如何尽早地输出结果,但保持顺序,比如b请求完成后,要等a也完成后才输出(需要一个数组保存各个请求的状态)(TODO)

见 205.有一系列访问的urls,给定最大并发请求数max(max小于urls的数组长度),当所有url都执行完会调用callback,写函数满足当前条件

163. 解释什么是变量提升和函数提升,并举例说明

zhuanlan.zhihu.com/p/438563024

164.看代码说输出(字节的题)

var a = 3
var i = 10
var total = 0
var res = []

function foo (a) { 
  var i = 0
  for (; i < 3; i++) {
    res[i] = function () {
      console.log(i, a)
      total = i * a
      console.log(total)
    }
  }
}

foo(1)
res[0]()
res[1]()
res[2]()

165.实现一个高阶函数,让一个API请求函数失败时自动重试指定的次数(TODO)

166.有一系列访问的urls,给定最大并发请求数max(max小于urls的数组长度),当所有url都执行完会调用callback,写函数满足当前条件(TODO)

167.用堆栈创建树(TODO)

168.以下代码存在问题,指出并修复(TODO)

const coder = {
  skills: ['js', 'css'],
  run: function() {
    for (var i = 0; i < skills.length - 1; ++i) {
      setTimeout(function() {
        console.log(this.skills[i] + i);
      }, 1000);
    }

  }
}

169.实现instanceof

blog.csdn.net/Yi2008yi/ar…

170.Proxy 与Object.defineProperty有什么区别?

blog.csdn.net/qq_58717344…

171.判断布尔条件时 例如if(XX),遵循什么规则?

除了undefined,null,false,NaN,'',0,-0,其他所有值都转为true,包括所有对象。

172.如何获取安全的安全的undefined 值

使用 void 0
zhuanlan.zhihu.com/p/572897014

173.常见的位运算符有哪些 其计算规则是什么

www.iamshuaidi.com/4989.html

174.escape、encodeURI、encodeURIComponent 有何区别?

www.zhihu.com/question/21…
1、escape和它们不是同一类

简单来说,escape是对字符串(string)进行编码(而另外两种是对URL),作用是让它们在所有电脑上可读。
编码之后的效果是%XX或者%uXXXX这种形式。
其中 ASCII字母 数字 @/+ 这几个字符不会*被编码,其余的都会。
最关键的是,当你需要对URL编码时,请忘记这个方法,这个方法是针对字符串使用的,不适用于URL。

2、最常用的encodeURI和encodeURIComponent

对URL编码是常见的事,所以这两个方法应该是实际中要特别注意的。

它们都是编码URL,唯一区别就是编码的字符范围,其中

encodeURI方法不会对下列字符编码ASCII字母 数字 ~!@#$&*()=:/,;?+'

encodeURIComponent方法不会对下列字符编码 ASCII字母 数字 ~!*()'

所以encodeURIComponent比encodeURI编码的范围更大。

实际例子来说,encodeURIComponent会把 http:// 编码成 http%3A%2F%2F 而encodeURI却不会。

175.事件代理、事件冒泡、事件捕获是什么?

事件代理:又称之为事件委托,是JavaScript中常用绑定事件的常用技巧。“事件代理”是把原本需要绑定在子元素的响应事件(click、keydown......)委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。 事件代理优点:

  1. 可以大量节省内存占用,减少事件注册;
  2. 可以实现当新增子对象时无需再次对其绑定(动态绑定事件)。

一个事件触发后,会在子元素和父元素之间传播(propagation),这种传播分成三个阶段:

  1. 捕获阶段:从window对象传导到目标节点(上层传到底层)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件;
  2. 目标阶段:在目标节点上触发,称为“目标阶段”
  3. 冒泡阶段:从目标节点传导回window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。事件代理即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层。

176.实现 add(1)(2)(3) (函数柯里化)

github.com/lgwebdream/…

177.怎样判断一个对象是否是数组,如何处理类数组对象?

判断是否数组方法:www.cnblogs.com/peerless102…
处理类数组对象:展开运算符和Array.from()都可以将类数组转换成数组,也可以用for遍历

178.扁平化一个多维数组(更像是算法题)

const flatten = (arr) => {
    return arr.reduce((cur, next) => {
        return cur.concat(Array.isArray(next) ? flatten(next) : next);
    }, []);
}

179.给定一个由url构成的数组,实现一个请求器,保证同一时刻最多只能有3个请求并发

/**
 * es6的实现方式主要采用了递归的方式
 * @param {*} limit 同一时刻并发数
 * @param {*} arrays 请求数组
 * @param {*} fn 回调函数
 * @returns 
 */
function asyncPoolEs6(limit, array, fn) {
    let taskQueue = [];
    let executingTaskQueue = [];
    let i = 0;
    const enqueue = () => {
        if (i === array.length) {
            return Promise.resolve();
        }
        const num = array[i++];
        const p = Promise.resolve().then(() => fn(num));
        taskQueue.push(p);
        // 构建一个resolved的promsie方便后面then调用
        let r = Promise.resolve();
        if (array.length >= limit) {
            const p2 = p.then(() => executingTaskQueue.splice(executingTaskQueue.indexOf(p2), 1));
            executingTaskQueue.push(p2);
            if (executingTaskQueue.length >= limit) {
                r = Promise.race(executingTaskQueue);
            }
        }
        return r.then(() => enqueue());
    }
    return enqueue().then(() => Promise.all(taskQueue));
}

180.给定一个url,将url中的get各种参数按照key-value形式返回({a: 1, b: 2, c: 3})

export const getQueryObject = url => {
    if (!url) {
        return {};
    }
    let search = url.substring(url.lastIndexOf('?') + 1);
    let obj = {};
    let reg = /([^?&=#]+)=([^?&#]*)/g;
    search.replace(reg, function (rs, $1, $2) {
        let name = $1;
        let val = $2;
        obj[name] = val || '';
        return rs;
    });
    return obj;
};

181.实现一个具备缓存能力的函数

const memorize = (fn, ctx) => {
    let cache = Object.create(null)
    ctx = ctx || this;
    return (...args) => {
        if (!cache[args]) {
            cache[args] = fn.apply(ctx, args);
        }
        return cache[args];
    }
}

182.实现一个发布订阅模式

class EventEmitter {

    constructor() {

        this.cache = {}

    }

    on(name, fn) {

        if (this.cache[name]) {

            this.cache[name].push(fn)

        } else {

            this.cache[name] = [fn]

        }

    }

    off(name, fn) {

        let tasks = this.cache[name]

        if (tasks) {

            const index = tasks.findIndex(f => f === fn || f.callback === fn)

            if (index >= 0) {

                tasks.splice(index, 1)

            }

        }

    }

    emit(name, once = false, ...args) {

        if (this.cache[name]) {

            // 创建副本,如果回调函数内继续注册相同事件,会造成死循环

            let tasks = this.cache[name].slice()

            for (let fn of tasks) {

                fn(...args)

            }

            if (once) {

                delete this.cache[name]

            }

        }

    }

}

183.定时器的执行顺序或机制

因为js是单线程的,浏览器遇到setTimeout或者setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行事件队列里面,等到浏览器执行完当前代码之后会看一下事件队列里面有没有任务,有的话才执行定时器的代码。所以即使把定时器的时间设置为0还是会先执行当前的一些代码。

184.fetch发送2次请求的原因 / fetch发送post请求的时候,为什么总是发送2次,第一次状态码是204,第二次才成功?

用fetch的post请求的时候,导致fetch 第一次发送了一个Options请求,询问服务器是否支持修改的请求头,如果服务器支持,则在第二次中发送真正的请求。

185.简述一下原型 / 构造函数 / 实例

  • 原型(prototype): 一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个JavaScript对象中都包含一个__proto__(非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。
  • 构造函数: 可以通过new来 新建一个对象的函数。
  • 实例: 通过构造函数和new创建出来的对象,便是实例。实例通过__proto__指向原型,通过constructor指向构造函数

186.JS中的堆和栈,栈和队列有什么区别?

堆(heap)和栈(stack)的区别:
堆:队列优先,先进先出;由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
栈:先进后出;动态分配的空间 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。

栈和队列的区别:
栈:只允许在表尾一端进行插入和删除,栈是先进后出;
队列:只允许在表尾一端进行插入,在表头一端进行删除, 队列是先进先出。

187.JavaScript和typescript的区别

  1. TypeScript 从核心语言方面和类概念的模塑方面对 JavaScript 对象模型进行扩展。

  2. JavaScript 代码可以在无需任何修改的情况下与 TypeScript 一同工作,同时可以使用编译器将 TypeScript 代码转换为 JavaScript。

  3. TypeScript 通过类型注解提供编译时的静态类型检查。

  4. TypeScript 中的数据要求带有明确的类型,JavaScript不要求。

  5. TypeScript 为函数提供了缺省参数值。

  6. TypeScript 引入了 JavaScript 中没有的“类”概念。

  7. TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中

188.MongoDB和MySQL的区别

数据库MongoDBMySQL
数据库模型非关系型关系型
存储方式以类JSON的文档的格式存储不同引擎有不同的存储方式
查询语句MongoDB查询方式(类似JavaScript的函数)SQL语句
数据处理方式基于内存,将热数据存放在物理内存中,从而达到高速读写不同引擎有自己的特点
成熟度新兴数据库,成熟度较低成熟度高
广泛度NoSQL数据库中,比较完善且开源,使用人数在不断增长开源数据库,市场份额不断增长
事务性仅支持单文档事务操作,弱一致性支持事务操作
占用空间占用空间大占用空间小
join操作MongoDB没有joinMySQL支持join

189.JavaScript数组去重方式

  1. 双重循环去重实现
Array.prototype.unique = function () {
  const newArray = [];
  
  for (let i = 0; i < this.length; i++) {
    for (let j = i + 1; j < this.length; j++) {
      if (this[i] === this[j]) {
        j = ++i;
      }
    }
    newArray.push(this[i]);
  }
  return newArray;
}
  1. 使用Array.prototype.indexOf()
Array.prototype.unique = function () {
  return this.filter((item, index) => {
    return this.indexOf(item) === index;
  })
}
  1. 使用Array.prototype.sort()
Array.prototype.unique = function () {
  const newArray = [];
  this.sort();
  for (let i = 0; i < this.length; i++) {
    if (this[i] !== newArray[newArray.length - 1]) {
      newArray.push(this[i]);
    }
  }
  return newArray;
}
  1. 使用Array.prototype.includes()
Array.prototype.unique = function () {
  const newArray = [];
  this.forEach(item => {
    if (!newArray.includes(item)) {
      newArray.push(item);
    }
  });
  return newArray;
}
  1. 使用Array.prototype.reduce()
Array.prototype.unique = function () {
  return this.sort().reduce((init, current) => {
    if(init.length === 0 || init[init.length - 1] !== current){
      init.push(current);
    }
    return init;
  }, []);
}
  1. 对象键值对
    基本思路:利用了对象的key不可以重复的特性来进行去重。
    但需要注意:
    1. 无法区分隐式类型转换成字符串后一样的值,比如 1 和 '1'
    2. 无法处理复杂数据类型,比如对象(因为对象作为 key 会变成 [object Object])
    3. 特殊数据,比如 'proto',因为对象的 proto 属性无法被重写

解决第一、第三点问题:

Array.prototype.unique = function () {
  const newArray = [];
  const tmp = {};
  for (let i = 0; i < this.length; i++) {
    if (!tmp[typeof this[i] + this[i]]) {
      tmp[typeof this[i] + this[i]] = 1;
      newArray.push(this[i]);
    }
  }
  return newArray;
}

解决第二点问题:

Array.prototype.unique = function () {
  const newArray = [];
  const tmp = {};
  for (let i = 0; i < this.length; i++) {
    // 使用JSON.stringify()进行序列化
    if (!tmp[typeof this[i] + JSON.stringify(this[i])]) {
      // 将对象序列化之后作为key来使用
      tmp[typeof this[i] + JSON.stringify(this[i])] = 1;
      newArray.push(this[i]);
    }
  }
  return newArray;
}
  1. 使用Map
Array.prototype.unique = function () {
  const tmp = new Map();
  return this.filter(item => {
    return !tmp.has(item) && tmp.set(item, 1);
  })
}
  1. 使用set
Array.prototype.unique = function () {
  return [...new Set(this)];
}

190.实现数组的map方法

使用循环实现

const selfMap = function(fn,context){
    let arr = Array.prototype.slice.call(this)
    let mappedArr = Array()
    for(let i=0;i<arr.length;i++){
        // 判断稀疏数组的情况
        if(!arr.hasOwnProperty(i)) continue
        mappedArr[i] = fn.call(context,arr[i],i,this)
    }
    return mappedArr
}

Array.prototype.selfMap = selfMap
[1,2,3].selfMap(number=>number*2) // 结果[2,4,6]

使用reduce实现

const selfMap = function (fn, context) {
    let arr = Array.prototype.slice.call(this)
    return arr.reduce((pre,cur,index)=>{
        return [...pre,fn.call(context,cur,index,this)]
    })
}

191.实现数组 filter 方法

循环实现数组filter方法

const selfFilter = function(fn,context){
    let arr = Array.prototype.slice.call(this)
    let filteredArr = []
    for(let i=0;i<arr.length;i++){
        if(!arr.hasOwnProperty(i)) continue
        fn.call(context,arr[i],i,this) && filteredArr.push(arr[i])
    }
    return filteredArr
}

使用 reduce 实现数组 filter 方法

const selfFilter = function (fn, context) {
    return this.reduce((pre, cur, index) => {
        return fn.call(context, cur, index, this) ? [...pre, cur] : [...pre]˝
    })
}

192.使用循环实现数组的some方法

const selfSome = function (fn, context) {
    let arr = Array.prototype.slice.call(this)
    if (!arr.length) return false
    for (let i = 0; i < arr.length; i++) {
        if (!arr.hasOwnProperty(i)) continue
        let res = fn.call(context, arr[i], i, this)
        if (res) return true
    }
    return false
}

193.使用循环实现数组的reduce方法

Array.prototype.selfReduce = function (fn, initialValue) {
    let arr = Array.prototype.slice.call(this)
    let res
    let startIndex
    if (initialValue === undefined) {
        for (let i = 0; i < arr.length; i++) {
            if (!arr.hasOwnProperty(i)) continue
            startIndex = i
            res = arr[i]
            break
        }
    } else {
        res = initialValue
    }
    for (let i = ++startIndex || 0; i < arr.length; i++) {
        if (!arr.hasOwnProperty(i)) continue
        res = fn.call(null, res, arr[i], i, this)
    }
    return res
}

194.使用reduce实现数组的flat方法

const selfFlat = function (depth = 1) {
    let arr = Array.prototype.slice.call(this)
    if (depth === 0) return arr
    return arr.reduce((pre, cur) => {
        if (Array.isArray(cur)) {
            return [...pre, ...selfFlat.call(cur, depth - 1)]
        } else {
            return [...pre, cur]
        }
    }, [])
}

195.用尽量优化的方式实现斐波那契数列

function fibonacci_DP(n) {
    let res = 1
    if (n === 1 || n === 2) return res
    n = n - 2
    let cur = 1
    let pre = 1
    while (n) {
        res = cur + pre
        pre = cur
        cur = res
        n--
    }
    return res
}

196.实现简易的CO模块

function run(generatorFunc) {
    let it = generatorFunc()
    let result = it.next()
    return new Promise(() => {
        const next = function (result) {
            if (result.done) {
                resolve(result.value)
            }
            result.value = Promise.resolve(result.value)
            result.value.then(res => {
                let result = it.next(res)
                next(result)
            }).catch(err => {
                reject(err)
            })
        }
        next(result)
    })
}

197.实现Object.assign方法

'use strict'
const isComplexDataType = (obj) => {
    return (typeof obj === 'object' || typeof obj === 'function') && obj != null
}

const selfAssign = function (target, ...source) {
    if (target == null) {
        throw new TypeError('Cannot convert undifined or null object')
    }
    return source.reduce((acc, cur) => {
        isComplexDataType(acc) || (acc = new Object(acc))
        if (cur == null) {
            return acc
        }
        [...Object.keys(cur), ...Object.getOwnPropertySymbols(cur)].forEach(key => {
            acc[key] = cur[key]
        });
        return acc
    }, target)
}

198.实现instanceof

const selfinstanceof = function (left, right) {
    let proto = Object.getPrototypeOf(left)
    while (true) {
        if (proto == null) {
            return false
        }
        if (proto === right.prototype) {
            return true
        }
        proto = Object.getPrototypeOf(proto)
    }
}

199.实现私有变量

  1. 使用 Proxy 代理所有含有 _ 开头的变量,使其不可被外部访问。通过闭包的形式保存私有变量,缺点在于类的所有实例访问的都是同一个私有变量。
const proxy = function (obj) {
    return new Proxy(obj, {
        get(target, key) {
            if (key.startsWith('_')) {
                throw new Error('private key')
            }
            return Reflect.get(target, key)
        },
        ownKeys(target) {
            return Reflect.ownKeys(target).filter(key => !key.startsWith('_'))
        }
    })
}
const Person = (function () {
    const _name = Symbol('name')
    class Person {
        constructor(name) {
            this[_name] = name
        }

        getName() {
            return this[_name]
        }
    }
    return Person
})()
  1. 通过 WeakMap 和闭包,在每次实例化时保存当前实例和所有私有变量组成的对象,外部无法访问闭包中的 WeakMap,使用 WeakMap 好处在于当没有变量引用到某个实例时,会自动释放这个实例保存的私有变量,减少内存溢出的问题。解决了上面那种闭包的缺点,每个实例都有各自的私有变量,缺点是舍弃了 class 语法的简洁性,将所有的特权方法(访问私有变量的方法)都保存在构造函数中
const Person = (function () {
    let wp = new WeakMap()
    class Person {
        constructor(name) {
            wp.set(this, { name })
        }

        getName(){
            return wp.get(this).name
        }
    }
    return Person
})()

200.实现洗牌算法

原地的洗牌算法,不需要声明额外的数组从而更加节约内存占用率,原理是依次遍历数组的元素,将当前元素和之后的所有元素中随机选取一个,进行交换。

function shuffle(arr) {
    for (let i = 0; i < arr.length; i++) {
        let randomIndex = i + Math.floor(Math.random() * (arr.length - i))
            ;[arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]]
    }
    return arr
}

201.实现单例

通过 ES6 的 Proxy 拦截构造函数的执行方法来实现的单例模式

function proxy(func) {
    let instance
    let handler = {
        construct(target, args) {
            if (!instance) {
                instance = Reflect.construct(func, args)
            }
            return instance
        }
    }
    return new Proxy(func, handler)
}

202.实现promisfy

function promisify(asyncFunc) {
    return function (...args) {
        return new Promise((resolve, reject) => {
            args.push(function callback(err, ...values) {
                if (err) {
                    return reject(err)
                } else {
                    return resolve(...values)
                }
            })
            asyncFunc.call(this, ...args)
        })
    }
}
const fsp = new Proxy(fs, {
    get(target, key) {
        return promisify(target[key])
    }
})

203.实现JSON.stringify

使用 JSON.stringify 将对象转为 JSON 字符串时,一些非法的数据类型会失真,主要表现如下:

  • 如果对象含有 toJSON 方法会调用 toJSON
  • 在数组中
    • 存在 Undefined/Symbol/Function 数据类型时会变为 null
    • 存在 Infinity/NaN 也会变成 null
  • 在对象中
    • 属性值为 Undefined/Symbol/Function 数据类型时,属性和值都不会转为字符串
    • 属性值为 Infinity/NaN ,属性值会变为 null
  • 日期数据类型的值会调用 toISOString
  • 非数组/对象/函数/日期的复杂数据类型会变成一个空对象
  • 循环引用会抛出错误

另外 JSON.stringify 还可以传入第二第三个可选参数

代码:

// 简单实现 JSON.stringify 方法

const isString = value => typeof value === 'string';
const isSymbol = value => typeof value === 'symbol'
const isUndefined = value => typeof value === 'undefined'
const isDate = obj => Object.prototype.toString.call(obj) === '[object Date]'
const isFunction = obj => Object.prototype.toString.call(obj) === '[object Function]';
const isComplexDataType = value => (typeof value === 'object' || typeof value === 'function') && value !== null;
const isValidBasicDataType = value => value !== undefined && !isSymbol(value); // 合法的基础类型
const isValidObj = obj => Array.isArray(obj) || Object.prototype.toString.call(obj) === '[object Object]';// 合法的复杂类型(对象)
const isInfinity = value => value === Infinity || value === -Infinity


// 在数组中存在 Symbol/Undefined/Function 类型会变成 null
// Infinity/NaN 也会变成 null
const processSpecialValueInArray = value =>
    isSymbol(value) || isFunction(value) || isUndefined(value) || isInfinity(value) || isNaN(value) ? null : value;

// 根据 JSON 规范处理属性值
const processValue = value => {
    if (isInfinity(value) || isNaN(value)) {
        return null
    }
    if (isString(value)) {
        return `"${value}"`
    }
    return value
};

let s = Symbol('s')
let obj = {
    str: "123",
    arr: [1, {e: 1}, s, () => {
    }, undefined,Infinity,NaN],
    obj: {a: 1},
    Infinity: -Infinity,
    nan: NaN,
    undef: undefined,
    symbol: s,
    date: new Date(),
    reg: /123/g,
    func: () => {
    },
    dom: document.querySelector('body'),
};

// obj.loop = obj

const jsonStringify = (function () {
    // 闭包 + WeakMap 防止循环引用
    let wp = new WeakMap()
    // 递归调用 jsonStringify 的都是闭包中的这个函数,而非 const 声明的 jsonStringify 函数
    return function jsonStringify(obj) {
        if (wp.get(obj)) throw new TypeError('Converting circular structure to JSON');
        let res = "";

        if (isComplexDataType(obj)) { // 复杂类型的情况
            if (obj.toJSON) return obj.toJSON; // 含有 toJSON 方法则直接调用
            if (!isValidObj(obj)) {  // 非法的复杂类型直接返回
                return
            }
            wp.set(obj, obj);

            if (Array.isArray(obj)) {  // 数组的情况
                res += "[";
                let temp = []; //声明一个临时数组用来控制属性之间的逗号
                obj.forEach((value) => {
                    temp.push(
                        isComplexDataType(value) && !isFunction(value) ?
                            jsonStringify(value) :
                            `${processSpecialValueInArray(value, true)}`
                    )
                });
                res += `${temp.join(',')}]`
            } else {  // 对象的情况
                res += "{";
                let temp = [];
                Object.keys(obj).forEach((key) => {
                    // 值是对象的情况
                    if (isComplexDataType(obj[key])) {
                        // 值是合法对象的情况
                        if (isValidObj(obj[key])) {
                            temp.push(`"${key}":${jsonStringify(obj[key])}`)
                        } else if (isDate(obj[key])) { // Date 类型调用 toISOString
                            temp.push(`"${key}":"${obj[key].toISOString()}"`)
                        } else if (!isFunction(obj[key])) { // 其余非函数类型返回空对象
                            temp.push(`"${key}":{}`)
                        }
                    } else if (isValidBasicDataType(obj[key])) {   // 值是基本类型
                        temp.push(`"${key}":${processValue(obj[key])}`)
                    }
                });
                res += `${temp.join(',')}}`
            }
        } else if (isSymbol(obj)) { // Symbol 返回 undefined
            return
        } else {
            return obj  // 非 Symbol 的基本类型直接返回
        }
        return res
    }
})();


console.log(jsonStringify(obj));
console.log(JSON.stringify(obj));

204.如何判断输入框中输入的是一个正确的网址

正则:

// vue-element-admin 上面的方法
function validURL(url) {
  const reg = /^(https?|ftp)://([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+.)*[a-zA-Z0-9-]+.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(/($|[a-zA-Z0-9.,?'\+&%$#=~_-]+))*$/;
  return reg.test(url);
}

URL类:

const isUrl = (urlStr) => {
  try {
    const { href, origin, host, hostname, pathname } = new URL(urlStr);
    return href && origin && host && hostname && pathname && true;
  } catch (e) {
    return false;
  }
};

205.有一系列访问的urls,给定最大并发请求数max(max小于urls的数组长度),当所有url都执行完会调用callback,写函数满足当前条件

使用Promise.all

Promise.all来捏合全部的请求,通过控制传入Promise.all的数组来控制并发数

示例代码:

let p1 = new Promise((resolve, reject) => {
  resolve('成功了')
})

let p2 = new Promise((resolve, reject) => {
  resolve('success')
})

let p3 = Promise.reject('失败')

Promise.all([p1,p3,p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)     
})

更完善的控制

参照 206.实现一个并发控制函数

206.实现一个并发控制函数

juejin.cn/post/702402…

207.实现一个pipe函数

www.cnblogs.com/sexintercou…

208.写一个定时器 mySetInterVal(fn, a, b),每次间隔 a,a+b,a+2b,...,a+nb 的时间执行fn,然后写一个 myClear,停止上面的定时器 mySetInterVal

function mySetInterVal(fn, a, b) {
    let time = 0;
    let timer = {
        id: -1
    };

    const start = (timeout) => {
        timer.id = setTimeout(() => {
            fn();
            time++;
            start(timeout + time * b);
        }, timeout)
    }

    start(a);

    return timer;
}

function myClear(timer) {
    clearTimeout(timer.id)
}

var a = new mySetInterVal(() => {
    console.log(`time: ${new Date().getSeconds()}`)
}, 100, 150);

myClear(a)

209.这几种继承的方式有哪些问题?

const Shape = function() {};  
const Area = function() {};
//1) 
Area.prototype = Shape.prototype; 
//2) 
Area.prototype = new Shape();
//3) 
Area.prototype = Object.create(Shape.prototype);

Area.prototype = Shape.prototype
问题:
父类构造函数原型与子类相同。修改子类原型添加方法会修改父类

Area.prototype = new Shape()
问题:

  • 父类实例属性为引用类型时,不恰当地修改会导致所有子类被修改
  • 创建父类实例作为子类原型时,可能无法确定构造函数需要的合理参数,这样提供的参数继承

Area.prototype = Object.create(Shape.prototype)
问题:
这里Area.prototype.constructor会丢失(导致会沿着原型链找到Shape), 需要重新设置为 Area。

修改:

Area.prototype = Object.create(Shape.prototype)
Area.prototype.constructor = Area

210.以下代码存在问题指出并修复

const coder = {
  skills: ['js', 'css'],
  run: function() {
    for (var i = 0; i < skills.length - 1; ++i) {
      setTimeout(function() {
        console.log(this.skills[i] + i);
      }, 1000);
    }
  }
}

分析意图是间隔一秒顺序打印出skills的全部元素。

但是setTimeout是异步执行,每一次for循环的时候,setTimeout都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里,等待执行。只有主线上的任务执行完,才会执行任务队列里的任务。也就是说它会等到for循环全部运行完毕后,才会执行console.log逻辑

修改一:使用闭包

const coder = {
    skills: ['js', 'css'],
    run: function () {
        for (var i = 0; i < skills.length - 1; ++i) {
            (function (j) {
                setTimeout(function timer() {
                    console.log(this.skills[j] + j);
                }, j * 1000);
            })(i);
        }
    }
}

修改二:拆分结构

const coder = {
    skills: ['js', 'css'],
    timer: function (i) {
        setTimeout(function timer() {
            console.log(this.skills[i] + i);
        }, i * 1000)
    },
    run: function () {
        for (var i = 0; i < skills.length - 1; ++i) {
            timer(i)
        }
    }
}

修改三:使用let

const coder = {
  skills: ['js', 'css'],
  run: function() {
    for (let i = 0; i < skills.length - 1; ++i) {
      setTimeout(function() {
        console.log(this.skills[i] + i);
      }, i*1000);
    }
  }
}

修改四:使用setTimeout第三个函数

const coder = {
    skills: ['js', 'css'],
    run: function () {
        for (let i = 0; i < skills.length - 1; ++i) {
            setTimeout(function () {
                console.log(this.skills[i] + i);
            }, i * 1000, i);
        }
    }
}

211.网络请求封装只弹一个窗怎么实现?

思路:使用单例保存弹窗标记位
单例实现:www.cnblogs.com/roseAT/p/14…

212.单例的优缺点

优点:

  1. 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
  2. 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
  3. 提供了对唯一实例的受控访问。
  4. 由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
  5. 允许可变数目的实例。
  6. 避免对共享资源的多重占用。

缺点:

  1. 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
  2. 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
  3. 单例类的职责过重,在一定程度上违背了“单一职责原则”。
  4. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

213.广告数据打点,停留3s检测,如何保证数据上报准确性?

一般广告是需要计算停留的时长,不仅仅是做3s的检测,要点就是要实现一个精准的计时器。但是setTimeout和setInterval的机制无法保证计时精确度。

setTimeout和setInterval的运行机制是,将指定的代码移出本次执行,等到下一轮Event Loop时,再检查是否到了指定时间。如果到了,就执行对应的代码;如果不到,就等到再下一轮Event Loop时重新判断。这意味着,setTimeout指定的代码,必须等到本次执行的所有代码都执行完,才会执行。 每一轮Event Loop时,都会将“任务队列”中需要执行的任务,一次执行完。setTimeout和setInterval都是把任务添加到“任务队列”的尾部。因此,它们实际上要等到当前脚本的所有同步任务执行完,然后再等到本次Event Loop的“任务队列”的所有任务执行完,才会开始执行。由于前面的任务到底需要多少时间执行完,是不确定的,所以没有办法保证,setTimeout和setInterval指定的任务,一定会按照预定时间执行。

所以不能单纯使用setTimeout和setInterval实现用户停留观看广告的精准计时,需要想办法缩小计时误差,可以以系统或者网络时间为准,在计时器每次间隔循环时,统计实际时间间隔和预期时间间隔之间的误差,并在下一次执行间隔时间中进行修正,代码示例:

function AdjustingInterval(workFunc, interval, errorFunc) {
    var that = this;
    var expected, timeout;
    this.interval = interval;

    this.start = function () {
        // 下一次计时器循环预期执行的时间戳
        expected = Date.now() + this.interval;
        timeout = setTimeout(step, this.interval);
    }

    this.stop = function () {
        clearTimeout(timeout);
    }

    function step() {
        // 计算计时器此次实际执行间隔和预期的偏差
        var drift = Date.now() - expected;
        if (drift > that.interval) {
            // 不能出现偏差比间隔要大的情况,如果出现则视为出现错误
            if (errorFunc) errorFunc();
        }
        //执行业务逻辑,比如判断是否到达3s
        workFunc();
        //更新下次计时循环预期执行的时间戳
        expected += that.interval;
        // 在下一次计时器循环中弥补上一次产生的偏差
        timeout = setTimeout(step, Math.max(0, that.interval - drift));
    }
}

214.什么叫静态作用域、动态作用域、自由变量?

静态作用域

静态作用域是指函数的作用域在函数和变量定义时就已经确定了。
JavaScript采用静态作用域的方式访问变量

动态作用域

动态作用域是指函数的作用域在运行时才确定。
bash语法就是采用动态作用域的方式访问变量

自由变量

如在全局中定义了一个变量a,然后我在函数中使用了这个a,这个a就可以称之为自由变量,可以这样理解,凡是跨了自己的作用域的变量都叫自由变量

215.描述下JS对象的属性/变量查找机制

JavaScript变量是按照作用域链来进行查找的。如果不写var,作用域链查找会将变量隐式提升至全局。

首先,当JavaScript代码执行一段可执行代码(executable code)时,会创建对应的执行上下文。包括:变量对象(Variable object,VO),作用域链(Scope chain),this。当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

216.说一下对websocket的理解?

是什么?

  • WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
  • 它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
  • Websocket是一个持久化的协议

原理简述

websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信 在websocket出现之前,web交互一般是基于http协议的短连接或者长连接 websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"。

和http比较

相同点:

  1. 都是基于tcp的,都是可靠性传输协议
  2. 都是应用层协议
  3. 都使用Request/Response模型进行连接的建立。
  4. 在连接的建立过程中对错误的处理方式相同,在这个阶段WS可能返回和HTTP相同的返回码。
  5. 都可以在网络中传输数据。

不同点:

  1. WS使用HTTP来建立连接,但是定义了一系列新的header域,这些域在HTTP中并不会使用。
  2. WS的连接不能通过中间人来转发,它必须是一个直接连接。
  3. WS连接建立之后,通信双方都可以在任何时刻向另一方发送数据。
  4. WS连接建立之后,数据的传输使用帧来传递,不再需要Request消息。
  5. WS的数据帧有序。

联系:
WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的

websocket建立过程

  1. 首先,客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等;
  2. 然后,服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据;
  3. 最后,客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。

websocket出现之前http的问题

http存在的问题

  • http是一种无状态协议,每当一次会话完成后,服务端都不知道下一次的客户端是谁,需要每次知道对方是谁,才进行相应的响应,因此本身对于实时通讯就是一种极大的障碍
  • http协议采用一次请求,一次响应,每次请求和响应就携带有大量的header头,对于实时通讯来说,解析请求头也是需要一定的时间,因此,效率也更低下
  • 最重要的是,需要客户端主动发,服务端被动发,也就是一次请求,一次响应,不能实现主动发送。

http实现long poll(长轮询)问题

  • 推送延迟。服务端数据发生变更后,长轮询结束,立刻返回响应给客户端。
  • 服务端压力。长轮询的间隔期一般很长,例如 30s、60s,并且服务端 hold 住连接不会消耗太多服务端资源。

http实现Ajax轮询问题

  • 推送延迟。
  • 服务端压力。配置一般不会发生变化,频繁的轮询会给服务端造成很大的压力。
  • 推送延迟和服务端压力无法中和。降低轮询的间隔,延迟降低,压力增加;增加轮询的间隔,压力降低,延迟增高

websocket解决的问题

一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开WebSocket连接或Server端中断连接前,不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实现了“真·长链接”,实时性优势明显。

217.说一下对Web Worder的理解

作用

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。

工作机制

主线程

1.创建worker线程

在主线程通过new命令,调用Worker()构造函数,新建一个 Worker 线程。

var worker = new Worker('work.js');

Worker()构造函数的参数是一个脚本文件,该文件就是 Worker 线程所要执行的任务。由于 Worker 不能读取本地文件,所以这个脚本必须来自网络。如果下载没有成功(比如404错误),Worker 就会失败。

2.主线程向Worker线程发布数据

主线程通过调用worker.postMessage()方法,向 Worker线程 发消息:

worker.postMessage('Hello World');
3.主线程监听Worker线程发送的信息

主线程通过worker.onmessage指定监听函数,接收子线程发回来的消息:

worker.onmessage = function (event) {
    // 获取worker线程传递过来的数据
  console.log(event.data);
}

然后通过事件对象的data属性可以获取 Worker 发来的数据

4.关闭Worker线程
worker.terminate();

Worker线程

1.监听主线程发布的数据

Worker 线程通过监听message事件。来获取主线程发布的数据

self.addEventListener('message', function (e) {
  self.postMessage( e.data);
}, 

self代表子线程自身,即子线程的全局对象。

事件对象的data属性包含主线程发来的数据

2.向主线程发布消息

Worker线程通过self.postMessage()方法用来向主线程发送消息。

self.postMessage( e.data);
3.关闭自身线程

Worker 线程 可以通过 close方法关闭Worker线程

self.close();

218.Generator的*表示什么意思?如果在yield后面加*号表示什么?

*表示这是个Generator函数。

ES6之前多个 Generator 函数嵌套,写起来就非常麻烦。ES6 提供了yield*表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数:

function* bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 等同于
function* bar() {
  yield 'x';
  for (let v of foo()) {
    yield v;
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}
// "x"
// "a"
// "b"
// "y"

219.修改一个用const定义的对象中的元素值,是否会报错?

不会。
const定义中的不变指的是指向对象的指针不变,因为修改对象中的属性并不会让指向对象的指针发生改变,所以可以改变const定义对象的属性。

220.如何中断ajax请求?

一种是设置超时时间让ajax自动断开,另一种是手动停止ajax请求,其核心是调用XML对象的abort方法,ajax.abort()

221.字符串是原始类型,为什么会有 length 方法?

包装对象:让对象能覆盖所有类型,方便调用某些方法

222.