前端面试题目汇总

1、为什么typeof null 是object?

不同的数据类型在底层都是通过二进制表示的,二进制前三位为000则会被判断为object类型,

而null底层的二进制全都是0,那前三位肯定也是000,所以被判断为object

2、0.1 + 0.2 === 0.3,对吗?不对

JavaScript的计算存在精度丢失问题

  • 原因:JavaScript中小数是浮点数,需转二进制进行运算,有些小数无法用二进制表示,所以只能取近似值,所以造成误差
  • 解决方法:
    • 先变成整数运算,然后再变回小数
    • toFixed() 性能不好,不推荐

3、addEventListence的第三个参数是干嘛的?

xxx.addEventListence('click', function(){}, false)

第三个变量传一个布尔值,需不需要阻止冒泡,默认是false,不阻止冒泡

4、Ajax、Axios、Fetch有啥区别?

  • Ajax:是对XMLHttpRequest对象(XHR)的封装
  • Axios:是基于Promise对XHR对象的封装
  • Fetch:是window的一个方法,也是基于Promise,但是与XHR无关,不支持IE
    angular中的 HttpClient 是基于浏览器的 XMLHttpRequest 接口

5、load、$(document).ready、DOMContentLoaded的区别?

DOM文档加载的步骤为:

  • 1、解析HTML结构。
  • 2、加载外部脚本和样式表文件。
  • 3、解析并执行脚本代码。
  • 4、DOM树构建完成。// DOMContentLoaded触发、$(document).ready触发
  • 5、加载图片等外部文件。
  • 6、页面加载完毕。// load触发

6、如何实现数组去重?

复制代码
// 使用 Map 去重
function quchong1(arr) {
  const newArr = []
  arr.reduce((pre, next) => {
    if (!pre.get(next)) {
      pre.set(next, 1)
      newArr.push(next)
    }
    return pre
  }, new Map())
  return newArr
}

// 使用 Set 去重
function quchong (arr) {
    return [...new Set(arr)]
}
复制代码

7、call、apply、bind改变this

复制代码
const obj1 = {
  name: '林三心',
  sayName: function() {
    console.log(this.name)
  }
}
const obj2 = {
  name: 'Sunshin_Lin'
}
// 改变sayName的this指向obj2
obj1.sayName.call(obj2) // Sunshin_Lin
// 改变sayName的this指向obj2
obj1.sayName.apply(obj2) // Sunshin_Lin
// 改变sayName的this指向obj2
const fn = obj1.sayName.bind(obj2)
fn() // Sunshin_Lin
复制代码

8、BOM 和 DOM 的关系

BOM全称Browser Object Model,即浏览器对象模型,主要处理浏览器窗口和框架。

DOM全称Document Object Model,即文档对象模型,是 HTML 和XML 的应用程序接口(API),遵循W3C 的标准,所有浏览器公共遵守的标准。

JS是通过访问BOM(Browser Object Model)对象来访问、控制、修改客户端(浏览器),由于BOM的window包含了document,window对象的属性和方法是直接可以使用而且被感知的,

因此可以直接使用window对象的document属性,通过document属性就可以访问、检索、修改XHTML文档内容与结构。因为document对象又是DOM的根节点。

可以说,BOM包含了DOM(对象),浏览器提供出来给予访问的是BOM对象,从BOM对象再访问到DOM对象,从而js可以操作浏览器以及浏览器读取到的文档。

9、JS中的substr()和substring()函数有什么区别

substr() 函数的形式为substr(startIndex,length)。 它从startIndex返回子字符串并返回'length'个字符数。
var s = "hello";
( s.substr(1,4) == "ello" ) // true

substring() 函数的形式为substring(startIndex,endIndex)。 它返回从startIndex到endIndex - 1的子字符串。

var s = "hello";
( s.substring(1,4) == "ell" ) // true

10、Object和Map的区别

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

复制代码
const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false
复制代码

  将对象o当作m的一个键,然后又使用get方法读取这个键,接着使用delete方法删除了这个键。

复制代码
const map = new Map([
  ['name', '张三'],
  ['title', 'Author']
]);

map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
复制代码

  Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。

const map = new Map();

map.set(['a'], 555);
map.get(['a']) // undefined

上面代码的setget方法,表面是针对同一个键,但实际上这是两个不同的数组实例,内存地址是不一样的,因此get方法无法读取该键,返回undefined

复制代码
const map = new Map();

const k1 = ['a'];
const k2 = ['a'];

map
.set(k1, 111)
.set(k2, 222);

map.get(k1) // 111
map.get(k2) // 222
复制代码

Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。

这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。

let map = new Map()
  .set(1, 'a')
  .set(2, 'b')
  .set(3, 'c');

set方法返回的是当前的Map对象,因此可以采用链式写法。

11、weakMap

WeakMap只接受对象作为键名null除外),不接受其他类型的值作为键名

用途:

复制代码
let myWeakmap = new WeakMap();

myWeakmap.set(
  document.getElementById('logo'),
  {timesClicked: 0})
;

document.getElementById('logo').addEventListener('click', function() {
  let logoData = myWeakmap.get(document.getElementById('logo'));
  logoData.timesClicked++;
}, false);
复制代码

上面代码中,document.getElementById('logo')是一个 DOM 节点,每当发生click事件,就更新一下状态。

我们将这个状态作为键值放在 WeakMap 里,对应的键名就是这个节点对象。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

12、(a == 1 && a == 2 && a ==3) 有可能是 true 吗?

当两个类型不同时进行==比较时,会将一个类型转为另一个类型,然后再进行比较。
比如Object类型与Number类型进行比较时,Object类型会转换为Number类型。
Object转换为Number时,会尝试调用Object.valueOf()Object.toString()来获取对应的数字基本类型。
var a = {
    i: 1,
    toString: function () {
        return a.i++;
    }
}
console.log(a == 1 && a == 2 && a == 3) // true

13、函数的length是多少?

可以看出,function有多少个形参length就是多少;
但是如果有默认参数,就是第一个具有默认值之前的参数个数;剩余参数是不算进length的计算之中的
复制代码
function fn1 (name) {}

function fn2 (name = '仙人掌') {}

function fn3 (name, age = 22) {}

function fn4 (name, age = 22, gender) {}

function fn5(name = '仙人掌', age, gender) { }

console.log(fn1.length) // 1
console.log(fn2.length) // 0
console.log(fn3.length) // 1
console.log(fn4.length) // 1
console.log(fn5.length) // 0
//剩余参数
function fn6(name, ...args) {}
console.log(fn6.length) // 1
复制代码

