前端面试题整理(不定时更新)

9,101 阅读50分钟

HTML篇

1.说说你对HTML5语义化的理解

让页面能够结构化的展示,比如段落用p标签,头部用header标签,主要内容用main标签,侧边栏用aside标签等等

比较容易阅读,方便开发人员快速了解页面的结构,更具有可读性,利与开发和维护

2.meta viewport 是做什么用的

<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

控制页面在移动端不要缩小显示

CSS篇

1.说说盒模型

box-sizingcontent-box;  width=内容区宽度
box-sizingborder-box;   width=内容区宽度+padding宽度+border宽度

2.Flex布局常用属性

display: flex  //设置Flex模式
flex-direction: column  //决定元素是横排还是竖着排
flex-wrap: wrap     //决定元素换行格式
flex-shrink:0   //方式缩放后变瘦,1是默认值,缩放后一起变
flex-grow:1     //占1份,一般写在导航栏,左边logo为0,右边头像为0,中间导航为1
justify-content: space-between  //同一排下对齐方式,空格如何隔开各个元素
align-items: center     //同一排下元素如何对齐
align-content: space-between    //多行对齐方式

3.居中方式

1.水平居中:
    内联元素:爸爸身上写text-align:center;
    块级元素:margin-left: auto; margin-right: auto;
    flex布局: display:flex; justify-content:center;
2.垂直居中:
    行高 = 元素高度: line-height: height;
    flex布局:display: flex; align-items: center

4.选择器和选择器权重?为什么用class不用id?

1.选择器越具体,优先级越高,比如: #xxx > .yyy
2.如果是同样的优先级,后面写的会覆盖前面的
3.权重:!important > 行内样式 > id(#xxx) > class(.yyy) > 继承 > 默认

为什么用class不用id1.因为id只能有一个,class可以有多个,会更灵活
2.具有相似样式的元素可以使用相同的class类名定义样式,减少代码量,而id只能用在一个元素上
3.id选择器权重太靠前,再项目中有时候会出现一些样式污染

拓展:为什么js操作用id1.因为js根据id去获取dom结构是最快的

5.rem和em的区别,移动端适配使用rem/em的原理是什么?

rem是根据根的font-size变化,而em是根据父级的font-size变化

rem:相对于根元素html的font-size,假如html为font-size12px,那么,在其当中的div设置为font-size2rem,就是当中的div为24px
em:相对于父元素计算,假如某个p元素为font-size:12px,在它内部有个span标签,设置font-size2em,那么,这时候的span字体大小为:12*2=24px

移动端适配原理:
1.假如用户显示器的分辨率的宽是1440,浏览器默认的font-size16px(一般都是16)那么以16为此页面分辨率下的统一比例来计算
其他组件的实际应该显示的宽度和高度,做个除法,1440 / 16px = 90,那么以此为统一比例的1rem就代表16px,90意思是整个页
面宽度划分为90rem,也就是说假如你要写一个宽度是32px的按钮,你也可以写成2rem2.假如此时用户换了1920的显示器,那么宽度就变成了1920px,此时我们只需按着rem比例反向除法就能算出在1920为宽的分辨率下应
该呈现给用户的实际字体大小为 21.333333px

144016  =  1920 : 21.3333

3.刚刚用户在1440的分辨率下看到的32px的按钮,现在在1920分辨率下就会变为42.6666px大小
4.以此类推,在js里加上实时获取窗口innerWidth再通过计算得出该分辨率下初始字体大小,就可以实现自适应布局,得益于这个数字
是针对屏幕分辨率和字体大小做比的,从而正面解决了布局自适应分辨率的问题

总结:rem实际上是把屏幕分成了 分辨率宽度/根字体大小 个栅格,这是个十分巧妙的办法,尤其适应类似文章页面文字较多的情况,
而em其实是依据父元素的font-size,原理和rem是完全一样的,比较适合相对较局部的自适应问题

6.CSS3新特性

transition:过渡
transform:旋转、缩放、移动或者倾斜
animation:动画
gradient:渐变
shadow:阴影
border-radius:圆角

7.绝对定位和相对定位

position: relative
相对定位:相对定位是相对于元素在文档中的初始位置

position: absolute
绝对定位:是相对上一个relative进行定位

8.什么是BFC?如何产生一个BFC?

BFC就是格式化上下文,产生一个独立隔离容器,容器内的元素不会被外面的元素影响

如何产生:
display: inline-block

position: absolute/fixed

9.sass的mixin

mixin类似于对象,对象里面写好CSS属性,哪里用到了就在哪里引用这个对象,可以避免写重复的CSS

10.清除浮动代码说一下

第一种:clearfix
 .clearfix:after{
     content: '';
     display: block; /*或者 table*/
     clear: both;
 }
 .clearfix{
     zoom: 1; /* IE 兼容*/
 }
 
第二种:父级div定义 overflow:hidden

第三种:给父级元素单独定义高度(height)
如果父级元素没有定义高度,父元素的高度完全由子元素撑开时,父级div手动定义height,就解决了父级div无法自动获取到高度的问题。

11.css 隐藏元素的方式

1.transform:scale(0,0)  //占空间
2.visibility:hidden  //占空间,无法点击
3.position绝对定位,left:-999999px; right:-99999px; //不占空间,无法点击
4.position绝对定位,z-index:-1000;  //不占空间无法点击
5.display:none  //不占空间,无法点击

JS篇

1.JS的基本数据类型

四基 / 两空 / 一对象
string,number,boolean,symbol / null,undefined / object
其中object包括了数组,函数,日期等对象

基础类型(NumberStringBooleanNullUndefined)和引用类型(Object)的区别?
1.基础类型的值是不可变得,引用类型的值是可变的
2.基础类型的比较是值的比较,引用类型的比较是引用的比较
3.基础类型的变量是存放在栈区,引用类型的值是同时保存在栈内存和堆内存中的对象

nullundefined的区别?
1.Number转换的值不同,Number(null)输出为0, Number(undefined)输出为NaN
2.null表示一个值被定义了,但是这个值是空值;undefined表示缺少值,即此处应该有值,但是还没有定义

2.数组常用操作:

map: 不会改变原数组,遍历数组,返回回调返回值组成的新数组
forEach: 不会改变原数组,无法break,可以用try/catchthrow new Error来停止
filter: 过滤
some: 有一项返回true,则整体为true
every: 有一项返回false,则整体为false
join: 通过指定连接符生成字符串
push / pop: 末尾推入和弹出,改变原数组, 返回推入/弹出项【有误】
unshift / shift: 头部推入和弹出,改变原数组,返回操作项【有误】
sort(fn) / reverse: 排序与反转,改变原数组
concat: 连接数组,不影响原数组, 浅拷贝
slice(start, end): 返回截断后的新数组,不改变原数组
splice(start, number, value...): 返回删除元素组成的数组,value 为插入项,改变原数组
indexOf / lastIndexOf(value, fromIndex): 查找数组项,返回对应的下标
reduce / reduceRight(fn(prev, cur), defaultPrev): 两两执行,prev 为上次化简函数的return值,cur 为当前值(从第二项开始)

reduce拓展:
1.当reduce()方法的第二个值为空时,第一次循环方法中的第一个参数(prev)为数组的第一项值,第二个参数(cur)为数组的第
二项值,反之,第一次循环方法中的第一个参数(prev)为reduce的第二个参数值,第二个参数(cur)为数值的第一项值。

2.reduce()方法的第一个参数,就是每次遍历都会执行的匿名函数,当前函数的返回值就会传给下一次执行函数的第一个值。也就是prev

3.Promise/Promise.all/Promise.race怎么使用

1.背代码 Promise 用法
 function fn(){
     return new Promise((resolve, reject)=>{
         成功时调用 resolve(数据)
         失败时调用 reject(错误)
     })
 }
 fn().then(success, fail).then(success2, fail2)

2.背代码 Promise.all 用法
 Promise.all([promise1, promise2]).then(success1, fail1)
 promise1和promise2都成功才会调用success1

3.背代码 Promise.race 用法
 Promise.race([promise1, promise2]).then(success1, fail1)
promise1和promise2只要有一个成功就会调用success1

补充: catch则是用来指定发生错误时的回调函数

4.AJAX手写一下

1.完整版
 var request = new XMLHttpRequest()
 request.open('GET', '/a/b/c?name=ff', true);
 request.onreadystatechange = function () {
   if(request.readyState === 4 && request.status === 200) {
     console.log(request.responseText);
   }};
 request.send();
 
2.简化版
 var request = new XMLHttpRequest()
 request.open('GET', '/a/b/c?name=ff', true)
 request.onload = ()=> console.log(request.responseText)
 request.send()

5.说一下闭包

函数A 里面包含了 函数B,而 函数B 里面使用了 函数A 的变量,那么 函数B 被称为闭包。
又或者:闭包就是能够读取其他函数内部变量的函数,例子:
function A() {
  var a = 1;
  function B() {
    console.log(a);
  }
  return B();
}

用途:使用闭包主要是为了设计私有的方法和变量
优点:可以避免变量被全局变量污染
缺点:函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
解决方法:在退出函数之前,将不使用的局部变量全部删除

闭包的经典题,输出什么?
for(var i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i);
  }, 1000);
}
//3个3,首先,for 循环是同步代码,先执行三遍 for,i 变成了 3;然后,再执行异步代码 setTimeout,这时候输出的 i,只能是 3 个 3 了

