前端面试合集(更新中……)

一花一世界,一木一浮生,我佛慈悲,善哉

一、CSS

1.display:none、visibility:hidden和opacity: 0的区别?
  • display: none
    DOM 结构:浏览器不会渲染 display 属性为 none 的元素,不占据空间;
    事件监听:无法进行 DOM 事件监听;
    性能:动态改变此属性时会引起重排,性能较差;
    继承:不会被子元素继承,毕竟子类也不会被渲染;
    transition:transition 不支持 display。
  • visibility: hidden
    DOM 结构:元素被隐藏,但是会被渲染不会消失,占据空间;
    事件监听:无法进行 DOM 事件监听;
    性 能:动态改变此属性时会引起重绘,性能较高;
    继 承:会被子元素继承,子元素可以通过设置 visibility: visible; 来取消隐藏;
    transition:visibility 会立即显示,隐藏时会延时。
  • opacity: 0
    DOM 结构:透明度为 100%,元素隐藏,占据空间;
    事件监听:可以进行 DOM 事件监听;
    性 能:提升为合成层,不会触发重绘,性能较高;
    继 承:会被子元素继承,且子元素并不能通过 opacity: 1 来取消隐藏;
    transition:opacity 可以延时显示和隐藏

二、JavaScript

1.说一说JS数据类型有哪些,区别是什么?
  • JS数据类型分为两类:
    一类是基本数据类型,也叫简单数据类型,包含7种类型,分别是Undefined、String、Symbol、Number 、Null、BigInt、Boolean;
    一类是引用数据类型也叫复杂数据类型,通常用Object代表,普通对象,数组,正则,日期,Math数学函数都属于Object。
    (即为“u so nb”)
  • 数据分成两大类的本质区别:
    基本数据类型和引用数据类型它们在内存中的存储方式不同。
    基本数据类型是直接存储在栈中的简单数据段,占据空间小,属于被频繁使用的数据。
    引用数据类型是存储在堆内存中,占据空间大。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。
  • Symbol是ES6新出的一种数据类型,这种数据类型的特点就是没有重复的数据,所以 Symbol() != Symbol(),数据的创建方法Symbol(),因为它的构造函数不够完整,所以不能使用new Symbol()创建数据。
  • BigInt也是ES6新出的一种数据类型,这种数据类型的特点就是数据涵盖的范围大,能够解决超出普通数据类型范围报错的问题。
    使用方法:
    -整数末尾直接+n:647326483767797n
    -调用BigInt()构造函数:BigInt(“647326483767797”)
2.说一说你对闭包的理解?

闭包形成的原理:作用域链,当前作用域可以访问上级作用域中的变量;
闭包解决的问题:能够让函数作用域中的变量在函数执行结束之后不被销毁,同时也能在函数外部可以访问函数内部的局部变量。
(“闭包”关住了它所需要的变量,并将其保存起来)
闭包不会导致内存泄露! 内存泄露是指你用不到(访问不到)的变量的变量,依然占居着内存空间,不能被再次利用起来。 闭包里面的变量就是我们需要的变量,不能说是内存泄露。
解释看这里

3.Ajax和axios的区别是什么?

Ajax是对原生XHR,实现异步数据交互的技术,axios是对这种技术的封装。

  • Ajax是使用XMLHttpRequest (XHR)对象与服务器进行交互的实例,实现异步数据交互的技术。在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。
    axios是用Promise实现的对原生XHR的封装,是目前最流行的Ajax请求库。
  • Ajax原理:有客户端请求Ajax引擎,再由Ajax引擎请求服务器,服务器做出一系列相应之后返回Ajax引擎,由Ajax引擎决定将这个结果写入到客户端的什么位置,实现页面无刷新更新数据;
    axios特性:在浏览器中创建XHR,在nodejs中则请求http请求,支持Promise API,支持拦截请求和响应数据,取消请求,自动转换成JSON数据格式,客户端支出防御XSRF。
4.说一说promise是什么与使用方法?

