Aiden's blog Aiden's blog
首页
  • 前端文章

    • JavaScript
    • Vue
  • 学习笔记

    • JavaScript教程
    • JavaScript高级程序设计
    • ES6 教程
    • Vue
    • Vue3.0
    • React
    • TypeScript 从零实现 axios
    • Git
    • TypeScript
    • JS设计模式总结
    • 小程序
    • 小程序云开发
    • Echarts
    • 微前端
    • H5
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 搜索引擎
  • ES系列
  • 经典面试题
  • 知识点总结
  • uni-app
  • 算法
  • Vue3实战
  • 小程序chatgpt
  • 小程序配网流程
  • 程序WIFI配网
  • 小程序WebSocket
  • H5 WebSocket
  • H5 TTS
  • Vue3实现OSS存储
  • 大文件分片上传
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Aiden Wu

前端界的小学生
首页
  • 前端文章

    • JavaScript
    • Vue
  • 学习笔记

    • JavaScript教程
    • JavaScript高级程序设计
    • ES6 教程
    • Vue
    • Vue3.0
    • React
    • TypeScript 从零实现 axios
    • Git
    • TypeScript
    • JS设计模式总结
    • 小程序
    • 小程序云开发
    • Echarts
    • 微前端
    • H5
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 搜索引擎
  • ES系列
  • 经典面试题
  • 知识点总结
  • uni-app
  • 算法
  • Vue3实战
  • 小程序chatgpt
  • 小程序配网流程
  • 程序WIFI配网
  • 小程序WebSocket
  • H5 WebSocket
  • H5 TTS
  • Vue3实现OSS存储
  • 大文件分片上传
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术文档

  • GitHub技巧

  • Nodejs

  • 博客搭建

  • 搜索引擎

  • ES系列

  • 经典面试题

    • 初级面试题
    • 进阶面试题
      • 纯函数(pure function)
        • immutable
        • mutable
      • js继承
      • 属性继承
      • 继承方法
        • 寄生组合式继承
        • 混入方式继承多个对象
        • ES6类继承extends
        • 总结
      • 函数提升高于变量提升
      • 全局函数
      • 立即执行函数的this
      • 进程和线程的关系,以及死锁是什么
        • WebWorker
        • 死锁
      • for循环
      • 如何设置0.5px的边
      • 画体型
      • 画椭圆
      • 画1/4圆
      • 画扇形
      • 画箭头
      • flex属性
      • div 垂直水平居中
      • IFC
        • IFC一般有什么用呢?
      • TDZ(暂时性死区)
        • typeof的“死区”陷阱
        • 传参的“死区”陷阱
        • 总结
      • new 命令的原理
      • 为什么不在浏览器也是用CommonJS
        • 总结
      • 解决0.1+0.2等于0.3
        • 解决办法
      • QUIC协议
        • 特点
        • 什么时候适合用QUIC
        • 总结
      • 手写promise.all
        • 验证
      • 手写深克隆
      • Undefined和Null区别
        • undefined
        • null
        • 总结
    • 高阶面试题
  • 知识点总结

  • uni-app

  • 算法

  • Vue3实战

  • 小程序chatgpt

  • 小程序WIFI配网

  • 小程序配网流程

  • 小程序WebSocket

  • H5 WebSocket

  • H5 TTS

  • Vue3实现OSS存储

  • 大文件分片上传

  • 技术
  • 经典面试题
wushengxin
2021-09-30
目录

进阶面试题

# 纯函数(pure function)

函数给予相同的输入总是返回相同的输出

# immutable

减少更改变量的机会,减少负作用

const、slice(immutable function)

# mutable

let、splice(mutable function)

# js继承

# 属性继承

image-20210927130034115

# 继承方法

Son.prototype = new Father()

Son.prototype.constructor = Son

# 寄生组合式继承

结合借用构造函数传递参数和寄生模式实现继承

