「原题 + 精讲 」7.8 月份新出炉面试题含大厂100道1.2万字整理(一)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 一些核心面试题整理

7.8 月份面试题新鲜出炉啦,里面有很多大厂面试题原题,大家喜欢的可以给笔者点个赞,花费了很长时间整理,这些面试题更适合为中级前端进阶高级前端的小伙伴查缺补漏,和为打算金九银十换工作的同学们保驾护航。

陆续还有相关文章分享连载,喜欢的可以关注一下笔者和笔者的公众号:前端Sharing

废话不说,面试题奉上

html篇

问题一:Meta标签常用属性值的写法和作用

答:
meta 标签提供关于HTML文档的元数据。元数据不会显示在页面上,但是对于机器是可读的。它可用于浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 web 服务。

必要属性

name:属性名
content:属性内容
charset: charset为HTML5中新增的,用来声明字符编码;
http-equiv:属性在HTML4中有很多值,在HTML5中只有refresh、default-style、content-type可用

name的值和说明

application name 当前页所属Web应用系统的名称
keywords 描述网站内容的关键词,以逗号隔开,用于SEO搜索
description 当前页的说明
author 当前页的作者名
copyright 版权信息
renderer renderer是为双核浏览器准备的,用于指定双核浏览器默认以何种方式渲染页面
viewreport 它提供有关视口初始大小的提示,仅供移动设备使用

viewreport
meta标签的name属性值为viewreport时的视口的大小

1.content内容为空时,默认视口宽度为980
2.content设置width,不设置initail-scale时,视口宽度为设置的width值
3.content不设置width,只设置initail-scale时,是可以根据initail-scale的值计算出视口的宽度
initail-scale = 屏幕宽度 / 视口宽度
4.content同时设置width和initail-scale时,视口宽度为width的值,页面显示按照initail-scale比率进行缩放
5.一般都是进行如下设置,来实现视口宽等于设备宽,布局完成后屏幕显示也不进行缩放

<meta name="viewport" content="width=device-width, initial-scale=1.0">

声明字符编码
charset属性为HTML5新增的属性,用于声明字符编码,以下两种写法效果一样

<meta charset="utf-8"> //HTML5

模拟http标头字段

http-equiv属性与content属性结合使用, http-equiv属性为指定所要模拟的标头字段的名称,content属性用来提供值。

<meta http-equiv="参数" content="具体的描述">

content-Type 声明网页字符编码:

<meta http-equiv="content-Type" content="text/html charset=UTF-8">

refresh 指定一个时间间隔(以秒为单位),在此时间过去之后从服务器重新载入当前页面,也可以另外指定一个页面.

<meta http-equiv="refresh" content="2;URL=http://www.baidu.com">//2秒后在当前页跳转到百度

X-UA-Compatible 浏览器采取何种版本渲染当前页面

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> //指定IE和Chrome使用最新版本渲染当前页面

expires 用于设定网页的到期时间,过期后网页必须到服务器上重新传输

<meta http-equiv="expires" content="Sunday 22 July 2016 16:30 GMT">

catch-control 用于指定所有缓存机制在整个请求/响应链中必须服从的指令

<meta http-equiv="cache-control" content="no-cache">

js篇

问题1: 给对象加上iterator接口,使之能被 for of遍历

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。Symbol.iterator 属性本身是一个函数,就是当前数据结构默认的遍历器生成函数

因为object 没有 Symbol.iterator 属性,所以不能被 for of 遍历。


const text = {
   
   
   a: 1,
   b: 2,
   c:3
}
for(let i of text){
   
   
    console.log(i) //报错: Uncaught TypeError: text is not iterable
}

所以,object想要被 for ... of遍历 ,必须在原来的基础上加上 Symbol.iterator 接口属性。

const text = {
   
   
   a: 1,
   b: 2,
   c:3
}
text[Symbol.iterator] = function (){
   
   
    const _this = this
    return {
   
   
        index:-1,
        next(){
   
   
            const arr = Object.keys( _this )
            if(this.index < arr.length){
   
   
                this.index++
                return {
   
   
                    value:_this[arr[this.index]],
                    done:false
                }
            }else{
   
   
                return {
   
   
                    value:undefined,
                    done:true
                }
            }
        }
    }
}

for(let i of text){
   
   
    console.log(i) // 1 2 3 undefined
}

问题2:统计按钮一秒钟点击次数?

这道题是变相考对防抖函数+闭包的理解

<script>

const button = document.getElementById('button')
function debounce(fn, time) {
    
    
    let number = 0 , timer  = null
    return function(...arg) {
    
     
        fn.apply(this, arg)
        number++
        if (timer) return
        timer = setTimeout(() => {
    
    
           console.log( '点击'+ number + '次' )
           number = 0
           timer = null
        }, time)
    }
}
button.onclick = debounce(()=>{
    
    
    console.log(1111)
},1000)