14、includes 比 indexOf好在哪?

includes可以检测NaN,indexOf不能检测NaN,includes内部使用了Number.isNaNNaN进行了匹配

15、map、object和set的区别

map:对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。构造函数Map可以接受一个数组作为参数。

object:

  • 一个Object 的键只能是字符串或者 Symbols,但一个Map 的键可以是任意值。
  • Map中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
  • Map的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

set:对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值。 

总结:

  • 初始化需要的值不一样,Map需要的是一个二维数组,而Set 需要的是一维 Array 数组
  •  Map 和 Set 都不允许键重复
  • Map的键是不能修改,但是键对应的值是可以修改的;Set不能通过迭代器来改变Set的值,因为Set的值就是键。
  • Map 是键值对的存在,值也不作为健;而 Set 没有 value 只有 key,value 就是 key;

 16、vue和react的diff算法的区别

vue和react的diff算法,都是忽略跨级比较,只做同级比较。vue diff时调动patch函数,参数是vnode和oldVnode,分别代表新旧节点。

1. vue比对节点,当节点元素类型相同,但是className不同,认为是不同类型元素,删除重建,而react会认为是同类型节点,只是修改节点属性

2. vue的列表比对,采用从两端到中间的比对方式,而react则采用从左到右依次比对的方式。当一个集合,只是把最后一个节点移动到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移动到第一个。总体上,vue的对比方式更高效。

虚拟dom:一个用来标识真实DOM的对象

<ul id="list">
    <li class="item">哈哈</li>
    <li class="item">呵呵</li>
    <li class="item">嘿嘿</li>
</ul>

对应的虚拟dom为:

复制代码
let oldVDOM = { // 旧虚拟DOM
        tagName: 'ul', // 标签名
        props: { // 标签属性
            id: 'list'
        },
        children: [ // 标签子节点
            {
                tagName: 'li', props: { class: 'item' }, children: ['哈哈']
            },
            {
                tagName: 'li', props: { class: 'item' }, children: ['呵呵']
            },
            {
                tagName: 'li', props: { class: 'item' }, children: ['嘿嘿']
            },
        ]
    }
复制代码

这时候,我修改一个li标签的文本:

<ul id="list">
    <li class="item">哈哈</li>
    <li class="item">呵呵</li>
    <li class="item">哈哈哈哈</li> // 修改
</ul>

这就是咱们平常说的新旧两个虚拟DOM,这个时候的新虚拟DOM是数据的最新状态,那么我们直接拿新虚拟DOM去渲染成真实DOM的话,效率真的会比直接操作真实DOM高吗?那肯定是不会的,看下图:

 

 由上图,一看便知,肯定是第2种方式比较快,因为第1种方式中间还夹着一个虚拟DOM的步骤,所以虚拟DOM比真实DOM快这句话其实是错的,或者说是不严谨的。那正确的说法是什么呢?虚拟DOM算法操作真实DOM,性能高于直接操作真实DOM虚拟DOM虚拟DOM算法是两种概念。虚拟DOM算法 = 虚拟DOM + Diff算法

什么是Diff算法

上面说了虚拟DOM,也知道了只有虚拟DOM + Diff算法才能真正的提高性能,那讲完虚拟DOM,我们再来讲讲Diff算法吧,还是上面的例子

 上图中,其实只有一个li标签修改了文本,其他都是不变的,所以没必要所有的节点都要更新,只更新这个li标签就行,Diff算法就是查出这个li标签的算法。

总结:Diff算法是一种对比算法。对比两者是旧虚拟DOM和新虚拟DOM,对比出是哪个虚拟节点更改了,找出这个虚拟节点,并只更新这个虚拟节点所对应的真实节点,而不用更新其他数据没发生改变的节点,实现精准地更新真实DOM,进而提高效率

使用虚拟DOM算法的损耗计算:总损耗 = 虚拟DOM增删改+(与Diff算法效率有关)真实DOM差异增删改+(较少的节点)排版与重绘

直接操作真实DOM的损耗计算:总损耗 = 真实DOM完全增删改+(可能较多的节点)排版与重绘

Diff算法的原理

Diff同层对比

新旧虚拟DOM对比的时候,Diff算法比较只会在同层级进行, 不会跨层级比较。所以Diff算法是:广度优先算法。 时间复杂度:O(n)

  Diff对比流程

当数据改变时,会触发setter,并且通过Dep.notify去通知所有订阅者Watcher,订阅者们就会调用patch方法,给真实DOM打补丁,更新相应的视图

 17、如何遍历输出页面上的所有元素

使用createNodeIterator

const body = document.getElementByTagName("body")[0]
const it = document.createNodeIterator(body)
let root = it.nextNode()
while(root){
      console.log(root)
      root = it.nextNode()    
}

18、判断元素是否在可视区域内

使用getBoundingClientRect

Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。返回的是一个对象,

对象里有这8个属性:left,right,top,bottom,width,height,x,y

 

 

根据这个用处,咱们可以实现:懒加载和无限滚动

复制代码
<div id="box"></div>

body {
       height: 3000px;
       width: 3000px;
      }

#box {
       width: 300px;
       height: 300px;
       background-color: red;
       margin-top: 300px;
       margin-left: 300px;
    }
    
// js
const box = document.getElementById('box')
        window.onscroll = function () {
            // box完整出现在视口里才会输出true,否则为false
            console.log(checkInView(box))
        }

function checkInView(dom) {
        const { top, left, bottom, right } = dom.getBoundingClientRect()
        console.log(top, left, bottom, right)
        console.log(window.innerHeight, window.innerWidth)
        return top >= 0 &&
                left >= 0 &&
                bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
                right <= (window.innerWidth || document.documentElement.clientWidth)
        }
复制代码

19、getComputedStyle

Window.getComputedStyle()方法返回一个对象,该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有CSS属性的值。私有的CSS属性值可以通过对象提供的API或通过简单地使用CSS属性名称进行索引来访问。

window.getComputedStyle(element, pseudoElement)
  • element: 必需,要获取样式的元素。
  • pseudoElement: 可选,伪类元素,当不查询伪类元素的时候可以忽略或者传入 null。

搭配getPropertyValue可以获取到具体样式

复制代码
// html
#box {
            width: 300px;
            height: 300px;
            background-color: yellow;
    }
    
<div id="box"></div>