有什么办法输出 0 1 2:
第一种:
把var改成let,每个 let 和代码块结合起来形成块级作用域,当 setTimeout() 打印时,会寻找最近的块级作用域中的 i,所以依次打印出 0 1 2
第二种:使用立即执行函数,创建一个独立的作用域
for(let i = 0; i < 3; i++) {
  (function(i){
    setTimeout(function() {
      console.log(i);
    }, 1000);
  })(i)
}

闭包的具体使用场景:
1.setTimeout 
原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。
function f1(a) {
    function f2() {
        console.log(a);
    }
    return f2;
}
var fun = f1(1);
setTimeout(fun,1000);//一秒之后打印出1

2.函数防抖,见下面第173.回调,见下面的例子
    <a href="#" id="size-12">12</a>
    <a href="#" id="size-20">20</a>
    <a href="#" id="size-30">30</a>

    <script type="text/javascript">
        function changeSize(size){
            return function(){
                document.body.style.fontSize = size + 'px';
            };
        }

        var size12 = changeSize(12);
        var size14 = changeSize(20);
        var size16 = changeSize(30);

        document.getElementById('size-12').onclick = size12;
        document.getElementById('size-20').onclick = size14;
        document.getElementById('size-30').onclick = size16;

6.说说this?箭头函数和普通函数的区别?为什么箭头函数不能new?

fn()
    this => window/global
obj.fn()
    this => obj
fn.call(xx)
    this => xx
fn.apply(xx)
    this => xx
fn.bind(xx)
    this => xx
new Fn()
    this => 新的对象
fn = ()=> {}
    this => 外面的 this
    
普通函数和箭头函数的区别?为什么箭头函数不能new?
1.箭头函数没有this
2.箭头函数实质是一个匿名内部类,不含有 prototype、没有自己的 this 指向、不可以使用 arguments
对于 new 操作实质上是定义一个具有构造函数内置对象的实例,所以不能使用 new

7.什么是立即执行函数?使用立即执行函数的目的?

 ;(function (){
    var name
 }())
 ;(function (){
    var name
 })()
 !!!!!!!function (){
    var name
 }()
 ~function (){
    var name
 }()
 
目的是造出一个函数作用域,防止污染全局变量

ES6 新语法
 {
    let  name
 }

8.async/await 语法了解吗?目的是什么?

 function returnPromise(){
    return new Promise( function(resolve, reject){
        setTimeout(()=>{
            resolve('fuck')
        },3000)
    })
 }

 returnPromise().then((result)=>{
    result === 'fuck'
 })

 var result = await returnPromise()
 result === 'fuck'

目的是把异步代码写成同步代码

9.如何实现浅拷贝和深拷贝?深拷贝和浅拷贝的区别?

浅拷贝:
第一种:Object.assign
ES6中拷贝对象的方法,接受的第一个参数是拷贝的目标target,剩下的参数是拷贝的源对象sources(可以是多个)
let target = {};
let source = {
	a: '123',
	b: {
		name: 'javascript'
	}
}
Object.assign(target,source);
console.log(target); // {a:'123',b:{name:'javascript'}}

第二种:Array.prototype.slice
let array = [{a:1},{b:2}]
let array1 = array.slice(0);
console.log(array1)

第三种:Array.prototype.concat
let array = [{a:1},{b:2}]
let array1 = [].concat(array)
console.log(array1)

第四种:扩展运算符
let obj = {a:1,b:{c:1}}
let obj2 = {...obj}
console.log(obj2)


深拷贝:
第一种:JSON 来深拷贝
 var a = {...}
 var b = JSON.parse( JSON.stringify(a) )
缺点:JSON 不支持函数、引用、undefinedRegExpDate……

第二种:递归拷贝
 function clone(object){
     var object2
     if(! (object instanceof Object) ){
         return object
     }else if(object instanceof Array){
         object2 = []
     }else if(object instanceof Function){
         object2 = eval(object.toString())
     }else if(object instanceof Object){
         object2 = {}
     }
     你也可以把 Array Function Object 都当做 Object 来看待,参考 https://juejin.im/post/6844903461000069128
     for(let key in object){
         object2[key] = clone(object[key])
     }
     return object2
 }
 思路:先判断是否是对象,如果不是的话判断是否为Array,是否是Function,是否是Object,最后遍历进行复制
 
 区别:
 1.浅拷贝并不复制对象本身而是复制一个指针,新对象和旧对象都是在同一块内存地址
 2.深拷贝则会复制整个对象,新对象与旧对象是两块内存地址

10.数组去重

1.计数排序的逻辑(只能正整数)
 var a = [4,2,5,6,3,4,5]
 var hashTab = {}
 for(let i=0; i<a.length;i++){
     if(a[i] in hashTab){
         // 什么也不做
     }else{
         hashTab[ a[i] ] = true
     }
 }
 //hashTab: {4: true, 2: true, 5: true, 6:true, 3: true}
 console.log(Object.keys(hashTab)) // ['4','2','5','6','3']
 
2.Set 去重
 Array.from(new Set(a))
 
3.WeakMap 任意类型去重

11.如何用正则实现 string.trim() ?

function trim(string){
    return string.replace(/^\s+|\s+$/g, '')
}

12.说说原型和原型链

1.var a = [1,2,3]
2.只有012、length 4 个key
3.为什么可以 a.push(4) ,push 是哪来的?
4.a.proto === Array.prototype
5.push 就是沿着 a.proto 找到的,也就是 Array.prototype.push,这个寻找的过程可以看做原型链
6.Array.prototype 还有很多方法,如 join、pop、slice、splice
7.Array.prototype 就是 a 的原型(proto)

13.说说ES6中的class

