生疏知识点

103 阅读18分钟
  1. async 跟defer的区别

在 HTML 中,可以通过以下方式来引入 JavaScript 文件:

<script src="script.js"></script>

如果将 script 标签放在页面中间或页面底部,当浏览器遇到该标签时,页面的解析和渲染就会被阻塞,直到该文件下载、解析和执行完毕。这对于一些需要优先加载的 JavaScript 文件来说是不利的,会导致页面加载时间过长,影响用户体验。

为了解决这个问题,可以使用异步加载 JavaScript 文件的方式,即在文件下载时不阻塞页面的渲染和执行。目前有两种常见的方式:使用 deferasync 属性。

defer 属性:该属性表示脚本将在文档解析结束后再执行。如果有多个 defer 脚本,它们会按照在页面中出现的顺序依次执行。

<script src="script.js" defer></script>

该脚本会被并行下载,但是会推迟执行,直到文档解析完成。

async 属性:该属性表示脚本的执行不会阻止页面的解析。该脚本下载的同时,页面继续解析,但不保证脚本的执行顺序与它们在页面中出现的顺序相同。

<script src="script.js" async></script>

该脚本会被并行下载,但下载完成后会立即执行,不会推迟到文档解析完成后再执行。

需要注意的是,异步加载 JavaScript 文件有一些副作用,包括:

  • 异步加载的脚本不能保证执行顺序,如果多个脚本之间存在依赖关系,则可能会导致逻辑错误。
  • 在异步加载的脚本中,使用 document.write 方法会导致页面加载出错。
  • 异步加载的脚本不会阻止页面的解析和渲染,但如果脚本依赖于页面中的某些元素,可能需要使用 DOMContentLoaded 事件等等待页面解析完毕之后再进行脚本的操作。

因此,在使用 deferasync 属性时,需要仔细考虑页面中脚本的执行顺序和对页面的影响,并根据实际情况进行选择。

  1. js的垃圾回收机制,怎么让变量快速被回收

V8的垃圾回收策略主要是基于分代式垃圾回收机制,其根据对象的存活时间将内存的垃圾回收进行不同的分代,然后对不同的分代采用不同的垃圾回收算法

  • 新生代(new_space):大多数的对象开始都会被分配在这里,这个区域相对较小但是垃圾回收特别频繁,该区域被分为两半,一半用来分配内存,另一半用于在垃圾回收时将需要保留的对象复制过来。

  • 老生代(old_space):新生代中的对象在存活一段时间后就会被转移到老生代内存区,相对于新生代该内存区域的垃圾回收频率较低。老生代又分为老生代指针区老生代数据区,前者包含大多数可能存在指向其他对象的指针的对象,后者只保存原始数据对象,这些对象没有指向其他对象的指针。

  • 大对象区(large_object_space):存放体积超越其他区域大小的对象,每个对象都会有自己的内存,垃圾回收不会移动大对象区。

  • 代码区(code_space):代码对象,会被分配在这里,唯一拥有执行权限的内存区域。

  • map区(map_space):存放Cell和Map,每个区域都是存放相同大小的元素,结构简单(这里没有做具体深入的了解,有清楚的小伙伴儿还麻烦解释下)。

在V8引擎的内存结构中,新生代主要用于存放存活时间较短的对象。新生代内存是由两个semispace(半空间)构成的,内存最大值在64位系统和32位系统上分别为32MB16MB,在新生代的垃圾回收过程中主要采用了Scavenge算法

Scavenge算法的具体实现中,主要采用了Cheney算法,它将新生代内存一分为二,每一个部分的空间称为semispace,也就是我们在上图中看见的new_space中划分的两个区域,其中处于激活状态的区域我们称为From空间,未激活(inactive new space)的区域我们称为To空间。这两个空间中,始终只有一个处于使用状态,另一个处于闲置状态。我们的程序中声明的对象首先会被分配到From空间,当进行垃圾回收时,如果From空间中尚有存活对象,则会被复制到To空间进行保存,非存活的对象会被自动回收。当复制完成后,From空间和To空间完成一次角色互换,To空间会变为新的From空间,原来的From空间则变为To空间。