</script>

效果如下

问题3:单页面应用路由的原理

无论我们用vue还是react构建单页面应用,都离不开路由的概念,路由跳转监听url改变,根据路由的改变来决定渲染的页面。

hash模式

改变路由

const path = "home"
window.location.hash = path

监听路由

window.addEventListener('hashchange',function(e){
   
   
  //路由发生改变时切换渲染组件...
});

histroy模式

改变路由

var path = "home"
history.pushState(null,null,'?='+path);

监听路由

window.addEventListener('popstate',function(e){
   
   
   //路由发生改变时切换渲染组件...
})

问题4:如下打印结果

执行如下代码,回发生什么?

function fn1(){
   
   
    console.log(a)
}
var a = 1

function fn2(){
   
   
    console.log(a)
    let a = 2;
    console.log(a)
    fn1()
}

fn2()

结果://Uncaught ReferenceError: Cannot access 'a' before initialization

let 暂时性死区

let/const是使用区块作用域;var是使用函数作用域

在let/const声明之前就访问对应的变量与常量,会抛出ReferenceError错误;但在var声明之前就访问对应的变量,则会得到undefined


console.log(a) // undefined
console.log(b) // causes ReferenceError: b is not defined
var a = 1
let b = 2

问题5:apply和call,bind的区别

apply,call和bind都是 用来改变this的指向

apply和call会让当前函数立即执行,而bind会返回一个函数,后续需要的时候再调用执行

apply,call的区别实参数不同

const foot ={
   
   
    apple:'苹果'
}
function eat(a,b){
   
   
   console.log(a,b,this)
}

const bindEat = eat.bind(foot)

eat(1,2) // 1 2 Window

bindEat(1,2) //1 2  foot 

eat.call(foot,1,2) //1 2  foot

/* apply参数已数组形式传递  */
eat.apply(foot,[1,2]) //1 2  foot

问题6:开发过程中遇到的内存泄露情况,如何解决的?

javascript内存泄漏几种情况

1 意外的全局变量

function foo(arg) {
   
    
    bar = "this is a hidden global variable"; 
}

另一种意外的全局变量可能由 this 创建:


function foo() {
   
    
    this.variable = "potential accidental global"; 
} 
// Foo 调用自己,this 指向了全局对象(window) 
// 而不是 undefined 
foo();

尽管我们讨论了一些意外的全局变量,但是仍有一些明确的全局变量产生的垃圾。它们被定义为不可回收(除非定义为空或重新分配)。尤其当全局变量用于 临时存储和处理大量信息时,需要多加小心。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。与全局变量相关的增加内存消耗的一个主因是缓存。缓存数据是为了重用,缓存必须有一个大小上限才有用。高内存消耗导致缓存突破上限,因为缓 存内容无法被回收。

2被遗忘的计时器或回调函数

var someResource = getData(); 
setInterval(function() {
   
    
    var node = document.getElementById('Node'); 
    if(node) {
   
    
        // 处理 node 和 someResource 
        node.innerHTML = JSON.stringify(someResource)); 
    } 
}, 1000);

与节点或数据关联的计时器不再需要,node 对象可以删除,整个回调函数也不需要了。可是,计时器回调函数仍然没被回收(计时器停止才会被回收)。同时,someResource 如果存储了大量的数据,也是无法被回收的。

3脱离 DOM 的引用

var elements = {
   
    
    button: document.getElementById('button'), 
    image: document.getElementById('image'), 
    text: document.getElementById('text') 
}; 

function doStuff() {
   
    
    image.src = 'http://some.url/image'; 
    button.click(); 
    console.log(text.innerHTML); 
    // 更多逻辑 
} 

function removeButton() {
   
    
    // 按钮是 body 的后代元素 
    document.body.removeChild(document.getElementById('button')); 

    // 此时,仍旧存在一个全局的 #button 的引用 
    // elements 字典。button 元素仍旧在内存中,不能被 GC 回收。 
}

4 闭包的错误使用

var theThing = null; 
var replaceThing = function () {
   
    
  var originalThing = theThing; 
  var unused = function () {
   
    
    if (originalThing) 
      console.log("hi"); 
  }; 
  theThing = {
   
    
    longStr: new Array(1000000).join('*'), 
    someMethod: function () {
   
    
      console.log(someMessage); 
    } 
  }; 
}; 
setInterval(replaceThing, 1000);