把 MDN class 章节看完
记住一个例子,比如
class Rectangle {
    // constructor
    constructor(height, width) {
        this.height = height;
        this.width = width;
    }
    // Getter
    get area() {
        return this.calcArea()
    }
    // Method
    calcArea() {
        return this.height * this.width;
    }
}
const square = new Rectangle(10, 10);
console.log(square.area); // 100

14.不用 class 如何实现继承?用 class 又如何实现?

1.不用 class 这样实现
 function Animal(color){
     this.color = color
 }
 Animal.prototype.move = function(){} // 动物可以动
 function Dog(color, name){
     Animal.call(this, color) // 或者 Animal.apply(this, arguments)
     this.name = name
 }
 // 下面三行实现 Dog.prototype.__proto__ = Animal.prototype
 function temp(){}
 temp.prototye = Animal.prototype
 Dog.prototype = new temp()

 Dog.prototype.constuctor = Dog // 这行看不懂就算了,面试官也不问
 Dog.prototype.say = function(){ console.log('汪')}

 var dog = new Dog('黄色','阿黄')

2.class 就简单了
 class Animal{
     constructor(color){
         this.color = color
     }
     move(){}
 }
 class Dog extends Animal{
     constructor(color, name){
         super(color)
         this.name = name
     }
     say(){}
 }

15.说说call,apply,bind的区别

1.call()和apply()的第一个参数相同,就是指定的对象。这个对象就是该函数的执行上下文。
2.call()和apply()的区别就在于,两者之间的参数。
3.call()在第一个参数之后的 后续所有参数就是传入该函数的值。
4.apply() 只有两个参数,第一个是对象,第二个是数组,这个数组就是该函数的参数。
5.bind() 方法和前两者不同在于:bind()方法会返回执行上下文被改变的函数而不会立即执行,而前两者是直接执行该函数。他的参数和call()相同。

总结:call和apply方法都是在调用之后立即执行的。而bind调用之后是返回原函数,需要再调用一次才行。

16.图片的预加载和懒加载

预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染
懒加载:用户滑到某个位置的时候才开始加载图片,主要目的是作为服务器前端的优化,减少请求数或延迟请求数

本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。
预加载则会增加服务器前端压力,懒加载对服务器有一定的缓解压力作用。

17.函数的节流和防抖

 // 1.节流(一段时间执行一次之后,就不执行第二次)
 // 函数节流就是fps游戏的射速,就算一直按着鼠标射击,也只会在规定射速内射出子弹。
 function throttle(fn, delay){
     let canUse = true
     return function(){
         if(canUse){
             fn.apply(this, arguments)
             canUse = false
             setTimeout(()=>canUse = true, delay)
         }
     }
 }

 const throttled = throttle(()=>console.log('hi'))
 throttled()
 throttled()

注意,有些地方认为节流函数不是立刻执行的,而是在冷却时间末尾执行的(相当于施法有吟唱时间),那样说也是对的。

 // 2.防抖(一段时间会等,然后带着一起做了)
 // 函数防抖就是法师发技能的时候要读条,技能读条没完再按技能就会重新读条。
 function debounce(fn, delay){
     let timerId = null
     return function(){
         const context = this
         if(timerId){window.clearTimeout(timerId)}
         timerId = setTimeout(()=>{
             fn.apply(context, arguments)
             timerId = null
         },delay)
     }
 }
 const debounced = debounce(()=>console.log('hi'))
 debounced()
 debounced()

18.事件循环机制eventloop 和 JS垃圾回收机制

事件循环机制:
1.在代码的执行过程中会先执行同步代码,然后宏任务(script,setTimeout...)进入宏任务队列,微任务(Promise.then(),Promise)进入微任务队列
2.当宏任务执行完之后出队,检查微任务列表,继续执行微任务直到执行完毕
3.执行浏览器的UI渲染过程
4.检查是否有Web Worker任务,有则执行
5.继续下一轮的宏任务和微任务

垃圾回收机制:
方法一:标记清除(比较常见)
1.当变量进入执行环境时,就标记这个变量为“进入环境”。
//从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。
2.当变量离开环境时,则将其标记为“离开环境”。
3.垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记,它会去掉环境中的变量以及被环境中的变量引用的标记。
4.在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。
5.最后垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。

方法二:引用计数(不常见)
1.跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是12.相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减13.当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。
4.这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。

19.JS怎么取消事件?

1.如果是用onclick方式绑定的事件可以用如这种方法取消:btn.onclick=null;//删除事件处理程序

2.如果使用addEventListener()方法添加事件,可以通过removeEventListener()移出事件,需要注意两点:
    2.1 removeEventListener()的第三个参数必须和addEventListener()方法的第三个参数一致。
    2.2 通过addEventListener()方法添加的匿名函数将无法移除。

btn.aaddEventListener('click',function(){alert(1);},false);
btn.removeEventListener('click',function(){alert(1);},false);//没有用!

aaddEventListener和removeEventListener看似传入了相同的参数,
但实际上removeEventListener的第二个参数与aaddEventListener的第二个参数是完全不同的函数,想要移出必须这样:

var fn=function(){
	alert(1);
};
btn.aaddEventListener('click',fn,false);
btn.removeEventListener('click',fn,false);//有效

20.JS获取DOM节点转字符串

1.用 jquery 比较容易实现:$(".xxx").html()
2.思路
    2.1 我们可以创建一个 div 元素
    2.2 然后将获取的 dom 节点放到 div 里面
    2.3 利用innerHTML就可以获取到dom 节点的字符串

function domToString (node) {  
     let tmpNode = document.createElement('div')
     tmpNode.appendChild(node) 
     let str = tmpNode.innerHTML
     tmpNode = node = null; // 解除引用,以便于垃圾回收  
     return str;  
}  

21.Script标签为什么要放在body标签的底部?

浏览器在解析到<body>标签之前,不会渲染页面的任何部分。
把脚本放到页面顶部会导致明显的延迟,通常表现为显示空白页面,用户无法浏览内容,也无法和页面进行交互。

22.如何理解script标签是个宏任务?

可以这样理解,排前面的 script 先执行,执行其内部的【同】,再执行其【微】,接着就轮到下一个大的宏,也就是执行下一个 script,【同】、【微】。。。
顺序执行完后,再从头开始,看第一个 script 是否有需要执行的【宏】,再去下一个 script 中找 【宏】,等大家宏结束后,进入下一轮循环。

23.讲一讲拖拽事件

一般会用到三个api 
div拖拽api: drop dragover dragenter

drop 目标元素 被拖放的元素在目标元素上同时鼠标放开触发的事件
dragover 目标元素 拖放元素在目标元素上时
dragenter 目标元素 拖放元素进入目标元素时

拖放API基础

drag:被拖放元素 在拖拽的过程中触发
dragend 被拖放元素 拖拽完成时
dragenter 目标元素 拖放元素进入目标元素时
dragover 目标元素 拖放元素在目标元素上时
dragleave 目标元素 拖放元素在目标元素上离开
drop 目标元素 被拖放的元素在目标元素上同时鼠标放开触发的事件
注:需要阻止dragover的默认行为才会触发drop事件

被拖拽的元素上需要设置属性:draggable="true",才能被拖拽
被拖拽的元素有三个事件,分别是:dragstart开始拖拽、drag正在拖拽、dragend拖拽结束(鼠标抬起)

ES6篇

1.var、let、const之间的区别

