2024前端八股文

1,209 阅读1小时+

一、CSS

1.盒模型

  • 盒子组成:content、padding、border、margin
  • 标准盒模型:box-sizing:content-box;

容器占据空间大小计算方式:content(容器宽高)+ margin(外边距) + border(边框)自动外扩

  • IE盒模型:box-sizing:border-box;

容器占据空间大小计算方式:content(容器宽高)+padding(内边距)+border(边框)自动内减

2.隐藏元素的方法

  • display:none元素在页面上消失,不占据位置
  • opacity:0占据位置
  • visibility:hidden 占据位置
  • position:absolute移出页面
  • clip:path剪切

3.HTM5\CSS3新特性

HTML5:

  1. 语义化标签:HTML5引入了一系列的语义化标签,如<header><footer><nav><article><section>等,用于更清晰地定义网页的结构和内容,提高可读性和可访问性。
  2. 视频和音频:HTML5提供了<video><audio>标签,使得在网页中嵌入视频和音频内容变得更加简单。通过这些标签,可以直接在网页上播放视频和音频文件,而无需使用第三方插件。
  3. Canvas:HTML5的<canvas>元素允许通过JavaScript动态绘制图形、图像和动画。开发人员可以使用Canvas API来实现各种复杂的绘图效果,如图表、游戏和数据可视化等。
  4. 本地存储:HTML5引入了Web Storage API,包括localStorage和sessionStorage。这些API提供了在浏览器中存储和检索数据的方法,可以用于保存用户的偏好设置、表单数据、应用程序状态等。
  5. Web Workers:HTML5的Web Workers允许在后台运行脚本,独立于主线程,以提高网页的性能和响应性。Web Workers可以执行复杂的计算任务,而不会阻塞用户界面的操作。
  6. 地理定位:HTML5的地理定位API使得网页可以获取用户的地理位置信息。通过这个API,开发人员可以开发基于位置的应用程序,如地图导航、附近的商家搜索等。
  7. 表单增强:HTML5为表单元素引入了一些新的特性,如输入类型(如日期、时间、邮箱、电话等)、表单验证、自动完成等,使得开发人员可以更轻松地创建功能丰富的表单。
  8. 拖放功能:HTML5的拖放API使得网页元素可以被拖动和放置。开发人员可以通过拖放API实现拖拽排序、文件上传、图像预览等交互功能。

CSS3:

  1. 选择器:CSS3引入了一些新的选择器,如属性选择器([attribute=value])、伪类选择器(:nth-child()、:first-of-type等)和伪元素选择器(::before、::after等),使得选择元素的方式更加灵活和精确。
  2. 盒模型:CSS3中引入了box-sizing属性,允许开发人员更精确地控制盒模型的计算方式。通过设置box-sizing为border-box,可以使元素的宽度和高度包括边框和内边距,而不会增加额外的空间。
  3. 边框样式:CSS3新增了一些边框样式,如圆角边框(border-radius)、阴影效果(box-shadow)和边框图片(border-image),使得开发人员可以创建更具有创意和复杂性的边框效果。
  4. 渐变:CSS3引入了渐变(gradient)功能,可以在元素的背景中创建平滑的过渡效果。线性渐变(linear-gradient)和径向渐变(radial-gradient)可以用于创建多彩的背景效果。
  5. 过渡和动画:CSS3中的过渡(transition)和动画(animation)功能使得开发人员可以在元素的属性变化时添加平滑的过渡效果和动画效果。通过定义过渡和关键帧动画,可以实现元素的平滑过渡和复杂的动画效果。
  6. 弹性布局:CSS3中的弹性布局(Flexbox)是一种用于创建灵活的、自适应的布局结构的功能。通过设置容器和项目的属性,可以实现简单而强大的布局控制,适用于各种屏幕尺寸和设备。
  7. 媒体查询:CSS3的媒体查询(Media Queries)允许开发人员根据设备的特性和屏幕尺寸来应用不同的样式。通过媒体查询,可以创建响应式设计,使网页在不同的设备上呈现出最佳的用户体验。
  8. 字体和文本效果:CSS3新增了一些字体和文本效果的属性,如@font-face用于引入自定义字体、text-shadow用于添加文字阴影效果、text-overflow用于处理文本溢出等。

4.重排重绘

  • 重排(回流):对dom的大小,位置进行修改后,浏览器需要重新计算元素的几何属性
  • 重绘:一个元素外观的改变所触发的浏览器行为(例如改变visibility,outline,background等属性),浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。

5.垂直水平居中

  • 文本居中:使用text-algin:centerline-height:height
  • 盒子居中:
//方法一
div {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    top: 0;
    margin: auto;
}
//方法二
div {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
}
//方法三
div{
  display:flex;
  justify-content: center; /* 水平居中 */
  align-items: center; /* 垂直居中 */
}
//方法四table布局
.container {
  display: table;
}
.centered {
  display: table-cell;
  text-align: center; /* 水平居中 */
  vertical-align: middle; /* 垂直居中 */
}
//方法五grid布局
.container {
  display: grid;
  place-items: center; /* 水平和垂直居中 */
}

6.属性继承

可继承的属性:

  • 字体相关属性:font、font-family、font-size、font-style、font-weight、line-height
  • 文本相关属性:color、direction、letter-spacing、text-align、text-indent、text-transform、visibility、white-space、word-spacing
  • 元素框模型相关属性:border-collapse、border-spacing
  • 列表相关属性:list-style、list-style-type、list-style-position
  • 表格相关属性:caption-side、empty-cells、table-layout

不可继承的属性:

  • 盒模型相关属性:width、height、margin、padding、border、display、position、float、clear
  • 背景相关属性:background、background-color、background-image、background-repeat、background-position
  • 定位相关属性:top、right、bottom、left、z-index
  • 文本相关属性:text-decoration、text-shadow、text-overflow、vertical-align、overflow、white-space
  • 其他属性:color、outline、visibility、cursor

需要注意的是,尽管某些属性在默认情况下是可继承的,但可以通过CSS的特殊设置来阻止继承。例如,使用 inheritinitial 关键字可以覆盖某个属性的继承行为,使其不被子元素继承

此外,还有一些属性是部分继承的,即它们的某些值被继承,而其他值不被继承。例如,line-height 属性的数值是可继承的,但具体的长度单位(如px、em)不会被继承。

7.CSS预处理器,less与sass

CSS预处理器是一种扩展CSS的工具,它引入了一些编程语言的特性,使得CSS的编写更加灵活、可维护和可复用。预处理器通过使用变量嵌套规则混合(Mixin)、函数等功能,提供了更强大的样式表达能力,并允许开发者以更模块化的方式组织和管理CSS代码。