每次调用 replaceThing ,theThing 得到一个包含一个大数组和一个新闭包(someMethod)的新对象。同时,变量 unused 是一个引用 originalThing 的闭包(先前的 replaceThing 又调用了 theThing )。思绪混乱了吗?最重要的事情是,闭包的作用域一旦创建,它们有同样的父级作用域,作用域是共享的。someMethod 可以通过 theThing 使用,someMethod 与 unused 分享闭包作用域,尽管 unused从未使用,它引用的 originalThing 迫使它保留在内存中(防止被回收)。当这段代码反复运行,就会看到内存占用不断上升,垃圾回收器(GC)并无法降低内存占用。本质上,闭包的链表已经创建,每一个闭包作用域携带一个指向大数组的间接的引用,造成严重的内存泄露。

解决内存泄漏方式

1 使用严格模式,合理声明变量。使用严格模式可以避免第一种情况的发生。

2 及时清理定时器,延时器,对于不需要的定时器和延时器,一定要及时清除。

3 合理应用闭包,合理的应用闭包,避免闭包函数反复执行导致内存无法及时释放。

问题7:介绍一下proto和prototype

proto和prototype 是我们在平时工作中容易忽略的问题,对象原型 和 原型链 的概念也容易混淆。

proto是每个对象都有的属性 ,在JS里,万物皆对象。方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。即:对象具有属性proto,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

prototypee是函数才有的属性,方法(Function)方法这个特殊的对象,除了和其他对象一样有上述proto属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法就是该实例的proto。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。

 let a = function (){
   
   

 }
 a.prototype.eat = function(){
   
    console.log(111) }
 const na = new a()
 console.log( na.__proto__ === a.prototype ) // ture

问题8:说一下arguments对象

在函数调用的时候,浏览器每次都会传递进arguments对象,arguments 对象实际上是所在函数的一个内置类数组对象,arguments对象不是一个 Array 。它类似于Array,但除了length属性和索引元素之外没有任何Array属性。例如,它没有 pop 方法。但是它可以被转换为一个真正的Array.typeof参数返回 'object'。

属性
arguments.callee指向参数所属的当前执行的函数。指向调用当前函数的函数。
arguments.length传递给函数的参数数量。
arguments[@@iterator]返回一个新的Array 迭代器 对象,该对象包含参数中每个索引的值。

问题9:怎么实现一个队列的数据结构?

介绍
队列也是一种线性表。它允许在表的一端插入数据,在另一端删除元素。插入元素的这一端称之为队尾。删除元素的这一端我们称之为队首。

特性
1 在队尾插入元素,在队首删除元素。
2 FIFO(先进先出),就向排队取票一样。

简单js实现

class Queue {
   
   
    constructor(){
   
   
        this.queue = []
    }
    /* 进入队列 */
    enqueue(item){
   
   
        this.queue.push(item)
    }
    /* 移除队列 */
    dequeue(){
   
   
        this.queue.shift()
    }
    /* 获取队列长度 */
    size(){
   
   
        return this.queue.length
    }
    /* 判断是否为空 */
}

css

问题一: 什么是vw+vh布局?有哪些有优点?有哪些缺陷?

解答:

1什么是什么是vw/vh

css3中引入了一个新的单位vw/vh,与视图窗口有关,vw表示相对于视图窗口的宽度,vh表示相对于视图窗口高度,除了vw和vh外,还有vmin和vmax两个相关的单位。各个单位具体的含义如下:
单位含义vw相对于视窗的宽度,视窗宽度是100vwvh相对于视窗的高度,视窗高度是100vhvminvw和vh中的较小值vmaxvw和vh中的较大值;
这里我们发现视窗宽高都是100vw/100vh,那么vw或者vh,下简称vw,很类似百分比单位。vw和%的区别为:
单位含义%大部分相对于祖先元素,也有相对于自身的情况比如(border-radius、translate等)vw/vh相对于视窗的尺寸
从对比中我们可以发现,vw单位与百分比类似,单确有区别,前面我们介绍了百分比单位的换算困难,这里的vw更像"理想的百分比单位"。任意层级元素,在使用vw单位的情况下,1vw都等于视图宽度的百分之一。

2vw单位换算

同样的,如果要将px换算成vw单位,很简单,只要确定视图的窗口大小(布局视口),如果我们将布局视口设置成分辨率大小,比如对于iphone6/7 375*667的分辨率,那么px可以通过如下方式换算成vw:

1px =1/375*100 vw

3缺陷

1 绝大多数的浏览器支持vw单位,但是ie9-11不支持vmin和vmax,考虑到vmin和vmax单位不常用,vw单位在绝大部分高版本浏览器内的支持性很好,但是opera浏览器整体不支持vw单位,如果需要兼容opera浏览器的布局,不推荐使用vw。

2 由于是相对手机窗口,针对不同的手机视图大小不同,所以需要对单位进行换算处理。