1.var声明变量可以重复声明,而let不可以重复声明
2.var是不受限于块级的,而let是受限于块级
3.var会与window相映射(会挂一个属性),而let不与window相映射
4.var可以在声明的上面访问变量,而let有暂存死区,在声明的上面访问变量会报错
5.const声明之后必须赋值,否则会报错
6.const定义不可变的量,改变了就会报错
7.constlet一样不会与window相映射、支持块级作用域、在声明的上面访问变量会报错

2.解析赋值

数组解析:
let [a, b, c] = [1, 2, 3]   //a=1, b=2, c=3
let [d, [e], f] = [1, [2], 3]    //嵌套数组解构 d=1, e=2, f=3
let [g, ...h] = [1, 2, 3]   //数组拆分 g=1, h=[2, 3]
let [i,,j] = [1, 2, 3]   //不连续解构 i=1, j=3
let [k,l] = [1, 2, 3]   //不完全解构 k=1, l=2

对象解析:
let {a, b} = {a: 'aaaa', b: 'bbbb'}      //a='aaaa' b='bbbb'
let obj = {d: 'aaaa', e: {f: 'bbbb'}}
let {d, e:{f}} = obj    //嵌套解构 d='aaaa' f='bbbb'
let g;
(g = {g: 'aaaa'})   //以声明变量解构 g='aaaa'
let [h, i, j, k] = 'nice'    //字符串解构 h='n' i='i' j='c' k='e'

3.for in和for each

1.forEach更多的用来遍历数
2.for in 一般常用来遍历对象或json
3.for in循环出的是key

for 和 foreach 的区别
1.在固定长度或者长度不需要计算的时候for循环效率高于foreach,在不确定长度或者计算长度有损性能的时候用foreach比较方便
2.foreach适用于只是进行集合或数组遍历,for则在较复杂的循环中效率更高。
3.如果对集合中的值进行修改,就要用for循环了。其实foreach的内部原理其实也是Iterator,但它不能像Iterator一样可以人为的
控制,而且也不能调用iterator.remove();更不能使用下标来访问每个元素,所以不能用于增加,删除等复杂的操作。
4.forEach相比普通的for循环的优势在于对稀疏数组的处理,会跳过数组中的空位。

拓展:双重for循环如何内部循环打断外部循环

使用label 给for循环制定一个标签名jump,内部循环break的时候用标签名去打断

var array = [1,2,3,4,5]; 
jump: 
for (let i = 0; i < array.length; i++) { 
    for (let j = 0; j < array.length; j++) { 
        if ( i == 2 && j == 2 ) { 
            break jump; 
        } 
        console.log(i , j); 
    } 
}

DOM篇

1.DOM事件模型

1.冒泡  
补充:阻止事件冒泡
    1.1 event.stopPropagation(); // 阻止了事件冒泡,但不会阻止默认行为
    1.2 return false; //阻止全部事件
    1.3 event.preventDefault(); //阻止默认事件
2.捕获
3.如果这个元素是被点击的元素,那么捕获不一定在冒泡之前,顺序是由监听顺序决定的。

2.移动端的触摸事件了解吗?

1.touchstart touchmove touchend touchcancel
2.模拟 swipe 事件:记录两次 touchmove 的位置差,如果后一次在前一次的右边,说明向右滑了。

3.事件委托是什么?有什么好处?

假设父元素有4个儿子,我不监听4个儿子,而是监听父元素,看触发事件的元素是哪个儿子,这就是事件委托。
好处:可以监听还没有出生的儿子(动态生成的元素),省监听器。

初级版:
ul.addEventListener('click', function(e){
     if(e.target.tagName.toLowerCase() === 'li'){
         fn() // 执行某个函数
     }
 })
 
bug:如果用户点击的是 li 里面的 span,就没法触发 fn,这显然不对。
 
高级版:
function delegate(element, eventType, selector, fn) {
     element.addEventListener(eventType, e => {
       let el = e.target
       while (!el.matches(selector)) {
         if (element === el) {
           el = null
           break
         }
         el = el.parentNode
       }
       el && fn.call(el, e, el)
     })
     return element
   }

HTTP篇

1.HTTP的特性和状态码说一下,HTTP和HTTPS的区别?

特性:
HTTP 是无连接无状态的
HTTP 一般构建于 TCP/IP 协议之上,默认端口号是 80
HTTP 可以分为两个部分,即请求和响应。

区分状态码
1××开头  - 信息提示
2××开头  - 请求成功
3××开头  - 请求被重定向
4××开头  - 请求错误
5××开头  - 服务器错误

常见状态码
200 OK 客户端请求成功。
301 Moved Permanently 请求永久重定向。
302 Moved Temporarily 请求临时重定向。
304 Not Modified 文件未修改,可以直接使用缓存的文件。
400 Bad Request 由于客户端请求有语法错误,不能被服务器所理解。
401 Unauthorized 请求未经授权,无法访问。
403 Forbidden 服务器收到请求,但是拒绝提供服务。服务器通常会在响应正文中给出不提供服务的原因。
404 Not Found 请求的资源不存在,比如输入了错误的URL。
500 Internal Server Error 服务器发生不可预期的错误,导致无法完成客户端的请求。
503 Service Unavailable 服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常。

补充:301302 的区别是什么?
301 永久重定向,浏览器会记住
302 临时重定向

HTTP和HTTPS的区别

微信截图_20240507152945.png

2.Cookie 是什么?Session 是什么?

1.Cookie:
    HTTP响应通过 Set-Cookie 设置 Cookie
    浏览器访问指定域名是必须带上 Cookie 作为 Request Header
    Cookie 一般用来记录用户信息

2.Session:
    Session 是服务器端的内存(数据)
    Session 一般通过在 Cookie 里记录 SessionID 实现
    SessionID 一般是随机数

3.LocalStorage 和 Cookie 的区别是什么?

Cookie 会随请求被发到服务器上,而 LocalStorage 不会
Cookie 大小一般4k以下,LocalStorage 一般5Mb 左右

4.HTTP 缓存怎么做?讲讲强缓存和协商缓存?

Cache-Control: max-age=300
http://cdn.com/1.js?v=1 避开缓存

缓存中header的参数:

强缓存:服务器通过设置http中hdader的Expires和cache-control字段告诉浏览器缓存的有效期。

Expires:response header里的过期时间,浏览器再次加载资源时,如果在这个过期时间内,则命中强缓存。
Cache-Control: 当值设为max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中
强缓存。
cache-control除了该字段外,还有下面几个比较常用的设置值:
-no-cache: 不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。
-no-store: 直接禁止浏览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。
-public: 可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。
-private: 只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存

协商缓存:就是通过服务器来判断缓存是否可用

Last-Modify/If-Modify-Since:
1.浏览器第一次请求一个资源时,服务器返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间;
2.当浏览器再次请求该资源时,request的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。
3.服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存

Etag: web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规则由服务器决定)。

If-None-Match1.当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上If-None-Match (Etag的值)。
2.web服务器收到请求后发现有头If-None-Match 则与被请求资源的相应校验串进行比对,决定是否命中协商缓存;

5.Cache-Control 和 Etag 的区别是什么?

Cache-Control表示浏览器使用缓存,不向服务器发请求
ETag是会发请求的,只不过服务器根据请求的东西的内容有无变化来判断是否返回请求的资源

6.怎么跨域?JSONP 是什么?CORS 是什么?postMessage 是什么?