const box = document.getElementById('box')
const styles = window.getComputedStyle(box)
// 搭配getPropertyValue可以获取到具体样式
const height = styles.getPropertyValue("height")
const width = styles.getPropertyValue("width")
console.log(height, width) // ’300px‘ '300px'
复制代码

20、DOMContentLoaded

是什么:

当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完全加载。

这时问题又来了,“HTML 文档被加载和解析完成”是什么意思呢?或者说,HTML 文档被加载和解析完成之前,浏览器做了哪些事情呢?那我们需要从浏览器渲染原理来谈谈。

浏览器向服务器请求到了 HTML 文档后便开始解析,产物是 DOM(文档对象模型),到这里 HTML 文档就被加载和解析完成了。如果有 CSS 的会根据 CSS 生成 CSSOM(CSS 对象模型),然后再由 DOM 和 CSSOM 合并产生渲染树。有了渲染树,知道了所有节点的样式,下面便根据这些节点以及样式计算它们在浏览器中确切的大小和位置,这就是布局阶段。有了以上这些信息,下面就把节点绘制到浏览器上。所有的过程如下图所示:

 

 现在你可能了解 HTML 文档被加载和解析完成前浏览器大概做了哪些工作,但还没完,因为我们还没有考虑现在前端的主角之一 JavaScript。

JavaScript 可以阻塞 DOM 的生成,也就是说当浏览器在解析 HTML 文档时,如果遇到

当 HTML 文档被解析时如果遇见(同步)脚本,则停止解析,先去加载脚本,然后执行,执行结束后继续解析 HTML 文档。过程如下图

 

 defer 脚本:

当 HTML 文档被解析时如果遇见 defer 脚本,则在后台加载脚本,文档解析过程不中断,而等文档解析结束之后,defer 脚本执行。另外,defer 脚本的执行顺序与定义时的位置有关。过程如下图:

async 脚本:

当 HTML 文档被解析时如果遇见 async 脚本,则在后台加载脚本,文档解析过程不中断。脚本加载完成后,文档停止解析,脚本执行,执行结束后文档继续解析。过程如下图:

 

 async 和 defer 对 DOMContentLoaded 事件触发的影响:

defer 与 DOMContentLoaded

如果 script 标签中包含 defer,那么这一块脚本将不会影响 HTML 文档的解析,而是等到 HTML 解析完成后才会执行。而 DOMContentLoaded 只有在 defer 脚本执行结束后才会被触发。所以这意味着什么呢?HTML 文档解析不受影响,等 DOM 构建完成之后 defer 脚本执行,但脚本执行之前需要等待 CSSOM 构建完成。在 DOM、CSSOM 构建完毕,defer 脚本执行完成之后,DOMContentLoaded 事件触发。

async 与 DOMContentLoaded

如果 script 标签中包含 async,则 HTML 文档构建不受影响,解析完毕后,DOMContentLoaded 触发,而不需要等待 async 脚本执行、样式表加载等等。

DOMContentLoaded和load

当 HTML 文档解析完成就会触发 DOMContentLoaded,而所有资源加载完成之后,load 事件才会被触发。

另外需要提一下的是,我们在 jQuery 中经常使用的