Sass与Less的区别

  1. 语法差异:

    • Sass使用缩进式语法,使用缩进和换行来表示代码块和嵌套关系。例如:

      .container 
        width: 100%;
        .item 
          color: red;
      
    • Less使用类似CSS的语法,使用花括号和分号来表示代码块和声明。例如:

      .container {
        width: 100%;
        .item {
          color: red;
        }
      }
      
  2. 变量符号:

    • Sass使用$符号来定义和引用变量。例如:$primary-color: #ff0000;
    • Less使用@符号来定义和引用变量。例如:@primary-color: #ff0000;
  3. 混合(Mixin)语法:

    • Sass使用:@mixin

      关键字定义混合,使用:@include

      关键字引用混合。例如:

      @mixin button($bg-color) {
        background-color: $bg-color;
        color: white;
      }
      ​
      .btn {
       @include button(#ff0000);
      }
      
    • Less使用:.

      符号定义混合,使用.()

      语法引用混合。例如:

      .button(@bg-color) {
        background-color: @bg-color;
       color: white;
      }
      ​
      .btn {
       .button(#ff0000);
      }
      

8.CSS性能优化

  1. 减少CSS文件大小:

    • 压缩CSS文件:使用CSS压缩工具(如UglifyCSS、CSSNano)来去除注释、空格和无效代码,减小文件大小。
    • 合并CSS文件:将多个CSS文件合并为一个,减少HTTP请求次数。
    • 移除不必要的代码:删除未使用的样式规则和选择器,减少文件大小。
  2. 使用适当的选择器:

    • 避免使用通用选择器(如*)和标签选择器(如div),因为它们会增加选择器匹配的开销。
    • 尽量使用ID选择器和类选择器,它们的匹配速度较快。
    • 避免使用嵌套过深的选择器,因为它们的匹配速度较慢。
  3. 避免使用昂贵的CSS属性:

    • 避免频繁使用影响性能的属性,如box-shadowborder-radiustext-shadow等。在需要使用时,尽量减少其使用频率和范围。
    • 避免使用复杂的CSS过渡和动画效果,尤其是涉及到大量元素的情况。
  4. 使用CSS Sprites和图标字体:

    • 将多个小图标合并为一个图像,使用CSS Sprites技术减少HTTP请求。
    • 使用图标字体(如Font Awesome、Material Icons)代替图像,减少图像加载和渲染开销。
  5. 避免使用@import:

    • 使用<link>标签代替@import导入外部CSS文件,因为<link>标签可以并行加载,而@import会阻塞页面渲染。
  6. 使用媒体查询和响应式设计:

    • 使用媒体查询根据屏幕尺寸和设备特性加载不同的CSS样式,避免加载不必要的样式规则。
    • 使用响应式设计,根据屏幕尺寸和布局调整样式,提供更好的移动设备支持。
  7. 将CSS放置在合适的位置:

    • 将CSS文件放置在<head>标签中,避免在页面底部加载CSS。
    • 避免将CSS样式直接写在HTML标签的style属性中,尽量使用外部CSS文件。
  8. 使用缓存和CDN:

    • 配置适当的缓存策略,使浏览器能够缓存CSS文件,减少重复加载。
    • 使用内容分发网络(CDN)来加速CSS文件的加载,将文件部署到离用户更近的服务器上。

二、JavaScript

1.js的数据类型和内置对象

数据类型

  • 字符串(String):表示文本数据,使用引号(单引号或双引号)括起来。
  • 数字(Number):表示数值数据,包括整数和浮点数。
  • 布尔值(Boolean):表示逻辑值,只有两个取值:true(真)和false(假)。
  • null:表示空值或空对象。
  • undefined:表示未定义的值。
  • BigInt:表示任意精度的整数。它可以用于表示超出 JavaScript 数字类型范围的大整数。BigInt 类型的值需要在数字后面加上 "n" 或使用 BigInt() 函数进行创建。
  • Symbol:表示唯一的标识符。Symbol 类型的值是独一无二的,用于对象属性的键,以防止命名冲突。Symbol 类型的值可以使用 Symbol() 函数创建。
  • 对象(Object):表示复杂的数据结构,可以包含多个属性和方法。

内置对象

  • Math:提供数学相关的操作和函数,如数值计算、随机数生成等。
  • Date:用于处理日期和时间。
  • RegExp:用于进行文本模式匹配。
  • Array:用于操作和处理数组数据。
  • String:用于操作和处理字符串数据。
  • JSON:用于解析和序列化 JSON 数据的对象。

2.js中为false的值

false0''nullundefinedNaN

3.js中的逻辑短路与断路

逻辑短路:

  • 逻辑与 &&,如果第一个操作数为 false,则返回第一个操作数;否则,返回第二个操作数。如果第一个操作数为假值(如 false0'' 等),则不会对第二个操作数进行求值。
  • 逻辑或 ||,如果第一个操作数为 true,则返回第一个操作数;否则,返回第二个操作数。如果第一个操作数为真值(如非零数字、非空字符串等),则不会对第二个操作数进行求值。

逻辑中断

  • 对于逻辑与运算符 &&,如果第一个操作数为假值,则返回第一个操作数;否则,返回第二个操作数。
  • 对于逻辑或运算符 ||,如果第一个操作数为真值,则返回第一个操作数;否则,返回第二个操作数。

返回值

逻辑||,逻辑&&表达式返回值不是true或是false,而是看最终确定逻辑||与逻辑&&true或false的式子的值,将此值作为返回,比如逻辑&&表达式全真则真,若式子左边为真,还需看右边的式子是否为真,则返回右边式子的值,而不是true或false,逻辑||也是一样的,下面是结论

1、逻辑与 (&&) 表达式的返回值:

  • 如果第一个操作数为真值(非零数字、非空字符串等),则返回第二个操作数。
  • 如果第一个操作数为假值(false0'' 等),则返回第一个操作数。

2、逻辑或 (||) 表达式的返回值:

  • 如果第一个操作数为真值(非零数字、非空字符串等),则返回第一个操作数。
  • 如果第一个操作数为假值(false0'' 等),则返回第二个操作数。
const result1 = true && 'Hello';    // 返回 'Hello'
const result2 = false && 'Hello';   // 返回 false
const result3 = 0 && 'Hello';       // 返回 0
const result4 = '' && 'Hello';      // 返回 ''
​
const result1 = true || 'Hello';    // 返回 true
const result2 = false || 'Hello';   // 返回 'Hello'
const result3 = 0 || 'Hello';       // 返回 'Hello'
const result4 = '' || 'Hello';      // 返回 'Hello'

4.js常用数组方法

改变原数组的七个方法:push()、pop()、shift()、unshift()、splice()、sort()、reverse()

  1. push(): 将一个或多个元素添加到数组的末尾,并返回修改后的数组长度。
  2. pop(): 移除并返回数组的最后一个元素。
  3. shift(): 移除并返回数组的第一个元素。
  4. unshift(): 将一个或多个元素添加到数组的开头,并返回修改后的数组长度。
  5. concat(): 连接两个或多个数组,返回一个新数组。
  6. join(): 将数组的所有元素连接成一个字符串。
  7. slice(): 返回一个从指定位置开始到指定位置结束的新数组,不会修改原数组。
  8. splice(): 在指定位置删除或替换数组的元素,并可以插入新元素。
  9. indexOf(): 返回指定元素在数组中第一次出现的索引,如果不存在则返回 -1。
  10. lastIndexOf(): 返回指定元素在数组中最后一次出现的索引,如果不存在则返回 -1。
  11. forEach(): 遍历数组的每个元素,并对每个元素执行指定的函数。
  12. map(): 遍历数组的每个元素,并根据每个元素返回一个新的值,返回一个新数组。
  13. filter(): 遍历数组的每个元素,并根据指定的条件筛选出符合条件的元素,返回一个新数组。
  14. reduce(): 从数组的第一个元素开始,依次对数组中的每个元素执行指定的函数,返回一个累积的结果。
  15. sort(): 对数组的元素进行排序,可以传入一个比较函数来指定排序规则。
  16. reverse(): 反转数组的顺序。
  17. includes(): 判断数组是否包含指定的元素,返回一个布尔值。
  18. some(): 判断数组中是否至少有一个元素满足指定条件,返回一个布尔值。
  19. every(): 判断数组中的所有元素是否都满足指定条件,返回一个布尔值。
  20. find(): 返回数组中满足指定条件的第一个元素,如果不存在则返回 undefined
  21. findIndex(): 返回数组中满足指定条件的第一个元素的索引,如果不存在则返回 -1。

5.js数据类型检测方式

  • typeof() 对基本数据类型没问题,遇到引用数据类型不管用
 console.log(typeof 666) //number
 console.log(typeof [1,2,3]) //object
  • instanceof() 只能判断引用数据类型,不能判断基本数据类型
console.log([] instanceof Array) //true
console.log('abc' instanceof String) //false
  • constructor() 几乎可以判断所有类型,但是如果声明了一个构造函数,且将原型指向了Array,则就会判断失误
console.log(('abc').constructor === String) //true
  • `Object.prototype.toString.call()基本完美
const opt = Object.prototype.toString
console.log(opt.call(123))     //[object Number]
console.log(opt.call(true))    //[object Boolean]
console.log(opt.call('123'))   //[object String]
console.log(opt.call([]))      //[object Array]
console.log(opt.call({}))      //[object Object]

6.闭包

什么是闭包?函数嵌套函数,内部函数可以访问外部函数的变量和参数,即使外部函数已经执行结束,闭包仍然可以访问和操作外部函数的变量,它保留了对外部函数作用域的引用。

优点:可以重复利用变量,并且不会污染全局

  1. 封装变量:闭包可以用于创建私有变量和方法,通过将变量和方法封装在闭包内部,可以防止外部访问和修改,实现数据的封装和隐藏。
  2. 保持状态:闭包可以保持外部函数的变量和参数的状态,即使外部函数执行完毕,闭包仍然可以访问和操作这些状态。这使得闭包在某些场景下非常有用,例如在事件处理程序中保持状态。
  3. 实现模块化:通过使用闭包,可以创建具有私有变量和方法的模块,将相关的功能和状态封装在一起,提供更好的代码组织和封装性。

缺点:这个变量一直保存在内存中,不会被垃圾回收(可以手动将变量设置为null)

  1. 内存占用:闭包会一直保持对外部作用域的引用,导致外部作用域的变量无法被垃圾回收机制回收,从而可能导致内存占用过高,特别是在使用闭包的过程中创建大量的闭包时。
  2. 性能影响:由于闭包需要保持对外部作用域的引用,访问外部变量时需要通过作用域链进行查找,这可能会带来一定的性能开销,尤其是在嵌套的闭包中。
  3. 容易造成内存泄漏:如果闭包中引用了外部作用域的对象,并且这些对象不再需要时没有被释放,就会造成内存泄漏的问题。因此,在使用闭包时,需要注意适当释放不再需要的资源。

使用场景:防抖,节流,高阶函数,迭代器,缓存,以下是关于重复求和的缓存案例

var fn = (function(){
    var cache={} //缓存对象
    var calc = function(arr){  //计算函数
        var sum = 0
        for(var i=0;i<arr.length;i++){
            sum+=arr[i]
        }
        return sum
    }
    return function(){
        // arguments转换成数组
        var args = Array.prototype.slice.call(arguments,0)
        // 将args转为字符串
        var key = args.join(',')
        var result,tSum = cache[key]
        if(tSum){//如果缓存有
            console.log('从缓存中取:',cache)
            result = tSum
        }else{
            // 重新计算并存入缓存同时复制给result
            result = cache[key] = calc(args)
            console.log('存入缓存',cache)
        }
        return result
    }
})()
​
fn(1,2,3,4,5)
fn(1,2,3,4,5)
fn(1,2,3,4,6)
fn(1,2,3,4,7)
fn(1,2,3,4,6)

7.js的垃圾回收机制与内存泄漏

JavaScript 中的垃圾回收机制是自动管理内存的一种机制,它负责检测不再使用的对象,并释放它们所占用的内存

  1. 标记清除(Mark and Sweep):这是 JavaScript 最常用的垃圾回收算法。它的基本原理是通过标记活动对象和清除非活动对象来进行垃圾回收。垃圾收集器会从根对象(通常是全局对象)开始,递归地遍历所有对象,标记活动对象。然后,它会清除未被标记的对象,并回收它们所占用的内存。
  2. 引用计数(Reference Counting):这是另一种常见的垃圾回收算法。它的原理是为每个对象维护一个引用计数器,记录对象被引用的次数。当引用计数器为零时,表示该对象不再被引用,可以被回收。然而,引用计数算法有一个问题是无法解决循环引用的情况,即两个或多个对象相互引用,但无法被外部访问到,导致引用计数器无法归零,造成内存泄漏。
  3. 分代回收(Generational Collection):这是一种优化的垃圾回收策略。它基于一个观察结果:大部分对象很快就会变得不再活跃。根据这个观察,分代回收将堆内存分为几个代(generation),通常是新生代(young generation)和老生代(old generation)。新创建的对象首先分配在新生代,如果经过几次垃圾回收后仍然存活,就会被晋升到老生代。这样可以针对不同代的对象采用不同的回收策略,提高垃圾回收的效率。

JavaScript 中的内存泄漏指的是程序中存在不再使用的对象仍然占用内存,导致内存无法被垃圾回收机制释放

  1. 无意的全局变量引用:如果在函数内部声明变量时忘记使用 varletconst 关键字,该变量会成为全局变量,即使函数执行完毕,该变量仍然存在于全局作用域中,占用内存而无法被回收。
  2. 定时器未清除:使用 setTimeout()setInterval() 创建定时器时,如果没有及时清除定时器,即使不再需要定时器,它仍然会保持对函数的引用,导致相关的对象无法被回收。
  3. 事件监听器未移除:通过 addEventListener() 方法添加的事件监听器,如果没有正确地移除,会导致元素或对象一直被引用,无法被垃圾回收。
  4. 闭包未释放:闭包可以保持对外部函数的变量和参数的引用,如果闭包存在于全局作用域或长时间存在,它会持续占用内存,直到闭包不再被引用。
  5. DOM 引用未释放:在 JavaScript 中,如果通过变量引用了 DOM 元素,即使该元素从 DOM 树中移除,仍然会保持对该元素的引用,导致元素无法被垃圾回收。

为避免内存泄漏,需要注意以下几点:

  • 使用 varletconst 关键字声明变量,避免全局变量的隐式创建。
  • 在不再需要使用的情况下,及时清除定时器和移除事件监听器。
  • 注意闭包的使用,确保在不再需要时释放闭包。
  • 在不需要引用的情况下,手动解除对 DOM 元素的引用。

8.事件委托

原理就是利用了事件冒泡的机制来实现,也就是把子元素的事件绑定到了父元素身上,从而减少事件绑定

阻止事件冒泡:event.stopPropagation()/addEventListener('click',func,true/false)第三个参数默认为false(事件冒泡),false(事件捕获)

9.基本数据类型与引用数据类型的区别

  1. 存储方式:基本数据类型的值直接存储在变量所分配的栈内存中,而引用数据类型的值是存储在堆内存中的对象,并通过引用(指针)存储在变量中。
  2. 复制方式:基本数据类型的赋值是按值复制,即将原始值复制到新变量中,它们之间互不影响。而引用数据类型的赋值是按引用复制,即将引用(指针)复制到新变量中,它们指向同一个对象,修改其中一个变量会影响到另一个变量。
  3. 比较方式:基本数据类型的比较是按值比较,即比较它们的值是否相等。而引用数据类型的比较是按引用比较,即比较它们是否指向同一个对象。
  4. 可变性:基本数据类型是不可变的,一旦创建,它们的值就不能被修改。而引用数据类型是可变的,可以通过修改对象的属性来改变对象的值。

10.原型链

实例对象的__proto__属性指向构造函数的原型prototype,原型对象上的constructor又指回构造函数

  1. 原型(prototype):每个 JavaScript 对象都有一个原型,它可以是另一个对象或者 null。原型对象上的属性和方法可以被继承到其他对象。
  2. __proto__ 属性:每个对象都有一个特殊的隐藏属性__proto__ ,它指向该对象的原型。通过__proto__ 属性,对象与其原型对象建立了关联。
  3. constructor 属性:原型对象上有一个 constructor 属性,它指向创建该对象的构造函数。
  4. 原型链(prototype chain):当访问一个对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript 引擎会沿着__proto__ 属性链式向上查找,直到找到相应的属性或方法或者到达原型链的顶端(即 null)。

11.new操作符做了什么

new 操作符是 JavaScript 中创建对象实例的一种常见方式,但它并不是唯一的方式。还可以使用其他方式创建对象,如对象字面量、工厂函数等

  1. 创建一个空对象:new 操作符会创建一个空对象,这个对象会成为新创建的实例。
  2. 将对象的原型指向构造函数的原型new 操作符会将新创建的对象的 __proto__ 属性指向构造函数的 prototype 属性。这样就建立了对象与构造函数原型之间的关联,使得新对象可以访问构造函数原型上的属性和方法。
  3. 执行构造函数new 操作符会调用构造函数,并将新创建的对象作为构造函数的上下文(this)绑定到构造函数中。这样构造函数就可以在新对象上设置属性和方法。
  4. 返回新对象:如果构造函数没有显式返回一个对象(如:返回的是原始值或没有返回值),则 new 操作符会返回新创建的对象实例;如果构造函数显式返回一个对象,则返回该对象。

12.js中的继承

  1. 原型链继承:通过将子类的原型对象指向父类的实例,子类可以继承父类的属性和方法。缺点是所有子类实例共享父类的属性,且无法向父类的构造函数传递参数
  2. 构造函数继承:构造函数继承通过在子类的构造函数中调用父类的构造函数来实现继承。这样可以实现每个子类实例都拥有独立的属性,缺点:无法继承父类原型上的方法,无法进行函数复用
  3. 组合继承:将原型链继承和构造函数继承结合起来的一种方式。通过调用父类的构造函数继承属性,然后将子类的原型对象指向父类的实例来继承方法。这种方式既可以继承父类的属性,也可以继承父类原型上的方法,但会导致父类的构造函数被调用两次。
  4. 原型式继承:原型式继承是通过创建一个临时的构造函数,然后将父类的实例作为该构造函数的原型对象来实现继承。这种方式可以实现简单的对象继承,但无法实现复杂的继承关系。
  5. 寄生式继承:寄生式继承是在原型式继承的基础上,通过在新创建的对象上添加额外的方法或属性来增强对象。这种方式可以实现对父类的增强,但也存在对象共享的问题。
  6. 类继承:ES6 引入了 class 关键字,通过 extends 关键字可以实现类的继承。在子类的构造方法中调用super方法继承父类的this对象,然后对其加工。而super方法表示的就是父类的构造函数,用来新建父类this对象。

13.js的设计原理

JS引擎、运行上下文、调用栈、事件循环、回调

简单来说:JS引擎把代码转为计算机可执行的代码,然后通过一些API让浏览器可以执行,另外,JS是单线程的,我们每次从调用栈取出代码进行调用,如果你取出的这个代码非常耗时,则会阻塞这个线程,导致浏览器崩溃,回调函数就是通过加入到事件队列里,等待事件循环,然后放到调用栈去执行,只有事件循环监听到调用栈为空时才会从事件队列中取出事件,放入调用栈继续执行

14.js的设计模式

  1. 单例模式(Singleton Pattern): 单例模式用于限制一个类只能创建一个实例。在 JavaScript 中,可以使用闭包来实现单例模式。
  2. 工厂模式(Factory Pattern): 工厂模式用于创建对象,而不需要暴露对象创建的逻辑。通过工厂函数或者类的静态方法来创建对象,可以隐藏对象的具体实现细节。
  3. 观察者模式(Observer Pattern): 观察者模式定义了一种一对多的依赖关系,当一个对象状态发生变化时,它的所有依赖者都会收到通知并自动更新。在 JavaScript 中,可以使用事件机制来实现观察者模式。
  4. 发布-订阅模式(Publish-Subscribe Pattern): 发布-订阅模式和观察者模式类似,但是它使用一个调度中心(或者称为事件总线)来管理订阅者和发布者之间的关系。
  5. 适配器模式(Adapter Pattern): 适配器模式用于将一个类的接口转换成客户端所期望的另一个接口,以解决接口不兼容的问题。在 JavaScript 中,可以使用对象适配器或者类适配器来实现适配器模式。
  6. 装饰者模式(Decorator Pattern): 装饰者模式用于动态地给一个对象添加额外的功能,而无需修改原始对象的结构。在 JavaScript 中,可以使用装饰器函数或者装饰器类来实现装饰者模式。
  7. 策略模式(Strategy Pattern): 策略模式定义了一系列的算法,并将每个算法封装起来,使它们可以互相替换。在 JavaScript 中,可以使用函数或者类来实现策略模式。
  8. MVC 模式(Model-View-Controller Pattern): MVC 模式是一种用于组织代码的架构模式,它将应用程序分为三个部分:模型(Model)、视图(View)和控制器(Controller)。模型用于处理数据逻辑,视图用于显示用户界面,控制器用于处理用户输入和调度模型和视图。

15.js中的this指向

  1. 全局作用域:this 指向全局对象,即 window 对象(在浏览器环境中)
  2. 函数中:this 的指向取决于函数的调用方式。如果函数是作为普通函数调用,this 指向全局对象(在浏览器环境中是 window 对象);如果函数作为对象的方法调用,this 指向调用该方法的对象。
  3. 构造函数中:this 指向通过 new 关键字创建的实例对象。
  4. 方法中:this 指向调用该方法的对象。
  5. 事件处理函数中:this 指向触发事件的元素。
  6. 在箭头函数中,this 的指向是词法上的,即它继承自外部作用域,不受函数调用方式的影响。
  7. apply,call,bind:可以改变this指向(除箭头函数外)
  8. 匿名函数:匿名函数的执行环境具有全局性,this永远指向了window

16.setTimeout最小执行时间

setTimeout4ms ,在较繁忙的情况下,setTimeout 的实际执行时间可能会比预期的延迟时间稍微长一些。如果需要更精确的定时器,可以考虑使用 requestAnimationFrameWeb Workers 等其他技术。

setInterval10ms,需要注意的是,即使设置了较小的时间间隔,实际执行时间仍然可能会受到其他任务和浏览器性能的影响。因此,如果回调函数的执行时间超过了设置的时间间隔,可能会导致回调函数的连续重叠,造成性能问题。为了更精确地控制定时器,可以结合使用 setTimeout 来实现。例如,可以在回调函数内部使用 setTimeout,在每次执行完后再设置下一次的定时器,以确保回调函数的执行间隔。

关于浏览器休眠(切换标签页,最小化浏览器时),setInterval的执行间隔,不同的浏览器可能不同,在谷歌中当标签页进入休眠状态,所有小于1000ms的定时器将会置为1000ms,如果你需要在后台标签页中进行精确的定时器操作,可以考虑使用 requestAnimationFrameWeb WorkersrequestAnimationFrame 是一个在浏览器每次绘制之前执行的回调函数,通常用于执行动画和定时任务。Web Workers 是在后台运行的 JavaScript 线程,可以独立于主线程执行任务。

17.call、apply、bind的区别

callapplybind 是 JavaScript 中用于改变函数执行上下文(即函数内部的 this 值)的方法。它们的主要区别在于参数的传递方式和函数的执行时机。

callapply 的作用是立即调用函数,并指定函数执行时的上下文(即 this 的值)。callapply 的区别在于参数的传递方式。call 方法的参数是逐个传入的,而 apply 方法的参数是作为数组传入的。call的性能要比apply要好一些

function greet(name) {
  console.log(`Hello, ${name}! I'm ${this.name}.`);
}
​
const person1 = { name: 'Alice' };
const person2 = { name: 'Bob' };
​
greet.call(person1, 'John'); // Hello, John! I'm Alice.
greet.apply(person2, ['Mary']); // Hello, Mary! I'm Bob.

callapply 不同,bind 方法不会立即调用函数,而是返回一个新的函数,该函数的 this 值被绑定到指定的值。这使得我们可以稍后调用该函数。

function greet(name) {
  console.log(`Hello, ${name}! I'm ${this.name}.`);
}
​
const person = { name: 'Alice' };
​
const greetPerson = greet.bind(person);
greetPerson('John'); // Hello, John! I'm Alice.

18.递归

在 JavaScript 中,递归可以用于解决需要重复执行相同或类似任务的问题。递归函数通常包含两个部分:基本情况(base case)和递归调用(recursive call)。

基本情况是递归函数中的停止条件,它指定了递归的结束条件。当满足基本情况时,递归函数将不再调用自身,而是返回一个结果或执行其他操作。

递归调用是指在函数内部调用自身。通过递归调用,函数可以多次重复执行相同的任务,每次处理一个较小的子问题,直到达到基本情况为止。

19.深拷贝与浅拷贝

  1. 浅拷贝:是创建一个新的对象或数组,并将原始对象或数组的引用复制给新对象或数组。这意味着新对象或数组和原始对象或数组共享相同的内部引用,如果修改其中一个对象或数组的属性,另一个对象或数组也会受到影响。

  2. 深拷贝:是创建一个全新的对象或数组,并将原始对象或数组的所有属性和元素递归地复制到新对象或数组中。这意味着新对象或数组和原始对象或数组完全独立,彼此之间没有引用关系,修改其中一个对象或数组不会影响另一个对象或数组。

  3. 常见的拷贝方法

    • 扩展运算符:如果只是一层数组或是对象,其元素只是简单类型的元素,那么属于深拷贝,如果数组或对象中的元素是引用类型的元素,那么就是浅拷贝

    • Object.assign():如果对象的属性值为简单类型(string,number),通过Object.assign({},srcobj);得到的新对象为深拷贝;如果属性值为对象或其他引用类型,那对于这个对象而言其实是浅拷贝的

    • JSON.parse(JSON.stringify(obj)):这种实现深拷贝的方法有局限性,它只适用于一般数据的拷贝(对象、数组)其他结果如下:

    如果json里面有时间对象,则序列化后时间对象会转为字符串;如果json里有对象是由构造函数生成的,则序列化的结果会丢弃对象的 constructor;如果json里有NaN、Infinity和-Infinity,则序列化的结果会变成null;如果json里有 function,undefined,则序列化的结果会把 function,undefined 丢失;如果json里有RegExp、Error对象,则序列化的结果将只得到空对象 RegExp、Error => {};

    • 递归复制:通过递归遍历对象或数组的属性或元素,并进行复制操作,实现深拷贝。
    function copy(origin, isDeep) {
        let obj = {}
        if (origin instanceof Array) {
            obj = []
        }
        for (const key in origin) {
            let value = origin[key]
            obj[key] = (!!isDeep && typeof value === 'object' && value !== null)
                ? copy(value, isDeep) : value
        }
        return obj
    }
    let origin = {
        name:"张三",
        age:18,
        undefined,
        say(){
            console.log('hello')
        },
        arr:[[1,2],3,4,5]
    }
    const deepcopy_origin = copy(origin,true)
    console.log(deepcopy_origin)
    

***20.事件循环

主线程先执行同步任务,然后才去执行任务队列里的任务,先执行微任务再执行宏任务,等任务队列全部执行完后,再看主线程的同步任务,调用结束后可能会出现新的异步任务,再去查看任务队列里的微任务与宏任务,以此循环

宏任务包括定时器回调、事件回调(如点击事件、AJAX 请求的回调)、I/O 操作(如读取文件、发送网络请求)等,而微任务包括 Promise 的回调、async/await 的异步函数等。

21.浏览器的存储机制

  • cookies:由服务器发送给浏览器并存储在用户的计算机上。浏览器在后续的请求中会将 Cookie 发送回服务器,优点:兼容性好,请求头自带cookie;缺点:存储量小(4kb),资源浪费,使用麻烦(封装)
  • localstorage/sessionstoragelocalStorage 可以长期保存数据,可以达到5M或更大,即使关闭浏览器后再次打开也可以访问到;sessionStorage 只在当前会话中有效,关闭浏览器后数据会被清除。
  • indexedDb:一种在浏览器中保存结构化数据的高级存储机制。它可以存储大量的数据,并提供了强大的查询和索引功能,适用于需要离线访问和处理大量数据的应用程序。

22.浏览器内核

常⻅的浏览器内核有: 1)trident(三叉戟)---- IE浏览器、360安全浏览器、UC浏览器、搜狗⾼速浏览器、百度浏览器 2)gecko(壁⻁) ---- Mozilla、Firefox 3)pestro -> Blink ---- Opera 4)Webkit ---- Safari、360极速浏览器、搜狗⾼速浏览器、移动端浏览器 5)Webkit -> Blink ----Chrome、Edge