因为浏览器出于安全考虑,有同源策略。也就是说,如果协议、域名或者端口有一个不同就是跨域,Ajax 请求会失败。
为来防止CSRF攻击
1.JSONP
    JSONP 的原理很简单,就是利用 <script> 标签没有跨域限制的漏洞。
    通过 <script> 标签指向一个需要访问的地址并提供一个回调函数来接收数据当需要通讯时。
    <script src="http://domain/api?param1=a&param2=b&callback=jsonp"></script>
    <script>
        function jsonp(data) {
        	console.log(data)
    	}
    </script>
    JSONP 使用简单且兼容性不错,但是只限于 get 请求。
2.CORS
    CORS 需要浏览器和后端同时支持,需要设置 Access-Control-Allow-Origin:http://foo.example
    IE 89 需要通过 XDomainRequest 来实现。
3.postMessage
    这种方式通常用于获取嵌入页面中的第三方页面数据。一个页面发送消息,另一个页面判断来源并接收消息
    
补充:
JSONP和AJAX相比的优缺点?
1.JSONP可以跨域 
2.因为JSONP是通过script标签发送的GET请求,所以读不到AJAX那么精确的状态码

7.ajax的四个步骤

1.创建ajax实例
2.执行open 确定要访问的链接 以及同步异步
3.监听请求状态
4.发送请求

 var request = new XMLHttpRequest()
 request.open('GET', '/a/b/c?name=ff', true)
 request.onload = ()=> console.log(request.responseText)
 request.send()

8.说说get和post的区别

数据传输方式不同:GET通过URL传输数据,而POST的数据通过请求消息体(body)传输
特性不同:GET是安全且幂等的,POST是非安全非幂等的(幂等的意思就是不管发多少次请求,结果都一样)
包:GET请求只发送一个包,POST请求需要发送两个以上的包(因为POST有请求消息体)
缓存:
GET可以类似查找的过程,用户获取数据,不需要每次都与数据库连接,所以可以使用缓存
POST一般是操作过程,比如修改、删除等,所以必须和数据库交互,不能使用缓存
用法:GET用来读取数据,POST用来写数据

补充:
GET传参长度的误区:我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的,实际上是因为:
1.GET的最大长度显示是因为 浏览器和 web服务器限制了 URI的长度
2.不同的浏览器和WEB服务器,限制的最大长度不一样

9.从输入URL到页面展示中间发生了什么

1.读取缓存: 
        搜索自身的 DNS 缓存。(如果 DNS 缓存中找到IP 地址就跳过了接下来查找 IP 地址步骤,直接访问该 IP 地址。)
2.DNS 解析:将域名解析成 IP 地址
3.TCP 连接:TCP 三次握手,简易描述三次握手
           客户端:服务端你在么? 
           服务端:客户端我在,你要连接我么? 
           客户端:是的服务端,我要链接。 
           连接打通,可以开始请求来
4.发送 HTTP 请求
5.服务器处理请求并返回 HTTP 报文
6.浏览器解析渲染页面
7.断开连接:TCP 四次挥手

关于第六步浏览器解析渲染页面又可以聊聊如果返回的是html页面
根据 HTML 解析出 DOM 树
根据 CSS 解析生成 CSS 规则树
结合 DOM 树和 CSS 规则树,生成渲染树
根据渲染树计算每一个节点的信息
根据计算好的信息绘制页面

10.前端对后端返回的数据如何处理?

前端通过javascript对后端返回的json或者xml进行格式化处理
JSON:
var jsonObj = JSON.parse(后端返回的json字符串);
var result = JSON.stringify(jsonObj, null, 2);//格式化

XML:
简单一点就先使用 xml2json转化为JSON格式,然后再JSON.parseJSON.stringfy进行格式化

详细一点可以看这篇博客: blog.csdn.net/txp1993/art…

11.项目中的token如何处理?

用到了一个jwt_decode去解析token,可以获取userID等需要的数据

12.JSON和XML的区别

XMLJSON的区别?
1. 数据体积方面
JSON相对于XML来讲,数据的体积小,传递的速度更快些。
2. 数据交互方面
JSONJavaScript的交互更加方便,更容易解析处理,更好的数据交互。
3. 数据描述方面;
JSON对数据的描述性比XML较差。
4. 传输速度方面:
JSON的速度要远远快于XML。

补充:
1.JSONXML:用json2xml
var xml_content = $.json2xml(json);
console.log(xml_content);
2.XMLJSON:用xml2json
var json_obj = $.xml2json(xmlstr);
console.log(json_obj);

13.讲讲CSRF XSS攻击?如何防范?二者的区别是什么?

XSS攻击:(Cross Site Scripting)跨域脚本攻击。
原理:
不需要你做任何的登录认证,它会通过合法的操作(比如在 url 中输入、在评论框中输入),
向你的页面注入脚本(可能是 js、hmtl 代码块等)。
防范:
1.  编码;对于用户输入进行编码
2.  过滤;移除用户输入和事件相关的属性。(过滤 script、style、iframe等节点)
3.  校正;使用 DOM Parse 转换,校正不配对的 DOM 标签


CSRF攻击:SRF(Cross-site request forgery)跨站请求伪造。
原理:
1.  登录受信任网站 A,并在本地生成 Cookie
(如果用户没有登录网站 A,那么网站 B 在诱导的时候,请求网站 A 的 api 接口时,会提示你登录)
2.  在不登出 A 的情况下,访问危险网站 B(其实是利用了网站 A 的漏洞)
防范:
1.  token 验证;
2.  隐藏令牌;把 token 隐藏在 http 请求的 head 中。
3.  referer 验证;验证页面来源。

二者的区别:
1.  CSRF:需要用户先登录网站 A,获取 cookie。
    XSS:不需要登录。
2.  CSRF:是利用网站 A 本身的漏洞,去请求网站 A 的 api。
    XSS:是向网站 A 注入 JS 代码,然后执行 JS 里的代码,篡改网站 A 的内容。

VUE篇

1.什么是生命周期,生命周期的作用是什么?

生命周期:Vue 实例从创建到销毁的过程,就是生命周期。
也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期
作用:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑

2.生命周期有几个阶段?生命周期执行顺序问题(拓展)。

它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后

拓展1:A页面跳转到B页面的生命周期

    a-beforeCreate -> a-Created -> a-beforeMounte -> a-Mounted -> b-beforeCreate -> b-Created ->
    b-beforeMounte -> a-beforeDestory -> a-destoryed -> b-Mounted
    
    精简:a挂载后 -> b挂载前 -> a销毁 -> b挂载

拓展2:父子组件的生命周期

加载渲染过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->
            子mounted->父mounted

销毁过程     父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

3.第一次页面加载会触发哪几个钩子?

第一次页面加载时会触发创建和载入的钩子,也就是 beforeCreate, created, beforeMount, mounted 

4.DOM渲染在哪个周期中就已经完成?

DOM 渲染在 mounted 中就已经完成了

5.每个生命周期适合哪些场景?

beforecreate : 可以在这加个loading事件,在加载实例时触发
created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用
mounted : 挂载元素,获取到DOM节点,一般在这个钩子请求数据
updated : 如果对数据统一处理,在这里写上相应函数
beforeDestroy : 可以做一个确认停止事件的确认框
nextTick : 更新数据后立即操作dom

6.v-show与v-if区别

v-show是css切换,将元素样式隐藏起来;v-if是完整的销毁和重新创建

使用 频繁切换时用v-show,运行时较少改变时用v-if

7.Vue 如何实现组件通信?

1.父子组件:使用 v-on 通过事件通信
2.爷孙组件:使用两次 v-on 通过爷爷爸爸通信,爸爸儿子通信实现爷孙通信
3.任意组件:使用 eventBus = new Vue() 来通信,eventBus.$on 和 eventBus.$emit 是主要API
4.任意组件:使用 Vuex 通信