(1)概念:异步编程的一种解决方案,解决了地狱回调的问题;
(2)使用方法:new Promise((resolve,reject) => { resolve(); reject(); })里面有多个resovle或者reject只执行第一个。如果第一个是resolve的话后面可以接.then查看成功消息。如果第一个是reject的话,.catch查看错误消息。
(new Promise同步代码,加入执行栈,Promise的回调是异步微任务,加入微任务队列,当执行栈中为空时执行)

5.说一说JavaScript有几种方法判断变量的类型?

(1)typeof(null和array返回的是“object”、NaN返回的是“number”);
(2)instanceof:主要用于区分引用数据类型(检测方法是检测的类型在当前实例的原型链上)
(3)Object.prototype.toString().call(),Object.prototype.toString 表示一个返回对象类型的字符串,call()方法可以改变this的指向,那么把Object.prototype.toString()方法指向不同的数据类型上面,返回不同的结果.各种数据类型都能检测且检测精准,打印结果为[Object xxx]。

6.说一说map 和 forEach 的区别?

(1)forEach是针对数组中每一个元素,提供一个可执行的函数操作,因此它(可能)会改变原数组中的值,不会返回有意义的值,或者说会返回undefined;
(2)map是会分配内存空间创建并存储一个新的数组,新数组中的每一个元素由调用的原数组中的每一个元素执行所写的函数得来,返回的就是新数组,因此不会改变原数组的值;
(3)map的处理速度比forEach快,而且返回一个新的数组,方便链式调用其他数组新方法,比如filter、reduce let arr = [1, 2, 3, 4, 5]; let arr2 = arr.map(value => value * value).filter(value => value > 10); // arr2 = [16, 25]。

7.js获取数组最后一个元素的方法?

(1)数组的pop方法,这个方法用来删除数组的最后一个方法,并返回该元素,但是如果原数组还有价值,不妨先深拷贝一份数据;
(2)数组的arr[length - 1];
(3)数组的slice方法,这个方法可以指定数组的起始索引,截取出对应元素,返回有这些元素组成的新数组,当使用负数作为参数时,就表示从数组得到最后一个元素开始计数,所以可以使用arr.slice(-1);
(4)数组的at方法,这个方法接收一个整数作为索引值,并返回索引对应的元素,所以arr.at(-1)。

8.原型和原型链

(1)构造函数虽然好用,但是存在浪费内存的问题。但创建实例对象的时候,若是复杂数据类型,则会单独再开辟一个空间来存放该复杂数据类型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YoEc9c7K-1669694821835)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9801b27a4887ac2680f7b15910a7~tplv-k3u1fbpfcp-zoom-1.image)]

(2)js中每个构造函数都有一个 prototype 属性,称之为原型,因为这个属性的值是一个对象,所以又称原型对象(包含了所有实例共享的属性和方法)。可以把不变的方法直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法
原型是一个对象,我们也可以称prototype为原型对象,原型的作用是实现方法的共享

(3)对象都有一个属性__proto__指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有__proto__的存在。
(ldh.proto===Star.prototype)
原型分为显式原型prototype、隐式原型__proto__,实例对象的隐式原型指向的是原型对象,隐式原型的作用在于当前对象中没有找到我们需要的属性,会基于__proto__指向的对象prototype中进行查找。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C0aoFEFG-1669694821837)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1bab00d4a09e4fae8c00c7fed97ea33f~tplv-k3u1fbpfcp-zoom-1.image)]
(4)对于原型对象来说有一个constructor属性,指向构造函数,指向构造函数本身,主要用于记录该对象引用于那个构造函数。如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数。
在这里插入图片描述

原型对象也是对象也就意味着它也有_proto_隐式属性 ,指向Object.prototype。可以理解成Person.prototyoe = new Object(),这样就可以解释为什么_proto_指向的是Object.prototype,但是Object.prototype原型对象的_proto_比较特殊,它指向的是null,这就是我们所谓的原型链。
在这里插入图片描述

  • 总结

在这里插入图片描述

三、前端性能优化

1.细说浏览器渲染的重排与重绘

秒懂

2.动画的优化:为什么transform 比 margin的性能好?

(1)margin属于布局属性,该属性变化会导致页面重排;对布局属性进行动画,需要不断计算动画元素的布局并更新它的像素信息,浏览器需要为每一帧进行重回并上传到CPU中进行渲染。
(2)tansform是合成属性,既不影响布局,也不会触发重绘,这就是它高性能的原因。浏览器会为元素创建一个独立的复合层,当元素内容没有发生变化,该层不会被重绘,通过重新复合来创建动画。transform和opacity为什么高性能