*** 23.for in 与for of的区别

for...in :遍历以任意顺序迭代⼀个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性。 for...of:ES6新增,遍历在可迭代对象,包括 Array , Map , Set , String , TypedArray ,arguments 对象等等

  • for...in可以遍历对象和数组,for...of不能遍历对象
  • for...in 循环不仅遍历对象的键名,还会遍历⼿动添加的其它键,甚⾄包括原型链上的键
  • for...in遍历的索引为字符串类型
  • for..of适⽤遍历数/数组对象/字符串/map/set等拥有迭代器对象的集合,但是不能遍历对象
  • for...of与forEach()不同的是,它可以正确响应break、continue和return语句
  • 具有迭代器对象才可以使⽤for...of

24.js的单线程

这主要和js的⽤途有关,js是作为浏览器的脚本语⾔,主要是实现⽤户与浏览器的交互,以及操作dom,这决定了它只能是单线程,否则会带来很复杂的同步问题。⽐如js被设计了多线程,如果有⼀个线程要修改⼀个dom元素,另⼀个线程要删除这个dom元素,此时浏览器就会⼀脸茫然,不知所措。

事件循环:JavaScript可以使用事件循环(Event Loop)机制来处理异步操作。JavaScript将耗时的操作(如网络请求、文件读写等)委托给浏览器的其他线程(如网络线程、文件系统线程等)处理,而不会阻塞主线程的执行。当异步操作完成时,会将回调函数加入到事件队列中,等待主线程空闲时执行。

尽管JavaScript本身是单线程的,但现代浏览器提供了一些API(如Web Workers)来创建多线程的JavaScript环境,以便处理一些密集的计算任务。但这些多线程环境通常是在独立的线程中运行,与主线程相互隔离,不会直接操作DOM,从而避免了潜在的竞态条件和同步问题。

25.IIFE

IIFE立即执行函数的主要目的是创建一个独立的函数作用域,避免变量污染全局命名空间。另外,IIFE还可以用于创建闭包,即在函数内部定义的变量可以在函数外部访问,但是外部的代码无法直接访问函数内部的变量。这种机制可以用于封装私有变量和实现模块化的代码结构。

26.作用域链

EC的作用是为了给当前代码提供数据的,查找数据先在自己的EC中查找,如果找不到,就去它的父EC中找, 如果还找不到,就去父的EC的父的EC中找,直到找到ECG。这逐层查找机制就是所谓的作用域链。

27.防抖、节流

节流不管事件触发多频繁保证在一定时间内一定会执行一次函数。 防抖是只在最后一次事件触发后才会执行一次函数

防抖应用场景:当用户进行了某个行为(例如点击)之后。不希望每次行为都会触发方法,而是行为做出后,一段时间内没有再次重复行为,才给用户响应,例如窗口调整、输入框输入等。 防抖实现原理:每次触发事件时设置一个延时调用方法,并且取消之前的延时调用方法。(每次触发事件时都取消之前的延时调用方法)

function debounce(func, delay) {
  let timerId;
  return function(...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  }
}
// 示例用法
function handleInput() {
  console.log("Input event debounced");
}
const debouncedHandleInput = debounce(handleInput, 300);
// 监听输入框的输入事件
input.addEventListener('input', debouncedHandleInput);

节流应用场景:用户进行高频事件触发(滚动),但在限制在n秒内只会执行一次。 节流实现原理:每次触发时间的时候,判断当前是否存在等待执行的延时函数

function throttle(func, delay) {
  let timerId;
  let lastExecutedTime = 0;
  return function(...args) {
    const currentTime = Date.now();
    if (currentTime - lastExecutedTime >= delay) {
      func.apply(this, args);
      lastExecutedTime = currentTime;
    } else {
      clearTimeout(timerId);
      timerId = setTimeout(() => {
        func.apply(this, args);
        lastExecutedTime = Date.now();
      }, delay);
    }
  }
}
// 示例用法
function handleScroll() {
  console.log("Scroll event throttled");
}
const throttledHandleScroll = throttle(handleScroll, 200);
// 监听窗口的滚动事件
window.addEventListener('scroll', throttledHandleScroll);

28.***for-in 循环怎么避免遍历到原型上的属性

只遍历对象自身的属性,而不遍历继承于原型链上的属性,需要使用hasOwnProperty 方法过滤一下。

Object.prototype.say="cgl";
var person ={
  age: 18
};
for (var key in person) {
  if(person.hasOwnProperty(key)){
    console.log(key, eval("person."+key));
  }
}

29.异步的解决方案有哪些?

1.回调函数callback 2.事件发布订阅 3.Promise 4.Generator 5.async/await

三、ES6