8.子组件向父组件通信

父组件向子组件传递事件方法,子组件通过$emit触发事件,回调给父组件

补充:prop的作用域
组件实例的作用域是孤立的。这意味着不能(也不应该)在子组件的模板内直接引用父组件的数据。要让子组件使用父组件的数据,我们需要通过子组件的props选项。

prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态。

9.说说VueRouter

1.Vue Router 是 Vue.js 官方的路由管理器,说出核心概念的名字和作用:Hash 模式/History 模式/路由守卫/路由懒加载
    Hash模式:由于 hash 发生变化的url都会被浏览器记录下来,所以浏览器的前进后退可以使用,
             尽管浏览器没有请求服务器,但是页面状态和 url 关联起来。后来人们称其为前端路由,成为单页应用标配。
    History模式:页面刷新的时候不重新加载数据

2.常用 API:router-link/router-view/this.$router.push/this.$router.replace/this.$route.params

10.知道路由守卫吗

常用的两个路由守卫:router.beforeEach 和 router.afterEach

每个守卫方法接收三个参数:

to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。

在项目中,一般在beforeEach这个钩子函数中进行路由跳转的一些信息判断。
判断是否登录,是否拿到对应的路由权限等等。

11.MVVM 与 MVC

M - Model,Model 代表数据模型,也可以在 Model 中定义数据修改和操作的业务逻辑

V - ViewView 代表 UI 组件,它负责将数据模型转化为 UI 展现出来

C - Control 控制器,对数据进行一些操作

VM - ViewModel,ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步 View 和 Model 的对象,连接 Model 和 View

区别
MVC:m是保存数据的地方,v是视图,c是对数据进行一些操作的过程,基本上是c对m数据操作,然后更新v视图。
MVVM:m是保存数据的地方,v是视图,vm相当于把v和m结合起来取代了c。可以进行数据响应式,v视图变化会影响m数据,m数据变化会影响v视图,这中间就是通过vm来实现的。

12.watch 和 computed 和 methods 区别是什么?

1.computed 和 methods 相比,最大区别是 computed 有缓存:如果 computed 属性依赖的属性没有变化,那么 computed属性就不会重新计算。methods 则是看到一次计算一次。
2.watch 和 computed 相比,computed 是计算出一个属性(废话),而 watch 则可能是做别的事情(如上报数据)

总结:
1. 当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed
2. 如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化

13.为什么需要用到key?加上key之后diff操作会变快么?

key是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确

比如渲染一堆input框,中间删除一个就会产生问题,下面的input会被顶上来,vue会选择复用节点(Vue的就地更新策略),导致之前
节点的状态被保留,从而产生一系列的bug

“如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。”--官方文档

根据官方文档描述,加上Key之后实际上是会让diff操作变慢的,但不加key的话会容易造成渲染错误,所以还是加上Key更为恰当。

14.知道keep-alive吗?

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,避免重新渲染 ,其有以下特性:

1.一般结合路由和动态组件一起使用,用于缓存组件;
2.提供 include 和 exclude 属性,两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 3.表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;
4.对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。

15.说说nextTick()

原理:nextTick实际上是设置一个回调,用于异步执行。
就是把你设置的回调放在 setTimeout 中执行,这样就算异步了,等待当时同步代码执行完毕再执行。

执行机制:
在vue中更新dom是异步的,当同步任务完成后才会去更新dom,nextTick会把此时的回调添加到异步任务队列中,
当dom更新的任务完成后才会去执行nextTick函数

在下次DOM更新循环结束之后执行延迟回调。在修改数据之后,立即使用的这个回调函数,获取更新后的DOM

// 修改数据
vm.msg = 'Hello'
// DOM 还未更新
Vue.nextTick(function () {
  // DOM 更新
})

Vue.nextTick()在ajax请求数据后操作dom,获取高度为0的问题

遇到的问题:通过接口请求出来的数据,渲染到页面上,再获取元素内容高度的时候,高度为0

为什么高度会是01.this.$nextTick是有dom渲染完成触发的,虽然数据获取到了,但是页面还没有及时的渲染出来,所以获取的内容高度就为02.如果延迟2秒获取内容高度,会发现内容高度是渲染完成后的正常高度,但是这样肯定是不行的
3.要在this.$nextTick(callback)这个回调函数里面获取ul的高度
4.这样回调函数将在 DOM 更新完成后被调用,就可以获取正常高度了

如果在this.$nextTick(callback)里面获取到的还是0,怎么处理?
可以再加一个宏任务

mounted:function(){
    setTimeout(function(){
        this.list=[1,2,3,4,5];
        this.$nextTick(function(){
        console.log( '2:'+this.$el.offsetHeight );//for渲染完,获取正常
    });
    console.log('1:'+this.$el.offsetHeight)//先触发,高度0
    }.bind(this),1000);//延时1s模拟下ajax
}

16.说说Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式;或者说就是一个仓库,仓库里放了很多对象。

Vuex有5种属性: 分别是 state、getter、mutation、action、module;

1.state
Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据

2.mutations
mutations定义的方法动态修改Vuex 的 store 中的状态或数据
注:异步操作需要通过action来提交mutation实现
每个mutation执行完成后都会对应到一个新的状态变更,如果mutation支持异步操作,就没有办法知道状态是何时更新的

3.getters
类似vue的计算属性,主要用来过滤一些数据

4.action
actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action

5.module
当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter

总结
1.vuex 一般用于中大型 web 单页应用中对应用的状态进行管理,
2.对于一些组件间关系较为简单的小型应用,使用 vuex的必要性不是很大,因为完全可以用组件prop属性或者事件来完成父子组件之间的通信
3.vuex 更多地用于解决跨组件通信以及作为数据中心集中式存储数据

17.v-model的原理知道吗?

vue 项目中主要使用 v-model 指令在表单 inputtextarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

1.texttextarea 元素使用 value 属性和 input 事件;
2.checkbox 和 radio 使用 checked 属性和 change 事件;
3.select 字段将 value 作为 prop 并将 change 作为事件;

以 input 表单元素为例:
<input v-model='something'>

相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:
<ModelChild v-model="message"></ModelChild>

子组件:
<div>{{value}}</div>

props:{
    value: String
},
methods: {
  test1(){
     this.$emit('input', '小红')
  },
}

18.说说Vue数据响应式的原理

Vue通过数据劫持配合发布者-订阅者的设计模式,内部通过调用object.defineProperty()来劫持各个属性的gettersetter,在数据变化的时候通知订阅者,并触发相应的回调

19.Vue是如何实现数据双向绑定的?

Vue数据双向绑定是指:数据变化更新视图,视图变化更新数据,例如输入框输入内容变化时,data中的数据同步变化;data中的数据变化时,文本节点的内容同步变化

Vue主要通过以下4个步骤实现数据双向绑定:

1.实现一个监听器「Observer」:
    对数据对象进行遍历,包括子属性对象的属性,利用Object.defineProperty()在属性上都加上gettersetter,这样后,给对象的某个值赋值,就会触发setter,那么就能监听到数据变化
2.实现一个解析器「Compile」:
    解析Vue模板指令,将模板中的变量都替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新
3.实现一个订阅者「Watcher」:
    Watcher订阅者是Observer和Compile之间通信的桥梁,主要任务是订阅Observer中的属性值变化的消息,当收到属性值变化的消息时,触发解析器Compile中对应的更新函数
4.实现一个订阅器「Dep」:
    订阅器采用发布-订阅设计模式,用来收集订阅者Watcher,对监听器Observer和订阅者Watcher进行统一管理