(document).ready(function()//...代码...);其实监听的就是DOMContentLoaded事件

而 ((document).load(function() { // ...代码... }); 监听的是 load 事件。

 使用:

document.addEventListener("DOMContentLoaded", function(event) {
      console.log("DOM fully loaded and parsed");
  });

21、 webpack配置中的3种hash值

事先准备3个文件(main.js、main.css、console.js)

在main.js中引入main.css

import './main.css'
console.log('我是main.js')

webpack.config.js

复制代码
// 多入口打包
entry: {
    main: './src/main.js',
    console: './src/console.js'
  },
// 输出配置
output: {
    path: path.resolve(__dirname, './dist'),
    // 这里预设为hash
    filename: 'js/[name].[hash].js',
    clean: true
  },
plugins: [
      // 打包css文件的配置
      new MiniCssExtractPlugin({
      // 这里预设为hash
      filename: 'styles/[name].[hash].css'
    })
]
复制代码

1、全局hash

打包后,所有文件的文件名hash值都是一致的,修改一下main.css这个文件,重新打包,所有文件的hash值跟着变

结论:整个项目文件是一致的,修改其中一个会导致所有跟着一起改。

2、chunkhash

consfig中把输出文件名规则修改为chunkhash

hash值会根据入口文件的不同而分出两个阵营:

  • main.js、main.css一个阵营,都属于main.js入口文件
  • console.js一个阵营,属于console.js入口文件

 

 3、contenthash

 每个文件hash值都不一样,每个文件的hash值都是根据自身的内容去生成的

当某个文件内容修改时,打包只会修改其本身的hash值,不会影响其他文件的hash值

22、package.lock.json的作用

锁定安装模块的版本

比如在package.json中,vue的版本是^2.6.14

^的意思是,加入过几天vue在大版本2下更新了小版本2.6.15,那么当npm install的时候vue会自动升级为2.16.5

引起的问题:

比如有A\B两个开发者

  • 程序员A:接手项目时vue版本时2.16.4,并一直使用这个版本
  • 程序员B:一个月后加入项目,这时vue已经升级到了2.9.14,npm install的时候会自动升级

这时候会导致两个开发时vue版本不一致,从而导致合作中产生一些问题和错误

package.lock.json解决该问题

比如现在有A、B两个开发者

A:接手项目时vue的版本时2.6.14,此版本被所在了package-lock.json中

B:一个月后加入该项目,这时vue已经升级到了2.9.14,npm install的时候,按理说会自动升级,但是由于package-lock.json中锁着2.6.14这个版本,所以阻止了自动升级,保证版本还是2.6.14

23、MutationObserver

作用

例子:添加水印,使用MutationObserver阻止用户恶意破坏水印,因为在控制台修改水印的background-image或者将水印的div删掉,都会引起MutationObserver的监控触发

  • observer:开启监控DOM的变化
  • disconnect:停止检测DOM的变化

代码:

1、定义画水印的函数

复制代码
import type { Directive, App } from 'vue'

interface Value {
  font?: string
  textColor?: string
  text?: string
}

const waterMarkId = 'waterMark'
const canvasId = 'can'

const drawWatermark = (el, value: Value) => {
  const {
    font = '16px Microsoft JhengHei',
    textColor = 'rgba(180, 180, 180, 0.3)',
    text = 'nlf大菜鸟',
  } = value
  // 创建一个canvas标签
  const canvas = document.getElementById(canvasId) as HTMLCanvasElement
  // 如果已有则不再创建
  const can = canvas || document.createElement('canvas')
  can.id = canvasId
  el.appendChild(can)
  // 设置宽高
  can.width = 400
  can.height = 200
  // 不可见
  can.style.display = 'none'
  const ctx = can.getContext('2d')!
  // 设置画布的样式
  ctx.rotate((-20 * Math.PI) / 180)
  ctx.font = font
  ctx.fillStyle = textColor
  ctx.textAlign = 'left'
  ctx.textBaseline = 'middle'
  ctx.fillText(text, can.width / 3, can.height / 2)

  // 水印容器
  const waterMaskDiv = document.createElement('div')
  waterMaskDiv.id = waterMarkId
  // 设置容器的属性样式
  // 将刚刚生成的canvas内容转成图片,并赋值给容器的 background-image 样式
  const styleStr = `
    width: 100%;
    height: 100%;
    position: fixed;
    z-index: -1;
    top: 0;
    left: 0;
    pointer-events: none;
    background-image: url(${can.toDataURL('image/png')})
  `
  waterMaskDiv.setAttribute('style', styleStr)

  // 将水印容器放到目标元素下
  el.appendChild(waterMaskDiv)

  return styleStr
}
//不使用监测
const watermarkDirective: Directive = {
  mounted(el, { value }) {
    // 接收styleStr,后面可以用来对比
    el.waterMarkStylestr = drawWatermark(el, value)
  }
}
复制代码

使用的时候直接以v-watermark来使用:

复制代码
<div 
    v-watermark="
    { 
    text: '水印名称',
    textColor: 'rgba(180, 180, 180, 0.3)' 
    }
    "
  >
</div>
复制代码

2、使用监控

复制代码
const watermarkDirective: Directive = {
  mounted(el, { value }) {
    // 接收styleStr,后面可以用来对比
    el.waterMarkStylestr = drawWatermark(el, value)
    // 先定义一个MutationObserver
    el.observer = new MutationObserver(() => {
      const instance = document.getElementById(waterMarkId)
      const style = instance?.getAttribute('style')
      const { waterMarkStylestr } = el
      // 修改样式 || 删除div
      if ((instance && style !== waterMarkStylestr) || !instance) {
        if (instance) {
          // div还在,说明只是修改style
          instance.setAttribute('style', waterMarkStylestr)
        } else {
          // div不在,说明删除了div
          drawWatermark(el, value)
        }
      }
    })
    // 启动监控
    el.observer.observe(document.body, {
      childList: true,
      attributes: true,
      subtree: true,
    })
  },
  unmounted(el) {
    // 指定元素销毁时,记得停止监控
    el.observer.disconnect()
    el.observer null
  },
}
复制代码

现在,控制台修改style或者删除容器div,都会重新生成水印,这样恶意用户就无法得逞

 24、HTTPS加密的过程

 

1、首先,客户端发起握手请求,以明文传输请求信息

2、服务器必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面。这套证书其实就是一对公钥和私钥。如果对公钥不太理解,可以想象成一把钥匙和一个锁头,只是世界上只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。

3、服务端返回证书、加密算法等信息给客服端
4、客户端验证证书的合法性,包括可信性,是否吊销,过期时间和域名,如果异常,弹框提示,没有异常则生成一串随机数
5、客户端使用公匙对对称密匙加密,发送给服务端。
6、服务器用私钥解密,拿到对称加密的密匙

25、性能优化

代码层面:

  • 防抖和节流(resize,scroll,input)。
  • 减少回流(重排)和重绘。
  • 事件委托。
  • css 放 ,js 脚本放 最底部。
  • 使用字体图标iconfont代替图片
  • 降低css选择器的复杂性
  • 减少 DOM 操作。
  • 尽量使用css而不是强制加载和卸载组件
  • 按需加载,比如 React 中使用 React.lazyReact.Suspense ,通常需要与 webpack 中的 splitChunks 配合。

构建方面:

  • 压缩代码文件,在 webpack 中使用 terser-webpack-plugin 压缩 Javascript 代码;使用 css-minimizer-webpack-plugin 压缩 CSS 代码;使用 html-webpack-plugin 压缩 html 代码。
  • 开启 gzip 压缩,webpack 中使用 compression-webpack-plugin ,node 作为服务器也要开启,使用 compression
  • 常用的第三方库使用 CDN 服务,在 webpack 中我们要配置 externals,将比如 React, Vue 这种包不打倒最终生成的文件中。而是采用 CDN 服务。

webpack性能优化:

  • 优化构建速度:
  1. 缩小文件搜索范围:resolve字段告诉webpack怎么去搜索文件,所以首先要重视resolve字段的配置(设置 resolve.modules:[path.resolve(__dirname, 'node_modules')] 避免层层查找)
  2. 配置oneOf(每个不同类型的文件在load转换时,会遍历module中rules所有的loader,即使匹配到某个规则后也会继续向下匹配。如果将规则放在oneOf中,一旦匹配到某个规则后就停止匹配)
  3. 使用DllPlugin减少基础模块编译次数(原理是把网页依赖的基础模块抽离出来打包到dll文件中,当需要导入的模块存在于某个dll中时,这个模块不再被打包,而是去dll中获取。为什么会提升构建速度呢?原因在于dll中大多包含的是常用的第三方模块,如react、react-dom,所以只要这些模块版本不升级,就只需被编译一次)
  4. 使用HappyPack或者thread-load开启多进程Loader转换(在整个构建流程中,最耗时的就是Loader对文件的转换操作了,而运行在Node.js之上的Webpack是单线程模型的,也就是只能一个一个文件进行处理,不能并行处理。HappyPack可以将任务分解给多个子进程,最后将结果发给主进程。JS是单线程模型,只能通过这种多进程的方式提高性能)

  5. 使用ParallelUglifyPlugin开启多进程压缩JS文件(使用UglifyJS插件压缩JS代码时,需要先将代码解析成Object表示的AST(抽象语法树),再去应用各种规则去分析和处理AST,所以这个过程计算量大耗时较多。ParallelUglifyPlugin可以开启多个子进程,每个子进程使用UglifyJS压缩代码,可以并行执行,能显著缩短压缩时间)

  • 优化输出质量-压缩文件体积
  1. 区分环境-减少生产环境代码体积(代码运行环境分为开发环境和生产环境,代码需要根据不同环境做不同的操作,许多第三方库中也有大量的根据开发环境判断的if else代码,构建也需要根据不同环境输出不同的代码,所以需要一套机制可以在源码中区分环境,区分环境之后可以使输出的生产环境的代码体积减小。Webpack中使用DefinePlugin插件来定义配置文件适用的环境,用来支持process.env.NODE_ENV === 'production' 语句)
  2. 压缩js、es、css
  3. 使用tree-shaking剔除js死代码
  • 优化输出质量-加速网络请求
  1. 使用CDN加速静态资源的加载

  2. 多页面应用提取页面间公共代码,以利用缓存(把公共文件提取出一个文件,那么当用户访问了一个网页,加载了这个公共文件,再访问其他依赖公共文件的网页时,就直接使用文件在浏览器的缓存,这样公共文件就只用被传输一次。)
  3. 分割代码按需加载
    例如:一个最简单的例子:网页首次只加载main.js,网页展示一个按钮,点击按钮时加载分割出去的show.js,加载成功后执行show.js里的函数
    复制代码
    //main.js
    document.getElementById('btn').addEventListener('click',function(){
        import(/* webpackChunkName:"show" */ './show').then((show)=>{
            show('Webpack');
        })
    })
    
    //show.js
    module.exports = function (content) {
        window.alert('Hello ' + content);
    }
    复制代码

    import(/* webpackChunkName:show */ './show').then() 是实现按需加载的关键

其它:

  • 使用 http2。因为解析速度快,头部压缩,多路复用,服务器推送静态资源。
  • 使用服务端渲染。
  • 图片压缩。
  • 使用 http 缓存,比如服务端的响应中添加 Cache-Control / Expires

26、loader和plugin的区别

  • 两者都是扩展webpack的功能,loader只专注于转化文件,完成压缩、打包和语言翻译;而plugin只局限在打包,资源加载上,还可以打包优化和压缩,重新定义环境变量等
  • loader是运行在打包文件之前,plugins在整个编译周期都起作用
  • 一个loader的职责是单一的,只需要完成一种转换,一个loader其实就是一个node.js模块。当需要多个loader去转换一个文件时,每个loader会链式的顺序执行
  • webpack运行的生命周期中广播出许多事件,plugins会监听这些事件,在合适的时机通过webpack提供的API改变输出结果

  原理:

    loader loader 的作用是用来处理非js文件,它是一个函数,实现原理是:将所需处理的文件内容使用相应的转换插件转成AST(抽象语法树),然后按照loader规则对于这个 AST 进行处理,处理之后再转成原本的内容格式,然后输出,达到了处理内容的效果

    plugin plugin 的作用是拓展webpack的打包功能。它是一个类,使用方式是new Plugin(option),这个类内部有一个apply方法,打包时会执行这个方法,且这个apply方法接收的参数中有一个plugin方法,此方法中有许多钩子函数,使用这些钩子函数可以在不同的打包阶段做一些事,例如修改文件,新增文件,删除文件等等

 27、vue中不需要响应式的数据如何处理

vue中,会有一些数据,从始至终都未曾改变,这种死数据,既然不会改变,也不需要对其做响应式处理了,不然会消耗性能。

比如一些写死的下拉框

复制代码
//方法1:将数据定义在data之外
data(){
  this.list = {xxxx}
  return {}    
}
//方法2、Object.freeze()
data(){
  return {
    list:Object.freeze({xxxx})
  }  
}
复制代码

28、父子组件生命周期的顺序

父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

29、computed和watch的区别

  • computed是依赖已有的变量来计算一个目标变量,大多数情况都是多个变量凑在一起计算出一个变量,并且computed具有缓存机制,依赖值不变的情况下其会直接读取缓存进行复用,computed不能进行异步操作
  • watch是监听某一个变量的变化,并执行相应的回调函数,通常是一个变量的变化决定多个变量的变化,watch可以进行异步操作
  • 简单记就是:一般情况下computed多对一watch一对多

30、 vue的修饰符

 31、使用vite为什么会变快

  • 1、esbuild预构建依赖:代码分为依赖源码依赖就是那些npm包,一般不会变,缓存起来;源码是会频繁更改的那一部分代码
  • 2、利用浏览器可以运行ES Module,将代码转成ES Module后交给浏览器,把压力放在浏览器那边,从而提高项目启动速度
  • 3、按需加载:浏览器根据ES Module去使用http请求,按需加载所需页面
  • 4、利用协商缓存,缓存文件,无变化的文件不需要重新加载

 32、webpack babel vue都用到了AST,对抽象语法树的理解

现在的很多工具,例如webpack、vite、eslint、babel等等都离不开一个东西——AST。

AST是正常代码,使用工具,根据不用的代码节点类型,转化成的一个JSON格式的数据

const a = 5;
// 转换成AST
[{value: 'const', type: 'keyword'}, {value: 'a', type: 'identifier'}, ...]

 33、TCP/UDP的区别

都属于TCP/IP协议族

  • TCP是面向连接的,UDP是面向无连接的
  • TCP是可靠的,UDP是不可靠的
  • TCP是面向字节流,UDP是面向报文的
  • TCP只有一对一的传输方式,而UDP不仅可以一对一,还可以一对多,多对多

 TCP拥塞控制:若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络性能就会变坏,这种情况叫做网络拥塞

TCP的4种拥塞控制算法:慢开始、拥塞控制、快重传、快恢复

TCP和http的区别

http:超文本传输协议,是运行TCP之上的

TCP:传输控制协议,用来控制传输的,为了在不可靠的互联网上提供可靠的端到端字节流,作用范围比HTTP大

34、跨域

意思:协议、域名和端口不一致均会导致跨域

产生的原因:

  1、基于浏览器的同源策略限制

  2、同源策略是一种约定,它是浏览器核心也是最基本的安全功能,它会组织一个域的js脚本和另外一个域的内容进行交互。如果缺少了同源策略,浏览器很容易受到XSS、CSRF的攻击。

  3、同源就是两个页面具有相同的协议、域名和端口

35、get和post的区别

相同:都是http协议请求,tcp连接

不同:

get:

  1. get不安全,参数在url中
  2. 参数有长度限制
  3. 能被缓存,可以收藏为书签,参数保留在浏览器历史中
  4. 只发送1个tcp数据包,即http header和data共同发送给服务器

post:

  1. 安全,参数在request body
  2. 参数没有长度限制
  3. 不会被缓存
  4. 发送2个tcp数据包,第一次发送http header,如果服务器响应100,则发送第二个数据包data

 36、requestAnimationFrame 

requestAnimationFrame 比起 setTimeout、setInterval的优势主要有两点:

1、requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。

2、在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。

requestAnimationFrame 动画原理:

  假设某图像正在以每秒60次的频率刷新,由于刷新频率很高,因此你感觉不到它在刷新。而动画本质就是要让人眼看到图像被刷新而引起变化的视觉效果,这个变化要以连贯的、平滑的方式进行过渡。 那怎么样才能做到这种效果呢?

  刷新频率为60Hz的屏幕每16.7ms刷新一次,我们在屏幕每次刷新前,将图像的位置向左移动一个像素,即1px。这样一来,屏幕每次刷出来的图像位置都比前一个要差1px,因此你会看到图像在移动;由于我们人眼的视觉停留效应,当前位置的图像停留在大脑的印象还没消失,紧接着图像又被移到了下一个位置,因此你才会看到图像在流畅的移动,这就是视觉效果上形成的动画。

 setTimeOut卡顿的原因:

1、setTimeout的执行时间并不是确定的。在Javascript中, setTimeout 任务被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 setTimeout 的实际执行时间一般要比其设定的时间晚一些。

2、刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而 setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。

  以上两种情况都会导致setTimeout的执行步调和屏幕的刷新步调不一致,从而引起丢帧现象。 那为什么步调不一致就会引起丢帧呢?

  原因:setTimeout的执行只是在内存中对图像属性进行改变,这个变化必须要等到屏幕下次刷新时才会被更新到屏幕上。如果两者的步调不一致,就可能会导致中间某一帧的操作被跨越过去,而直接更新下一帧的图像。

 37、Cookies中的SameSite

SameSite可以让Cookie在跨站请求时不会被发送,从而组织跨站请求伪造攻击CSRF

设置:

  1. 客户端发送 HTTP 请求到服务器
  2. 当服务器收到 HTTP 请求时,在响应头里面添加一个 Set-Cookie 字段
  3. 浏览器收到响应后保存下 Cookie
  4. 之后对该服务器每一次请求中都通过 Cookie 字段将 Cookie 信息发送给服务器。

SameSite 可以有下面三种值:

  1. Strict 仅允许一方请求携带 Cookie,即浏览器将只发送相同站点请求的 Cookie,即当前网页 URL 与请求目标 URL 完全一致。
  2. Lax 允许部分第三方请求携带 Cookie
  3. None 无论是否跨站都会发送 Cookie

之前默认是 None 的,Chrome80 后默认是 Lax。

主要用于3个方面:

  1. 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  2. 个性化设置(如用户自定义设置、主题等)
  3. 浏览器行为跟踪(如跟踪分析用户行为等)

38、事件冒泡和事件捕获

事件冒泡可以形象地比喻为把一颗石头投入水中,泡泡会一直从水底冒出水面。也就是说,事件会从最内层的元素开始发生,一直向上传播,直到document对象。

因此上面的例子在事件冒泡的概念下发生click事件的顺序应该是

p -> div -> body -> html -> document

另一种事件流名为事件捕获(event capturing)。与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。

上面的例子在事件捕获的概念下发生click事件的顺序应该是 

document -> html -> body -> div -> p

 39、BFC:块级格式化上下文

触发的条件:

·body根元素

·设置浮动,不包括none

·设置定位,absoulte或者fixed

·行内块显示模式,inline-block

·设置overflow,即hidden,auto,scroll

·表格单元格,table-cell

·弹性布局,flex

一个BFC区域只包含其子元素,不包括其子元素的子元素。

并不是所有的元素都能成为一块BFC区域,只有当这个元素满足上面条件之一的时候才会成为一块BFC区域。

不同的BFC区域之间是相互独立的,互不影响的。利用这个特性我们可以让不同BFC区域之间的布局不产生影响。

 40、箭头函数的this

this:表示当前对象的一个引用

  • 通常情况this在非严格模式下,指向的是全局window对象,在严格模式下,普通函数内的this不是全局对象(undefined)

  • 迷惑的this指向问题,正常情况this指向的是被调用的那个对象,但是如果是箭头函数,那么指向的是全局对象window

  • bind,call, apply改变this指向

箭头函数没有自己的this,所以内部的this就是外层代码块的this,它的this是继承而来的,默认指向在定义时所处的宿主对象

 41、npm run xxx的时候发生了什么?

1、首先会去项目的package.json文件里找scripts 里找对应的xxx,然后执行 xxx的命令,例如启动vue项目 npm run serve的时候,实际上就是执行了vue-cli-service serve 这条命令

package.json文件:

{
  "name": "h5",
  "version": "1.0.7",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve"
   },
}

  拓展:为社么不直接执行  vue-cli-service serve 而要执行 npm run serve呢?因为 直接执行vue-cli-service serve,会报错,因为操作系统中没有存在vue-cli-service这一条指令

2、那既然  vue-cli-service 这条指令不存在操作系统中,为什么执行  npm run serve 的时候,也就是相当于执行了  vue-cli-service serve ,为什么这样它就能成功,而且不报指令不存在的错误呢?

 当使用  npm run serve  执行  vue-cli-service serve  时,虽然没有安装  vue-cli-service 的全局命令,但是 npm 会到  ./node_modules/.bin  中找到  vue-cli-service  文件作为 脚本来执行,则相当于执行了
   ./node_modules/.bin/vue-cli-service serve, 
  .bin 目录,这个目录不是任何一个 npm 包。该目录下的文件,表示这是一个个软链接
 
3、这个bin目录下的那些软连接文件是哪里来的呢?它又是怎么知道这条软连接是执行哪里的呢?

  在node-modules下找到vue-cli-service包,打开该安装包下面的package.json,可以看到 bin 存在项目最外层的package-lock.json文件中.

  当我们npm i 整个新建的vue项目的时候,npm 将 bin/vue-cli-service.js 作为 bin 声明了。

  所以在 npm install 时,npm 读到该配置后,就将该文件软链接到 ./node_modules/.bin 目录下,而 npm 还会自动把node_modules/.bin加入$PATH,这样就可以直接作为命令运行依赖程序和开发依赖程序,不用全局安装了。

  

 总结:也就是说,npm i 的时候,npm 就帮我们把这种软连接配置好了,其实这种软连接相当于一种映射,执行npm run xxx 的时候,就会到 node_modules/bin中找对应的映射文件,然后再找到相应的js文件来执行。

拓展:在node_modules/bin中 有三个vue-cli-service文件。为什么会有三个文件呢?

# unix 系默认的可执行文件,必须输入完整文件名
vue-cli-service

# windows cmd 中默认的可执行文件,当我们不添加后缀名时,自动根据 pathext 查找文件
vue-cli-service.cmd

# Windows PowerShell 中可执行文件,可以跨平台
vue-cli-service.ps1

 42、vite为什么快

以往的打包模式:项目启动时,需要先将所有文件打包成一个文件bundle.js,然后在html引入,这个多文件 -> bundle.js的过程是非常耗时间的。

 

 Vite的打包方式:Vite是直接把转换后的es module的JavaScript代码,扔给支持es module的浏览器,让浏览器自己去加载依赖,也就是把压力丢给了浏览器,从而达到了项目启动速度快的效果。

 

 vite解决了更新缓慢的问题,原因是在项目启动时,将模块分成依赖源码,当你更新代码时,依赖就不需要重新加载,只需要精准地找到是哪个源码的文件更新了,更新相对应的文件就行了。这样做使得更新速度非常快。

 43、子元素是相对于父元素的padding、border还是content进行定位的

复制代码
<div id="id1">
    <div id="id2"></div>
</div>

body {
  background-color: black;
}
#d1 {
  width: 300px;
  height: 300px;
  margin: 40px;
  border: 40px solid red;
  padding: 40px;
  position: relative;
  background-color: #eee;
}
#d2 {
  width: 50px;
  height: 50px;
  position: absolute;
  top: 0;
  left: 0;
  background-color: yellow;
}
复制代码

结果:

 火狐、IE浏览器,以及设置box-sizing分别为border-box和content-box,定位的效果也是这样

从结果中可以看到,绝对定位的子元素紧紧贴着父元素的内边框,所以绝对定位的原点是在padding的左上角

44、周期函数beforeCreated和created中间都做了什么

初始化data、props、computed、watcher、provide

 45、setTimeout、Promise、Async/Await的区别

主要是考察宏任务和微任务的区别:setTimeout的回调函数会放到宏任务队列里,等到执行栈清空以后执行;promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完成再执行;async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行

1、setTimeout:
console.log("script start")
setTimeout(function(){
    console.log("setTimeout")
})
console.log("script end")  //script start => script end => setTimeout

 2、promise

复制代码
console.log("script start")
let promise1 = new Promise(function(resolve){
    console.log('promise1')
    resolve()
    console.log("promise1 end")
}).then(function(){
    console.log("promise2")  // 放到微任务队列
})
setTimeout(function(){
    console.log("setTimeout")  // 放到宏任务队列
})
console.log("script end") //script start=>promise1=>promise1 end=>script end=>promise2=>setTimeout
复制代码

当js主线程执行到Promise对象时:

  • promise1.then()的回调就是一个task
  • promise1是resolved或rejected:那么这个task就会放入当前事件循环回合的微任务队列
  • promise1是pending:那么这个task就会放入事件循环的未来的某个回合的微任务队列中
  • setTimeout的回调也是个task,它会被放入宏任务队列,即使是0ms的情况

 3、async/await

复制代码
async function async1(){
    console.log("async1 start")
    await async2()
    console.log("async1 end")  // 放到微任务队列里
}
async function async2(){
    console.log("async2")
}
console.log("script start")
async1()
console.log("script end")  //script start=>async1 start=>async2=>script end=>async1 end
复制代码

async函数返回一个Promise对象,当函数执行的时候,一旦遇到await就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为是为了让出线程,跳出async的函数体

 46、理解devDependencies和dependencies的区别:

  devDependencies和dependencies的区别核心在于npm包中。如果是自己项目使用,不需要发npm包的话,把依赖安装到devDependencies或dependencies,实质上是没有任何区别的。如果开发的项目是发npm包提供给外部、其他业务项目使用的,需要非常注意依赖安装的地方。

例如:假设npm包开发者不小心把vue3的依赖写到了dependencies中(用于开发调试的),版本是3.0.36,而业务项目自身使用了vue@3.0.0这个依赖,此时会在安装npm包时同时安装36版本的vue

  问题:由于npm包中会用到vue,代码是这样引入的: import {onMount} from "vue",此时,npm包会在自己内部的node_modules中找到vue@3.0.36的包并使用,此时就会产生两个vue3的实例,就很容易出现一些奇怪的bug。(业务项目中的vue@3.0.0和npm包的vue@3.0.36)。

 47、Symbol的用法?有哪些需要注意的点

  • 场景 1:使用 Symbol 来作为对象属性名(key)
复制代码
let obj = {
  [Symbol("name")]: "yd",
  age: 6,
  title: "symbol",
};
Object.keys(obj); // ["age","title"]
for (let p in obj) {
  console.log(p); // 分别输出 “age” 和 “title”
}
Object.getOwnPropertyNames(obj); // ["age","title"]
JSON.stringify(obj); // {"age":6,"title":"symbol"}
// 使用Object的API
Object.getOwnPropertySymbol(obj); // [Symbol(name)]

// 使用新增的的反射API
Reflect.ownKeys(obj); // [Symbol(name),"age","title"]
复制代码
  • 场景 2:使用 Symbol 代替常量
    const TYPE_AUDIO = "AUDIO";
    const TYPE_VIDEO = "VIDEO";
    const TYPE_IMAGE = "IMAGE";
    //使用symbol
    const TYPE_AUDIO = Symbol();
    const TYPE_VIDEO = Symbol();
    const TYPE_IMAGE = Symbol();
  • 场景3:使用 Symbol 定义类的私有属性/方法

 在文件a中

复制代码
const PASSWORD = Symbol();

class Login {
  constructor(userName, password) {
    this.userName = userName;
    this[PASSWORD] = password;
  }
  checkPassword(pwd) {
    return this[PASSWORD] === pwd;
  }
}
export default Login;
复制代码

在文件b中

复制代码
import Login from "./a";

const login = new Login("admin", "123456");

login.checkPassword("123456"); // true

login.PASSWORD; // oh!no!
login[PASSWORD]; // oh!no!
login["PASSWORD"]; // oh!no!
复制代码

由于 Symbol 常量 PASSWORD 被定义在 a.js 所在的模块中,外面的模块获取不到这个 Symbol,也不可能再创建一个一模一样的 Symbol 出来(因为 Symbol 是唯一的),因此这个 PASSWORD 的 Symbol 只能被限制在 a.js 内部使用,所以使用它来定义的类属性是没有办法被模块外访问到的,达到了一个私有化的效果。

需要注意的点:

  1. Symbol 函数前不能使用 new 命令,否则会报错。
  2. Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
  3. Symbol 作为属性名,该属性不会出现在 for...infor...of 循环中,也不会被 Object.keys()Object.getOwnPropertyNames()JSON.stringify() 返回。
  4. Object.getOwnPropertySymbols 方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
  5. Symbol.for 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。
  6. Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key。

 48、对Reflect的理解,以及Proxy?

Reflect 和 Proxy 是相对的,我们可以用 Reflect 操作对象。

  Reflect 存在的意义:

  • 1)将 Object 对象一些内部的方法,放到 Reflect 对象上。比如 Object.defineProperty
  • 2)操作对象时出现报错返回 false
    比如:Object.defineProperty(obj,name,desc) 在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj,name,desc)则会返回 false,这样会更合理一些。
  • 3)让操作对象的编程变为函数式编程
    // 老写法
    "assign" in Object; // true
    // 新写法
    Reflect.has(Object, "assign"); //true
  • 4)保持和 Proxy 对象的方法一一对应
    说明:Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。

    proxy:

    Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)

   Proxy 就像在目标对象之间的一个代理,任何对目标的操作都要经过代理。代理就可以对外界的操作进行过滤和改写。

   Proxy 是构造函数,它有两个参数 target 和 handler

    target是用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。

    handler 是一个对象,其属性是当执行一个操作时定义代理的行为函数。