1.ES6新增特性

  1. 块级作用域和常量声明:引入了 letconst 关键字,用于声明块级作用域的变量和常量。
  2. 箭头函数:使用箭头函数语法 () => {},提供了更简洁的函数定义方式,并且自动绑定了上下文。
  3. 默认参数值:函数参数可以指定默认值,当调用函数时未提供相应参数时,将使用默认值。
  4. 模板字面量:使用反引号(`)创建字符串模板,可以在其中插入变量和表达式,更方便地拼接字符串。
  5. 解构赋值:可以从数组或对象中提取值,并将其赋给变量,简化了变量的声明和赋值过程。
  6. 扩展运算符:使用三个点(...)可以将数组或对象展开,方便地进行数组合并、对象合并等操作。
  7. 类和模块:引入了类和模块的概念,使用 class 关键字定义类,并使用 importexport 关键字导入和导出模块。
  8. Promise 和异步/await:引入了 Promise 对象和 async/await 语法,简化了异步操作的处理方式。
  9. 迭代器Iterator和生成器Generator:提供了迭代器和生成器的概念,使得遍历和生成序列的操作更加灵活和简洁。
  10. 新的数据类型:引入了 Symbol 类型,表示唯一的标识符。
  11. setmap数据结构:使用 SetMap 可以更方便地处理数据集合和键值对。它们提供了高效的查找和操作方法,并且在需要存储唯一值或将值与特定键关联时非常有用。
  12. Proxy与Reflect等新增对象,以及Object新增方法,如Object.is()Object.assign()Object.getOwnPropertyDescriptors()Object.setPrototypeOf()Object.getPrototypeOf()Object.keys()Object.values()Object.entries()

2.var let const的区别

  1. 作用域

    • var 声明的变量具有函数作用域或全局作用域。在函数内部声明的 var 变量在整个函数内部都是可见的,而在函数外部不可见。在全局作用域中声明的 var 变量在整个代码中都是可见的。
    • letconst 声明的变量具有块级作用域。块级作用域是指在 {} 中声明的变量只在该块内部可见,包括 for 循环、if 语句等。
  2. 可变性

    • varlet 声明的变量是可变的,可以重新赋值。
    • const 声明的变量是常量,一旦赋值就不能再修改。尝试修改 const 变量的值会导致错误。
  3. 声明时机

    • var 变量在整个作用域范围内都会被提升(hoisting),即变量的声明会被提升到作用域的顶部。这意味着可以在变量声明之前使用变量,但其值为 undefined
    • letconst 变量也会被提升,但在声明之前使用变量会导致暂时性死区(Temporal Dead Zone,TDZ)的出现,即在声明之前访问变量会抛出错误。

3.promise

它可以让异步代码更加可读和易于管理。Promise 提供了一种更结构化的方式来管理回调函数,避免了回调地狱(callback hell)的问题。

  1. 异步操作Promise 用于处理异步操作,例如网络请求、文件读写、定时器等。它可以将异步操作封装成一个 Promise 对象,通过该对象可以获取异步操作的结果或处理错误。

  2. 状态Promise 有三种状态:

    • Pending(进行中) :初始状态,表示异步操作正在进行中,尚未完成。
    • Fulfilled(已完成) :表示异步操作已成功完成。
    • Rejected(已失败) :表示异步操作发生错误或失败。

    一旦 Promise 进入 FulfilledRejected 状态,就称为 settled(已定型)状态,不可再改变

  3. 方法Promise 提供了一些方法来处理异步操作的结果和错误:

    • then() :用于处理异步操作成功的情况。接收两个参数,第一个参数是成功回调函数,用于处理异步操作的结果;第二个参数是可选的失败回调函数。
    • catch() :用于处理异步操作失败的情况。接收一个参数,即失败回调函数。
    • finally() :无论 Promise 是 Fulfilled 还是 Rejected 状态,都会执行的回调函数。
  4. 链式调用:Promise 具有链式调用的特性,可以通过 then() 方法将多个异步操作串联起来。每个 then() 方法返回一个新的 Promise 对象,可以继续调用 then() 方法或 catch() 方法,形成链式调用。

  5. 错误处理:Promise 可以通过 catch() 方法捕获异步操作中的错误,并进行统一的错误处理。在链式调用中,如果前面的 Promise 发生错误,后续的 then() 方法会被跳过,直接执行 catch() 方法。

  6. 并行执行:Promise 可以使用 Promise.all() 方法来并行执行多个异步操作,并等待它们全部完成。Promise.all() 方法接收一个 Promise 对象数组作为参数,返回一个新的 Promise 对象,当所有的 Promise 都进入 Fulfilled 状态时,新的 Promise 进入 Fulfilled 状态并返回结果数组。

4.async与await

async/await 使用更直观、类似同步代码的方式处理异步操作。使用 async 关键字声明异步函数,其中可以使用 await 关键字来暂停代码的执行,等待 Promise 对象的状态变为 Fulfilled 或 Rejected。

async/await 使用 try/catch 块来捕获异步操作中的错误。在异步函数内部使用 await 关键字时,可以使用 try/catch 来捕获可能发生的异常。

5.set与map

Set:

  • Set 是一种无重复值的集合,它的值是唯一的,不会重复。
  • Set 中的值可以是任意类型的,包括原始类型和对象引用。
  • Set 中的值是无序的,不会按照插入顺序存储。
  • Set 是可迭代的,可以使用 for...of 循环遍历其中的值。
  • Set 提供了一些方法来操作集合,如 add()、delete()、has()、clear() 等。

Map:

  • Map 是一种键值对的集合,每个值都有对应的唯一键。
  • Map 中的键和值可以是任意类型的,包括原始类型和对象引用。
  • Map 中的键是有序的,按照插入顺序存储。
  • Map 是可迭代的,可以使用 for...of 循环遍历其中的键或值。
  • Map 提供了一些方法来操作集合,如 set()、get()、delete()、has()、clear() 等。

Set 和 Map 在实际应用中有不同的用途。如果你需要存储一组唯一的值,并且不关心顺序,可以使用 Set。如果你需要存储键值对,并且需要按照键的顺序进行操作,可以使用 Map。

6.Symbol

Symbol 是 一种基本数据类型,表示一个独一无二的标识符。

  1. 唯一性:每个 Symbol 值都是唯一的,不会与其他任何值相等,包括其他的 Symbol 值。这意味着可以用 Symbol 创建一个不会与现有属性冲突的新属性键。
  2. 不可变性:Symbol 值是不可变的,一旦创建就不能被修改。这意味着无法给 Symbol 值添加属性或方法。
  3. 作为属性键:Symbol 值可以作为对象的属性键,用于创建对象的非字符串属性。这样可以确保属性名的唯一性,避免属性名冲突。

Symbol 的应用场景:创建唯一的属性键定义类的私有成员自定义迭代器定制对象的字符串表示

7.Proxy

Proxy创建一个代理对象,用于拦截并自定义对目标对象的操作。通过使用 Proxy,可以对对象的读取、赋值、函数调用等操作进行拦截和处理,从而实现对对象行为的修改和扩展。

Proxy 的基本语法如下:

const proxy = new Proxy(target, handler);
  • target:要代理的目标对象。
  • handler:一个包含各种拦截操作的处理程序对象。

handler 对象中可以定义多个拦截操作(也称为"陷阱"),例如:

  • get(target, property, receiver):拦截对目标对象属性的读取操作。
  • set(target, property, value, receiver):拦截对目标对象属性的赋值操作。
  • apply(target, thisArg, argumentsList):拦截对目标对象的函数调用。
  • has(target, property):拦截对目标对象是否具有某个属性的判断。
  • deleteProperty(target, property):拦截对目标对象属性的删除操作。
  • getOwnPropertyDescriptor(target, property):拦截对目标对象属性描述符的获取操作。

8.Reflect

1)Reflect是⼀个对象,提供了多种⽅法⽅便我们统⼀管理对象。 2)Reflect 是⼀个内置的对象,它提供拦截 JavaScript 操作的⽅法。这些⽅法与proxy handlers (enUS)的⽅法相同。 3)与⼤多数全局对象不同 Reflect 并⾮⼀个构造函数,所以不能通过new 运算符对其进⾏调⽤,或者将 Reflect 对象作为⼀个函数来调⽤。 4)Reflect 的所有属性和⽅法都是静态的(就像 Math 对象)。

为什么需要使⽤Reflect: 1)在对对象进⾏操作时有些⽅法会有返回值,操作对象变的更加规范 2)Object作为构造函数,操作对象的⽅法放在它身上不是很合适,早期的设计不规范导致的。 3)在使⽤Proxy监听对象时,⽤Reflect来操作对象避免了对原对象的直接操作。

9.CommonJS与ESModules的区别

  1. 语法差异:

    • CommonJS使用require来导入模块,使用module.exportsexports来导出模块。
    • ES模块化使用import来导入模块,使用export来导出模块。
  2. 动态与静态导入:

    • CommonJS模块的导入和导出是动态的,可以在运行时根据条件进行导入和导出。模块的导入是同步的,导入的模块会被缓存起来,多次导入同一个模块只会执行一次。
    • ES模块化在设计上是静态的,导入和导出的模块路径必须在编译时确定,不能根据条件动态导入。模块的导入是异步的,导入的模块是只读的,每次导入都会重新执行模块。
  3. 上下文差异:

    • CommonJS模块在导入时会创建一个新的模块上下文,并将导出的值作为模块的属性。模块内部的变量和函数只在模块内部可见,不会污染全局作用域。
    • ES模块化使用严格模式,每个模块都有自己的作用域,模块内部的变量和函数默认不会泄漏到全局作用域,需要显式导出才能在其他模块中使用。
  4. 浏览器支持:

    • CommonJS模块规范主要用于服务器端开发,Node.js是其主要实现者,可以直接在Node.js中使用。
    • ES模块化是ECMAScript标准的一部分,从ES6开始原生支持模块化。在现代浏览器中,可以直接使用ES模块化,无需额外的构建工具。

10.生成器与迭代器

生成器是一种特殊类型的函数,可以通过使用function*语法定义。生成器函数可以暂停和恢复执行,并且可以生成多个值。生成器函数内部使用yield关键字来定义生成器的每个值。调用生成器函数不会立即执行函数体,而是返回一个迭代器对象。通过调用迭代器的next()方法,可以逐步执行生成器函数并获取生成的值。

形式上,Generator函数是一个普通函数,但是有两个特征:

  1. function关键字与函数名之间有一个星号
  2. 函数体内部使用yield表达式,定义不同的内部状态
function* numberGenerator() {
  yield 1;
  yield 2;
  yield 3;
}
const generator = numberGenerator();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }

迭代器是一种对象,它提供了一种顺序访问集合中每个元素的方式,同时具有一些特定的方法。迭代器可以用于遍历数组、字符串、集合、映射等可迭代对象。迭代器对象必须实现一个名为next()的方法,该方法返回一个包含valuedone属性的对象。value表示当前迭代的值,done表示迭代是否结束。

function createIterator(array) {
  let index = 0;
  return {
    next: function() {
      return index < array.length ?
        { value: array[index++], done: false } :
        { value: undefined, done: true };
    }
  };
}
const myIterator = createIterator([1, 2, 3]);
console.log(myIterator.next()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: 3, done: false }
console.log(myIterator.next()); // { value: undefined, done: true }

11.promise 常见方法和 all 和 race

Promise.race() race的用法:谁跑的快,以谁为准执行回调。

race的使用场景:比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作

Promise.all() all的用法:谁跑的慢,以谁为准执行回调。

在前端的开发实践中,我们有时会遇到需要发送多个请求并根据请求顺序返回数据的需求

四、网络

1.AJAX是什么,怎么实现

AJAX(Asynchronous JavaScript and XML)是一种用于在后台与服务器进行异步通信的技术。它允许网页在不刷新整个页面的情况下更新部分内容,提供了更好的用户体验。

  1. XMLHttpRequest 对象:XMLHttpRequest 是浏览器提供的内置对象,用于发送 HTTP 请求和接收服务器响应。通过创建 XMLHttpRequest 对象,可以与服务器进行异步通信。
  2. 事件监听器:可以为 XMLHttpRequest 对象添加事件监听器,以便在请求的不同阶段(如请求发送、响应接收等)触发相应的事件,从而执行相应的操作。
  3. 回调函数:通常使用回调函数来处理服务器响应。可以在发送请求时指定一个回调函数,在接收到服务器响应后,该回调函数将被调用,并处理服务器返回的数据。
// 创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();
// 设置请求的方法和 URL
xhr.open('GET', 'https://api.example.com/data', true);
// 注册事件监听器,处理服务器响应
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    // 服务器响应成功
    var response = xhr.responseText;
    console.log(response);
  }
};
// 发送请求
xhr.send();

2.get与post有什么区别

  • get一般是获取数据,post一般是提交数据
  • get参数会放在url上,安全性较差,post放在body中
  • get请求刷新服务器或退回是没有影响的,post请求退回时会重新提交数据
  • get请求时会被缓存,post不会
  • get请求会被保存在浏览器历史记录中,post不会
  • get请求只能进行url编码,post请求支持多种
  • get 请求的参数长度有限制,不同的浏览器和服务器有不同的限制,通常在几千个字符左右,而post理论上没有限制,但实际上会受到服务器和网络的限制

3.HTTP状态码

1xx(信息性状态码) :表示请求已被接收,继续处理。

  • 100 Continue:请求已成功接收,客户端应继续发送请求的其余部分。

2xx(成功状态码) :表示请求已成功处理。

  • 200 OK:请求已成功,返回请求的资源。
  • 201 Created:请求已成功,并且服务器创建了新的资源。
  • 204 No Content:请求已成功处理,但没有返回任何内容。

3xx(重定向状态码) :表示需要进一步操作以完成请求。

  • 301 Moved Permanently:请求的资源已永久移动到新的URL。
  • 302 Found:请求的资源暂时移动到新的URL。
  • 304 Not Modified:客户端的缓存资源是最新的,可以直接使用缓存。

4xx(客户端错误状态码) :表示客户端发送的请求有错误。

  • 400 Bad Request:请求无效,服务器无法理解。
  • 401 Unauthorized:请求需要身份验证。
  • 404 Not Found:请求的资源不存在。

5xx(服务器错误状态码) :表示服务器在处理请求时发生错误。

  • 500 Internal Server Error:服务器遇到了意外错误,无法完成请求。
  • 503 Service Unavailable:服务器当前无法处理请求,通常是由于过载或维护。

4.三次握手四次挥手

通过三次握手,客户端和服务器都确认了对方的可达性和通信能力,建立了可靠的双向通信连接。

  1. 第一次握手(SYN):客户端发送一个带有SYN(同步)标志的TCP包给服务器,表示客户端请求建立连接。客户端进入SYN_SENT状态。
  2. 第二次握手(SYN + ACK):服务器收到客户端的请求后,回复一个带有SYN和ACK(确认)标志的TCP包给客户端,表示同意建立连接。服务器进入SYN_RECEIVED状态。
  3. 第三次握手(ACK):客户端收到服务器的回复后,再次发送一个带有ACK标志的TCP包给服务器,表示确认连接建立。服务器和客户端都进入ESTABLISHED状态,连接建立成功。

通过四次挥手,双方都确认了对方的关闭请求,并完成了连接的关闭过程。

  1. 第一次挥手(FIN):当客户端或服务器决定关闭连接时,发送一个带有FIN(结束)标志的TCP包给对方,表示要关闭连接。发送方进入FIN_WAIT_1状态。
  2. 第二次挥手(ACK):接收到关闭请求的一方收到FIN后,发送一个带有ACK标志的TCP包给对方,表示确认收到关闭请求。发送方进入FIN_WAIT_2状态。
  3. 第三次挥手(FIN):接收到确认关闭请求的一方在准备好关闭连接后,发送一个带有FIN标志的TCP包给对方,表示自己也要关闭连接。发送方进入LAST_ACK状态。
  4. 第四次挥手(ACK):接收到关闭请求的一方收到FIN后,发送一个带有ACK标志的TCP包给对方,表示确认收到关闭请求。双方都进入CLOSED状态,连接关闭。

5.跨域是什么?有哪些解决跨域的方法和方案?

同源协议是指,域名、协议、端口均为相同。 跨域,不同的域名、协议、端口皆为不同域,一个域与另一个域名、协议或者端口不同的域的之间访问都叫跨域

解决跨域的方法和方案: 1:通过服务端代理请求。如PHP,服务端语言php是没有跨域限制的,让服务器去别的网站获取内容然后返回给页面。

2:jsonp跨域:浏览器的同源策略限制了js的跨域能力,但没有限制link img iframe script 的跨域行为,利用js创建一个script标签,把json的url赋给script的scr属性,把这个script插入到页面里,让浏览器去跨域获取资源,JS先声明好回调函数,插入页面后会代为执行该函数,并且传入json对象为其参数。缺点:jsonp只针对get请求,script标签加载回来的资源会被当成js在全局执行

3:CORS 跨域资源共享 CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing) 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制 整个CORS通信过程,都是浏览器自动完成,不需要用户参与 对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样 实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以跨源通信

4:nginx代理跨域 通过nginx服务器转发跨域请求,达到跨域的目的

五、NodeJS

1.使用 Node.js 实现遍历文件夹并输出所有的文件名

const fs = require('fs')
const path = require('path')
const getAllFile = function (dir) {
    function traverse(dir) {
        fs.readdirSync(dir).forEach(file => {
            const pathname = path.join(dir, file)
            if (fs.statSync(pathname).isDirectory()) {
                traverse(pathname)
            } else {
                console.log(file)
            }
        })
    }
    traverse(dir)
}

2.koa 中间件的实现原理

  1. 每个中间件默认接受两个参数,第一个参数是 Context 对象,第二个参数是 next 函数。只要调用 next 函数,就可以把执行权转交给下一个中间件。
  2. 如果中间件内部没有调用 next 函数,那么执行权就不会传递下去。
  3. 多个中间件会形成一个栈结构,以“先进后出”的顺序执行。整个过程就像,先是入栈,然后出栈的操作。

3.koa洋葱模型

在洋葱模型中,请求处理链的执行顺序是从外到内,然后再从内到外。也就是说,当一个中间件函数调用了next()时,控制权会传递给下一个中间件函数,但是当前中间件函数会暂停执行,直到下一个中间件函数执行完毕后再继续执行。这种机制使得中间件函数可以在请求的前后进行处理,例如在请求前进行一些预处理操作,然后在请求后进行一些清理操作。

4.图片上传到服务器的过程

  • 前端业务:根据后台接口发送请求,图片作为参数,需要带上一个 name 字段
  • 后台业务:后台接收图片可以使用 nodefspath 文件系统加上 multer 包实现。主要思想是通过 multer 创建一个临时空间用来接收并存储前端发送过来的二进制图片数据。通过 fs 模块读取临时空间的数据,并使用 pipe 方法注入到 fs 模块创建 path 模块指向的服务器文件夹下
var express = require('express')
var router = express.Router()
var fs = require('fs')
var path = require('path')
/* 用于处理非表单的文件数据流 */
var multer = require('multer')
// 配置数据流向的文件,绝对路径,相对于根目录
var upload = multer({ dest: 'upload/' })
// 创建一个接收为编码的二进制数据流的方法实例 接收 name 为 newimg 字段的上传文件,最大接收为 1
var cpUpload = upload.fields([{ name: 'newimg', maxCount: 1 }])
// 接口
router.post('/add', cpUpload, (req, res) => {
    // 前端发送请求后,服务器已经接受到了前端传递过来的图片数据,保存在 files 对象下
    // 加上 cpUpload,数据就会从这个方法所设置的地址流过来,生成一个本地临时空间,类似于虚拟 DOM
    // 获取这段数据
    var img = req.files.newimg[0]
    // fs 模块读取临时空间的数据
    var readStream = fs.createReadStream(img.path)
    // 设置图片存入的路径,并给文件名前面加上一个时间轴,防止命名重复
    var imgpath = `/cdn/${Date.now()}-${img.originalname}`
    // 创建一个写入图片数据的地址
    var writeStram = fs.createWriteStream(
        path.resolve(__dirname, `../public${imgpath}`)
    )
    // 设置一个 pipe 管道,将读取的数据解析并注入到写入地址
    readStream.pipe(writeStram)
    // 监听注入地址的 close 事件,表示注入完毕
    writeStram.on('close', () => {
        // 返回给前端一个图片地址
        res.json({ err: 0, msg: 'success', data: { img: imgpath } })
    })
})
module.exports = router

5.koa与express的区别

  • 最大的区别在于语法,experss 的异步采用的是回调函数的形式,而 koa1 支持 generator + yeild,koa2 支持 await/async,无疑更加优雅。
  • 中间件的区别,koa 采用洋葱模型,进行顺序执行,出去反向执行,支持 context 传递数据 express 本身无洋葱模型,需要引入插件,不支持 context express 的中间件中执行异步函数,执行顺序不会按照洋葱模型,异步的执行结果有可能被放到最后,response 之前。 这是由于,其中间件执行机制,递归回调中没有等待中间件中的异步函数执行完毕,就是没有 await 中间件异步函数
  • 集成度区别 express 内置了很多中间件,集成度高,使用省心, koa 轻量简洁,容易定制

6.MySQL 和 MongoDB 的区别

  • 数据库模型 mysql 是关系型数据库,现在使用最多的数据存储技术 mongodb 是非关系型数据库,并且是非关系型数据库中最像关系型的数据库
  • 存储方式 mongodb-以类 JSON 的文档的格式存储 mysql-不同引擎有不同的存储方式
  • 数据处理方式 mongodb-基于内存,将热数据存放在物理内存中,从而达到高速读写 mysql-不同引擎有自己的特点
  • mongodb 的查询语句类似于 js 使用 api 的场景,通过 . 来调用,而 mysql 则是标准的 sql 语句,同样查询代码如下
db.users.find({ username: '张三', age: 27 })
const sql = `select * from users where "username" = "张三" and age = 27`

7.谈谈 socket 的常见使用方式

  • 第一种方式是 netSocket,主要使用的是 node 中的 net 模块。服务端通过 new net.createServer() 创建服务,使用 on('connection') 方法建立连接,在回调函数中即可获取到客户端发送的信息。客户端通过 new net.Socket() 创建 Socket,通过 connect 连接指定端口和域名后,即可调用 write 方法发送数据
  • 第二种方式是 webSocket,服务端引入第三方插件 ws 创建 socket 服务,客户端使用 H5 新增 API new WebSocket 连接服务端,通过 send 方法发送数据,onmessage 方法接收数据
  • 第三种方式是 socket.io,服务端引入 socket.io模块创建服务,客户端引入 socket.io.js 文件,建立连接后,客户端和服务端都是通过 on 方法接收数据,都是使用 emit 方法发送数据。

六、Vue

1.MVVM

一句话总结 Web 前端 MVVM:操作数据,就是操作视图,就是操作 DOM(所以无须操作 DOM )。

  • Model: 域模型,用于持久化
  • View: 作为视图模板存在
  • ViewModel: 作为视图的模型,为视图服务

无须操作 DOM !借助 MVVM 框架,开发者只需完成包含 声明绑定 的视图模板,编写 ViewModel 中业务数据变更逻辑,View 层则完全实现了自动化。这将极大的降低前端应用的操作复杂度、极大提升应用的开发效率。MVVM 最标志性的特性就是 数据绑定 ,MVVM 的核心理念就是通过 声明式的数据绑定 来实现 View 层和其他层的分离。完全解耦 View 层这种理念,也使得 Web 前端的单元测试用例编写变得更容易。

2.Vue响应式原理

Vue2中:主要是利用了Object.defineProperty的方法里面的settergetter方法的观察者模式来实现。在组件初始化时会给每一个data属性注册gettersetter,然后再new 一个自己的Watcher对象,此时watcher会立即调用组件的render函数去生成虚拟DOM。在调用render的时候,就会需要用到data的属性值,此时会触发getter函数,将当前的Watcher函数注册进sub里。(当属性的值发生变化时,setter会被触发,并通知所有依赖该属性的组件进行更新。Vue使用发布-订阅模式来实现这个机制,其中属性充当发布者,而依赖的组件充当订阅者。)当data属性发生改变之后,就会遍历sub里所有的watcher对象,通知它们去重新渲染组件。

而对于数组,不可避免的会存在大量数据,按照目前处理逻辑,数组中的 9999 条数据,将全部被添加 get、set 方法,为了实现对数组索引的劫持,就需要对数组中每一项进行观测,开销可能会比较大,所以,权衡性能和应用场景,Vue 源码中没有采用Object.defineProperty对数组进行处理;

在 Vue 中,重写了 能够改变原数组的7 个方法:pushpopspliceshiftunshiftreversesort;调用原生对应的方法对数组进行更新。-->重新解析模板,进而更新页面。

缺点: 无法监测对象属性的添加和删除数组索引和长度的变更,如需给后添加的属性做响应式,需使用Vue.set(target,propertyName/index,value)vm.$set(target,propertyName/index,value)

Vue3中:主要是使用了ES6的Proxy代理对象,Proxy 可以直接监听对象而非属性,可以直接监听数组的变化;在创建Vue实例时,Vue会将data对象转换为一个代理对象。代理对象会拦截对属性的访问和修改,并触发相应的更新。在访问属性时,Vue会收集当前正在进行渲染的组件(Watcher)作为依赖。这样,当属性发生变化时,Vue能够知道哪些组件需要进行更新。当属性的值发生变化时,Proxy对象会触发相应的代理操作,并通知所有依赖该属性的组件进行更新。Vue 3.x的响应式系统采用了懒代理的策略,即只有在属性被访问时才会进行代理操作。这样可以提高性能,减少不必要的代理操作。

优点:Proxy 有多达 13 种拦截方法,不限于 applyownKeysdeletePropertyhas 等是 Object.defineProperty 不具备的; 缺点: Proxy 存在浏览器兼容性问题

3.组合式API

1:在Compostion API 中时根据逻辑相关组织代码的,提高可读性和可维护性,类似于reacthook写法。 2:更好的重用逻辑代码,在选项式API中通过MIxins重用逻辑代码,容易发生命名冲突且关系不清。 3:解决在生命周期函数经常包含不相关的逻辑,但又不得不把相关逻辑分离到了几个不同方法中的问题,如在mounted中设置定时器,但需要在destroyed中来清除定时器,将同一功能的代码拆分到不同的位置,造成后期代码维护的困难。

4.混入

Vue 2.x中的混入是一种将一组选项混入到组件中的方式,它可以用来共享组件之间的逻辑。混入可以包含组件选项、生命周期钩子、方法等,并且可以在多个组件中进行复用。然而,混入的使用可能会导致命名冲突和代码耦合,特别是当多个混入对象具有相同的选项时。因此,在使用混入时需要小心,避免潜在的问题。

5.Vuex可以直接修改state的值吗?

可以直接修改,但是极其不推荐,state的修改必须在mutation来修改,否则无法被devtool所监测,无法监测数据的来源,无法保存状态快照,也就无法实现时间漫游/回滚之类的操作。

6.Vuex的mutation不能做异步操作

Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,否则无法被devtools所监测。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

7.v-model与Vuex的冲突

当使用v-model绑定一个表单元素时,它会自动更新组件的数据属性。如果这个数据属性是从Vuex中获取的状态,当通过v-model修改表单元素的值时,会直接修改Vuex中的状态,从而可能导致Vuex中的状态被不同组件同时修改,进而引发一些意想不到的问题。

为了避免这种冲突,可以采取以下策略:

  1. 不直接使用v-model绑定Vuex中的状态,而是通过计算属性或方法来获取和设置Vuex中的状态。
  2. 在组件中使用局部数据来绑定v-model,并在需要时手动更新Vuex中的状态。
  3. 使用Vuex的gettersmutations来获取和更新状态,而不是直接修改v-model绑定的数据。

8.单向数据流与双向数据绑定

对于 Vue 来说,组件之间的数据传递具有单向数据流这样的特性称为单向数据流,单向数据流(Unidirectional data flow)方式使用一个上传数据流和一个下传数据流进行双向数据通信,两个数据流之间相互独立,单向数据流指只能从一个方向来修改状态。

而双向数据绑定即为当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化,两个数据流之间互为影响。

9.vue-router实现原理

它通过监听URL的变化,并根据配置的路由规则来匹配对应的组件,实现页面的切换和组件的加载。

  1. 路由配置: 在Vue应用中,我们需要先定义一组路由规则,用来描述URL和对应组件之间的映射关系。路由规则可以通过Vue Router提供的API进行配置,通常是一个包含路径和组件的对象数组。
  2. 路由初始化: 在Vue应用初始化时,Vue Router会创建一个路由实例,并将路由规则进行解析和处理。它会根据当前URL匹配对应的路由规则,并加载对应的组件。
  3. 路由导航: 当用户在应用中切换路由或点击链接时,Vue Router会捕获URL的变化,并根据配置的路由规则进行匹配。它会触发路由导航的过程,包括导航守卫的执行、组件的销毁和创建等。
  4. 导航守卫: Vue Router提供了导航守卫的功能,用于在路由导航过程中进行拦截和控制。导航守卫包括全局守卫、路由守卫和组件守卫,可以用来实现路由的权限控制、页面的切换动画等功能。
  5. 组件加载: 当路由匹配成功后,Vue Router会根据配置的路由规则加载对应的组件。它可以通过异步组件加载、懒加载等方式来优化应用的性能。
  6. 路由切换: 当路由切换时,Vue Router会处理组件的销毁和创建。它会销毁当前组件实例,并创建新的组件实例,并将新的组件渲染到应用中。

在Vue中利用数据劫持defineProperty在原型prototype上初始化了一些getter,分别是router代表当前Router的实例 、 router代表当前Router的实例、router代表当前Router的实例、route 代表当前Router的信息。在install中也全局注册了router-view,router-link,其中的Vue.util.defineReactive, 这是Vue里面观察者劫持数据的方法,劫持route,当route触发setter方法的时候,则会通知到依赖的组件。 接下来在init中,会挂载判断是路由的模式,是history或者是hash,点击行为按钮,调用hashchange或者popstate的同时更route, route的更新会触发route-view的重新渲染。

10.routeroute 和 router

$route用来获取路由的信息的,它是路由信息的一个对象,里面包含路由的一些基本信息,包括namemetapathhashqueryparamsfullPathmatchedredirectedFrom等。而$router主要是用来操作路由的,它是VueRouter的实例,包含了一些路由的跳转方法,钩子函数

11.自定义指定

通过directive来自定义指令,自定义指令分为全局指令和局部指令,自定义指令也有几个的钩子函数,常用的有bindupdate,当 bindupdate 时触发相同行为,而不关心其它的钩子时可以简写。一个表达式可以使用多个过滤器。过滤器之间需要用管道符“|”隔开。其执行顺序从左往右。

12. Vue中 keep-alive

keep-aliveVue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。一旦使用keepalive包裹组件,此时moutedcreated等钩子函数只会在第一次进入组件时调用,当再次切换回来时将不会调用。此时如果我们还想在每次切换时做一些事情,就需要用到另外的周期函数,activeddeactived,这两个钩子函数只有被keepalive包裹后才会调用。

13.生命周期

Vue2的生命周期

  1. beforeCreate: 在实例初始化之后,数据观测和事件配置之前调用。在该阶段,组件的data和methods等属性还未初始化。
  2. created: 在实例创建完成后调用。在这个阶段,组件的data已经初始化,可以访问和操作数据,也可以进行一些初始的异步操作。
  3. beforeMount: 在组件挂载之前调用。在该阶段,模板已经编译完成,但尚未将组件渲染到页面上。
  4. mounted: 在组件挂载到页面之后调用。在这个阶段,组件已经被渲染到页面上,可以访问DOM元素,进行DOM操作或初始化一些第三方库等。
  5. beforeUpdate: 在组件更新之前调用。在该阶段,组件的数据发生改变,但尚未重新渲染DOM。
  6. updated: 在组件更新之后调用。在这个阶段,组件的数据已经更新,DOM也已经重新渲染完成。
  7. beforeDestroy: 在组件销毁之前调用。在该阶段,组件仍然完全可用,可以进行一些清理工作,如清除定时器、解绑事件等。
  8. destroyed: 在组件销毁之后调用。在这个阶段,组件实例被销毁,所有的事件监听器和观察者都被移除,组件不再可用。
  9. activated:用于keep-alive组件,组件激活时的钩子
  10. deactivated:用于keep-alive组件,组件失活时的钩子
  11. nextTick:下一次dom更新完毕时调用

Vue3的生命周期

  1. beforeCreate: 在实例创建之前调用。在这个阶段,组件的配置已经完成,但组件实例尚未创建。
  2. created: 在实例创建完成后调用。在这个阶段,组件实例已经创建,可以访问和操作数据,也可以进行一些初始的异步操作。
  3. beforeMount: 在组件挂载之前调用。在该阶段,模板已经编译完成,但尚未将组件渲染到页面上。
  4. mounted: 在组件挂载到页面之后调用。在这个阶段,组件已经被渲染到页面上,可以访问DOM元素,进行DOM操作或初始化一些第三方库等。
  5. beforeUpdate: 在组件更新之前调用。在该阶段,组件的数据发生改变,但尚未重新渲染DOM。
  6. updated: 在组件更新之后调用。在这个阶段,组件的数据已经更新,DOM也已经重新渲染完成。
  7. beforeUnmount: 在组件卸载之前调用。在该阶段,组件仍然完全可用,可以进行一些清理工作,如清除定时器、解绑事件等。
  8. unmounted: 在组件卸载之后调用。在这个阶段,组件实例被卸载,所有的事件监听器和观察者都被移除,组件不再可用。

以下是在setup函数中常用的生命周期钩子函数的替代函数:

  1. onBeforeMount: 替代了Vue 2.x版本的beforeMount钩子函数。在组件挂载之前调用。
  2. onMounted: 替代了Vue 2.x版本的mounted钩子函数。在组件挂载到页面之后调用。
  3. onBeforeUpdate: 替代了Vue 2.x版本的beforeUpdate钩子函数。在组件更新之前调用。
  4. onUpdated: 替代了Vue 2.x版本的updated钩子函数。在组件更新之后调用。
  5. onBeforeUnmount: 替代了Vue 2.x版本的beforeDestroy钩子函数。在组件卸载之前调用。
  6. onUnmounted: 替代了Vue 2.x版本的destroyed钩子函数。在组件卸载之后调用。

14.插槽

插槽相当于预留了一个位置,可以将我们书写在组件内的内容放入,写一个插槽就会将组件内的内容替换一次,两次则替换两次。为了自定义插槽的位置我们可以给插槽取名,它会根据插槽名来插入内容,一一对应。

作用域插槽, 父组件可以向子组件传递数据,并在子组件内部使用该数据进行处理,然后将处理结果作为插槽内容返回给父组件进行渲染。

需要注意的是,作用域插槽在Vue.js 2.x版本中使用slot-scope来定义插槽的数据,而在Vue.js 3.x版本中使用v-slot来定义插槽,并将数据直接传递给插槽。这是Vue.js版本之间的一个变化,需要根据所使用的Vue.js版本来选择正确的语法。

15.Vue中的diff算法

Vue.js使用虚拟DOM和diff算法来高效地更新DOM。虚拟DOM是一个轻量级的JavaScript对象,它对应着真实的DOM结构。当数据发生变化时,Vue.js会通过diff算法比较新旧虚拟DOM树的差异,并将差异应用到真实的DOM上,从而实现DOM的局部更新,提高性能。

Vue.js中的diff算法主要包括以下几个步骤:

  1. 创建新的虚拟DOM树:当数据发生变化时,Vue.js会重新生成新的虚拟DOM树,该树和之前的虚拟DOM树进行比较。

  2. 比较新旧虚拟DOM树:Vue.js会逐层比较新旧虚拟DOM树的节点,以找出差异。比较的过程是深度优先的,先比较根节点,然后递归比较子节点。

  3. 更新差异节点:在比较过程中,Vue.js会标记出新旧虚拟DOM树中的差异节点。然后,根据差异的类型(如添加、删除、移动、更新等),对真实的DOM进行相应的操作。

    • 添加节点:将新节点插入到DOM中的正确位置。
    • 删除节点:从DOM中移除旧节点。
    • 移动节点:将节点移动到正确的位置,而不是删除和重新插入。
    • 更新节点:更新节点的属性和内容。
  4. 递归处理子节点: 如果差异节点有子节点,Vue.js会递归地进行上述的比较和更新操作,直到所有差异节点都被处理完毕。

16.v-model与.sync

对于普通的文本框来说,v-model = :value + @input。对单选框和复选框来说,v-model = :checked + @change.sync修饰符传递给子组件的是update事件,通过:title.sync="Title"---this.$emit('update:title', value);

17.computed和watch的区别

计算属性computed :

  1. 支持缓存,只有依赖数据发生改变,才会重新进行计算
  2. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
  3. computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
  4. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
  5. 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。

侦听属性watch:

  1. 不支持缓存,数据变,直接会触发相应的操作;
  2. watch支持异步
  3. 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
  4. 当一个属性发生变化时,需要执行对应的操作;一对多;
  5. 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数, immediate:组件加载立即触发回调函数执行, deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。 注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。

18.Vue2组件通信

  1. props
  2. $emit / v-on
  3. .sync
  4. v-model
  5. ref
  6. children/children / parent
  7. attrs/attrs / listeners
  8. provide / inject
  9. EventBus
  10. Vuex
  11. $root
  12. slot

19.Vue3组件通信

  • props
  • $emit
  • expose / ref
  • $attrs
  • v-model或modelValue
  • provide / inject
  • Vuex
  • mitt

20.v-for中的key

  • key是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确、更快速
  • diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后算出差异.
  • 用index作为key: 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。但如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。

21.Vue组件中的Data为什么是函数

如果data是一个函数的话,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。

22.v-if与v-for的优先级

在vue2中:当 v-ifv-for 一起使用时,v-for 具有比 v-if 更高的优先级。每次重渲染的时候遍历整个列表,不论是否发生了变化。

在vue3中:当 v-ifv-for 一起使用时,v-if 具有比 v-for 更高的优先级。由于 v-if 优先级高,导致页面什么也不会渲染,控制台还有报错,官方推荐的写法是这样的, 把 v-for 移动到容器元素上,例如ul,ol 或者外面包裹一层 template

23.v-if与v-show

1、手段:v-if是动态的向DOM树添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显示和隐藏。

2、编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载); v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留

3、性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗 使用场景:v-if适合运营条件不大可能改变;v-show适合频繁切换

24. Vue的常用修饰符

一、v-model修饰符

1、 .lazy:输入框改变,这个数据就会改变,lazy这个修饰符会在光标离开input框才会更新数据:

2、 .trim:输入框过滤首尾的空格:

3、 .number:先输入数字就会限制输入只能是数字

二、事件修饰符

  1. .stop:阻止事件冒泡,相当于调用了event.stopPropagation()方法
  2. .prevent:阻止默认行为,相当于调用了event.preventDefault()方法,比如表单的提交、a标签的跳转就是默认事件
  3. .self:只有元素本身触发时才触发方法,就是只有点击元素本身才会触发。比如一个div里面有个按钮,div和按钮都有事件,我们点击按钮,div绑定的方法也会触发,如果div的click加上self,只有点击到div的时候才会触发,变相的算是阻止冒泡
  4. .once:事件只能用一次,无论点击几次,执行一次之后都不会再执行
  5. .capture:事件的完整机制是捕获-目标-冒泡,事件触发是目标往外冒泡
  6. .keyCode:监听按键的指令,具体可以查看vue的键码对应表
  7. .sync:对prop进行双向绑定
  8. .active:给原生事件绑定

25.pros的值能不能改的问题

父子组件传值时,父组件传递的参数,数组和对象,子组件接受之后可以直接进行修改,并且会传递给父组件相应的值也会修改。 如果传递的值是字符串,直接修改会报错。 所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。

更改对象 / 数组类型的 props 当对象或数组作为 props 被传入时,虽然子组件无法更改 props 绑定,但仍然可以更改对象或数组内部的值。这是因为 JavaScript 的对象和数组是按引用传递,而对 Vue 来说,禁止这样的改动,虽然可能生效,但有很大的性能损耗,比较得不偿失。在大多数场景下,子组件应该抛出一个事件来通知父组件做出改变。

26.Vue3对比Vue2

性能比vue2.x快1.2~2倍、支持tree-shaking,按需编译,体积比vue2.x更小、支持组合API、更好的支持TS、更先进的组件

性能比vue2.x快1.2~2倍如何实现的呢

1.diff算法更快

vue2.0是需要全局去比较每个节点的,若发现有节点发生变化后,就去更新该节点,vue3.0是在创建虚拟dom中,会根据DOM的的内容会不会发生内容变化,添加静态标记, 谁有flag!比较谁。

2、静态提升

vue2中无论元素是否参与更新,每次都会重新创建,然后再渲染 vue3中对于不参与更新的元素,会做静态提升,只被创建一次,在渲染时直接复用即可

3、事件侦听缓存

默认情况下,onclick为动态绑定,所以每次都会追踪它的变化,但是因为是同一函数,没有必要追踪变化,直接缓存复用即可,在之前会添加静态标记 会把点击事件当做动态属性 会进行diff算法比较, 但是在事件监听缓存之后就没有静态标记了,就会进行缓存复用

为什么vue3.0体积比vue2.x小

在vue3.0中创建vue项目 除了vue-cli,webpack外还有 一种创建方法是ViteVite是作者开发的一款有意取代webpack的工具,其实现原理是利用ES6的import会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去webpack冗长的打包时间

27.ref与reactive

  1. refreactive都是vue3的监听数据的方法,本质是proxy
  2. ref 基本类型复杂类型都可以监听(我们一般用ref监听基本类型),reactive只能监听对象
  3. ref底层还是reactive

冷门or疑难or场景

1.script标签中色async与defer属性有什么区别

当没有async与defer时,浏览器会立即加载并执行指定脚本

  1. async 属性:

    • 当浏览器遇到带有 async 属性的 <script> 标签时,它会异步加载脚本,不会阻塞 HTML 文档的解析过程。
    • 异步加载的脚本会在加载完成后立即执行,不会按照它们在 HTML 中出现的顺序执行。
    • 如果有多个带有 async 属性的脚本,它们的执行顺序是不确定的,取决于加载完成的时间。
    • async 属性适用于独立的脚本,即脚本之间没有依赖关系,且不需要等待其他脚本加载和执行。
  2. defer 属性:

    • 当浏览器遇到带有 defer 属性的 <script> 标签时,它会异步加载脚本,但是会等到 HTML 文档解析完成后再执行脚本。
    • 多个带有 defer 属性的脚本会按照它们在 HTML 中出现的顺序执行。
    • defer 属性适用于有依赖关系的脚本,例如脚本需要操作 DOM 元素或依赖其他脚本中定义的变量或函数。

总结:

  • async 属性用于异步加载和执行脚本,不会阻塞 HTML 解析,适用于独立的脚本。
  • defer 属性用于异步加载脚本,等待 HTML 解析完成后再执行,适用于有依赖关系的脚本。

2.实现页面文本不可复制

有 CSS 和 JS 两种⽅法,以下任选其⼀或结合使⽤

  • 使⽤ CSS 如下:user-select: none;
  • 使⽤ JS 如下,监听 selectstart 事件,禁⽌选中。当⽤户选中⼀⽚区域时,将触发 selectstart 事件,Selection API 将会选中⼀⽚区域。禁⽌选中区域即可实现⻚⾯⽂本不可复制。
document.body.onselectstart = (e) => {  e.preventDefault(); };
document.body.oncopy = (e) => {  e.preventDefault();};

3.浏览器输⼊⼀个URL到⻚⾯显示的过程

输入URL到页面显示的一般过程,其中涉及了DNS解析、建立连接、发送请求、服务器处理、响应返回、渲染页面等多个步骤。整个过程是复杂而快速的,使用户能够在浏览器中浏览和访问各种网页。

URL 输⼊: 1)检查输⼊的内容是否是⼀个合法的 URL 链 2)判断输⼊的 URL 是否完整, 如果不完整,浏览器可能会对域进⾏猜测,补全前缀或者后缀 3)使⽤⽤户设置的默认搜索引擎来进⾏搜索

DNS 解析: 1)浏览器不能直接通过域名找到对应的服务器 IP 地址 2)所以需要进⾏ DNS 解析,查找到对应的 IP 地址进⾏访问。

建⽴ TCP 连接: 1)三次握手

发送 HTTP / HTTPS 请求(建⽴ TLS 连接): 1)向服务器 发起 TCP 连接请求 2)当这个请求到达服务端后,通过 TCP 三次握⼿,建⽴ TCP 的连接。

服务器响应请求: 1)当浏览器到 web 服务器的连接建⽴后,浏览器会发送⼀个初始的 HTTP GET 请求,请求⽬标通常是⼀个 HTML ⽂件。服务器收到请求后,将发回⼀个 HTTP 响应报⽂,内容包括相关响应头和 HTML 正⽂。

浏览器解析渲染⻚⾯: 1)处理 HTML 标记并构建 DOM 树。 2)处理 CSS 标记并构建 CSSOM 树。 3)将 DOM 与 CSSOM 合并成⼀个渲染树 4)根据渲染树来布局,以计算每个节点的⼏何信息。 5)将各个节点绘制到屏幕上。

HTTP 请求结束,断开 TCP 连接

4.iframe 跨域问题,页面之间怎么传值?

一般有两个解决方案,一个是建立一个代理页面,通过代理页面传值,另一个方法是通过H5的postMessage方法传值。

第二种方法演示 首先,在父页面A中建立一个iframe,其中src要写好子页面B的地址,然后在A页面中写如下方法:

var iframe = document.getElementById("onemap");
var msg = {loginName:'arcgis',loginPassword:'Esri1234'};
var childDomain = "https://geoplat.training.com";
var childDomain = "https://geoplat.training.com";

记住,childDomain与A的iframesrc地址不一样,childDomain是域,而src是域中的一个页面,msg是传输的信息,可以是字符串,也可以是对象。上面的方法一定要写在一个函数中,并通过点击事件调用,如果希望iframe开始为空,点击后在设置src,可以在设置src之后,通过setTimeout设置一定时间后在传输信息。

在子页面B中,通过对window添加事件获取传输过来的信息:

window.addEventListener("message",function(obj){
  var name = obj.data.loginName;
  var password = obj.data.loginPassword;
  login.iframeChildLogin(name,password);
},false);

5. 0.1+0.2 等不等于 0.3?自己封装一个让他们相等的方法

在JavaScript中的二进制的浮点数0.1和0.2并不是十分精确,在他们相加的结果并非正好等于0.3,而是一个比较接近的数字 0.30000000000000004 ,所以条件判断结果为false。

方法1:设置一个误差范围值,通常称为”机器精度“,而对于Javascript来说,这个值通常是2^-52, 而在ES6中,已经为我们提供了这样一个属性:Number.EPSILON,而这个值正等于2^-52。这个值非常非常小,在底层计算机已经帮我们运算好,并且无限接近0,但不等于0,。这个时候我们只要判断(0.1+0.2)-0.3小于Number.EPSILON,在这个误差的范围内就可以判定0.1+0.2===0.3为true。

function numbersequal(a,b){ 
    return Math.abs(a-b)<Number.EPSILON;
} 

方法2:转为整数运算

6.图片懒加载怎么实现?

原理:随着滚轮滚动,底部的图片会被不断地加载,从而显示在页面上,按需加载,当页面需要显示图片的时候才进行加载,否则不加载

  1. 页面加载完成时记录每个img标签的src值的字符串,
  2. 用鼠标滚轮判断图片是否出现在屏幕,如果是,则把记录的src值赋值给src属性
  3. 然后让imagesrc来发起请求,获取对应的图片放置到DOM树的这个位置上,从而实现图片的页面渲染! 于是就可以知道,当进入页面的时候,其实我们已经把所有的图片的这个地址信息拿到了,图片懒加载的作用就是让这个图片的src按需发起请求,获取图片。

7.插入几万个 dom ,如何实现页面不卡顿?

  1. 批量插入:将要插入的DOM元素先添加到文档片段中,然后一次性将文档片段插入到页面中。这样可以减少DOM操作的次数,提高性能。

    const fragment = document.createDocumentFragment();
    for (let i = 0; i < 10000; i++) {
      const element = document.createElement('div');
      // 设置element的属性和内容
      fragment.appendChild(element);
    }
    document.body.appendChild(fragment);
    
  2. 虚拟滚动:如果插入的DOM元素是列表形式展示的,可以考虑使用虚拟滚动技术。虚拟滚动只渲染可见区域的DOM元素,而不是全部渲染。这样可以大大减少DOM元素的数量,提高性能。

    虚拟滚动可以通过使用第三方库(如React-VirtualizedVue-Virtual-Scroller)来实现,或者手动实现虚拟滚动逻辑。

  3. 分批插入:将大量的DOM插入操作拆分成多个小批次进行,使用setTimeoutrequestAnimationFrame来控制每个批次的执行时间。这样可以让浏览器有时间处理其他任务,避免长时间的阻塞。

    const elements = []; // 存储要插入的DOM元素
    const batchSize = 100; // 每批次插入的数量
    let index = 0;
    ​
    function insertBatch() {
      const fragment = document.createDocumentFragment();
      const endIndex = Math.min(index + batchSize, elements.length);
    ​
      for (; index < endIndex; index++) {
        const element = document.createElement('div');
        // 设置element的属性和内容
        fragment.appendChild(element);
      }
    ​
      document.body.appendChild(fragment);
    ​
      if (index < elements.length) {
        // 仍有剩余元素,继续下一批次插入
        requestAnimationFrame(insertBatch);
      }
    }
    ​
    insertBatch();
    
  4. 避免强制同步布局:在插入DOM元素之前,避免频繁读取布局信息(如offsetWidthoffsetHeight),因为这会触发强制同步布局(reflow),导致性能下降。如果需要获取布局信息,可以先将元素设置为display: none,插入后再显示。

    const element = document.createElement('div');
    element.style.display = 'none';
    // 设置element的属性和内容
    document.body.appendChild(element);
    element.style.display = ''; // 显示元素
    

    5.多线程(web worker)

8.Vue路由传参,刷新后还有吗

通过params传参会出现参数丢失的情况,可以通过query的传参方式或者在路由匹配规则加入占位符即可以解决参数丢失的情况。

9.如何封装通用组件

通用组件的封装就是对可复用组件的解耦和样式复用,为了解耦一般数据都是通过父组件传递过来,在子组件中进行数据处理,对于一些较为复杂的数据可能还需要做数据验证,为了避免高耦合,逻辑最好放在父组件中,通过自定义事件将数据回传,子组件只是一个承载体,这样既降低耦合,保证子组件中数据和逻辑不会混乱。如果同一组件需要适应不同需求时,我们需要配合slot来使用,可以通过具名插槽灵活地解决了不同场景同一组件不同配置的问题。

10.Vue $forceUpdate的原理

1、作用: 迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。

2、内部原理: 实例需要重新渲染是在依赖发生变化的时候会通知watcher,然后通知watcher来调用update方法,就是这么简单。

Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
        vm._watcher.update()
    }
}

11.Fetch

Fetch 规范与 ajax 主要有三种方式的不同:

  1. 当接收到一个代表错误的 HTTP 状态码, 即使响应的 HTTP 状态码是 404 或 500。从 fetch() 返回的 Promise 不会被标记为 reject, 相反,会标记为 resolve (但是会把 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject
  2. fetch() 可以接收跨域 cookies;也可以使用 fetch() 建立起跨域会话。
  3. fetch 不会发送 cookies。除非你使用了credentials 的初始化选项

用于发起获取资源的请求。它返回一个 promise,这个 promise 会在请求响应后被 resolve,并传回 Response 对象。当遇到网络错误时,fetch()返回的 promise 会被 reject,并传回 TypeError。成功的 fetch() 检查不仅要包括 promise 被 resolve,还要包括 Response.ok 属性为 true。HTTP 404 状态并不被认为是网络错误。

12.SSR服务端渲染

SSR(服务器端渲染)是一种将前端应用程序在服务器端进行渲染的技术,它的实现原理可以概括为以下几个步骤:

  1. 接收请求:当客户端发送一个请求时,服务器接收到该请求。
  2. 路由匹配:服务器根据请求的 URL 路径来确定要渲染的页面组件。
  3. 获取数据:服务器根据路由匹配的结果,从后端 API 或其他数据源获取数据。这些数据将用于在渲染过程中填充页面。
  4. 组件渲染:服务器使用相应的前端框架(如React、Vue等)来创建应用程序的组件实例。这些组件将用于生成最终的 HTML。
  5. 数据注入:将获取的数据注入到组件实例中,以便组件在渲染过程中可以使用这些数据。
  6. 组件渲染和同步:服务器调用组件的渲染方法,将组件转换为 HTML 字符串。在此过程中,组件内部的生命周期钩子函数也会被触发。
  7. 生成 HTML:服务器将组件渲染得到的 HTML 字符串返回给客户端。
  8. 客户端激活:客户端接收到服务器返回的 HTML 后,会将其中的 JavaScript 代码进行解析和执行。解析后的 JavaScript 代码会重新创建组件实例,并与服务器端渲染的结果进行比对,以确保一致性。
  9. 客户端渲染:客户端接管页面的交互和渲染,并处理后续的用户操作。

hash路由和history路由具体实现方式

hash模式:监听浏览器地址hash值变化,执行相应的js切换网页

hash指的是地址中#号以及后面的字符,也称为散列值。hash也称作锚点,本身是用来做页面跳转定位的,通过监听window的hashchange事件,当散列值改变时,可以通过location.hash来设置hash值

history模式:利用history API实现url地址改变,网页内容改变 History.back():移动到上一个网址,等同于点击浏览器的后退键。对于第一个访问的网址,该方法无效果。 History.forward():移动到下一个网址,等同于点击浏览器的前进键。对于最后一个访问的网址,该方法无效果。 History.go():接受一个整数作为参数,以当前网址为基准,移动到参数指定的网址。如果参数超过实际存在的网址范围,该方法无效果;如果不指定参数,默认参数为0,相当于刷新当前页面。 History.pushState():该方法用于在历史中添加一条记录。pushState()方法不会触发页面刷新,只是导致 History 对象发生变化,地址栏会有变化。 History.replaceState():该方法用来修改 History 对象的当前记录,用法与 pushState() 方法一样。 History.popstate():每当 history 对象出现变化时,就会触发 popstate 事件。

父子组件的生命周期执行顺序

父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted->父beforeUpdate->子beforeUpdate->子updated->父updated->父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

provide和inject 数据不是响应式的怎么解决

父组件中以函数式的方式提供依赖,后代组件中采用函数的方式调用。

// 父级组件
 
{
    provide() {
        return {
            getName: () => this.$data.name
        }
    }
}
 
// 子组件
<template>
    <div>{{ getName() }}</div>
</template>
 
{
    inject: ['getName']
}

vue3为什么比vue2打包要小

Tree-shaking 改进:没有用到的代码不会打包

重写的响应式系统:proxy比definporperty6

Composition API 更容易进行代码的重用和组合,减少了重复代码的产生

Fetch与AJAX

Fetch 规范与 ajax 主要有三种方式的不同:

  1. 当接收到一个代表错误的 HTTP 状态码, 即使响应的 HTTP 状态码是 404 或 500。从 fetch() 返回的 Promise 不会被标记为 reject, 相反,会标记为 resolve (但是会把 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject
  2. fetch() 可以接收跨域 cookies;也可以使用 fetch() 建立起跨域会话。
  3. fetch 不会发送 cookies。除非你使用了credentials 的初始化选项

用于发起获取资源的请求。它返回一个 promise,这个 promise 会在请求响应后被 resolve,并传回 Response 对象。当遇到网络错误时,fetch()返回的 promise 会被 reject,并传回 TypeError。成功的 fetch() 检查不仅要包括 promise 被 resolve,还要包括 Response.ok 属性为 true。HTTP 404 状态并不被认为是网络错误。

Vuex跨模块调用方法

const actions = {
    updateName({ commit }, payload){
        commit("SET_NAME", payload)
        commit("A/SET_NAME", "这里更新模块A的a_name", { root: true })
    }
}

模块B的actions对象的updateName方法添加了commit("A/SET_NAME", "这里更新模块A的a_name", { root: true })。第一个参数携带了模块A的命名空间,第二个参数是,重点在于第三个参数{ root: true },添加了root:true,则表示开放权限模块A的权限,否则无法实现跨模块调用方法。

for in 与 for of的区别

  • for...in:用于遍历对象的可枚举属性。它会遍历对象自身及其原型链上的所有可枚举属性。
  • for...of:用于遍历可迭代对象(如数组、字符串、Set、Map等)。它只能遍历对象的元素,无法遍历对象的属性。
  • for...in:遍历的顺序是不确定的,可能会以任意顺序遍历对象的属性。
  • for...of:遍历的顺序是按照对象的迭代器返回的顺序进行的,通常是按照对象的插入顺序。
  • for...in:遍历的是对象的属性名(字符串类型)。
  • for...of:遍历的是对象的元素值。

需要注意的是,for...in 遍历的是对象的属性,包括原型链上的属性,而 for...of 遍历的是可迭代对象的元素。在遍历数组时,for...of 是更常用和推荐的方式,而 for...in 则更适用于遍历对象的属性。

for 与 forEach的区别

  1. 语法形式:

    • for 循环:使用传统的循环语法进行遍历。
    • forEach 方法:使用数组的内置方法进行遍历。
  2. 返回值:

    • for 循环:没有返回值。
    • forEach 方法:没有返回值,仅用于遍历数组的每个元素。
  3. 中断控制:

    • for 循环:可以使用 breakreturn 来中断循环。
    • forEach 方法:无法使用 breakreturn 中断循环,会一直遍历完整个数组。
  4. 索引访问:

    • for 循环:可以通过索引访问数组的元素。
    • forEach 方法:无法直接访问索引,只能访问当前遍历到的元素。
  5. 支持的操作:

    • for 循环:可以在循环体内执行任意操作,如修改数组元素、使用条件语句等。
    • forEach 方法:不能修改原数组的元素,也不能使用条件语句等控制流程的操作。

ES6新增的数组方法

  1. Array.from(): 将类数组对象或可迭代对象转换为真正的数组。
  2. Array.of(): 创建一个包含传入参数的新数组。
  3. Array.prototype.find(): 返回数组中满足条件的第一个元素。
  4. Array.prototype.findIndex(): 返回数组中满足条件的第一个元素的索引。
  5. Array.prototype.includes(): 判断数组是否包含指定元素。
  6. Array.prototype.fill(): 使用指定的值填充数组的所有元素。
  7. Array.prototype.flat(): 将嵌套的数组扁平化为一维数组。
  8. Array.prototype.flatMap(): 先映射每个元素,然后将结果扁平化为一维数组。

js中的堆和栈

我们知道在js中的数据类型可以分为基本类型和引用类型。基本类型是存在栈内存中的引用类型是存在堆内存中的,但是引用类型的引用还是存在栈内存中的。

webpack的loader与插件

Loader 是用于处理模块的转换器,它将源文件作为输入,并输出转换后的文件。Loader 可以将各种类型的文件转换为模块,例如将 ES6 代码转换为 ES5、将 SCSS 文件转换为 CSS 等。Loader 在 module.rules 配置中定义,并按照规则应用于匹配的文件。Webpack 在打包过程中会根据文件的后缀名或其他条件,自动选择合适的 Loader 进行处理。

  • babel-loader:用于将 ES6+ 代码转换为 ES5 代码。
  • css-loader:用于加载 CSS 文件,并解析其中的 @importurl()
  • style-loader:将 CSS 代码注入到页面的 <style> 标签中。
  • file-loader:用于处理文件,例如图片、字体等,将它们复制到输出目录,并返回文件路径。

Plugin 是用于扩展 Webpack 功能的插件,它可以在打包的不同阶段执行自定义的操作,例如优化、资源管理、注入环境变量等。Plugin 可以通过实现特定的钩子函数来介入 Webpack 的构建过程,并对构建过程中的模块、资源进行处理。

for或map里循环里面套async await 怎么保证数据的顺序

await不能直接打断for循环或其他同步操作。如果你想在特定条件下中断循环,你可以使用break语句来跳出循环。

使用for...of循环:通过使用for...of循环,你可以按顺序依次处理每个元素,并等待每个异步操作完成,然后再进行下一个迭代。这样可以确保数据的顺序。

async function processArray(array) {
  for (const item of array) {
    await processItem(item);
  }
}
​
async function processItem(item) {
  // 执行异步操作
  await someAsyncFunction(item);
  // 处理结果
  console.log(`Processed item: ${item}`);
}
​
const array = [1, 2, 3, 4, 5];
processArray(array);

使用Promise.allmap函数:如果你想并行处理数组中的元素,但仍然希望保持顺序,可以使用Promise.all结合map函数。

async function processArray(array) {
  await Promise.all(array.map(async (item) => {
    await processItem(item);
  }));
}
​
async function processItem(item) {
  // 执行异步操作
  await someAsyncFunction(item);
  // 处理结果
  console.log(`Processed item: ${item}`);
}
​
const array = [1, 2, 3, 4, 5];
processArray(array);

echarts哪些情况会出现白屏,怎么解决

数据加载问题

容器大小问题

异步加载问题:如果使用异步方式加载数据或配置项,确保在数据加载完成后再初始化 ECharts 实例。在数据加载完成后,调用 setOption 方法设置配置项,并重新渲染图表。

依赖项问题

resize事件(throttle节流,若使用防抖可能出现白屏)

vuex里action与mutation的区别

  • Mutations 用于同步地修改状态,而 Actions 用于处理异步操作和复杂的业务逻辑。
  • Mutations 必须通过提交方式触发,而 Actions 可以通过分发方式触发。
  • Mutations 只能修改状态,而 Actions 可以执行任意操作,并在需要时触发 Mutations 来修改状态。

dispatch与commit的区别

  • dispatch:用于触发 Action,即分发一个 Action。
  • commit:用于触发 Mutation,即提交一个 Mutation。

vue3的vuex的使用

//创建store
import { createStore } from 'vuex';
​
const store = createStore({
  // 定义状态(state)、变更(mutations)、动作(actions)等
});
​
export default store;
​
//组件内通过useStore
<script>
import { computed } from 'vue';
import { useStore } from 'vuex';
​
export default {
  setup() {
    const store = useStore();
    const count = computed(() => store.state.count);
​
    const increment = () => {
      store.commit('increment');
    };
​
    const incrementAsync = () => {
      store.dispatch('incrementAsync');
    };
​
    return {
      count,
      increment,
      incrementAsync,
    };
  },
};
</script>

浏览器存储,新开一个页签,怎么改变存储数据

<script>
// 监听localStorage的存储事件,set的时候会触发
window.addEventListener("storage", function (e) {
  if(e.key == "time" && e.newValue) {
    document.getElementById("time").innerHTML = e.newValue;
    // 调用removeItem方法再次set的时候,保证每次set事件都可以监听到,同时可以释放资源
    localStorage.removeItem("time");
  }
});
</script>

echarts

使用 echarts.init 方法初始化一个图表实例,并使用 setOption 方法设置图表的配置项和数据。通过配置项中的 series 属性,可以定义不同类型的图表。

  • 折线图(Line Chart):
const option = {
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  },
  yAxis: {
    type: 'value',
  },
  series: [{
    type: 'line',
    data: [120, 200, 150, 80, 70, 110, 130],
  }],
};
  • 柱状图(Bar Chart):
// 设置配置项和数据
const option = {
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
  },
  yAxis: {
    type: 'value',
  },
  series: [{
    type: 'bar',
    data: [120, 200, 150, 80, 70, 110, 130],
  }],
};
  • 饼图(Pie Chart):
const option = {
  series: [{
    type: 'pie',
    data: [
      { value: 335, name: 'Apple' },
      { value: 310, name: 'Banana' },
      { value: 234, name: 'Orange' },
      { value: 135, name: 'Grape' },
      { value: 1548, name: 'Watermelon' },
    ],
  }],
};
  • 散点图(Scatter Chart):
const option = {
  xAxis: {
    type: 'value',
  },
  yAxis: {
    type: 'value',
  },
  series: [{
    type: 'scatter',
    data: [
      [10.0, 8.04],
      [8.0, 6.95],
      [13.0, 7.58],
      [9.0, 8.81],
      [11.0, 8.33],
      [14.0, 9.96],
      [6.0, 7.24],
      [4.0, 4.26],
      [12.0, 10.84],
      [7.0, 4.82],
      [5.0, 5.68],
    ],
  }],
};

地图怎么计算两个坐标点的距离

map.getDistance(myP1, myP2)

proxy跨域代理的原理

Proxy 跨域代理的原理是通过在服务器端设置一个代理服务器,将客户端的请求发送到目标服务器,并将响应返回给客户端。这样,客户端就可以绕过浏览器的同源策略,实现跨域访问其他域的资源。

简单来说vite或webpack配置proxy后,会开启一个本地服务器,然后请求都转由这个中间服务器转发,服务器之间发请求是没有跨域的

uniapp兼容刘海屏

// 获取屏幕边界到安全区域距离

const { safeAreaInsets } = uni.getSystemInfoSync()

如何判断一个对象非空

  • JSON.stringify
var data = {};
var b = JSON.stringify(data) == "{}";
console.log(b); // true
  • Object 对象的 getOwnPropertyNames 方法,获取到对象中的属性名,存到一个数组中,返回数组对象,我们可以通过判断数组的 length 来判断此对象是否为空。
var data = {};
var arr = Object.getOwnPropertyNames(data);
console.log(arr.length == 0); // true
  • ES6的keys
var data = {};
var arr = Object.keys(data);
console.log(arr.length == 0); // true

typeof nul与undefined

在 JavaScript 中,使用 typeof 运算符检测 null 的类型会返回 "object"。这是 JavaScript 语言的一个历史遗留问题,被认为是一个设计错误。

使用 typeof 运算符检测 undefined 的类型会返回 "undefined"

js包装类型

在 JavaScript 中,有三种包装类型:StringNumberBoolean。 当我们使用原始值创建相应的包装类型时,JavaScript 引擎会自动创建一个临时的包装对象。这使得我们可以像操作对象一样操作原始值,并且可以使用对象的方法和属性。

sass与less怎么使用mixin和函数

使用 Sass 的 mixin 和函数:

// 定义一个 mixin
@mixin button($color) {
  background-color: $color;
  color: white;
  padding: 10px 20px;
  border-radius: 4px;
}
​
// 使用 mixin
.button {
  @include button(blue);
}
​
// 定义一个函数
@function calculate-width($width) {
  @return $width * 2;
}
​
// 使用函数
.container {
  width: calculate-width(200px);
}

使用 Less 的 mixin 和函数:

// 定义一个 mixin
.button(@color) {
  background-color: @color;
  color: white;
  padding: 10px 20px;
  border-radius: 4px;
}
​
// 使用 mixin
.button {
  .button(blue);
}
​
// 定义一个函数
.calculate-width(@width) {
  @result: @width * 2;
}
​
// 使用函数
.container {
  width: calculate-width(200px);
}

vue2data为什么是一个函数,vue3为什么不用data了

这是因为在 Vue 2 中,每个组件实例都需要拥有独立的数据副本,以避免组件之间共享数据的问题。通过将 data 选项定义为一个函数,每次创建组件实例时都会调用该函数,返回一个新的数据对象,确保每个组件实例都有自己的数据副本。 Vue 3 的 Composition API 提供了更灵活和可组合的方式来处理组件的状态逻辑,使得组件的数据可以更好地进行组合和重用。通过使用 setup 函数来定义组件的逻辑,你可以使用 reactiveref 等函数来创建响应式的数据。

递归组件

  1. 定义组件:首先,你需要定义一个组件,该组件将在自身内部进行递归调用。可以使用你所使用的框架或库提供的组件定义语法,例如 Vue 的组件选项对象或 React 的函数组件。
  2. 设置递归出口:在组件内部,你需要设置一个递归出口,即决定何时停止递归的条件。这可以是一个基本情况,当满足某个条件时,不再进行递归调用,而是返回一个终止状态。
  3. 调用自身:在组件的模板或函数体中,通过使用组件的名称,可以在组件内部调用自身。这将导致组件递归地创建多个实例。
  4. 传递数据:在递归调用时,你可能需要将不同的数据传递给递归组件的不同实例。这可以通过使用组件的属性(props)或参数来实现。确保在每次递归调用时,传递适当的数据,以便组件能够正确地渲染和处理。
  5. 处理递归结果:在递归组件的外部,你可能需要处理递归调用的结果。这可以是通过对组件的返回值进行处理,或者使用递归组件内部定义的回调函数来处理。
  6. 样式和交互:根据你的需求,可以为递归组件设计样式和交互。这可能涉及到对不同层级的组件应用不同的样式,或者为组件添加交互功能,例如展开/折叠节点或展示更多信息。

websokect协议

WebSocket 是一种在 Web 应用程序和服务器之间实现双向通信的协议。它提供了一种持久连接的机制,允许服务器主动发送数据给客户端,而不需要客户端发起请求。WebSocket 协议建立在 HTTP 协议之上,通过使用标准的 HTTP 握手过程来建立连接,然后升级到 WebSocket 协议。

  1. 客户端发起连接请求:客户端通过发送一个特殊的 HTTP 请求头(Upgrade 请求头)来发起 WebSocket 连接请求。该请求头指示服务器将连接升级为 WebSocket。
  2. 服务器确认连接:服务器接收到客户端的连接请求后,会检查请求头中的 Upgrade 请求头,并验证其他必要的信息。如果服务器支持 WebSocket 并且验证通过,它会发送一个特殊的 HTTP 响应头(Upgrade 响应头)来确认连接升级。
  3. 连接升级完成:客户端收到服务器的升级确认后,它会发送一个特殊的握手响应,以表示连接升级已完成。此后,客户端和服务器之间的连接将转换为 WebSocket 连接。
  4. 双向通信:一旦连接升级完成,客户端和服务器就可以通过 WebSocket 连接进行双向通信。它们可以相互发送消息,而不需要客户端发起请求。

computed的get和set

输入url到页面渲染全过程

如何优化打包体积----cdn崩了怎么办

webpack和vite的区别,vite的底层

代理跨域,生产环境如果有跨域怎么解决

图片懒加载的实现原理

vue-router的实现原理,为什么浏览器地址改变,页面不刷新,只是组件切换

forin与forof的区别,哪个会遍历对象原型上的属性

Promise.allSettled()

重排重绘有什么区别,分别什么情况会触发

父子组件创建时的生命周期执行顺序

当我想监听一个对象时,用watch还是computed,哪个更好,为什么

set与map

symbol

ts中的泛型

ts中的内置类型有哪些

new Function

该函数对象可以使用传递给 new Function() 的字符串参数作为函数体,并使用其他传递给它的参数作为函数参数,从而动态创建一个可执行的函数。

具体来说,new Function() 构造函数可以接受多个字符串参数作为函数的参数和函数体

new Function ([arg1[, arg2[, ...argn]],] functionBody)

其中,arg1, arg2, ..., argn 为函数的参数列表,functionBody 为函数体的字符串表示。当调用 new Function() 函数时,JavaScript 引擎会将 arg1, arg2, ..., argn 所表示的参数和 functionBody 所表示的函数体组合成一个新的函数对象,并将该对象返回。

const add = new Function('a', 'b', 'return a + b;');
console.log(add(2, 3)); // 5

new Function() 的使用场景主要是动态生成 Javascript 代码的情况。由于它可以使用字符串形式作为函数体,并接受可变数量的参数,因此很适合在需要动态生成 JavaScript 代码的场景中使用。

  1. 动态生成函数:使用 new Function() 可以动态生成函数,有时候这种方式比使用函数表达式更加灵活。
  2. 模板引擎:某些模板引擎使用 new Function() 动态生成 JavaScript 代码来进行文本渲染和数据绑定。
  3. 解析 JSON:从服务端获取 JSON 数据时,可以使用 new Function() 将其转换为具有更好可读性的 JavaScript 对象。 举例:
const json = '{"name": "张三", "age": "18", "gender": "男"}';
const parseJson = new Function(`return ${json}`);
​
console.log(parseJson()); // 输出:{name: "张三", age: "18", gender: "男"}
​

4.在浏览器中查找或执行某些 DOM 元素:可以将 JavaScript 代码传递给 new Function() 进行动态执行和查找。

需要注意的是,由于 new Function() 可以动态生成和执行任意 JavaScript 代码,因此其安全性和风险需要仔细考虑和评估。在使用 new Function() 时,应该避免用于可疑的或不可信任的代码执行,并严格控制传递给函数构造函数的参数,以避免潜在的安全漏洞。

组件封装

代码检查PR,git工作流规范