当一个对象在经过多次复制之后依旧存活,那么它会被认为是一个生命周期较长的对象,在下一次进行垃圾回收时,该对象会被直接转移到老生代中,这种对象从新生代转移到老生代的过程我们称之为晋升
对象晋升的条件主要有以下两个:

  • 对象是否经历过一次Scavenge算法
  • To空间的内存占比是否已经超过25%

在老生代中,因为管理着大量的存活对象,如果依旧使用Scavenge算法的话,很明显会浪费一半的内存,因此已经不再使用Scavenge算法,而是采用新的算法Mark-Sweep(标记清除)Mark-Compact(标记整理)来进行管理。在垃圾回收过程中,会将活动的对象往堆内存的一端移动,其他的内存回收

  1. let const 的区别 为什么优先使用const

let和const都是ES6中新增的关键字,用于声明变量和常量。

区别如下:

声明方式不同。let可声明可修改的变量,而const只能声明不可修改的常量。

作用域不同。let声明的变量只在块级作用域内有效,const声明的常量也只在块级作用域内有效。块级作用域定义为花括号({})内的代码块。

初始化的要求不同。使用let声明的变量可以在后面的代码中初始化,而使用const声明的常量必须在声明时初始化。

为什么优先使用const?

const声明的常量可以避免意外修改变量,提高代码的可维护性和可读性。

const常量在编译时就已经确定,而let变量在运行时才确定,使用const可以提高代码的性能。

const常量可以更好地支持模块化开发,因为它们在模块内部是不可变的,这有助于避免命名冲突和意外修改。

  1. 了解过bigInt么

JavaScript 是一种不支持大整数的语言,它的 Number 类型只能表示 53 位二进制浮点数。如果要处理非常大的整数,可能会导致精度丢失,因此需要使用大整数库来解决这个问题。

bigInt是一种数据类型

BigInt

const previouslyMaxSafeInteger = 9007199254740991n

const alsoHuge = BigInt(9007199254740991)
// 9007199254740991n

typeof 1n === 'bigint'           // true
typeof BigInt('1') === 'bigint'  // true


在 JavaScript 中,有许多大整数库可供使用,其中比较常用的有:

  • BigInteger.js:一个运行在浏览器和 Node.js 上的大整数库。
  • bignumber.js:一个 JavaScript 库,支持任意精度的十进制数学运算。
  • decimal.js:一个 JavaScript 库,支持任意精度的十进制数学运算。
  • mathjs:一个支持大整数、高精度浮点数、分数、单位等各种数学运算的 JavaScript 库。
  • big-integer:一个允许您使用大整数执行算术运算的 JavaScript 库。

这些库都支持大整数的基本运算,如加、减、乘、除等,还支持各种常见的数学运算,如取模、幂次方、开方等。

下面是一个使用 BigInteger.js 实现大数加法的示例:

const x = new BigInteger('24352349857345');
const y = new BigInteger('54324689392843');

const result = x.add(y);

console.log(result.toString()); // 78677039250188

首先创建两个大整数对象 xy,然后使用 add 方法将它们相加,最后将结果转换为一个字符串。BigInteger.js 还支持更多运算,具体使用方法可以参考它的文档。

5.typeOf的原理是什么?

6.src跟href的区别

srchref 用于不同的目的:

src:用于指定页面加载外部资源,通常指 JavaScript 文件、图片等等,它会直接嵌入到当前的页面中。例如:

<img src="example.jpg" />
<script src="example.js"></script>

href:用于指定当前文档和引用资源之间的关系,通常是指向样式表文件或其他页面,它只是用于指向或定位文件,而不像 src 那样将文件嵌入到当前文档中。例如:

<link href="example.css" rel="stylesheet" />
<a href="http://www.example.com">example</a>

总的来说,src 属性用于指定嵌入到当前页面中的外部资源路径,用于让浏览器获取并加载外部资源,而 href 属性则用于定位或链接外部资源、指向其他页面或跳转到其他网站。

7.怎么做tcp的流量控制 跟拥塞控制