复制代码
var obj = new Proxy(
  {},
  {
    get: function (target, key, receiver) {
      console.log(`getting ${key}`);
      return Reflect.get(target, key, receiver);
    },
    set: function (target, key, value, receiver) {
      console.log(`setting ${key}`);
      return Reflect.set(target, key, value, receiver);
    },
  }
);
obj.count = 1; // setting count
++obj.count; // getting count setting count
复制代码

 49、URI、URL、URN的区别

URI :统一资源标识符

用来标识唯一的资源

URI 一般由三部分组成:

  • 1.访问资源的命名机制
  • 2.存放资源的主机名
  • 3.资源自身的名称,由路径表示

看个例子:http://www.xxx.com/html/html4

URL:统一资源定位器

 它是一种具体的 URI

 组成:

  1. Internet 资源类型(schema):指出 www 客户程序用来操作的工具。如http://表示 www 服务器,ftp://表示 ftp 服务器,gopher://表示 Gopher 服务器,而new:表示 Newgroup 新闻组。必需的。
  2. 服务器地址(host):指出 www 网页所在的服务器域名。必需的。
  3. 端口(port):有时(并非总是这样),对某些资源的访问来说,需给出相应的服务器提供端口。可选的。
  4. 路径(path):指明服务器上某资源的位置。与端口一样,路径并非总是需要的。可选的。