四、Vue

1.Vue2响应式数据原理

讲的非常清楚,快看
(1)主要是通过数据劫持、收集依赖,派发更新的方式实现的,每次数据更改的时候,触发set拦截,通知watcher进行触发更新,watcher是观察者,收集了很多跟状态相关的依赖以及他的订阅者和发布者,触发订阅者进行调用,实现更新。

  • Object.definedProperty()缺点:
    无法监听es6的Set、Map的变化;
    无法监听Class类型的数据;
    属性和数组元素的新增或删除也无法监听
2.为什么 Vue2 新增响应式属性要通过额外的 API?

(1)Object.defineProperty 只会对属性进行监测,而不会对对象进行监测,为了可以监测对象 Vue2 创建了一个Observer类
(2)Observer 类的作用就是把一个对象全部转换成响应式对象,包括子属性数据,当对象新增或删除属性的时候负债通知对应的 Watcher 进行更新操作。
(3)Vue2 对数组的监测是通过重写数组原型上的 7 个方法来实现,Object.defineProperty 真的不能监听数组的变化吗?其实 Object.defineProperty 是可以监听数组的变化的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z8aSea80-1669694821839)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/680d16f9a4a646dea25a6864966c3c38~tplv-k3u1fbpfcp-zoom-1.image)]

其实数组就是一个特殊的对象,它的下标就可以看作是它的 key。
所以 Object.defineProperty 也能监听数组变化,那么为什么 Vue2 弃用了这个方案呢?
Object.defineProperty 对数组进行监听,但也监听不了 push、pop、shift 等对数组进行操作的方法,所以还是需要通过对数组原型上的那 7 个方法进行重写监听。所以为了性能考虑 Vue2 直接弃用了使用 Object.defineProperty 对数组进行监听的方案,所以通过下标操作数组是无法实现响应式操作的。

4.Vue2 中是怎么监测数组的变化的?

(1)我们知道如果使用 Object.defineProperty 对数组进行监听,当通过 Array 原型上的方法改变数组内容的时候是无发触发 getter/setter 的, Vue2 中是放弃了使用 Object.defineProperty 对数组进行监听的方案,而是通过对数组原型上的 7 个方法进行重写进行监听的。
(2)原理就是使用拦截器覆盖 Array.prototype,之后再去使用 Array 原型上的方法的时候,其实使用的是拦截器提供的方法,在拦截器里面才真正使用原生 Array 原型上的方法去操作数组。

5.Vue3 的响应式原理是怎么样的?
  • Vue3 是通过 Proxy 对数据实现 getter/setter 代理,从而实现响应式数据,然后在副作用函数中读取响应式数据的时候,就会触发 Proxy 的 getter,在 getter 里面把对当前的副作用函数保存起来,将来对应响应式数据发生更改的话,则把之前保存起来的副作用函数取出来执行。
    具体是副作用函数里面读取响应式对象的属性值时,会触发代理对象的 getter,然后在 getter 里面进行一定规则的依赖收集保存操作。
  • Vue3 对数组实现代理时,用于代理普通对象的大部分代码可以继续使用,但由于对数组的操作与对普通对象的操作存在很多的不同,那么也需要对这些不同的操作实现正确的响应式联系或触发响应。这就需要对数组原型上的一些方法进行重写。
  • 比如通过索引为数组设置新的元素,可能会隐式地修改数组的 length 属性的值。同时如果修改数组的 length 属性的值,也可能会间接影响数组中的已有元素。另外用户通过 includes、indexOf 以及 lastIndexOf 等对数组元素进行查找时,可能是使用代理对象进行查找,也有可能使用原始值进行查找,所以我们就需要重写这些数组的查找方法,从而实现用户的需求。原理很简单,当用户使用这些方法查找元素时,先去响应式对象中查找,如果没找到,则再去原始值中查找。
  • 另外如果使用 push、pop、shift、unshift、splice 这些方法操作响应式数组对象时会间接读取和设置数组的 length 属性,所以我们也需要对这些数组的原型方法进行重新,让当使用这些方法间接读取 length 属性时禁止进行依赖追踪,这样就可以断开 length 属性与副作用函数之间的响应式联系了。