20.v-for 和 v-if 区别,优先级?

v-for表示遍历渲染,v-if表示是否展示

v-for的优先级比v-if高

21.你有对 Vue 项目进行哪些优化?

1.不要在模板里面写过多表达式
2.循环调用子组件时添加key
3.频繁切换的使用v-show,不频繁切换的使用v-if
4.尽量少用float,可以用flex
5.按需加载,可以用require或者import()按需加载需要的组件
6.路由懒加载

22.vue操作虚拟DOM的原理

vue使用虚拟DOM对DOM进行了一些修改与调整。

比如一次操作中需要更新10次dom,虚拟dom不会立即操作dom,而是将这10次的更新内容diff保存早本地一个js对象中
最终将这个js对象一次性attch到DOM树上再进行操作,可以避免不必要的计算。

23.单页应用和多页应用的区别

单页应用(SPA):只有一个主页的应用,一开始只需要加载一次JS、CSS资源,单页应用的跳转其实就是组件切换,仅刷新局部资源。

多页应用(MPA):多个独立页面的应用,每个页面必须要重复加载JS、CSS资源,多页应用的跳转页面整体资源会刷新。

24.单页应用怎么实现多个路由

先在router.js中配置好各个路由,当路由显示时展示出对应的组件(这里可以使用hidden:true;判断路由是否展示)

补充:怎么动态渲染路由?
1.对配置好的路由进行遍历,有一个子路由遍历一遍,多个子路由的需要重新遍历一遍
2.然后在菜单组件比如<el-menu router>添加上router属性,地址栏就会动态渲染

25.VUE的全局属性有哪些

$ref / $data / $router / $route / $data / $el / $props / $root / $parent / $children / $slots ...

补充:
$route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。
$router 是“路由实例”对象,即使用 new VueRouter创建的实例,包括了路由的跳转方法,钩子函数等。

参考:cn.vuejs.org/v2/api/#vm-…

Vue VS React

React 和 Vue 相同点:
1、使用 Virtual DOM;
2、提供了响应式 (Reactive) 和组件化 (Composable) 的视图组件;
3、将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库;

React 和 Vue 不同点:
1、性能
vue和React的性能都非常高。
在 React 应用中:
当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。如要避免不必要的子组件的重渲染,你需要手动实现;
在 Vue 应用中,
组件的依赖是在渲染过程中自动追踪的,所以系统能精确知晓哪个组件确实需要被重渲染,开发者不需要考虑组件是否需要重新渲染之类的优化。

2HTML & CSS
React使用的JSX语法,将HTML、CSS和JS混写;
而Vue使用的是templates模板方式,完全融合于经典的Web技术。

3、组件作用域内的 CSS
CSS 作用域在 React 中是通过 CSS-in-JS 的方案实现的 (比如 styled-components、glamorous 和 emotion);
在Vue中是通过给style标签加scoped标记实现的。

4、规模
React 则是选择把这些问题交给社区维护,因此创建了一个更分散的生态系统。但相对的,React 的生态系统相比 Vue 更加繁荣。
Vue 的路由库和状态管理库都是由官方维护支持且与核心库同步更新的。

5、原生渲染
React Native 能使你用相同的组件模型编写有本地渲染能力的 APP (iOS和Android)。能同时跨多平台开发,对开发者是非常棒的。

Vue2 VS Vue3

1. vue3中为什么取消 vue.set() 方法了
因为vue2中是使用Object.defineProperty监听对象,会有缺陷,新增的属性监听不到,vue.set()就是用来解决这个问题的。
vue3中使用Proxy直接把这个问题解决了,可以监听到新增属性了,那么久不需要vue.set()这个方法了。

代码题

1.['1','2','3'].map(parseInt)输出什么?为什么?

输出结果:[1,Nan,Nan]

原因:
['1','2','3'].map(parseInt)即
parseInt('1',0);radix 为 0parseInt() 会根据十进制来解析,所以结果为 1parseInt('2',1);radix 为 1,超出区间范围,所以结果为 NaN;
parseInt('3',2);radix 为 2,用2进制来解析,应以 01 开头,所以结果为 NaN。

2.将一个多维数组拉平成一维数组

方法一:apply结合concat拉平数组
let arr=[[1,2,3],[4,5],[6]];
console.log([].concat.apply([],arr));  // [1, 2, 3, 4, 5, 6]

方法二:ES6新增数组扩展 flat()
[1, 2, [3, 4]].flat()  //[1,2,3,4]
如果我们不知道数组究竟层级有多深我们可以用Infinity关键字作为参数
[1, [2, [3]]].flat(Infinity)   // [1, 2, 3]

3.寻找缺失的数字

例:输入[3,0,1] ,输出 2

将数组排序之后,即可根据数组中每个下标处的元素是否和下标相等,得到丢失的数字。
let missingNumber = function(nums) {
    nums.sort((a, b) => a - b);
    const n = nums.length;
    for (let i = 0; i < n; i++) {
        if (nums[i] !== i) {
            return i;
        }
    }
    return n;
};

Webpack篇

1.有哪些常见的loader和plugin,你用过哪些

loader

file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件
url-loader:和 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中去
source-map-loader:加载额外的 Source Map 文件,以方便断点调试
image-loader:加载并且压缩图片文件
babel-loader:把 ES6 转换成 ES5
css-loader:加载 CSS,支持模块化、压缩、文件导入等特性
style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS。
eslint-loader:通过 ESLint 检查 JavaScript 代码

plugin

define-plugin:定义环境变量
commons-chunk-plugin:提取公共代码
uglifyjs-webpack-plugin:通过UglifyES压缩ES6代码

2.loader 和 plugin 的区别是什么?

不同的作用:
Loader直译为"加载器"Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。
Plugin直译为"插件"Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

不同的用法:
Loadermodule.rules中配置,也就是说他作为模块的解析规则而存在。类型为数组,每一项都是一个Object,里面描述了对于什么类型的文件(test),使用什么加载(loader)和使用的参数(options)。
Plugin在plugins中单独配置,类型为数组,每一项是一个plugin的实例,参数都通过构造函数传入。

3.如何按需加载代码?

Vue UI组件库的按需加载 为了快速开发前端项目,经常会引入现成的UI组件库如ElementUI、iView等,但是他们的体积和他们所提供的功能一样,是很庞大的。 
而通常情况下,我们仅仅需要少量的几个组件就足够了,但是我们却将庞大的组件库打包到我们的源码中,造成了不必要的开销。

不过很多组件库已经提供了现成的解决方案,如Element出品的babel-plugin-component和AntDesign出品的babel-plugin-import 安装以上插件后,
在.babelrc配置中或babel-loader的参数中进行设置,即可实现组件按需加载了。

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}
单页应用的按需加载 现在很多前端项目都是通过单页应用的方式开发的,但是随着业务的不断扩展,会面临一个严峻的问题——首次加载的代码量会越来越多,影响用户的体验。

通过import(*)语句来控制加载时机,webpack内置了对于import(*)的解析,会将import(*)中引入的模块作为一个新的入口在生成一个chunk。 
当代码执行到import(*)语句时,会去加载Chunk对应生成的文件。import()会返回一个Promise对象,所以为了让浏览器支持,需要事先注入Promise polyfill

4.如何提高webpack的构建速度