URN:统一资源名称

是通过名字来标识资源。比如mailto:java-net@java.sun.com

总结:

  URI 指的是统一资源标识符,用唯一的标识来确定一个资源,它是一种抽象的定义,也就是说,不管使用什么方法来定义,只要能唯一的标识一个资源,就可以称为 URI。它是以某种统一的(标准化的)方式标识资源的简单字符串。

  URL 和 URN 是 URI 的子集,URL 可以理解为使用地址来标识资源,URN 可以理解为使用名称来标识资源。

  Web 上地址的基本形式是 URI,它代表统一资源标识符,有两种形式

  • URL:目前 URI 的最普遍的形式就是无处不在的 URL 或统一资源定位器
  • URN:URL 的一种更新形式,统一资源名称不依赖于位置,并且有可能减少失效链接的个数。但是其流行还需要时间,因为它需要更精密软件的支持。

50、setInterval的缺点是什么,为什么使用setTimeout来模拟实现setInterval?

setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。

针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。 
function mySetInterval(fn, timeout) {
  function interval() {
    fn();
    setTimeout(interval, timeout);
  }
  setTimeout(interval, timeout);
}

 51、this指向判断

 52、httpcode中301和302的区别

301:永久重定向,有缓存;旧地址a的资源不可访问了,重定向到网址b。