function inheritPrototype(subType, superType){
  var prototype = Object.create(superType.prototype); // 创建对象,创建父类原型的一个副本
  prototype.constructor = subType;                    // 增强对象,弥补因重写原型而失去的默认的constructor 属性
  subType.prototype = prototype;                      // 指定对象,将新创建的对象赋值给子类的原型
}

// 父类初始化实例属性和原型属性
function SuperType(name){
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
  alert(this.name);
};

// 借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubType(name, age){
  SuperType.call(this, name);
  this.age = age;
}

// 将父类原型指向子类
inheritPrototype(SubType, SuperType);

// 新增子类原型属性
SubType.prototype.sayAge = function(){
  alert(this.age);
}

var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);

instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]
复制代码
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
35

img

这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和isPrototypeOf()

这是最成熟的方法,也是现在库实现的方法

# 混入方式继承多个对象

function MyClass() {
     SuperClass.call(this);
     OtherSuperClass.call(this);
}

// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;

MyClass.prototype.myMethod = function() {
     // do something
};
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Object.assign会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。

# ES6类继承extends

extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法,使用例子如下。

class Rectangle {
    // constructor
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
    
    // Getter
    get area() {
        return this.calcArea()
    }
    
    // Method
    calcArea() {
        return this.height * this.width;
    }
}

const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 输出 200

-----------------------------------------------------------------
// 继承
class Square extends Rectangle {

  constructor(length) {
    super(length, length);
    
    // 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
    this.name = 'Square';
  }

  get area() {
    return this.height * this.width;
  }
}

const square = new Square(10);
console.log(square.area);
// 输出 100
复制代码
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
35
36
37
38
39
40
41
42

extends继承的核心代码如下,其实现和上述的寄生组合式继承方式一样