1.多入口情况下,使用CommonsChunkPlugin来提取公共代码
2.通过externals配置来提取常用库
3.利用DllPlugin和DllReferencePlugin预编译资源模块 4.通过DllPlugin来对那些我们引用但是绝对不会修改的npm包来进行预编译,再通过DllReferencePlugin将预编译的模块加载进来。
5.使用Happypack 实现多线程加速编译
6.使用webpack-uglify-parallel来提升uglifyPlugin的压缩速度。 7.原理上webpack-uglify-parallel采用了多核并行压缩来提升压缩速度
8.使用Tree-shaking和Scope Hoisting来剔除多余代码

5.怎么配置单页应用?怎么配置多页应用?

单页应用可以理解为webpack的标准模式,直接在entry中指定单页应用的入口即可,这里不再赘述

多页应用的话,可以使用webpack的 AutoWebPlugin来完成简单自动化的构建,但是前提是项目的目录结构必须遵守他预设的规范。 

多页应用中要注意的是:
每个页面都有公共的代码,可以将这些代码抽离出来,避免重复的加载。比如,每个页面都引用了同一套css样式表
随着业务的不断扩展,页面可能会不断的追加,所以一定要让入口的配置足够灵活,避免每次添加新页面还需要修改构建配置

6.webpack的proxy是如何解决跨域的?

webpack的dev-serve模块会启动一个服务器,这个服务器不止帮我们做了自动更新,同时也可以做到反向代理。

就是我们把请求发送给webpack-dev-serve,然后webpack-dev-serve再去请求后端服务器,服务器之间的请求是没有跨域问题的。

只要后端返回了,webpack-dev-serve就能拿到,然后再返回给前端。

7.webpack有没有自己写过babel

babel的本质是解析文件后,在对应位置插入所需的内容,就是解析文件ast;
webpack对js每一个节点都有对应的描述,找到这个描述后在上面插入内容

性能优化

1.前端性能优化

三个方面来说明前端性能优化

一: webapck优化与开启gzip压缩
    1.babel-loader用 include 或 exclude 来帮我们避免不必要的转译,不转译node_moudules中的js文件
    其次在缓存当前转译的js文件,设置loader: 'babel-loader?cacheDirectory=true'
    2.文件采用按需加载等等
    3.具体的做法非常简单,只需要你在你的 request headers 中加上这么一句:accept-encoding:gzip
    4.图片优化,采用svg图片或者字体图标
    5.浏览器缓存机制,它又分为强缓存和协商缓存
二:本地存储——从 Cookie 到 Web Storage、IndexedDB
    说明一下SessionStorage和localStorage还有cookie的区别和优缺点
三:代码优化
    1.事件代理
    2.事件的节流和防抖
    3.页面的回流和重绘
    4.EventLoop事件循环机制
    5.代码优化等等
    
四:为什么浏览器需要缓存?讲讲强缓存和协商缓存?

浏览器缓存的优点有:

1.减少了冗余的数据传输,节省了网费
2.减少了服务器的负担,大大提升了网站的性能
3.加快了客户端加载网页的速度

浏览器缓存主要有两类:缓存协商和彻底缓存,也有称之为协商缓存和强缓存。

1.强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的network选项中可以看到该请求返回200的状态码;
2.协商缓存:向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存,如果命中,则返回
304状态码并带上新的response header通知浏览器从缓存中读取资源;
两者的共同点是,都是从客户端缓存中读取资源;区别是强缓存不会发请求,协商缓存会发请求。

2.性能优化-内存泄漏

一、什么是内存泄漏

本质上讲,内存泄漏是当一块内存不再被应用程序使用的时候,由于某种原因,这块内存没有返还给操作系统或空闲内存池的现象。
也就是说内存中有一块地方被占用了,但是你实际上是找不到它也用不到它的,这就是内存泄漏。

二、几种常见的内存泄漏

1、意外的全局变量:一个未声明变量的引用会在全局对象中创建一个新的变量
2、闭包引起的内存泄漏
3、DOM之外的引用
4、被遗漏的定时器和回调函数(没有及时清除)
5、未清理的Console输出

三、怎样避免内存泄漏
1、减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收
2、注意程序逻辑,避免“死循环”之类的 
3、避免创建过多的对象  原则:不用了的东西要及时归还

前端安全

1.讲讲CSRF XSS攻击?如何防范?二者的区别是什么?

XSS攻击:(Cross Site Scripting)跨域脚本攻击。
原理:
不需要你做任何的登录认证,它会通过合法的操作(比如在 url 中输入、在评论框中输入),
向你的页面注入脚本(可能是 js、hmtl 代码块等)。
防范:
1.  编码;对于用户输入进行编码
2.  过滤;移除用户输入和事件相关的属性。(过滤 script、style、iframe等节点)
3.  校正;使用 DOM Parse 转换,校正不配对的 DOM 标签


CSRF攻击:SRF(Cross-site request forgery)跨站请求伪造。
原理:
1.  登录受信任网站 A,并在本地生成 Cookie
(如果用户没有登录网站 A,那么网站 B 在诱导的时候,请求网站 A 的 api 接口时,会提示你登录)
2.  在不登出 A 的情况下,访问危险网站 B(其实是利用了网站 A 的漏洞)
防范:
1.  token 验证;
2.  隐藏令牌;把 token 隐藏在 http 请求的 head 中。
3.  referer 验证;验证页面来源。

二者的区别:
1.  CSRF:需要用户先登录网站 A,获取 cookie。
    XSS:不需要登录。
2.  CSRF:是利用网站 A 本身的漏洞,去请求网站 A 的 api。
    XSS:是向网站 A 注入 JS 代码,然后执行 JS 里的代码,篡改网站 A 的内容。

代码篇

1.用js实现input输入框的双向绑定

主要步骤:
1、定义一个对象
2、利用Object.defineProperty为对象定义一个属性
3、给input输入框绑定监听事件,用于获取用户输入的值并赋值给对象属性
4、由于对象属性值被修改从而触发set函数,然后我们在set函数中把用户输入的值赋值给input输入框下面的div,就可以实现双向数据绑定了


html:
    <!--输入框-->
    <input type="text" id="inputText" />
    <!--内容展示区-->
    <div id="showContent"></div>
    
js:
    // 创建一个数据对象用来保存输入值
    var dataObj = {}
    Object.defeineProperty(dataObj,'inputText',{
        get:function(){
        }
    })

工具类

1.npm与yarn的区别

Yarn文档中有说,Yarn 是为了弥补 npm 的一些缺陷而出现的

yarn 的速度更快;安装版本会更统一;更简洁的输出(配合emoji表情);更好的语义化,比如yarn add/remove 替代 npm install/uninstall

主观题

1.说说你经常遇到的问题

经常遇到的问题就是Cannot read property ‘prototype’ of undefined
解决办法通过浏览器报错提示代码定位问题,解决问题

Vue项目中遇到视图不更新,方法不执行,埋点不触发等问题
一般解决方案查看浏览器报错,查看代码运行到那个阶段未之行结束,阅读源码以及相关文档等
然后举出来最近开发的项目中遇到的算是两个比较大的问题。

2.说说你对前端工程化的理解

我个人理解主要是从模块化、组件化、规范化、自动化这四个方面来实现前端工程化的:
1.模块化:
    简单来说,模块化就是将一个大文件拆分成相互依赖的小文件,再进行统一的拼装和加载。
2.组件化:
    组件化有点类似模块化,但模块化主要是在文件层面上的,而组件化则是在平时写代码的时候就有封装组件的意识,让组件能够多次复用
3.规范化:
    比如目录结构的制定、代码的规范、接口的规范、命名的规范等,遵循一套规范可以让代码更容易维护,出问题的时候也方便快速定位
4.自动化:
    任何简单机械的重复劳动都应该让机器去完成,比如自动化测试、自动化部署等