6.Vue中的$nextTick

执行的过程当中出现了数据更新并会影响页面dom元素时,后续js代码并不会等待dom更新后才执行,那么就会出现后续的代码找不到更新后的dom元素

// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
  // DOM 更新了
})
// 作为一个 Promise 使用
// 2.1.0 起新增:如果没有提供回调且在支持 Promise 的环境中,则返回一个 Promise。
Vue.nextTick()
 .then(function () {
  // DOM 更新了
})

Vue中数据的更新不会同步触发dom元素的更新,也就是说dom更新是异步执行的,并且在更新之后调用了我们传入nextTick的函数。只要观察到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。
为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。

7.hash和history路由的区别?
  • hash 模式:
    • 把前端路由的路径用井号 # 拼接在真实 url 后面的模式,井号 # 后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发 onhashchange 事件;
    • hash变化会触发网页跳转,即浏览器的前进和后退;
    • hash 可以改变 url ,但是不会触发页面重新加载(hash的改变是记录在 window.history 中),即不会刷新页面。也就是说,所有页面的跳转都是在客户端进行操作。因此,这并不算是一次 http 请求,所以这种模式不利于 SEO 优化。hash 只能修改 # 后面的部分,所以只能跳转到与当前 url 同文档的 url 。
    • hash 通过 window.onhashchange 的方式,来监听 hash 的改变,借此实现无刷新跳转的功能。
    • hash 永远不会提交到 server 端。
  • History模式:
    • 利用H5的 history中新增的两个API pushState() 和 replaceState() 来实现无刷新跳转的功能和一个事件onpopstate监听URL变化;
    • 新的 url 可以是与当前 url 同源的任意 url ,也可以是与当前 url 一样的地址,但是这样会导致的一个问题是,会把重复的这一次操作记录到栈当中;
    • history每次刷新会重新像后端请求整个网址,也就是重新请求服务器。如果后端没有及时响应,就会报错404!
  • 两者选择
    • to B 的系统推荐用 hash ,相对简单且容易使用,且因为 hash 对 url 规范不敏感;(面向企业用户)
    • to C 的系统,可以考虑选择 H5 history ,但是需要服务端支持(面向个体用户,如淘宝)
8.原生微信小程序数据双向绑定和vue实现原理的区别?

(1)原生微信小程序:

<input value="{{value}}" />
  • 在WXML中,普通的属性的绑定都是单向的,this.data用来获取页面data对象,使用this.setData()函数将数据从逻辑层发送到视图层,同时改变对应的this.data的值,实现页面的更新。直接修改this.data无效,只是逻辑层的改变;
    • this.setData中设置的key如果只有key没有value,则从所在函数内找这个变量,直接渲染到前台并修改原data中的数据;若Page对象的data中没有定义该key,则setData自动创建。
  • 实现简易数据的双向绑定,可以使用model: 前缀,但是只能实现一个单一字段的绑定;且目前,尚不能 data 路径,如
<input model:value="{{ a.b }}" />

是目前暂不支持。

  • 在自定义组件中实现数据双向绑定:
Component({
  properties: {
    myValue: String
  }
})
<input model:value="{{myValue}}" />

这个自定义组件将自身的 myValue 属性双向绑定到了组件内输入框的 value 属性上。
这样,如果页面这样使用这个组件:

<custom-component model:my-value="{{pageValue}}" />

当输入框的值变更时,自定义组件的 myValue 属性会同时变更,这样,页面的 this.data.pageValue 也会同时变更,页面 WXML 中所有绑定了 pageValue 的位置也会被一同更新。

  • 在自定义组件中触发双向绑定更新
    自定义组件还可以自己触发双向绑定更新,做法就是:使用 setData 设置自身的属性。例如:
// custom-component.js
Component({
  properties: {
    myValue: String
  },
  methods: {
    update: function() {
      // 更新 myValue
      this.setData({
        myValue: 'leaf'
      })
    }
  }
})

如果页面这样使用这个组件:

<custom-component model:my-value="{{pageValue}}" />