TCP 定义了两种控制机制:

  1. 流量控制(Flow Control):用于控制发送方向接收方发送数据的速度,以避免接收方无法处理太多数据而导致数据丢失。 TCP 使用了滑动窗口(Sliding Window)的机制,通过动态调整窗口大小来控制发送数据的速度。发送方发送数据时,首先会查询接收方的窗口大小,然后在窗口内发送数据。接收方可以随时通知发送方窗口大小的变化,从而控制数据的发送速率。

  2. 拥塞控制(Congestion Control):用于防止网络拥塞,避免数据包丢失。TCP 使用了四种拥塞控制算法:慢启动(Slow Start)、拥塞避免(Congestion Avoidance)、快重传(Fast Retransmit)和快恢复(Fast Recovery)。TCP 的拥塞控制算法主要是通过动态调整发送方发送数据的速度,当网络出现拥塞时,发送方的拥塞窗口大小就会减小。当网络恢复正常后,发送方会逐渐增加拥塞窗口大小,以达到网络的最大吞吐量。

总的来说,流量控制和拥塞控制都是 TCP 的重要机制,用于控制数据的发送速度和保障网络的稳定性。两种机制主要的区别在于应用场景不同:流量控制用于保护接收方,而拥塞控制用于保护整个网络。

8.memory cache disk catch是什么,有什么区别

这三个概念均与浏览器缓存有关。它们的具体定义和区别如下:

  1. Memory Cache(内存缓存):内存缓存是浏览器中最快的缓存,数据存储在内存中。当我们第一次访问一个页面时,浏览器会先检查该页面是否存在内存缓存中,如果存在,直接从内存中读取缓存数据,如果不存在,则从网络中加载。

  2. Disk Cache(磁盘缓存):磁盘缓存是指数据存储在硬盘中,当内存缓存满了或者过期时,浏览器会将数据存储在硬盘中。当我们再次访问同一个页面时,浏览器会从磁盘缓存中读取数据。相比内存缓存,磁盘缓存的读写速度较慢,但是可以存储大量数据。

  3. Service Worker Cache(离线缓存):离线缓存是一种通过 Service Worker 技术来实现的缓存。它可以在浏览器离线时使用,也可以提高页面加载速度。当我们第一次访问一个页面时,浏览器会在后台下载离线资源,并将其存储在缓存中。当我们再次访问同一个页面时,浏览器会直接读取缓存中的数据。

综上所述,Memory Cache 和 Disk Cache 主要区别在于缓存数据存储位置不同,其读写速度也有差异。离线缓存是另一种特殊的缓存类型,可以在浏览器离线时使用。

9.class,里面有一些成员变量和方法,转成es5后,浏览器里面实际运行是什么样

在 ES5 中,没有类的概念,通常使用构造函数和原型链来模拟类的行为。因此,将 ES6 的类转换为 ES5 的代码就是使用构造函数和原型链来实现类的功能。

例如,以下是一个简单的 ES6 类定义:

class Example {
  constructor(value) {
    this.value = value;
  }
  getValue() {
    return this.value;
  }
}

该类使用了构造函数和方法的语法糖来简化构造函数和原型链的定义。经过 Babel 转换后,生成的 ES5 代码如下:

function Example(value) {
  this.value = value;
}
Example.prototype.getValue = function () {
  return this.value;
};

转换后的代码中使用 function 关键字定义了构造函数,使用 prototype 属性定义了方法。在实际运行时,浏览器会根据 Example 构造函数创建对象,调用其 getValue 方法,这时会通过原型链找到 Example.prototype 中的 getValue 方法,并执行其方法体。

总的来说,JavaScript 中没有类的概念,ES6 的类本质上只是对构造函数和原型链的一次封装。在转换成 ES5 代码后,其本质仍然是构造函数和原型链的实现方式。

  1. vue 动态加载时怎么设置preload跟prefetch

在 Vue 中设置 preload 和 prefetch 标记可以帮助我们更加高效地加载资源。Vue 通常会将组件拆分成多个 smaller chunks,减少浏览器的请求数,并通过 webpackChunkName 来实现更好的统一管理。Vue 2.x 中建议使用 @babel/plugin-syntax-dynamic-import 插件来进行按需加载的处理。

加载的方式有两种:

  1. 使用 import() 动态加载模块时,需要在 webpackChunkName 后面加上 webpackPreload: truewebpackPrefetch: true,设置好生成的 preload 和 prefetch 标记。