问题二:什么是rem布局?rem布局的缺陷。

答:

rem布局

rem是一个灵活的、可扩展的单位,由浏览器转化像素并显示。与em单位不同,rem单位无论嵌套层级如何,都只相对于浏览器的根元素(HTML元素)的font-size。默认情况下,html元素的font-size为16px,所以:
1 rem = 12px
为了计算方便,通常可以将html的font-size设置成:

html{
   
    font-size: 62.5% }

这种情况下:
1 rem = 10px
rem单位都是相对于根元素html的font-size来决定大小的,根元素的font-size相当于提供了一个基准,当页面的size发生变化时,只需要改变font-size的值,那么以rem为固定单位的元素的大小也会发生响应的变化。 因此,如果通过rem来实现响应式的布局,只需要根据视图容器的大小,动态的改变font-size即可。

rem布局缺陷

1 在响应式布局中,必须通过js来动态控制根元素font-size的大小。
1 css样式和js代码有一定的耦合性。且必须将改变font-size的代码放在css样式之前

问题三:怎么让Chrome支持小于12px 的文字**

谷歌浏览器默认最小字体为12px,若想让chorme支持12px字体,只需要用css3属性transform就可以

🌰例子:在谷歌浏览器写出10px字体

transform:scale(0.5);
font-size:20px;

搞定收工~~~

问题四:透明度opacity和rgba的区别

opacity

取值在0到1之间,0表示完全透明,1表示完全不透明。

.box{
   
   opacity: 0.5;}

rgba

rgba中的R表示红色,G表示绿色,B表示蓝色,三种颜色的值都可以是正整数或百分数。A表示Alpha透明度。取值0~1之间,类似opacity。

.box{
   
   background: rgba(255,0,0,0.5);}

rgba和opacity的区别
rgba()和opacity都能实现透明效果,但最大的不同是opacity作用于元素,以及元素内的所有内容的透明度,而rgba()只作用于元素的颜色或其背景色。

问题五:position的属性值有哪些?

absolute
生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。

元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。

fixed
生成绝对定位的元素,相对于浏览器窗口进行定位。

元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。

relative
生成相对定位的元素,相对于其正常位置进行定位。因此,"left:20" 会向元素的 LEFT 位置添加 20 像素。

static
默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
inherit
规定应该从父元素继承 position 属性的值。

问题六:display的属性值有哪些?

none(元素不会被显示);

block(元素将显示为块级元素,元素前后会带有换行符);

inline(元素会被显示为内联元素,元素前后没有换行符);

inline-block(行内块元素。CSS2.1新增的值);

table(元素会作为块级表格来显示,类似table,表格前后带有换行符);

table-row(元素会作为一个表格行显示,类似tr);

table-cell(元素会作为一个表格单元格显示,类似td和th)

flex 弹性盒结构

gird 网格结构

问题七:垂直水平居中的方案有哪些(尽量说全面一些)

position absolute 50% + 负margin -50%

.container {
   
   
    position: relative;
}
.box {
   
   
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -50%;
    margin-left: -50%;
}

position absolute 50% + transform -50%