302:临时重定向,无缓存;旧地址a的资源依然可以访问,只是临时从a地址跳转到了b地址。

尽量使用301跳转,以防止网页劫持。

 两者都是一个 post 请求经过301/302后会被浏览器转为 get 请求。

 53、post为什么会发两次请求?

非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为 预检请求。

需预检的请求要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。

简单请求必须满足一下条件,否则都是非简单请求:

  • HTTP 方法限制:只能使用 GET、HEAD、POST 这三种 HTTP 方法之一。如果请求使用了其他 HTTP 方法,就不再被视为简单请求。
  • 自定义标头限制:请求的 HTTP 标头只能是以下几种常见的标头:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type(仅限于 application/x-www-form-urlencodedmultipart/form-datatext/plain)。HTML 头部 header field 字段:DPR、Download、Save-Data、Viewport-Width、WIdth。如果请求使用了其他标头,同样不再被视为简单请求。
  • 请求中没有使用 ReadableStream 对象。
  • 不使用自定义请求标头:请求不能包含用户自定义的标头。
  • 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问

54、为什么本地使用 webpack 进行 dev 开发时,不需要服务器端配置 cors 的情况下访问到线上接口?

当你在本地通过 Ajax 或其他方式请求线上接口时,由于浏览器的同源策略,会出现跨域的问题。但是在服务器端并不会出现这个问题。

它是通过 Webpack Dev Server 来实现这个功能。

当你在浏览器中发送请求时,请求会先被 Webpack Dev Server 捕获,然后根据你的代理规则将请求转发到目标服务器,目标服务器返回的数据再经由 Webpack Dev Server 转发回浏览器。

这样就绕过了浏览器的同源策略限制,使你能够在本地开发环境中访问线上接口。

 2024年


 

55、less和sass在项目部署以后一定会剔除吗?

 less和sass的预处理就只是开发时依赖,项目部署上线之后都会转换成原生的css语法供浏览器解析和编译。

目前部分浏览器可以实验性的支持css的原生嵌套语法。所以会被踢出。

56、html代码第一行的作用

用于声明文档的类型,并且告诉浏览器使用哪种HTML的标准来解析页面。

 

 

 

 

 

 

 

 

 

 

end

 

 

 

 

 

 

 

posted @   聂丽芳  阅读(273)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示