component: () => import(/* webpackChunkName: "my-chunk-name", webpackPrefetch: true */ './MyComponent.vue')
  1. 在配置 Vue Router 时,也能设置组件的 preload 和 prefetch,同样也是使用webpackPrefetch: true 或者 webpackPreload: true
const router = new VueRouter({
  routes: [
    {
      path: '/',
      component: Home,
      // 告诉浏览器预加载这个组件
      // 一般这样设置费用比较昂贵,一定要确定这个组件会被用到才可以设置
      // 例如,你会打开某个特定的页面,来访问一个负责分类和展示商品货架的组件,那么,就可以为这个组件设置预加载
      // 用法同 import('xxx.vue'),webpackChunkName 仍是可用的
      // prefetch 代表着当前页面已加载完毕、资源空闲,但这个组件可能是下一个路由,我们保持一定带宽预留,等到这个组件真正换成当前页面后,便可以直接使用,而不用再次访问网络下载
      // 非当前页面就不用 preload 了,因为它会占用当前页面的部分带宽资源,影响当前页的加载性能
      // 从业务成本角度看,一般而言,我们只需要为非紧急情况,也就是 prefetch 内容做好预判即可
      // 如果在打包时都能将chunk拆分称为合理的块,可以优化页面调度和加载,避免首屏丢失过多用户
      // 最后一个配置 prefetch 会 bundle 线程优先去处理当前页面所有组件再处理预取资源,能够更好地提升速度
      // 对项目实时情况,前端项目中,主悬的是首屏渲染时间,对用户体验有极大的影响,因此在设置或者不设置都需要结合业务成本。
      prefetch: true,
      // 告诉浏览器预加载这个组件
      // 一般这样设置费用比较昂贵,一定要确定这个组件会被用到才可以设置
      // 从业务成本角度看,一般而言,我们只需要为非紧急情况,也就是 prefetch 内容做好预判即可
      // 如果在打包时都能将chunk拆分称为合理的块,可以优化页面调度和加载,避免首屏丢失过多用户
      // 最后一个配置 preload 会将这些内容在前置线程构建的时候一并预处理,能够更好地提升速度,一定程度缓解后端服务器的压力
      // 当前页面初次渲染即将结束时,我们可以借助浏览器带宽余量,去异步下载当前页面下一个路由中会用到的所有 js 文件。
      // 我们把他作为优化策略去使用,但是我们需要慎重考虑一些消耗,就比如当前页面只有这个组件,我们加载这个预取组件其实就是正常的加载这个组件,我们就不要设置 preload 和 prefetch 等资源预加载属性了。
      preload: true,
      children: [
        {
          path: '',
          component: Child
        }
      ]
    }
  ]
})

以上就是在 Vue 中设置动态加载时使用 preload 和 prefetch 的方式,注意需要根据实际需求调整和优化。

  1. 换肤功能如何实现

换肤功能是一种比较常见的网站定制化需求,实现该功能的方法有很多,以下是一些常见的实现方式:

  1. 前端利用 CSS 变量来实现,将需要动态修改的颜色等变量提前设置好,然后通过 JavaScript 动态修改这些变量的值,实现动态换肤的效果。

  2. 利用 Less、Sass、Stylus 等 CSS 预处理器来实现,将需要动态修改的样式定义为变量,然后在 JavaScript 中动态修改这些变量的值,即可实现动态换肤的效果。

  3. 前端利用 Cookie、localStorage 等本地存储方式来实现,将用户选择的主题选项存储在本地,每次加载页面时读取这些存储的值,然后根据不同的主题选项动态加载不同的 CSS 样式文件。

  4. 利用 CSS3 的特性实现,如:利用 filter 属性修改图像的色调等,从而实现换肤的效果。

  5. 利用 SVG 图像实现,SVG 图像本身是基于矢量图,只需要修改对应的 CSS 样式属性或者是对应的 SVG 图像标签的颜色等即可实现动态换肤的效果。

总的来说,实现动态换肤功能要遵循的原则是:尽量减少流量和资源的消耗,不与主流业务逻辑耦合、不影响业务流程和体验、保证界面的美观简洁等。

  1. 如何实现一个轮播图

实现轮播图的方法有很多种,这里介绍一种实现方式:

  1. HTML 结构:用一个 div 包裹轮播图组件,再在其中添加多个图片元素,需要轮播展示的图片数量即可,如下:
<div id="carousel">
  <img src="image1.jpg" alt="Image 1">
  <img src="image2.jpg" alt="Image 2">
  <img src="image3.jpg" alt="Image 3">
</div>
  1. CSS 样式:设置轮播图容器的宽度和高度,样式如下:
#carousel {
  width: 600px;
  height: 400px;
  overflow: hidden;
}
#carousel img {
  width: 600px;
  height: 400px;
  display: none;
}
#carousel img:first-child {
  display: block;
}

其中,将轮播图容器设置为固定宽高并将溢出内容隐藏;将所有图片元素的宽高设置为与轮播图容器相同,并添加 display: none 属性将其全部隐藏;最后将第一个图片元素的 display 属性设置为 block,以便在轮播开始时显示第一张图片。

  1. JavaScript 代码:
var carousel = document.getElementById('carousel');
var imgs = carousel.getElementsByTagName('img');
var currentIndex = 0;
var intervalId = setInterval(changeImage, 2000);

function changeImage() {
  currentIndex ++;
  if (currentIndex > imgs.length - 1) {
    currentIndex = 0;
  }
  for (var i = 0; i < imgs.length; i++) {
    imgs[i].style.display = 'none';
  }
  imgs[currentIndex].style.display = 'block';
}

carousel.addEventListener('mouseover', function() {
  clearInterval(intervalId);
});
carousel.addEventListener('mouseout', function() {
  intervalId = setInterval(changeImage, 2000);
});

通过定义变量对轮播容器及其中的图片元素进行获取,创建一个计时器来定时切换图片,changeImage 函数负责更新图片的显示状态。为了避免计时器与用户交互冲突,监听容器的 mouseovermouseout 事件,通过 clearIntervalsetInterval 来控制轮播的暂停和自动开始。

以上代码实现的轮播图简单易懂,可以对照着代码认真学习。注:下面的代码中不建议使用 ES6 语法,因为有些浏览器可能不支持。

const carousel = document.querySelector('#carousel');
const imgs = carousel.querySelectorAll('img');
let currentIndex = 0;

setInterval(() => {
  imgs[currentIndex].style.display = 'none';
  currentIndex = (currentIndex + 1) % imgs.length;
  imgs[currentIndex].style.display = 'block';
}, 2000);

使用 ES6 的语法,在每次更新时,无需使用循环将全部图片元素的样式全部修改隐藏,直接使用上一个图片元素和下一个图片元素的索引即可,可以使代码更加简洁易读。

  1. object.create() 跟new的区别

Object.create()new 关键字都可以用于创建新对象,但是它们的实现方式不同,并且适用于不同的场景。

  • new 关键字:new 关键字用于调用构造函数来创建一个对象。构造函数内部使用 this 来指向新创建的对象。通过 new 关键字调用构造函数时,JavaScript 引擎会自动创建一个新对象,并将这个新对象的 __proto__ 属性指向构造函数的原型对象。最终将这个新对象作为构造函数的返回值。

    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    
    const person1 = new Person('John', 30);
    console.log(person1); // 输出:Person {name: 'John', age: 30}
    
  • Object.create()Object.create() 方法可以用于创建一个新对象,并将其作为第一个参数指定的对象作为新对象的原型。这个方法的一个优点是可以明确指定新对象的原型,即 Object.create() 可以用于实现一种继承。

    const person = {
      name: 'John',
      age: 30,
    };
    
    const person1 = Object.create(person);
    console.log(person1); // 输出:{}
    console.log(person1.name); // 输出:'John'
    console.log(person1.age); // 输出:30
    

    在以上示例中,Object.create(person) 创建一个新对象 person1,并将 person 对象作为其原型。这样,在访问 person1 对象中的属性时,如果属性不存在,则会去原型对象 person 中查找。

总的来说

  1. 基于对象的不同:new调用的是构造函数;Object.create()调用对象类型来创建新的对象。
  2. 生成对象的内容不同:new创建的对象,挂载了构造函数的所有属性和方法,并且可以向上访问到构造函数原型链上共享的一些属性和方法;Object.create()创建的对象,本质上是参数对象的一个副本。