.container {
   
   
    position: relative;
}
.box {
   
   
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

css-table


/* 此处引用上面的公共代码 */
.container {
   
   
    display: table-cell;
    text-align: center;
    vertical-align: middle;
}
.box {
   
   
    display: inline-block;
}

flex

/* 此处引用上面的公共代码 */
.container {
   
   
    display: flex;
    justify-content: center;
    align-items: center;
}
.box-center {
   
   
    text-align: center;
}

grid

/* 此处引用上面的公共代码 */
.container {
   
   
    display: grid;
    justify-items: center;
    align-items: center;
}
.box-center {
   
   
    text-align: center;
}

vue 篇

问题1:vue各个生命周期及其作用

1.初始化

beforeCreate:大vue已经初始化,只是数据初始化与事件系统构建尚未形成,不能获取DOM节点(没有data,没有el)
使用场景:因为此时data和methods都拿不到,所以通常在实例以外使用。
created:实例已经创建,仍然不能获取DOM节点(有data,没有el)
使用场景:模板渲染成html前调用,此时可以获取data和methods, 可以初始化进行数据请求,得到渲染数据,,异步操作可以放在这里
2 挂载
beforeMount是个过渡阶段,此时依然获取不到具体的DOM节点,但是vue挂载的根节点已经创建(有data,有el)
mounted:组件挂载完成,数据和DOM都已经被渲染出来了
使用场景:模板渲染成html后调用,通常是初始化页面完成后再对数据和DOM做一些操作,需要操作DOM的方法可以放在这里
3.更新
beforeUpdate:检测到数据更新时,但在DOM更新前执行
updated:更新结束后执行
使用场景:需要对数据更新做统一处理的;如果需要区分不同的数据更新操作可以使用$nextTick
4.销毁
beforeDestroy:当要销毁vue实例时,在销毁前执行
destroyed:销毁vue实例时执行

父子组件mounted和destroyed顺序

beforeMount执行顺序 先父后子

mounted执行顺序,先子后父

beforeDestroy 执行顺序,先父后子

destroyed 执行顺序 , 先子后父

问题2:vue3的双向绑定原理,与vue2.0比起来有那些优势?

vue3.0 的数据绑定原理 proxy 对象
vue3.0 于 Proxy 的 observer 实现, 代替了Vue 2 系列中基于 Object.defineProperty 做为响应式原理

感兴趣的同学可以看一下笔者的vue3.0响应式原理详解

传送门:vue3.0 响应式原理(超详细)

优势:
1 对属性的添加、删除动作的监测;

2 对数组基于下标的修改、对于 .length 修改的监测;

3 对 Map、Set、WeakMap 和 WeakSet 的支持;;

4 vue3 对依赖收集用的是weaMap,WeakSet,保持了对键名所引用的对象的弱引用,即垃圾回收机制不将该引用考虑在内,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

5 vue2.0 初始化data时候,对于对象等引用数据类型,进行了递归处理,也就是对于一些挂载在data上属性,但是并没有用到的属性,也同样做了响应式处理,而vue3.0之后访问到父级属性之后,在进行下一层track,也就是说初始化data时候无需把大量性能浪费在递归上。

问题3:vue路由卫士?

vue中路由守卫一共有三种,一个全局路由守卫,一个是组件内路由守卫,一个是router独享守卫。

一、全局路由守卫

只要全局路由变化,就会触发全局的路由守卫。
全局路由守卫有个两个:一个是全局前置守卫,一个是全局后置守卫。

router.beforeEach((to, from, next) => {
   
   
    console.log(to) => // 到哪个页面去?
    console.log(from) => // 从哪个页面来?
    next() => // 一个回调函数
}
router.afterEach(to,from= {
   
   }

next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址,next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项.

二、组件路由守卫

// 跟methods: {}等同级别书写,组件路由守卫是写在每个单独的vue文件里面的路由守卫
beforeRouteEnter (to, from, next) {
   
   
    // 注意,在路由进入之前,组件实例还未渲染,所以无法获取this实例,只能通过vm来访问组件实例
    next(vm => {
   
   })
}
beforeRouteUpdate (to, from, next) {
   
   
    // 同一页面,刷新不同数据时调用,
}
beforeRouteLeave (to, from, next) {
   
   
    // 离开当前路由页面时调用
}

三、路由独享守卫
路由独享守卫是在路由配置页面单独给路由配置的一个守卫

export default new VueRouter({
   
   
    routes: [
        {
   
   
            path: '/',
            name: 'home',
            component: 'Home',
            beforeEnter: (to, from, next) => {
   
   
               // ...
            }
        }
    ]
})

问题4:vue 中watch和computed区别?

watch侧重点是对数据更新所产生的依赖追踪,而computed侧重点是对数据的缓存与处理引用,这就是watch和computed本质的区别 ,computed可以看作一种特殊的data数据类型 ,它内部进行了二次依赖收集 ,第一次依赖收集是引用computed属性值,而进行的依赖收集 ,第二次是对computed内部是否关联data或者props的属性,而又进行的一次依赖收集。

下面我们按照vue3.0两个例子,来分别两者之前在流程上有什么区别。

watch

<div id="app">
   <p>{
  
  { count }}</p>
   <button @input="add" >add</button>
</div>
<script>
const {
    
     reactive, watch, toRefs } = Vue
Vue.createApp({
    
    
  setup(){
    
    
    const state = reactive({
    
    
       count:1,
    })
    const add = () => state.count++
    watch(state.count,(count, prevCount) => {
    
    
       console.log('新的count=' , count )
    })
    return {
    
    
      ...toRefs(state),
      add
    }
  }
}).mount('#app')
</script>

从上述例子我们看出,当点击add后count变化 ,而是 watch作用就是,追踪到count变化 ,而促使回调函数执行。我们用一张流程图来解析整个流程。

computed

<div id="app">
   <p>{
  
  { plusOne }}</p>
   <button @input="add" >add</button>
</div>

<script>
Vue.createApp({
    
    
  data: () => ({
    
    
    number: 1
  }),
  computed: {
    
    
    plusOne() {
    
    
      return this.number + 1
    }  
  },
  methods: {
    
    
    add(){
    
    
      this.number++
    }
  }
}).mount('#app')
</script>

当我们点击add改变的是number,但是引用过this.number的computed也更新了新的值 ,页面更新,我们可以看出 plusOne计算属性可以看作一个对number缓存的数据类型,data下的number收集了plusOne依赖项,同样plusOne也收集了{ { plusOne }}的依赖促使更新视图,我们用一张流程图来解析整个流程。

如果想要看原理解析请看笔者的文章

传送门 vue3.0 watch 和 computed源码解析(举例图解)

问题5:vue中data为什么要是个函数?

vue中data必须是一个函数是和js本身特性有关。

我们做vue项目的时候,所有的vue组件都是基于大vue实例化的,我们可以用一个简单例子来解释一下:


function Vue(){
   
   

}
Vue.prototype.data = {
   
   
    name:'jack',
    age:22,
}
var componentA = new Vue();
var componentB = new Vue();
componentA.data.age=55;
console.log(componentA,componentB)

此时,componentA 和 componentB data之间指向了同一个内存地址,age 都变成了 55, 导致了问题!
接下来很好解释为什么 vue 组件需要 function 了:

function Vue(){
   
   
 this.data = this.data()
}
Vue.prototype.data = function (){
   
   
    return {
   
   
    name:'jack',
    age:22,
}
}
var componentA = new Vue();
var componentB = new Vue();
componentA.data.age=55;
console.log(componentA,componentB)

componentA 和 componentB data之间相互独立, age 分别是 55 和 22
当data是一个方法的时候,每一个实例化组件都会形成一个独立的data对象,相互之间没有影响。

react 篇

问题1:setState是同步的还是异步的?

对于这个问题,笔者自己总结了一下:对于setState是同步还是异步,对于整个react代码上下文执行栈来说,setState是同步的,但是setState触发以后,并不一定得到新的数据,这里有一个react有一个batchUpdate批量更新的概念。

我们来看一个例子🌰:

class Example extends React.Component {
   
   
  constructor() {
   
   
    super();
    this.state = {
   
   
      val: 0
    };
  }

  componentDidMount() {
   
   
    this.setState({
   
   val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({
   
   val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

    setTimeout(() => {
   
   
      this.setState({
   
   val: this.state.val + 1});
      console.log(this.state.val);  // 第 3 次 log

      this.setState({
   
   val: this.state.val + 1});
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }

  render() {
   
   
    return null;
  }
};

答案是: 0 0 2 3

在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state 。

问题2:介绍一下 react-hooks API及其如何使用?

usestate 无状态组件的state

useCallback,useMemo 性能优化利器.

useContext 可以使用操纵react context.

useEffect ,useLayoutEffect 副作用钩子 可以替代class声明组件中的声明周期 .useLayoutEffect 在浏览器渲染之前 , effect在浏览器渲染之后

useReducer 功能可以参考redux

useRef 可以获取元素和组件实例,还可以缓存数据

详细的react-hooks使用可以戳👇

传送门:react-hooks如何使用?

webpack篇

问题一:webpack性能优化?

打包的时间和打包之后文件的体积是影响webpack性能的主要因素。所以我们可以从这两个方面入手,来优化webpack性能。

1合理使用loader
用 include 或 exclude 来帮我们避免不必要的转译,优化loader的管辖范围。

2缓存babel编译过的文件

loader: 'babel-loader?cacheDirectory=true'
如上,我们只需要为 loader 增加相应的参数设定。选择开启缓存将转译结果缓存至文件系统,可以提交babel-loader的工作效率。

3 DllPlugin类库引入

DllPlugin 是基于 Windows 动态链接库(dll)的思想被创作出来的。这个插件会把第三方库单独打包到一个文件中,这个文件就是一个单纯的依赖库。这个依赖库不会跟着你的业务代码一起被重新打包,只有当依赖自身发生版本变化时才会重新打包。

4 happypack多进程编译

我们都知道nodejs是单线程。无法一次性执行多个任务。这样会使得所有任务都排队执行。happypack可以根据cpu核数优势,建立子进程child_process,充分利用多核优势解决这个问题。提高了打包的效率。

const HappyPack = require('happypack')
// 手动创建进程池
const happyThreadPool =  HappyPack.ThreadPool({
   
    size: os.cpus().length })
module.exports = {
   
   
  module: {
   
   
    rules: [
      ...
      {
   
   
        test: /\.js$/,
        // 问号后面的查询参数指定了处理这类文件的HappyPack实例的名字
        loader: 'happypack/loader?id=happyBabel',
        ...
      },
    ],
  },
  plugins: [
    ...
    new HappyPack({
   
   
      // 这个HappyPack的“名字”就叫做happyBabel,和楼上的查询参数遥相呼应
      id: 'happyBabel',
      // 指定进程池
      threadPool: happyThreadPool,
      loaders: ['babel-loader?cacheDirectory']
    })
  ],
}

happypack成功,启动了三个进程编译。加快了loader的加载速度。

5 scope Hoisting

scope Hoisting的作用是分析模块之前的依赖关系 , 把打包之后的公共模块合到同一个函数中去。它会代码体积更小,因为函数申明语句会产生大量代码;代码在运行时因为创建的函数作用域更少了,内存开销也随之变小。

const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');

module.exports = {
   
   
  resolve: {
   
   
    // 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 开启 Scope Hoisting
    new ModuleConcatenationPlugin(),
  ],
};

6 tree Shaking 删除冗余代码

Tree-Shaking可以通过分析出import/exports依赖关系。对于没有使用的代码。可以自动删除。这样就减少了项目的体积。

举个例子🌰:

import { a, b } from './pages'
a()
pages 文件里,我虽然导出了两个页面:
export const a = ()=>{ console.log(666) }
export const b = ()=>{ console.log(666) }
所以打包的结果会保留这部分:
export const a = ()=>{ console.log(666) }
b方法直接删掉,这就是 Tree-Shaking 帮我们做的事情。删掉了没有用到的代码。

7 按需加载

像vue 和 react spa应用,首次加载的过程中,由于初始化要加载很多路由,加载很多组件页面。会导致 首屏时间 非常长。一定程度上会影响到用户体验。所以我们需要换一种按需加载的方式。一次只加载想要看到的内容

require.ensure 形式

const getComponent => (location, cb) {
   
   
  require.ensure([], (require) => {
   
   
    cb(null, require('../pages/AComponent').default)
  }, 'a')
}
<Route path="/a" getComponent={
   
   getComponent}>

import形式

import B from '@/pages/business/b.vue'
//按需加载变成了:
const B = () => import('@/pages/business/b.vue')

8 按需引用

不知道大家有没有体会到,当我们用antd等这种UI组件库的时候。明明只想要用其中的一两个组件,却要把整个组件库连同样式库一起引进来,就会造成打包后的体积突然增加了好几倍。为了解决这个问题,我们可以采取按需引入的方式。

拿antd为例,需要我们在.babelrc文件中这样声明,

{
   
   
"presets": [
   [
    "@babel/preset-env",
    {
   
   
      "targets": {
   
   
          "chrome": "67"
      },
    "useBuiltIns": "usage",
     "corejs": 2
    }
   ],
    "@babel/preset-react"
 ],
  "plugins": [
  [
   "@babel/plugin-transform-runtime",
  ],
  //重点按需引入antd里面的style
  [  "import", {
   
   
   "libraryName": "antd",
   "libraryDirectory": "es",
   "style": true
  }]
 ]
}

经过如上配置之后,我们会发现体积比没有处理的要小很多。

问题二:webpack怎么配置多页面应用?

实际这个问题变相再问webpack,配置多入口和多个html对应

entry 应该这么配 ,entry支持 string array object

{
   
   
  //入口文件配置 string | array | object
  entry: {
   
   
    index: './src/index.js',
    list: './src/list.js',
    detail: './src/detail.js'
  },
}

html输出配置 输出html需要webpack插件 html-webpack-plugin

{
   
   
    plugins: [
    new htmlWebpackPulgin({
   
   
      title: 'hello 我是首页',
      template: './index.html',
      inject: 'head',
      filename: 'index.html',
      chunks: ['index'] //对应 index.js
    }),
    new htmlWebpackPulgin({
   
   
      title: 'hello 我是列表',
      template: './index.html',
      inject: 'body',
      filename: 'list.html',
      chunks: ['list'] // 对应 list.js
    }),
    new htmlWebpackPulgin({
   
   
      title: 'hello 我是详情',
      template: './index.html',
      inject: 'body',
      filename: 'detail.html',
      chunks: ['detail']  //对应detail.js
    })
  ]
}

http篇

问题1:URL请求页面之后浏览器的解析过程

1.用户输入网址,浏览器发起DNS查询请求,域名解析。

2.三次握手

3.建立tcp连接,发送http请求

4.服务器接受到请求,并相应http请求

5.浏览器对返回的html进行解析,在这期间可能继续请求css,js等文件,浏览器渲染、构建网页

6.断开连接,四次挥手

7.浏览器对页面进行渲染呈现给用户

问题2:javascript的同源策略

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

如果两个 URL 的 protocol、port (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。(“元组” 是指一组项目构成的整体,双重/三重/四重/五重/等的通用形式)。

源的继承
在页面中通过 about:blank 或 javascript: URL 执行的脚本会继承打开该 URL 的文档的源,因为这些类型的 URLs 没有包含源服务器的相关信息。

源的修改
满足某些限制条件的情况下,页面是可以修改它的源。脚本可以将 document.domain 的值设置为其当前域或其当前域的父域。如果将其设置为其当前域的父域,则这个较短的父域将用于后续源检查。

问题3:怎么解决ie浏览器对get请求的缓存?

不知道大家有没有过一种情况,在低版本ie浏览器下,在短暂的时间内发出相同的get情况(比如相同时间请求一个数据列表多次),就会发现请求只发送了一次,其他的请求都被浏览器缓存了,对于这种缓存ajax情况,我们可以在url拼上时间戳,这样浏览器就不会认为这是相同的情况,就不会缓存get情况。

以aixos为例,我们可以在每次发起请求的时候对get请求加以拦截

/* 拦截器 */
axios.interceptors.request.use(
    (config) => {
   
   
        const method   = config.method || 'get'
        if (method.toLowerCase() === 'get') {
   
     /* 防止浏览器缓存 */
            const url  = config.url || ''
            const t = new Date().getTime()
            config.url = `${url}${url.indexOf('?') === -1 ? '?' : '&'}t=${
     
     t}`
        }else if(method.toLowerCase() === 'post'){
   
    /* 设置不同请求头 */
            //...
        }
        return config
    },
    (error) => {
   
    
    }
)

如上,就完美解决了ajax被ie浏览器缓存的问题。

总结

感谢您的辛苦阅读,如果有什么需要改进的地方,请在下方留言,陆续还有相关文文章分享连载,喜欢的大家可以关注一下笔者和笔者的公众号:前端Sharing

相关文章
|
存储 C++
【五一创作】C++刷题 【入门4】数组
【五一创作】C++刷题 【入门4】数组
107 0
|
5月前
|
存储 人工智能 算法
程序与技术分享:Acwing算法笔记
程序与技术分享:Acwing算法笔记
炸了!力扣官方首发了这套1568页LeetCode算法刷题笔记(彩页版)
你知道现在LeetCode算法在大厂中的重要性吗? 前几天小编看了一个国内算法大神的短视频,他就在视频中指出了算法对当下无论是生活还是找工作中都是非常重要的! 没错这个人就是江湖人称“左神”的左程云老师 小编也简单看了一下一些比较知名互联网大厂的招聘,像阿里,字节,美团,京东,百度等都在简介明确写上了要求“算法精通”! 那么如何达到“算法精通”今天小编特意给大家分享出一套1568页的LeetCode算法刷题(彩页版)笔记,助力你早日在简历写上“算法精通”
炸了!力扣官方首发了这套1568页LeetCode算法刷题笔记(彩页版)
|
6月前
|
算法 Java 程序员
太全了!字节总监总结240道算法LeetCode刷题笔记
常言道「算法才是编程的灵魂」,不管是Java, python,还是PHP,都跨不过算法这个门槛。
|
算法 搜索推荐
字节跳动大神手写长达1134页的数据结构与算法刷题指南,简直绝了
前言 为什么要学习数据结构与算法呢?归根结底,你学习一个东西是因为你觉得他有收益,那么学习数据结构与算法,收益在哪里呢? 短期收益是应对考试、面试。 长期收益是“用”,来解决实际工程问题。 如果你在一家成熟的公司,或者 BAT 这样的大公司,面对的是千万级甚至亿级的用户,开发的是 TB、PB 级别数据的处理系统。性能几乎是开发过程中时刻都要考虑的问题。一个简单的 ArrayList、Linked List 选择问题,就可能会产生成千上万倍的性能差别。这个时候,数据结构和算法的意义就完全凸显出来了。
76 0
|
缓存 JavaScript 前端开发
头条秋招面试题以及答案
头条秋招面试题以及答案
96 0
|
机器学习/深度学习 C++
ACM刷题之路(十七)二分 2019暑期集训 POJ2785
ACM刷题之路(十七)二分 2019暑期集训 POJ2785
|
机器学习/深度学习 人工智能 JSON
CSDN 算法技能树 蓝桥杯-基础 刷题+思考总结
一根高筋拉面,中间切一刀,可以得到2根面条。 如果先对折1次,中间切一刀,可以得到3根面条。 如果连续对折2次,中间切一刀,可以得到5根面条。 那么,连续对折10次,中间切一刀,会得到多少面条呢?
113 0
|
算法 Java C++
2022 第十四届蓝桥杯模拟赛第一期(题解与标程)(上)
2022 第十四届蓝桥杯模拟赛第一期(题解与标程)
380 0
2022 第十四届蓝桥杯模拟赛第一期(题解与标程)(上)
|
存储 设计模式 缓存
阿里Java后端面经【记录牛客】(下)
阿里Java后端面经【记录牛客】(下)
261 0
阿里Java后端面经【记录牛客】(下)
下一篇
无影云桌面