当组件使用 setData 更新 myValue 时,页面的 this.data.pageValue 也会同时变更,页面 WXML 中所有绑定了 pageValue 的位置也会被一同更新。
(2)Vue:

  • 响应式原理:
    Vue响应原理的核心是数据劫持和依赖收集,主要是利用Object.defineProperty()实现对数据存取操作的拦截,我们把这个实现称为数据代理;同时我们通过对数据get方法的拦截,可以获取到对数据的依赖,并将出所有的依赖收集到一个集合中;而在我们给属性赋值(修改属性)时,会触发这里定义的setter函数,在次函数中会去通知集合中的依赖更新,做到数据变更驱动视图变更。
    在vue3中,核心思想一致,只不过数据的劫持使用Proxy而不是Object.defineProperty,只不过Proxy相比Object.defineProperty在处理数组和新增属性的响应式处理上更加方便。
  • 数据双向绑定
    Vue通过v-model指令为组件添加上input事件处理和value属性的赋值,实现数据的双向绑定。
<template>
    <input v-model='localValue'/>
   <!-- 相当于这里添加了input时间的监听和value的属性绑定 -->
   <input @input='onInput' :value='localValue' />
   <span>{{localValue}}</span>
</template>
<script>
  export default{
    data(){
      return {
        localValue:'',
      }
    },
    methods:{
      onInput(v){
         //在input事件的处理函数中更新value的绑定值
         this.localValue=v.target.value;
         console.log(this.localValue)
      }
    }
  }
</script>

因此当我们修改input输入框中的值时,我们通过v-model绑定的值也会同步修改
9.微信小程序地图的实现?

五、计算机网络

1.DNS查找解析域名的过程

大佬NewBee

六、操作系统

1.什么是进程、线程,进程和线程的区别?

(1)进程
进程就是正在执行的程序,是操作系统资源分配的基本单位;
(2)线程
线程是进程内部的不同的执行路径,是操作系统独立调度的基本单位;
一个进程中可以有多个线程,它们共享进程资源。比如说,微信和浏览器是两个进程,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件;
(3)区别
进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属于进程的资源;
线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换…

2.进程有哪些状态?
  • 进程一共有5中状态,分别是创建、就绪、运行、终止、阻塞;
  • 运行状态就是进程正在CPU上运行,在单处理机环境下,每一时刻最多只有一个进程处于运行状态;
  • 就绪状态就是说进程已处于准备运行的状态,即进程获得了除CPU之外的一切所需资源,一旦得到CPU即可运行;
  • 阻塞状态就是进程正在等待某一事件而暂停运行,比如等待某资源为可用或等待I/O完成,即使CPU空闲,该进程也不能运行。
    运行态→阻塞态:往往是由于等待外设,等待主存等资源分配或等待人工干预而引起的。

阻塞态→就绪态:则是等待的条件已满足,只需分配到处理器后就能运行。

运行态→就绪态:不是由于自身原因,而是由外界原因使运行状态的进程让出处理器,这时候就变成就绪态。例如时间片用完,或有更高优先级的进程来抢占处理器等。

就绪态→运行态:系统按某种策略选中就绪队列中的一个进程占用处理器,此时就变成了运行态

七、浏览器

1.浏览器的垃圾回收机制

看了包会

八、打包构建工具

1.webpack和Vite的区别?
  • webpack:分析依赖=> 编译打包=> 交给本地服务器进行渲染
    首先分析各个模块之间的依赖,然后进行打包,在启动开发服务器,请求服务器时,直接显示打包结果。webpack打包之后存在的问题:随着模块的增多,会造成打出的 bundle 体积过大,进而会造成热更新速度明显拖慢。
  • vite: 启动服务器=> 请求模块时按需动态编译显示
    vite相较于webpack没有打包过程,而是直接启动了一个开发服务器,vite劫持了浏览器的http请求,在后端进行响应的处理,将项目中使用的文件通过简单地分解与整合,然后再返回给浏览器,整个过程没有对文件进行打包编译,所以编译速度很快,需要打包的时候vite使用Rollup配置。
2.webpack热更新

Hot Module Replacement,简称HMR,无需完全刷新整个页面,更新模块。
热更新原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值