function _inherits(subType, superType) {
  
    // 创建对象,创建父类原型的一个副本
    // 增强对象,弥补因重写原型而失去的默认的constructor 属性
    // 指定对象,将新创建的对象赋值给子类的原型
    subType.prototype = Object.create(superType && superType.prototype, {
        constructor: {
            value: subType,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    
    if (superType) {
        Object.setPrototypeOf 
            ? Object.setPrototypeOf(subType, superType) 
            : subType.__proto__ = superType;
    }
}
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 总结

1、函数声明和类声明的区别

函数声明会提升,类声明不会。首先需要声明你的类,然后访问它,否则像下面的代码会抛出一个ReferenceError。

let p = new Rectangle(); 
// ReferenceError

class Rectangle {}
复制代码
1
2
3
4
5

2、ES5继承和ES6继承的区别

  • ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.call(this)).
  • ES6的继承有所不同,实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this。因为子类没有自己的this对象,所以必须先调用父类的super()方法,否则新建实例报错。

# 函数提升高于变量提升

首先, js有变量提升和函数提升,指的是用 var声明变量 或用 function 函数名(){ } 声明的,会在 js预解析 阶段提升到顶端;(es6的let 和 const 不会提升)

● 其次,函数提升优先级 高于 变量提升

console.log(foo);
var foo = 1 //变量提升
console.log(foo)
foo()
function foo(){ 
    //函数提升
    console.log('函数')
}
1
2
3
4
5
6
7
8

等价于

function foo(){ //提到顶端 
    console.log('函数')
}
var foo 
console.log(foo) 
//输出foo这个函数,因为上面foo没有被赋值,foo还是原来的值 
foo = 1; 
//赋值不会提升,赋值后 foo就不再是函数类型了,而是number类型
console.log(foo) //输出1
foo() //这里会报错,因为foo不是函数了
1
2
3
4
5
6
7
8
9
10

# 全局函数

js的全局属性:Infinity、NAN、undefined

JavaScript 中包含以下 7 个全局函数,用于完成一些常用的功能(以后的章节中可能会用到):

escape( )、eval_r( )、isFinite( )、isNaN( )、parseFloat( )、parseInt( )、unescape( )。

# 立即执行函数的this

指向window

# 进程和线程的关系,以及死锁是什么

  • 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
  1. Browser进程:浏览器的主进程(负责协调、主控),只有一个。作用有
    • 负责浏览器界面显示,与用户交互。如前进,后退等
    • 负责各个页面的管理,创建和销毁其他进程
    • 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上
    • 网络资源的管理,下载等
  2. 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
  3. GPU进程:最多一个,用于3D绘制等
  4. 浏览器渲染进程(浏览器内核)(Renderer进程,内部是多线程的):默认每个Tab页面一个进程,互不影响。主要作用为
    • 页面渲染,脚本执行,事件处理等

强化记忆:在浏览器中打开一个网页相当于新起了一个进程(进程内有自己的多线程)

当然,浏览器有时会将多个进程合并(譬如打开多个空白标签页后,会发现多个空白标签页被合并成了一个进程)

# WebWorker

  • 创建Worker时,JS引擎向浏览器申请开一个子线程(子线程是浏览器开的,完全受主线程控制,而且不能操作DOM)
  • JS引擎线程与worker线程间通过特定的方式通信(postMessage API,需要通过序列化对象来与线程交互特定的数据)

所以,如果有非常耗时的工作,请单独开一个Worker线程,这样里面不管如何翻天覆地都不会影响JS引擎主线程, 只待计算出结果后,将结果通信给主线程即可,perfect!

而且注意下,JS引擎是单线程的,这一点的本质仍然未改变,Worker可以理解是浏览器给JS引擎开的外挂,专门用来解决那些大量计算问题。

# 死锁

当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁。

环路等待条件指都是,在死锁发生的时候,两个线程获取资源的顺序构成了环形链。

比如,线程 A 已经持有资源 2,而想请求资源 1, 线程 B 已经获取了资源 1,而想请求资源 2,这就形成资源请求等待的环形图

img

个人整理了一些资料,有需要的朋友可以直接点击领取。

# 避免死锁问题的发生

资源有序分配法

我们先要清楚线程 A 获取资源的顺序,它是先获取互斥锁 A,然后获取互斥锁 B。

所以我们只需将线程 B 改成以相同顺序地获取资源,就可以打破死锁了。

# 总结

简单来说,死锁问题的产生是由两个或者以上线程并行执行的时候,争夺资源而互相等待造成的。

死锁只有同时满足互斥、持有并等待、不可剥夺、环路等待这四个条件的时候才会发生。

所以要避免死锁问题,就是要破坏其中一个条件即可,最常用的方法就是使用资源有序分配法来破坏环路等待条件。

# for循环

逗号表达式只有最后一项是有效的,即对于i<10,j<6; 来说,判断循环是否结束的是 j < 6;而对于 j<6,i<10; 来说,判断循环是否结束的是 i < 10。

var k = ``0``;``for``(var i=``0``,j=``0``;i<``10``,j<``6``;i++,j++){`` ``k += i + j;``}``console.log(k)  ``// 打印结果为 30
1
var k = 0;
for(var i=0,j=0;i<10,j<6;i++,j++){
  k += i + j;
}
console.log(k)    // 打印结果为 30
var k = 0;
for(var i=0,j=0;j<6,i<10;i++,j++){
  k += i + j;
}
console.log(k) //打印结果为 90
1
2
3
4
5
6
7
8
9
10

# 如何设置0.5px的边

.hr.scale-half {
    height: 1px;
    transform: scaleY(0.5);
    transform-origin: 50% 100%;
}
1
2
3
4
5
.hr.scale-half {
    box-shadow:0 0.5px 0 #000;
    height: 1px;
}
1
2
3
4

# 画体型

<style>
        .trapezoid {
            width: 50px;
            height: 50px;
            background: #ff0;
            border-top: 50px solid #f00;
            border-bottom: 50px solid #00f;
            border-left: 50px solid #0f0;
            border-right: 50px solid #0ff;
        }
    <div class="trapezoid"></div>
</style>
1
2
3
4
5
6
7
8
9
10
11
12

# 画椭圆

高度设置为宽度的一半

.container{
    width: 100px;
    height: 50px;
    border-radius: 50px/25px;
    background-color: #f00;
}
1
2
3
4
5
6

# 画1/4圆

.sector1 {
    border-radius:100px 0 0;
    width: 100px;
    height: 100px;
    background: #00f;
}
<div class="sector1"></div>
1
2
3
4
5
6
7

# 画扇形

<style>
    .sector2 {
        border: 100px solid transparent;
        width: 0;
        border-radius: 100px;
        border-top-color: #f00;
    }
    <div class="sector2"></div>
</style>
1
2
3
4
5
6
7
8
9

# 画箭头

<style>
        .arrow{
            width: 0;
            height: 0;
            border: 50px solid;
            border-color: transparent #0f0 transparent transparent;
            position: relative;
        }
        .arrow::after{
            content: '';
            position: absolute;
            /* right: -55px;
             top: -50px;*/
            transform:translate(-45%,-50%);
            border: 50px solid;
            border-color: transparent #fff transparent transparent;
        }
        
        <div class="arrow"></div>
</style>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# flex属性

  • 弹性盒布局,CSS3 的新属性,用于方便布局,比如垂直居中
  • flex属性是 flex-grow、flex-shrink 和 flex-basis 的简写,默认为0 1 auto,分别表示放大、缩小以及自身内容决定
  • flex:1改变的是flex-grow为1,并且auto为任意值即,flex:1===flex:1 1 除了auto任意值

# div 垂直水平居中

并完成 div 高度永远是宽度的一半(宽度可以不指定)

实现方式:padding-bottom: 50%;继承父元素宽度的一半,不要设置height

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        margin: 0;
        padding: 0;
      }

      html,
      body {
        width: 100%;
        height: 100%;
      }

      .outer {
        width: 400px;
        height: 100%;
        background: blue;
        margin: 0 auto;

        display: flex;
        align-items: center;
      }

      .inner {
        position: relative;
        width: 100%;
        height: 0;
        padding-bottom: 50%;
        background: red;
      }

      .box {
        position: absolute;
        width: 100%;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
      }
    </style>
  </head>
  <body>
    <div class="outer">
      <div class="inner">
        <div class="box">hello</div>
      </div>
    </div>
  </body>
</html>

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# IFC

行内格式化上下文,vertical:middle;text-align:center;

# IFC一般有什么用呢?

水平居中:当一个块要在环境中水平居中时,设置其为inline-block则会在外层产生IFC,通过text-align则可以使其水平居中。

# TDZ(暂时性死区)

如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。 总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”

if (true) {
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError

  let tmp; // TDZ结束
  console.log(tmp); // undefined

  tmp = 123;
  console.log(tmp); // 123
}
复制代码
1
2
3
4
5
6
7
8
9
10
11
12

上面代码中,在let命令声明变量tmp之前,都属于变量tmp的“死区”。

# typeof的“死区”陷阱

我们都知道使用typeof 可以用来检测给定变量的数据类型,也可以使用它来判断值是否被定义。当返回undefined时表示值未定义; 但是在const/let定义的变量在变量声明之前如果使用了typeof就会报错

typeof x; // ReferenceError
let x;
复制代码
1
2
3

因为x是使用let声明的,那么在x未声明之前都属于暂时性死区,在使用typeof时就会报错。所以在使用let/const进行声明的变量在使用typeof时不一定安全喔。

typeof y; // 'undefined'
复制代码
1
2

但是可以看出,如果我们只是用了typeof而没有去定义,也是不会报错的,从这粒可以看出只要没有明确规定使用const/let定义之前就是不会出错。

# 传参的“死区”陷阱

例如下面一段代码,我们在使用

function bar(x = y, y = 2) {
  return [x, y];
}

bar(); // 报错
1
2
3
4
5

上面代码中,调用bar函数之所以报错(某些实现可能不报错),是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。如果y的默认值是x,就不会报错,因为此时x已经声明了。

function bar(x = 2, y = x) {
  return [x, y];
}
bar(); // [2, 2]
1
2
3
4

使用var和let声明的另外一种区别。

// 不报错
var x = x;

// 报错
let x = x;
// ReferenceError: x is not defined
1
2
3
4
5
6

受“死区”的影响,使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“。

# 总结

ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。

总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。

# new 命令的原理

使用new命令时,它后面的函数依次执行下面的步骤。

  1. 创建一个空对象,作为将要返回的实例对象。
  2. 将这个空对象的原型,指向构造函数的prototype属性。
  3. 将这个空对象赋值给函数内部的this关键字。
  4. 开始执行构造函数内部的代码。(代码中this指向空对象(实例对象))
  5. 返回实例对象(或自定义对象)

# 为什么不在浏览器也是用CommonJS

回答这个问题之前,我们首先要清楚一个事实,CommonJS的 require 语法是同步的,当我们使用require 加载一个模块的时候,必须要等这个模块加载完后,才会执行后面的代码。如果知道这个事实,那我们的问题也就很容易回答了。NodeJS 是服务端,使用 require 语法加载模块,一般是一个文件,只需要从本地硬盘中读取文件,它的速度是比较快的。但是在浏览器端就不一样了,文件一般存放在服务器或者CDN上,如果使用同步的方式加载一个模块还需要由网络来决定快慢,可能时间会很长,这样浏览器很容易进入“假死状态”。所以才有了后面的AMD和CMD模块化方案,它们都是异步加载的,比较适合在浏览器端使用。

好了,解决了第一个疑问后,我们开始进入正题。

它们最大的两个差异就是:

  • CommonJS模块输出的是一个值的拷贝,ES6 模块输出的是值的引用;
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

我们先来看第一个差异。

CommonJS输出的是值的拷贝,换句话说就是,一旦输出了某个值,如果模块内部后续的变化,影响不了外部对这个值的使用。具体例子:

// lib.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};
1
2
3
4
5
6
7
8
9

然后我们在其它文件中使用这个模块:

var mod = require('./lib');
console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 3
1
2
3
4

上面的例子充分说明了如果我们对外输出了counter 变量,就算后续调用模块内部的incCounter 方法去修改它的值,它的值依旧没有变化。

ES6模块运行机制完全不一样,JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行的时候,再根据这个只读引用,到被加载的那个模块里去取值。

// lib.js
export let counter = 3;
export function incCounter() {
  counter++;
}

// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
1
2
3
4
5
6
7
8
9
10
11

上面代码说明,ES6 模块import的变量counter是可变的,完全反应其所在模块lib.js内部的变化。

# 总结

写到这里,本文也就基本结束了。我们总结一下文中涉及到的内容:

  • 因为CommonJS的require语法是同步的,所以就导致了CommonJS模块规范只适合用在服务端,而ES6模块无论是在浏览器端还是服务端都是可以使用的,但是在服务端中,还需要遵循一些特殊的规则才能使用 ;
  • CommonJS 模块输出的是一个值的拷贝,而ES6 模块输出的是值的引用;
  • CommonJS 模块是运行时加载,而ES6 模块是编译时输出接口,使得对JS的模块进行静态分析成为了可能;
  • 因为两个模块加载机制的不同,所以在对待循环加载的时候,它们会有不同的表现。CommonJS遇到循环依赖的时候,只会输出已经执行的部分,后续的输出或者变化,是不会影响已经输出的变量。而ES6模块相反,使用import加载一个变量,变量不会被缓存,真正取值的时候就能取到最终的值;
  • 关于模块顶层的this指向问题,在CommonJS顶层,this指向当前模块;而在ES6模块中,this指向undefined;
  • 关于两个模块互相引用的问题,在ES6模块当中,是支持加载CommonJS模块的。但是反过来,CommonJS并不能requireES6模块,在NodeJS中,两种模块方案是分开处理的。

# 解决0.1+0.2等于0.3

计算机是通过二进制的方式存储数据的,所以计算机计算0.1+0.2的时候,实际上是计算的两个数的二进制的和。0.1的二进制是0.0001100110011001100...(1100循环),0.2的二进制是:0.00110011001100...(1100循环),这两个数的二进制都是无限循环的数。

# 解决办法

属性:Number.EPSILON,而这个值正等于2^-52。这个值非常非常小,在底层计算机已经帮我们运算好,并且无限接近0,但不等于0,。这个时候我们只要判断(0.1+0.2)-0.3小于

Number.EPSILON`,在这个误差的范围内就可以判定0.1+0.2===0.3为`true
1
 function numbersequal(a,b){ 
   return Math.abs(a-b)<Number.EPSILON;
 } 
 var a=0.1+0.2, b=0.3;
 console.log(numbersequal(a,b)); //true
1
2
3
4
5

但是这里要考虑兼容性的问题了,在chrome中支持这个属性,但是IE并不支持(笔者的版本是IE10不兼容),所以我们还要解决IE的不兼容问题。

Number.EPSILON=(function(){     //解决兼容性问题
      return Number.EPSILON?Number.EPSILON:Math.pow(2,-52);
 })();
1
2
3

# QUIC协议

QUIC(Quick UDP Internet Connections),直译过来就是“快速的 UDP 互联网连接”,是 Google 基于 UDP 提出的一种改进的通信协议,作为传统 HTTP over TCP 的替代品,开源于 Chromium 项目中。

为了加快 TCP 的传输效率,Google 提出了 BBR 拥塞控制算法,将 TCP 的性能发挥到了极致。由于 TCP 和 UDP 协议是系统内核实现的。

# 特点

QUIC 就诞生了,它汇集了 TCP 和 UDP 的优点,使用 UDP 来传输数据以加快网络速度,降低延迟,由 QUIC 来保证数据的顺序、完整性和正确性,即使发生了丢包,也由 QUIC 来负责数据的 纠错。

# 什么时候适合用QUIC

移动端 由于 QUIC 并不使用 IP + 端口来标识客户身份,而是使用 ID,这使得在网络环境切换后还可以保持连接,非常适合用在移动网站上面,在手机信号不稳定的情况下,TCP + TLS 的开销是非常大的!QUIC 的 0-RTT 可以极大限度地提升访问速度。

# 总结

QUIC 实现的目标,就是利用 UDP 实现一个 TCP,支持 TCP 的所有特性,并且比 TCP 更快更好用。

QUIC 是从 2012 年开始的项目,到目前也还只是草案阶段,并且同样处于草案阶段的 TLS1.3 也同样拥有了 QUIC 中的很多优点(比如 0-RTT)。对于访问速度的优化方式越来越多,适当的选择可以为网站增色许多。

# 手写promise.all

function diPromiseAll(promises){
    return new Promise((resolve, reject)=>{
        // 参数判断
        if(!Array.isArray(promises)){
            throw new TypeError("promises must be an array")
        }
        let count = 0 // 记录有几个resolved
        let len = promises.length
        let result = Array(len) // 存放结果
        for(let i=0;i<len;i++){
            Promise.resolve(promises[i]).then((value) => {
                count++
                result[i] = value
                if(count===len){
                    return resolve(result)
                }
            },(reason)=>{
                return reject(reason)
            })
        }
    })
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 验证

let p1 = Promise.resolve(1),
    p2 = Promise.resolve(2),
    p3 = Promise.resolve(3);

diPromiseAll([p1, p2, p3]).then((res)=>{
    console.log(res, 'res')
}, (err)=>{
    console.log(err, 'err')
})
// [1, 2, 3]

1
2
3
4
5
6
7
8
9
10
11

# 手写深克隆

//标准的深拷贝 => 引用数据类型(数组,对象)
function deepClone(source){
    const target = source.constructor === Array ? [] : {}
    for(let keys in source){
        if(source.hasOwnProperty(keys)){
            if(source[keys] && typeof source[keys] === 'object'){
                target[keys] = source[keys].constructor === Array ? [] : {}
                target[keys] = deepClone(source[keys])
            }else{
                target[keys] = source[keys]
            }
        }
    }
    retrun target
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# Undefined和Null区别

# undefined

undefined 的字面意思就是:未定义的值 。这个值的语义是,希望表示一个变量最原始的状态,而非人为操作的结果 。 这种原始状态会在以下 4 种场景中出现:

# 1、声明一个变量,但是没有赋值

var foo;
console.log(foo); // undefined
复制代码
1
2
3

访问 foo,返回了 undefined,表示这个变量自从声明了以后,就从来没有使用过,也没有定义过任何有效的值。

# 2、访问对象上不存在的属性或者未定义的变量

console.log(Object.foo); // undefined
console.log(typeof demo); // undefined
复制代码
1
2
3

访问 Object 对象上的 foo 属性,返回 undefined , 表示Object 上不存在或者没有定义名为 foo 的属性;对未声明的变量执行typeof操作符返回了undefined值。

# 3、函数定义了形参,但没有传递实参

//函数定义了形参 a
function fn(a) {
    console.log(a); // undefined
}
fn(); //未传递实参
复制代码
1
2
3
4
5
6

函数 fn 定义了形参 a,但 fn 被调用时没有传递参数,因此,fn 运行时的参数 a 就是一个原始的、未被赋值的变量。

# 4、使用void对表达式求值

void 0 ; // undefined
void false; // undefined
void []; // undefined
void null; // undefined
void function fn(){} ; // undefined
复制代码
1
2
3
4
5
6

ECMAScript 明确规定 void 操作符 对任何表达式求值都返回 undefined ,这和函数执行操作后没有返回值的作用是一样的,JavaScript 中的函数都有返回值,当没有 return 操作时,就默认返回一个原始的状态值,这个值就是 undefined,表明函数的返回值未被定义。

# null

null 的字面意思是:空值 。这个值的语义是,希望表示一个对象被人为的重置为空对象,而非一个变量最原始的状态 。 在内存里的表示就是,栈中的变量没有指向堆中的内存对象。

# 1、一般在以下两种情况下我们会将变量赋值为null

  • 如果定义的变量在将来用于保存对象,那么最好将该变量初始化为null,而不是其他值。换句话说,只要意在保存对象的变量还没有真正保存对象,就应该明确地让该变量保存null值,这样有助于进一步区分null和undefined。
  • 当一个数据不再需要使用时,我们最好通过将其值设置为null来释放其引用,这个做法叫做解除引用。不过解除一个值的引用并不意味着自动回收改值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器在下次运行时将其回收。解除引用还有助于消除有可能出现的循环引用的情况。这一做法适用于大多数全局变量和全局对象的属性,局部变量会在它们离开执行环境时(函数执行完时)自动被解除引用。

# 2、特殊的typeof null

当我们使用typeof操作符检测null值,我们理所应当地认为应该返"Null"类型呀,但是事实返回的类型却是"object"。

var data = null;
console.log(typeof data); // "object"
复制代码
1
2
3

是不是很奇怪?其实我们可以从两方面来理解这个结果:

  • 一方面从逻辑角度来看,null值表示一个空对象指针,它代表的其实就是一个空对象,所以使用typeof操作符检测时返回"object"也是可以理解的。
  • 另一方面,其实在JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的(对象的类型标签是 0)。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null的类型标签也成为了 0,typeof null就错误的返回了"object"。在ES6中,当时曾经有提案为历史平凡, 将type null的值纠正为null, 但最后提案被拒了,所以还是保持"object"类型。

# 总结

用一句话总结两者的区别就是:undefined 表示一个变量自然的、最原始的状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。所以,在实际使用过程中,为了保证变量所代表的语义,不要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。

编辑 (opens new window)
#经典面试题
上次更新: 2021/12/06, 21:28:32
初级面试题
高阶面试题

← 初级面试题 高阶面试题→

最近更新
01
大文件分片上传
08-05
02
Vue3实现OSS存储
08-05
03
H5 TTS
08-05
更多文章>
Theme by Vdoing | Copyright © 2019-2025 Aiden Wu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×