前端基础面试题

  一、HTML               

1.行内元素有哪些?块级元素有哪些? 空(void)元素有哪些?

行内元素:span、img、input...

块级元素:div、footer、header、section、p、h1...h6...

空元素:br、hr...

2.元素之间的转换问题:

display: inline;              把某元素转换成了行内元素      ===>不独占一行的,并且不能设置宽高

display: inline-block;     把某元素转换成了行内块元素         ===>不独占一行的,可以设置宽高

display: block;                    把某元素转换成了块元素               ===>独占一行,并且可以设置宽高

3.页面导入样式时,使用link和@import有什么区别?

区别一:link先有,后有@import(兼容性link比@import兼容);

区别二:加载顺序差别,浏览器先加载的标签link,后加载@import 

​4.title与h1的区别、b与strong的区别、i与em的区别?

title与h1的区别:

定义:

    title:概括了网站信息,可以告诉搜索引擎或者用户关于这个网站的内容主题是什么

    h1:文章主题内容,告诉蜘蛛我们的网站内容最主要是什么

区别:

    title他是显示在网页标题上、h1是显示在网页内容上

    title比h1添加的重要 (title > h1 ) ==》对于seo的了解

场景:

    网站的logo都是用h1标签包裹的    

b与strong的区别:

定义:

    b:实体标签,用来给文字加粗的。

    strong:逻辑标签,用来加强字符语气的。

区别:

    b标签只有加粗的样式,没有实际含义。

    strong表示标签内字符比较重要,用以强调的。

题外话:为了符合css3的规范,b尽量少用该用strong就行了。

i与em的区别:

定义:

    i:实体标签,用来做文字倾斜的。

    em:是逻辑标签,用来强调文字内容的

区别:

    i只是一个倾斜标签,没有实际含义。

    em表示标签内字符重要,用以强调的。

场景:

    i更多的用在字体图标,em术语上(医药,生物)。

​ 5.img标签的title和alt有什么区别?

区别一:

    title : 鼠标移入到图片显示的值

    alt   : 图片无法加载时显示的值

区别二:

    在seo的层面上,蜘蛛抓取不到图片的内容,所以前端在写img标签的时候为了增加seo效果要加入alt属性来描述这张图是什么内容或者关键词。

​ 6.png、jpg、gif 这些图片格式解释一下,分别什么时候用?

png:无损压缩,尺寸体积要比jpg/jpeg的大,适合做小图标。

jpg:采用压缩算法,有一点失真,比png体积要小,适合做中大图片。

gif:一般是做动图的。

webp:同时支持有损或者无损压缩,相同质量的图片,webp具有更小的体积。兼容性不是特别好。

7.说一说html语义化?

对于开发者而言,语义化标签有着更好的页面结构,利于个人的代码编写。 对于用户而言,当网络卡顿时有良好的页面结构,有利于增加用户的体验。 对于爬虫来说,有利于搜索引擎的SEO优化,利于网站有更靠前的排名。 对于团队来讲,有利于代码的开发和后期的维护。

​ 二、CSS

​ 1.介绍一下CSS的盒子模型

CSS的盒子模型有哪些:标准盒子模型、IE盒子模型

CSS的盒子模型区别:

    标准盒子模型:margin、border、padding、content

    IE盒子模型 :margin、content( border +  padding  + content )

标准盒模型在设置width和height时设置的是content的大小,盒子的大小还要加上padding、border;

 ie盒模型设置width和height时设置的是盒子的大小,会压缩content区域。

通过CSS如何转换盒子模型:

    box-sizing: content-box;    /*标准盒子模型*/

    box-sizing: border-box;      /*IE盒子模型*/

2.line-height和heigh区别

line-height是每一行文字的高,如果文字换行则整个盒子高度会增大(行数*行高)。

height是一个死值,就是这个盒子的高度。

 3.CSS选择符有哪些?哪些属性可以继承?

CSS选择符:

    通配(*)

    id选择器(#)

    类选择器(.)

    标签选择器(div、p、h1...)

    相邻选择器(+)

    后代选择器(ul li)

    子元素选择器( > )

    属性选择器(a[href])

    CSS属性哪些可以继承:

        文字系列:font-size、color、line-height、text-align...

        不可继承属性:border、padding、margin...

​  4.CSS优先级算法如何计算?

优先级比较:!important > 内联样式 > id > class > 标签 > 通配

详细:!important > 内联样式 > ID 选择器(#id{}) > 类选择器(.class{}) = 属性选择器(a[href="segmentfault.com"]{}) = 伪类选择器( :hover{}) > 标签选择器(span{}) = 伪元素选择器( ::before{})= 后代选择器(.father .child{})> 子选择器(.father > .child{}) = 相邻选择器( .bro1 + .bro2{}) > 通配符选择器(*{})

CSS权重计算:

第一:内联样式(style)  权重值:1000

第二:id选择器                   权重值:100

第三:类选择器                   权重值:10

第四:标签&伪元素选择器   权重值:1

第五:通配、>、+         权重值:0

5.用CSS画一个三角形

/*用边框画(border),例如:*/
{
        width: 0;
        height: 0;
        border-left:100px solid transparent;
        border-right:100px solid transparent;
        border-top:100px solid transparent;
        border-bottom:100px solid #ccc;
}

​ 6.一个盒子不给宽度和高度如何水平垂直居中?

一般常见的几种居中的方法有: 对于宽高固定的元素 

(1)我们可以利用margin:0 auto来实现元素的水平居中。 

(2)利用绝对定位,设置四个方向的值都为0,并将margin设置为auto,由于宽高固定,因此对应方向实现平分,可以实现水 平和垂直方向上的居中。 

(3)利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过margin负值来调整元素 的中心点到页面的中心。

 (4)利用绝对定位,先将元素的左上角通过top:50%和left:50%定位到页面的中心,然后再通过translate来调整元素 的中心点到页面的中心。 

(5)使用flex布局,通过align-items:center和justify-content:center设置容器的垂直和水平方向上为居中对 齐,然后它的子元素也可以实现垂直和水平的居中。 对于宽高不定的元素,上面的后面两种方法,可以实现元素的垂直和水平的居中。

/*方式一:*/
main
.container{ display: flex; justify-content: center; align-items: center; width: 300px; height: 300px; border:5px solid #ccc; } .main{ background: red; } /*方式二:*/
main
.container{ position: relative; width: 300px; height: 300px; border:5px solid #ccc; } .main{ position: absolute; left:50%; top:50%; background: red; transform: translate(-50%,-50%); }

​ 7.display有哪些值?说明他们的作用。

none                 隐藏元素

block                把某某元素转换成块元素

inline               把某某元素转换成内联元素

inline-block     把某某元素转换成行内块元素

8.对BFC规范(块级格式化上下文:block formatting context)的理解?

BFC就是页面上一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。

了解BFC : 块级格式化上下文。

BFC的原则:如果一个元素具有BFC,那么内部元素再怎么弄,都不会影响到外面的元素。

如何触发BFC:

        float的值非none

        overflow的值非visible

        display的值为:inline-block、table-cell、table-caption、flex、inline-flex

        position的值为:absoute、fixed

BFC元素具有的特性:

1)、在BFC中,盒子从顶部开始垂直地一个接一个排列

2)、盒子垂直方向的距离由margin决定。同一个BFC的两个相邻盒子margin会重叠

3)、BFC中,margin-left会触碰到border-left(对于从左至右的方式,反之)

4)、BFC区域不会与浮动的盒子产生交集,而是紧贴边缘浮动

5)、计算BFC高度时,自然会检测浮动的盒子高度

主要用途:

1)、清除内部浮动,父元素设置为BFC可以清除子元素的浮动(最常用overflow:hidden,IE6需加上*zoom:1):计算BFC高度时会检测浮动子盒子高度

2)、解决外边距合并问题

3)、右侧盒子自适应:BFC区域不会与浮动盒子产生交集,而是紧贴浮动边缘。

9.清除浮动有哪些方式?

关于浮动: 

浮动的作用:常用于图片,可以实现文字环绕图片 

浮动的特点:脱离文档流,容易造成盒子塌陷,影响其他元素的排列 

解决塌陷问题:

                 触发BFC

               ①父元素中添加overflow:hidden;  

               ②给父元素添加高度。 

               ③建立空白div,添加clear 

               ④在父级添加伪元素::after{ content : ' ', clear : both , display : table}

​ 10.在网页中的应该使用奇数还是偶数的字体?为什么呢?

偶数 : 让文字在浏览器上表现更好看。

另外说明:ui给前端一般设计图都是偶数的,这样不管是布局也好,转换px也好,方便一点。

​ 11.position有哪些值?分别是根据什么定位的?

static [默认]  没有定位

fixed  固定定位,相对于浏览器窗口进行定位。

relative  相对于自身定位,不脱离文档流。

absolute    相对于第一个有relative的父元素,脱离文档流。

relative和absolute区别

1). relative不脱离文档流 、absolute脱离文档流

2). relative相对于自身 、 absolute相对于第一个有relative的父元素

3). relative如果有left、right、top、bottom ==》left、top

     absolute如果有left、right、top、bottom ==》left、right、top、bottom

​ 12.写一个左中右布局占满屏幕,其中左、右俩块固定宽200,中间自适应宽,要求先加载中间块,请写出结构及样式。

即三栏布局的实现方案?

三栏布局一般指的是页面中一共有三栏,左右两栏宽度固定,中间自适应的布局,一共有五种实现方式。这里以左边宽度固定为100px,右边宽度固定为200px为例。

 (1)利用绝对定位的方式,左右两栏设置为绝对定位,中间设置对应方向大小的margin的值。

 (2)利用flex布局的方式,左右两栏的宽度分别设置为100px和200px,中间一栏增长系数设置为1 。

(3)利用浮动的方式,左右两栏设置固定大小,并设置对应方向的浮动。中间一栏设置左右两个方向的margin值,注意这种方式,中间一栏必须放到最后。

 (4)圣杯布局,利用浮动和负边距来实现。父级元素设置左右的padding,三列均设置向左浮动,中间一列放在最前面,宽度设置为父级元素的宽度,因此后面两列都被挤到了下一行,通过设置margin负值将其移动到上一行,再利用相对定位,定位到两边。圣杯布局中间列的宽度不能小于左边列的宽度,否则左边列上不去,而双飞翼布局则不存在这个问题。 

(5)双飞翼布局,双飞翼布局相对于圣杯布局来说,左右位置的保留是通过中间列的margin值来实现的,而不是通过父元素的padding来实现的。本质上来说,也是通过浮动和外边距负值来实现的。​                

13.什么是CSS reset?

reset.css           是一个css文件,用来重置css样式的。

normalize.css     为了增强跨浏览器渲染的一致性,一个CSS 重置样式库。

14.css sprite是什么,有什么优缺点

是什么:

    把多个小图标合并成一张大图片。

优缺点:

    优点:减少了http请求的次数,提升了性能。

    缺点:维护比较差(例如图片位置进行修改或者内容宽高修改)

​ 15.display: none;与visibility: hidden;的区别

 占用位置的区别

display: none;                 是不占用位置的

visibility: hidden;   虽然隐藏了,但是占用位置

重绘和回流的问题

visibility: hidden; 、 display: none;  产生重绘

display: none;     还会产生一次回流

产生回流一定会造成重绘,但是重绘不一定会造成回流。

产生回流的情况:改变元素的位置(left、top...)、显示隐藏元素....

产生重绘的情况:样式改变、换皮肤

16.opacity 和 rgba区别

共同性:实现透明效果

 opacity 取值范围0到1之间,0表示完全透明,1表示不透明

rgba   R表示红色,G表示绿色,B表示蓝色,取值可以在正整数或者百分数。A表示透明度取值0到1之间

区别:继承的区别

opacity会继承父元素的opacity属性,而RGBA设置的元素的后代元素不会继承不透明属性。

17. 说一说CSS尺寸设置的单位?

①css一共有五个长度单位,分别是px,em,rem,vw,vh 

②除了px是绝对单位,其他都是相对单位。 

③em相对于自身大小(但在font-size中相对于父元素字体大小) 

④rem相对于根元素(html)的字体大小 

⑤vw相对于可视化窗口的宽(1vw就是1%可视化窗口宽度) 

⑥vh相对于可视化窗口的高(1vh就是1%可视化窗口高度) 

⑦一般采用rem+媒体查询或者rem+vw来实现响应式布局。原理是当窗口大小发生变化时,通过媒体查询或者vw改变根元素的字体大小,从而改变以rem为单位的元素大小。

18.::before 和 :after中双冒号和单冒号 有什么区别?解释一下这2个伪元素的作用。

 区别

    :是伪类、::伪元素  ===》是为了做区分

是什么?作用

    元素before之前 、 元素after之后

    作用:清除浮动、样式布局上也有作用

19.如何关闭IOS键盘首字母自动大写

​ 20.怎么让Chrome支持小于12px 的文字?

Chrome默认字体大小是:16px

**每个浏览器默认字体大小可能都不一样

 

21.ios系统中元素被触摸时产生的半透明灰色遮罩怎么去掉

22.webkit表单输入框placeholder的颜色值能改变吗?

23.禁止ios长按时触发系统的菜单,禁止ios&android长按时下载图片

/*禁止ios 长按时触发系统的菜单,禁止ios&android长按时下载图片*/
html,body{
    touch-callout: none;
    -webkit-touch-callout: none;
    user-select:none;
 -webkit-user-select:none;
}

24.禁止ios和android用户选中文字

html,body{
    user-select:none;
    -webkit-user-select:none;
}

25.自适应

淘宝无限适配【移动端】:淘宝无限适配 + 布局单位使用rem

26.响应式

1.) 是什么?

    一个URL可以响应多端

2). 语法结构

    @media only screen and (max-width: 1000px){

        ul li:last-child{

            display: none;

        }

    }

    only : 可以排除不支持媒体查询的浏览器

    screen : 设备类型

    max-width | max-height

    min-width | min-height 

3). 响应式图片【性能优化】

    

        

        

        

    

布局方案

1).什么情况下采用响应式布局

    数据不是特别多,用户量不是特别大,纯展示类的项目适合响应式布局

    例如:公司的官网、专题页面

    特别追求性能的项目,不太适合响应式,因为如果添加了很多的响应式就会造成加载速度变慢。

2)、pc + 移动端应该做什么样的布局方案

    注意:访问量还可以或者比较大,类似于淘宝网。

    pc是一套,会加入一点点响应式。

    移动端是一套,会使用自适应的布局方式。

3)、pc的设计图

    ui:1980

    笔记本电脑:1280

    ui图的宽度和电脑的宽度不对应该怎么办?

        1. 把ui图进行等比缩放,缩放成和电脑一样的尺寸

        2. 换1980的电脑     

4)、移动端的设计图

    宽度:750

    因为750设计图/2就是375,正好是iphone6的尺寸,我们要把iphone6的尺寸做为基准点。

三、JavaScript

1.延迟加载JS有哪些方式?(async、defer)

延迟加载:async、defer

        例如:

async : async是和html解析同步的(一起的),。

defer和async区别:

1),html文件都是按顺序执行的,script标签中没有加defer和async时,浏览器在解析文档时遇到script标签就会停止解析阻塞文档解析,先加载JS文件,加载完之后立即执行,执行完毕后才能继续解析文档。

2), 在script标签中写入defer或者async时,就会使JS文件异步加载,即html执行到script标签时,JS加载和文档解析同时进行,

3),async是在JS加载完成后立即执行JS脚本,阻塞文档解析,不是顺次执行js脚本(谁先加载完谁先执行),

4),defer则是JS加载完成后,在文档解析完成后顺次执行JS脚本。

2.JS数据类型有哪些?

基本类型:string、number、boolean、undefined、null、es6新加的数据类型:symbol、bigint

引用类型:object,函数,数组等

NaN是一个数值类型,但是不是一个具体的数字。

它们的本质区别是存储位置不同,基本类型被分配在栈内存中,引用类型被分配在堆内存中。symbol特点是没有重复的数据,可以用来当对象的键值,防止被改写和覆盖。bigint的特点是数据涵盖的范围比较大,可以用来表示数字类型不能表达的数据范围的数值。

​ 3.JS数据类型考题    

console.log( true + 1 );                 //2

console.log( 'name'+true );              //nametrue

console.log( undefined + 1 );         //NaN

console.log( typeof undefined ); //undefined

console.log( typeof(NaN) );       //number

console.log( typeof(null) );      //object

​ 4.null和undefined的区别

 作者在设计js的都是先设计的null(为什么设计了null:最初设计js的时候借鉴了java的语言)

null会被隐式转换成0,很不容易发现错误。

先有null后有undefined,出来undefined是为了填补之前的坑。

具体区别:

JavaScript的最初版本是这样区分的:null是一个表示"无"的对象(空对象指针),转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN。

undefined表示变量定义了但是未赋值,当没有返回值,访问一个对象的属性不存在,给函数定义了一个形参但是没有传递形参都会返回一个undefined,undefined通过typeof判断类型是'undefined'。null表示变量定义了并人为的赋值为了空对象,null通过typeof判断类型是‘object’。当需要释放一个对象,就将变量设置为null,表示对象已经被清空,目前无效状态。

​ 5.==和===有什么不同?

==  :  比较的是值

         string == number || boolean || number ....都会隐式转换

        通过valueOf转换(valueOf() 方法通常由 JavaScript 在后台自动调用,并不显式地出现在代码中。)

=== : 除了比较值,还比较类型

​ 6.JS微任务和宏任务

js是单线程的语言。

js代码执行流程:同步执行完==》事件循环

    同步的任务都执行完了,才会执行事件循环的内容

    进入事件循环:请求、定时器、事件....

事件循环中包含:【微任务、宏任务】

微任务:promise.then

宏任务:setTimeout..

要执行宏任务的前提是清空了所有的微任务

流程:同步==》事件循环【微任务和宏任务】==》微任务==》宏任务=》微任务...

7.JS作用域考题

除了函数外,js是没有块级作用域。

作用域链:内部可以访问外部的变量,但是外部不能访问内部的变量。

     注意:如果内部有,优先查找到内部,如果内部没有就查找外部的。

注意声明变量是用var还是没有写(window.)

 注意:js有变量提升的机制【变量悬挂声明】

优先级:声明变量 > 声明普通函数 > 参数 > 变量提升

面试的时候怎么看:

本层作用域有没有此变量【注意变量提升】

注意:js除了函数外没有块级作用域

普通声明函数是不看写函数的时候顺序

function c(){
    var b = 1;
    function a(){
        console.log( b );
        var b = 2;
        console.log( b );
    }
    a();
    console.log( b );
}
c();

var name = 'a';
(function(){
    if( typeof name == 'undefined' ){
        var name = 'b';
        console.log('111'+name);
    }else{
        console.log('222'+name);
    }
})()

function fun( a ){
    var a = 10;
    function a(){}
    console.log( a );
}
fun( 100 );

8.JS对象考题

JS对象注意点:

对象是通过new操作符构建出来的,所以对象之间不想等(除了引用外);

对象注意:引用类型(共同一个地址);

对象的key都是字符串类型;

对象如何找属性|方法;

    查找规则:先在对象本身找 ===> 构造函数中找 ===> 对象原型中找 ===> 构造函数原型中找 ===> 对象上一层原型查找

 [1,2,3] === [1,2,3]   //false
var obj1 = {
    a:'hellow'
}
var obj2 = obj1;
obj2.a = 'world';
console.log(obj1);     //{a:world}
(function(){
    console.log(a);     //undefined
    var a = 1;
})();
var a = {}
var b = {
    key:'a'
}
var c = {
    key:'c'
}
a[b] = '123';
a[c] = '456';
console.log( a[b] ); // 456

9.JS作用域+this指向+原型的考题

function Foo(){
    getName = function(){console.log(1)} //注意是全局的window.
    return this;
}
Foo.getName = function(){console.log(2)}
Foo.prototype.getName = function(){console.log(3)}
var getName = function(){console.log(4)}
function getName(){
    console.log(5)
}
Foo.getName();    //2
getName();           //4
Foo().getName();  //1
getName();          //1
new Foo().getName();//3
var o = {
    a:10,
    b:{
        a:2,
        fn:function(){
            console.log( this.a ); // 2
            console.log( this );   //代表b对象
        }
    }
}
o.b.fn();
window.name = 'ByteDance';
function A(){
    this.name = 123;
}
A.prototype.getA = function(){
    console.log( this );
    return this.name + 1;
}
let a = new A();
let funcA = a.getA;
funcA();  //this代表window
var length = 10;
function fn(){
    return this.length + 1;
}
var obj = {
    length:5,
    test1:function(){
        return fn();
    }
}
obj.test2 = fn;
console.log( obj.test1() );                             //1
console.log( fn()===obj.test2() );                 //false
console.log( obj.test1() == obj.test2() ); //false

10.JS判断变量是不是数组,你能写出哪些方法?

//isArray
var arr = [1,2,3];
console.log( Array.isArray( arr ) );
//instanceof  【可写,可不写】
var arr = [1,2,3];
console.log( arr instanceof Array );
//原型prototype
var arr = [1,2,3];
console.log( Object.prototype.toString.call(arr).indexOf('Array') > -1 );
//isPrototypeOf()
var arr = [1,2,3];
console.log(  Array.prototype.isPrototypeOf(arr) )
//constructor
var arr = [1,2,3];
console.log(  arr.constructor.toString().indexOf('Array') > -1 )

11.slice是干嘛的、splice是否会改变原数组

slice是来截取的

    参数可以写slice(3)、slice(1,3)、slice(-3)

    返回的是一个新的数组

splice 功能有:插入、删除、替换

    返回:删除的元素

    该方法会改变原数组

​12.JS数组去重

1).array.from(new Set(arr)):不考虑兼容性,这种方法去重代码最少。这种方法还没无法去掉“{}”空对象;

2).利用for嵌套for,使用splice(ES5中常用);

3).利用indexOf去重,新建一个空的结果数组,for循环原数组,判断结果数组中是否存在当前元素,如果存在则跳过,如果不存在,就push到结果数组;

4).利用sort(倒叙方法,然后根据排序后的结果进行遍历及相邻的元素比较);

5).利用include,判断数组中书否包含某个值,不包含则push,包含则跳过;

6).利用map数据结构去重:创建一个空的map数据结构,遍历需要去重的数组,把数组的每个元素作为key存在map中。由于map中不会出现相同的值,所以最终得到的是去重之后的结果;

7).[...new Set(arr)]

//new set 
var arr1 = [1,2,3,2,4,1];
function unique(arr){
    return [...new Set(arr)]
}
console.log(  unique(arr1) );

//indexOf
var arr2 = [1,2,3,2,4,1];
function unique( arr ){
    var brr = [];
    for( var i=0;i

12.找出多维数组最大值

function fnArr(arr){
    var newArr = [];
    arr.forEach((item,index)=>{
        newArr.push( Math.max(...item)  )
    })
    return newArr;
}
console.log(fnArr([
    [4,5,1,3],
    [13,27,18,26],
    [32,35,37,39],
    [1000,1001,857,1]
]));

13.给字符串新增方法实现功能

给字符串对象定义一个addPrefix函数,当传入一个字符串str时,它会返回新的带有指定前缀的字符串,例如:

//console.log( 'world'.addPrefix('hello') )  控制台会输出helloworld
//解答:
String.prototype.addPrefix = function(str){
    return str  + this;
}
console.log( 'world'.addPrefix('hello') )

14.找出字符串出现最多次数的字符以及次数

var str = 'aaabbbbbccddddddddddx';
var obj = {};
for(var i=0;i

15.new操作符具体做了什么

1).创建了一个空的对象

2).将空对象的__proto__(原型),指向于构造函数的prototype(原型对象)

3)将空对象作为构造函数的上下文(改变this指向) 构造函数绑定新对象的this并执行返回结果 

4).对构造函数有返回值的处理判断, 判断返回结果是否为null,如果为null,返回新对象,否则直接返回执行结果。

function Fun( age,name ){
    this.age = age;
    this.name = name;
}
function create( fn , ...args ){
    //1. 创建了一个空的对象
    var obj = {}; //var obj = Object.create({})
    //2. 将空对象的原型,指向于构造函数的原型
    Object.setPrototypeOf(obj,fn.prototype);
    //3. 将空对象作为构造函数的上下文(改变this指向)
    var result = fn.apply(obj,args);
    //4. 对构造函数有返回值的处理判断
    return result instanceof Object ? result : obj;
}
console.log( create(Fun,18,'张三')   )

16.闭包

闭包是什么:

    闭包是一个函数加上到创建函数的作用域的连接,作用域链,当前作用域可以访问上级作用域中的变量, 闭包“关闭”了函数的自由变量,

闭包可以解决什么问题【闭包的优点】:

        能够让函数作用域中的变量在函数执行结束之后不被销毁,同时也能在函数外部可以访问函数内部的局部变量。 

    闭包可以解决的问题:

 var lis = document.getElementsByTagName('li');
      for(var i=0;i

闭包的缺点:

        由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄漏【ie】,内存泄露积累多了就容易导致内存溢出。 

         解决:把闭包的函数设置为null

闭包的应用:

        能够模仿块级作用域,能够实现柯里化,在构造函数中定义特权方法、Vue中数据响应式Observer中使用闭包等。

17.原型链

原型可以解决什么问题:

    对象共享属性和共享方法

谁有原型:

函数拥有:prototype

对象拥有:__proto__

对象查找属性或者方法的顺序:

    先在对象本身查找 --> 构造函数中查找 --> 对象的原型 --> 构造函数的原型中 --> 当前原型的原型中查找

原型链:

    是什么:就是把原型串联起来

    原型链的最顶端是null

​18. JS继承有哪些方式和优缺点?

1)、原型链继承 --- 优点:写法简单、容易理解。缺点:①引用类型的值会被所有实例共享;②在子类实例对象创建时,不能向父类传参;

2)、借用构造函数继承 --- 优点:①避免了引用类型的值会被所有实例共享;②在子类实例对象创建时,可以向父类传参;缺点:方法在构造函数中,每次创建实例对象时都会重新创建一遍方法;

3)、组合继承 --- 融合原型链和借用构造函数的优点,是js中最常用的继承方式;缺点:无论什么情况下,父类构造函数都会被调用两次,一是创建子类原型对象时,二是子类构造函数内部。

4)、原型式继承 --- 优点:不需要单独创建构造函数;缺点:引用类型的值会被所有实例共享。

5)、寄生式继承 --- 优点:不需要单独创建构造函数;缺点:方法在构造函数中,每次创建实例对象时都会重新创建一遍。

6)、寄生组合继承 --- 优点:高效率只调用一次父类构造函数,并且避免了子类原型对象上不必要、多余的属性,同时,还能将原型链保持不变,因此能使用instanceof 和 isPrototypeOf。缺点:代码复杂。

//ES6
class Parent{
    constructor(){
        this.age = 18;
    }
}
class Child extends Parent{
    constructor(){
        super();
        this.name = '张三';
    }
}
let o1 = new Child();
console.log( o1,o1.name,o1.age );

//原型链继承
function Parent(){
    this.age = 20;
}
function Child(){
    this.name = '张三'
}
Child.prototype = new Parent();
let o2 = new Child();
console.log( o2,o2.name,o2.age );

//借用构造函数继承
function Parent(){
    this.age = 22;
}
function Child(){
    this.name = '张三'
    Parent.call(this);
}
let o3 = new Child();
console.log( o3,o3.name,o3.age );

//组合式继承
function Parent(){
    this.age = 100;
}
function Child(){
    Parent.call(this);
    this.name = '张三'
}
Child.prototype = new Parent();
let o4 = new Child();
console.log( o4,o4.name,o4.age );

19.说一下call、apply、bind区别

共同点:功能一致

call apply bind三个方法都可以用来改变函数的this指向;

语法: 函数.call()、函数.apply()、函数.bind()

 区别:

1)、fn.call (newThis,params) call函数的第一个参数是this的新指向,后面依次传入函数fn要用到的参数。会立即执行fn函数

2)、fn.apply (newThis,paramsArr) apply函数的第一个参数是this的新指向,第二个参数是fn要用到的参数数组,会立即执行fn函数

3)、fn.bind (newThis,params) bind函数的第一个参数是this的新指向,后面的参数可以直接传递,也可以按数组的形式传入。  不会立即执行fn函数,且只能改变一次fn函数的指向,后续再用bind更改无效。返回的是已经更改this指向的新fn。因为bind返回的是一个函数需要加入()执行。

参数不同:apply第二个参数是数组。call和bind有多个参数需要挨个写。

 场景:

//用apply的情况

var arr1 = [1,2,4,5,7,3,321];

console.log( Math.max.apply(null,arr1) )

//用bind的情况

var btn = document.getElementById('btn');

var h1s = document.getElementById('h1s');

btn.onclick = function(){

    console.log( this.id );

}.bind(h1s)

20.sort背后原理是什么?

V8 引擎 sort 函数只给出了两种排序 InsertionSort 和 QuickSort,数量小于10的数组使用 InsertionSort,比10大的数组则使用 QuickSort。

之前的版本是:插入排序和快排,现在是冒泡

21.深拷贝和浅拷贝

共同点:复制

//浅拷贝:只复制引用,而未复制真正的值。
var arr1 = ['a','b','c','d'];
var arr2 = arr1;
var obj1 = {a:1,b:2}
var obj2 = Object.assign(obj1);
//深拷贝:是复制真正的值 (不同引用)
var obj3 = {
    a:1,
    b:2
}
var obj4 = JSON.parse(JSON.stringify( obj3 ));
//递归的形式
function copyObj( obj ){
    if(  Array.isArray(obj)  ){
        var newObj = [];
    }else{
        var newObj = {};
    }
    for( var key in obj ){
        if( typeof obj[key] == 'object' ){
            newObj[key] = copyObj(obj[key]);
        }else{
            newObj[key] = obj[key];
        }
    }
    return newObj;
}
console.log(  copyObj(obj5)  )

22.localStorage、sessionStorage、cookie的区别

公共点:在客户端存放数据:都是浏览器存储 ;都存储在浏览器本地 。

区别:

1).cookie由服务器写入, sessionStorage以及localStorage都是由前端写入 。

2).cookie的生命周期由服务器端写入时就设置好的,localStorage是写入就一直存在,除非手动清除,sessionStorage是由页面关闭时自动清除 。

3).cookie存储空间大小约4kb,sessionStorage及localStorage空间比较大,大约5M 。

4).三者的数据共享都遵循同源原则,sessionStorage还限制必须是同一个页面 。

5).前端给后端发送请求时,自动携带cookie, session 及 local都不携带 。

6).cookie一般存储登录验证信息或者token,localStorage常用于存储不易变动的数据,减轻服务器压力,sessionStorage可以用来监测用户是否是刷新进入页面,如音乐播放器恢复进度条功能。

23. 说一说如何实现可过期的localstorage数据?

一种是惰性删除:惰性删除是指获取数据的时候,拿到存储的时间和当前时间做对比,如果超过过期时间就清除Cookie。 另一种是定时删除:每隔一段时间执行一次删除操作,并通过限制删除操作执行的次数和频率,来减少删除操作对CPU的长期占用。 LocalStorage清空应用场景:token存储在LocalStorage中,要清空。

24. 说一下token 能放在cookie中吗?

能,但是不建议!token本身就是用来鉴权的,防止CSRF攻击。如果将token放在cookie中,则token还是会随cookie自动携带至请求中,防止不了CSRF攻击。token一般存储在sessionStorage/localStorage里面。token的出现就是为了解决用户登录后的鉴权问题,如果采用cookie+session的鉴权方式,则无法有效地防止CSRF攻击,同时,如果服务端采用负载均衡策略进行分布式架构,session也会存在一致性问题,需要额外的开销维护session一致性。所以token是专门为了鉴权而生,常见的token为JWT(JSON Web Token),用户通过账户密码登入系统后,服务端会生成一个jwt。jwt一般包含三个部分header、payload和signature,header包括两个字段说明token的类型和采用的签名算法,payload包含用户的一些身份权限信息但不包含敏感信息,signature是服务端的签名由前两个部分采用base64编码后再经过签名算法加密生成,签名算法的私钥由服务器保管。服务端生成jwt后返回给客户端。客户端下次调用api的请求头中放入token用于鉴权,服务端会通过jwt的前两个部分和私钥经过签名算法生成一个签名,判断与jwt第三部分的签名是否一致,如果一致就认证通过。

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

1)、typeof 用来判断基本数据类型,对于引用数据类型function 返回function其他 都返回Object. 

2)、instanceof 主要用于区分引用数据类型,检测方法是检测的类型再当前实例的原型链上,用其检测出来的结果都是true,不太适合检测简单数据类型的检测,检测过程繁琐且对简单数据类型中的undefined null symbol检测不出来。 

3)、constructor:用于检测引用数据类型,检测方法是获取实例的构造函数判断和某个类是否相同,如哦相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类也加入进来,避免了原型链的干扰。 

4)、Object.prototype.toString.call():适用于所有类型的检测,检测方法是Object.prototype.toString.call(数据)返回的是该数据类型的字符串。最精准的就是这个方法。

26. 说一说伪数组和数组的区别?

1).伪数组的特点,类型是object,不能使用数组方法,比如增删改,可以用for in遍历,可以获取长度,可以用下标获取对应元素.

2).伪数组转换为数组的方法Array.prototype.slice.call(),Array.from(),扩展运算符等等。

3).有哪些是伪数组,函数参数的arguments,获取的dom,Map和Set的keys()、values()和entires()。

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

直白点说,forEach是针对数组中每一个元素,提供一个可执行的函数操作,因此它(可能)会改变原数组中的值。不会返回有意义的值,或者说会返回undefined;而map是会分配内存空间创建并存储一个新的数组,新数组中的每一个元素由调用的原数组中的每一个元素执行所写的函数得来,返回的就是新数组,因此不会改变原数组的值; 

个人感觉map更加贴近于函数式编程的特点,而且执行起来也会比forEach快很多,所以我在二者都可的情况下会更推荐map。

28. 说一说es6中箭头函数?

箭头函数比普通函数的定义写法更加简洁明了和快捷。但是两者又有区别:箭头函数没有原型prototype和super,所以无法创建this,其this是通过继承外部函数环境中的变量获取的,所以call、bind、apply都无法改变其this的指向;在找不到最外层的普通函数时,其this一般指向window;箭头函数不能使用new;箭头函数没有arguments;也不能作为generator函数,不能使用yield命令;箭头函数不能用于对象域和回调函数动态this中,一般用在内部没有this引用。

29. 事件扩展符用过吗(...),什么场景下?

对象中的扩展运算符(...)用于取出参数对象中的所有可遍历的属性,浅拷贝到当前的对象中,浅拷贝和深拷贝:浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

1)数组克隆 let a = [1,2,3];let b = [...a] 

2).数组合并 let a = [1,2,3];let b = [4,5,6];let c = [...a,...b] 

3).类数组转成真正的数组 let a = new Set([1,2,3]); let b = [...a]

30. 说一说JS变量提升?

var声明会存在变量提示,函数声明也会提升,let,const不会存在变量提升问题 变量提升,函数声明提示,都是在js编译阶段提升的,将当前变量、函数提升至当前作用域的最顶端 并且由于编译阶段,并没有开始执行代码,所以都会被赋初始值undefined,由于函数不同,函数会在堆内存中开辟一个空间用于来保存函数的执行体,会将当前内存地址赋值给函数变量,所以可以在函数声明上面,来调用该函数 并且let,const声明变量会形成暂时性死区,就是在该作用域当中,无法在变量声明之前去访问,即使全局存在一个同名变量,也无法访问

 this当在普通函数调用时,如果处于非严格模式之中将会是指向window、如果是严格模式将会是指向undefined。 在对象之中调用,将会是指向当前的对象。通过new创建出来的对象将会是指向当前的新对象 如果是使用call、bind、apply修改了指向,将会指向绑定后的this 

箭头函数: 1、箭头函数没有this,不能作为构造函数,所以也没有原型和new 2、箭头函数的this取决于它的外部函数 3、没有arguments。

在箭头函数之中将会指向函数的外层执行上下文,当函数定义之后将会确定当前的this。继承了上一层执行环境的this。

31.说一说promise是什么与使用方法?

是什么: Promise是异步编程的一种解决方案,用于解决回调地狱问题,让代码可读性更高,更利于维护。 

使用方法: Promise有三种情况fulfilled/resolved(成功)、rejected(失败)、pending(执行中)。 Promise通过new Promise()将构造函数实例化,Promise构造函数接收一个函数作为参数,这个函数有两个参数resolve,reject,这两个参数也是方法,成功调用resolve(),失败调用reject(),实例创建完成后,可以使用then方法指定成功或者失败的回调函数,也可以用catch捕获失败,then和catch最后也是返回promise对象,所以可以链式调用。 promise.all(iterable):iterable是由多个promise实例组成的可迭代对象(如Array, Map, Set),当iterable中的promise实例都完成(resolved)返回所有成功的结果组成的数组,只要有一个回调执行失败,then是不会执行的,则在catch中回调第一个失败的结果。 promise.race(iterable):只要iterable中的其中一个promise实例resolved或者rejected,返回的promise就会resolved或者rejected。

32. 说一说JS实现异步的方法?

异步微任务:回调函数,promise, async/await 异步宏任务:setTimeout setIntervial。

所有异步任务都是在同步任务执行结束之后,从任务队列中依次取出执行。回调函数是异步操作的最基本方法,比如AJAX回调,回调函数的优点:简单,容易理解和实现,缺点:不利于代码的阅读和维护。而且每个任务只能指定一个回调函数。不能使用try catch 捕获错误,promise不仅能捕获错误,而且很好的解决了地狱回调问题,缺点是无法取消promise,错误需要通过回调函数捕获。Generator 函数是 ES6 提供的一种异步编程解决方案。async/await是基于Promise实现的,async/awt使得异步代码看起来像同步代码,所以优点是,使用方法清晰明了,缺点是awt 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 awt 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。

33. 说一说axios的拦截器原理及应用?

1.拦截器分为 请求(request)拦截器和 响应(response)拦截器。

2.请求拦截器用于在接口请求之前做的处理,比如为每个请求带上相应的参数(token,时间戳等)。

3).返回拦截器用于在接口返回之后做的处理,比如对返回的状态进行判断(token是否过期)

4).拦截器原理:创建一个chn数组,数组中保存了拦截器相应方法以及dispatchRequest(dispatchRequest这个函数调用才会真正的开始下发请求),把请求拦截器的方法放到chn数组中dispatchRequest的前面,把响应拦截器的方法放到chn数组中dispatchRequest的后面,把请求拦截器和相应拦截器forEach将它们分unshift,push到chn数组中,为了保证它们的执行顺序,需要使用promise,以出队列的方式对chn数组中的方法挨个执行。

5).从浏览器中创建 XMLHttpRequests,从 node.js 创建 http 请求,支持 Promise API,可拦截请求和响应,可转换请求数据和响应数据,可取消请求,可自动转换 JSON 数据,客户端支持防御 XSRF。

34. 说一说创建ajax过程?

我对 ajax 的理解是,它是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,更新网页的相应部分,而不用刷新整个页面的一种方法。 创建一个 ajax 有这样几个步骤 首先是创建一个 XMLHttpRequest 对象。 然后在这个对象上使用 open 方法创建一个 http 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户的认证信息。 在发起请求前,我们可以为这个对象添加一些信息和监听函数。比如说我们可以通过 setRequestHeader 方法来为请求添加头信息。我们还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发onreadystatechange 事件,我们可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代表服务器返回的数据接收完成,这个时候我们可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时候我们就可以通过 response 中的数据来对页面进行更新了。 当对象的属性和监听函数设置完成后,最后我们调用 send 方法来向服务器发起请求,可以传入参数作为发送的数据体。

35. 说一下fetch 请求方式?

fecth是一种HTTP数据请求的方式,是XMLHttpRequest的一种替代方式,Fetch函数就是原生js,没有使用XMLHttpRequest对象。Fetch对象返回一个promise解析response来自request显示状态的方法。

XMLHTTPRequest特点:

1)、所有功能集中在一个对象上,写的代码可维护性不强且容易混乱。

2)、不能适配新的promise API;

Fetch特点:

1)、精细的功能分割:头部信息,请求信息,响应信息等均分布在不同的对象上,可以处理各种复杂的数据交互场景。

2)、也可以适配promise API;

3)、同源请求也可以自定义不带cookie,某些服务不需要cookie的场景下能少些流量。

36. 说一下有什么方法可以保持前后端实时通信?

前后端一般通过HTTP协议进行交互,但HTTP协议是基于“问答模式”的,即客户端发起询问,服务端才会响应。但对于一些实时的场景,比如股票趋势图、直播...等,服务端更新数据的速度很快,如果每次都要客户端询问,这样传输数据的效率十分低下,所以得通过其它交互模式支持实时通信。实现实时通信有以下几种方式: 1.短轮询:客户端设置定时器,每隔几秒就向服务端发送请求,通过频繁地请求到达实时的效果。这种方式要求服务器的响应速度很快。 2.长轮询:客户端和服务端保持一条长连接,一旦服务端有新的数据,不等客户端请求就会主动发送给对方。这种方式要求服务器有高并发能力。 3.WebSocket:一种全双工通信协议,客户端和服务端处于相同的地位。通过客户端与服务端建立的HTTP连接进行切换,客户端会发送一个带update:websocket字段的HTTP请求请求协议切换,服务端会回复带101状态码的响应表示协议切换成功。接着它们使用websocket进行通信,一旦有新的数据服务端可以直接发送给客户端。 4.SSE(Server-Sent Event):服务端与客户端建立的一个单向通道,只能由服务端传输特定形式的数据给服务端,这里并不是建立一个长连接。

37. 说一下浏览器输入URL发生了什么?

【1、URL解析】:判断浏览器输入的是搜索内容还是URL 

【2、查找缓存】:如果能找到缓存则直接返回页面,如果没有缓存则需要发送网络请求页面 

【3、DNS域名解析】:将域名转换为IP地址的过程,得到了服务器具体的IP地址,才可以进行TCP链接以及数据的传输。 

【4、三次握手建立TCP连接】: 

- 第一次握手:客户端主动链接服务器,发送初始序列号seq=x与SYN=1同步请求标志,并进入同步已发送SYN_SENT状态,等待服务器确认。 

- 第二次握手:服务端收到消息后发送确认标志ACK=1与同步请求标志SYN=1,发送自己的序列号seq=y以及客户端确认序号ack=x+1,此时服务器进入同步收到SYN_RECV状态。 

- 第三次握手:客户端收到消息后发送确认标志ACK=1,发送自己的序列号seq=x+1与服务器确认号ack=y+1,发送过后即确认链接已建立状态ESTABLISHED,服务端接收确认信息后进入链接已建立状态ESTABLISHED。 

【5、发起HTTP请求】:浏览器构建HTTP请求报文,并通过TCP协议传送到服务器的指定端口,HTTP请求报文一共有三个部分 

- 报文首部,通常包含请求行与各种请求头字段等; 

- 空行,告诉服务器请求头部到此为止 

- 报文主体,即发送的数据信息,通常并不一定要有报文主体。 

【6、服务器响应并返回结果】:服务端响应HTTP请求,返回响应报文,HTTP响应报文由四部分组成:响应行、响应头、空行、响应体。 

【7、通过四次握手释放TCP连接】 

【8、浏览器渲染】 

【9、js引擎解析】

38 说一下浏览器如何渲染页面的?

1). 获取html文件后,渲染引擎会对将HTML代码解析为DOM结构,同时获取CSS;

2). 将获取到的CSS解析为CSSOM树,与DOM树并发解析;

3).解析JS脚本对DOM树和CSSOM树进行修改。

4).将DOM树和CSSOM树合成为render树 。

5).对render树上节点进行布局,确定其位置,如果元素位置后续被改变而触发重新绘制叫做回流。

6).对摆好位置的节点进行色彩渲染,如果元素样式后续被改变而触发页面重新绘制叫做重绘。

7).加载剩余的img,video等媒体文件。

39. 说一下重绘、重排区别如何避免?

重排:当dom的变化影响元素的几何信息(元素的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面的正确的位置。这个过程叫重排。重绘:一个元素外观发生改变,但没有改变布局,重新把元素绘制出来的过程。重排一定会发生重绘,重绘就不一定要重排,重排对性能的消耗要更多一些。触发重排的方法:改变元素的位置;改变元素的尺寸,比如边距,填充,边框,宽度,高度等;页面初始渲染,这是开销最大的一次重排;改变元素的字体大小;改变浏览器窗口大小,比如resize事件发生时;改变元素内容,字体数量,图片大小;激活css伪类;设置style属性的值,因为设置style属性会改变结点样式,就会触发重排。查询某些属性、或者调用某些计算方法:offsetwidth,offheight。避免重排的方法是样式集中改变;使用absolute或fixed脱离文档流;使用gpu加速,transform。

40. 说一下浏览器垃圾回收机制?

浏览器的垃圾回收机制就是我们程序在工作的过程中或者执行之后,会产生一些用不到的内存变量,这些变量会一直占据这内存,所以这时候需要垃圾回收机制帮我清理这些用不到的变量,从而释放内存空间。垃圾回收机制最常见的有两种:1.标记清除算法。2:引用计数算法。标记清除算法就是执行前将所有的变量打上标记,执行完成后未被打上标记的变量就会当做垃圾回收。浏览器会隔一段时间进行一次标记清除标记清除算法的缺点:被释放的掉的内存空间是不连续被,之前一直占据内存的变量隔开,导致内存空间不连续,会出现分配不一的问题。引用计数算法:就是对对象的引用次数进行增加或者减少,如果引用次数变为0,那么该对象分配的内存空间立即就会被清除掉。缺点就是会出现相互引用的情况。会导致对象一直被引用无法被清除。

41. 说一说事件循环Event loop,宏任务与微任务?

浏览器的事件循环:执行js代码的时候,遇见同步任务,直接推入调用栈中执行,遇到异步任务,将任务挂起,等到异步任务有返回之后推入到任务队列中,当调用栈中所以同步任务全部执行完成,将任务队列中的任务按顺序一个一个推入并执行,重复执行这一系列的行为。异步任务又分为宏任务和微任务。宏任务;任务队列中的任务称为宏任务,每个宏任务都包含了一个微任务队列。微任务:等宏任务中的主要功能都完成后,渲染引擎不急着去执行下一个宏任务,而是执行当前宏任务中的微任务。宏任务包含script标签的内部代码、setTimeout/setInterval,ajax请求、post Message,MessageChannel,setlmmediate,I/O(Node.js);微任务包含:Promise,MutonObserver,Object.observe,process.nextTick(Node.js).

42. 说一说跨域是什么?如何解决跨域问题?

跨域:受浏览器同源策源(即协议,域名,端口号必须一致是浏览器的一种安全模式)的影响不能执行其他网站的脚本 解决的办法:前端:1.利用script中src属性发送jsonp请求但是这种方式只支持get请求无法解决其他的请求类型 2.前端配置一个代理服务器(proxy)代替浏览器去发送请求:因为服务器与服务器之间是可以通信的不受同源策略的影响。 服务端:服务端通过配置cors来解决跨域,配置响应头:setHeader(access-control-allow-origin,对应的域名)

四、vue

1. 说一说vue钩子函数?

Vue实例需要经过创建、初始化数据、编译模板、挂载DOM、渲染、更新、渲染、卸载等一系列过程,这个过程就是Vue的生命周期,在Vue的整个生命周期中提供很多钩子函数在生命周期的不同时刻调用,Vue中提供的钩子函数有: 

挂载阶段: 

- beforeCreate:创建实例之前 

- created:实例创建完成(执行new Vue(options)),可访问data、computed、watch、methods上的方法和数据,可进行数据请求,未挂载到DOM结构上,不能获取el属性,如果要进行dom操作,那就要用nextTick函数 

- beforeMount:在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数 

- mounted:实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,可进行数据请求 

更新阶段: 

- beforeUpdate:响应式数据更新时调用,发生在虚拟DOM打补丁之前,适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器 

- updated:虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新 

销毁阶段: 

- beforeDestroy:实例销毁之前调用,this仍能获取到实例,常用于销毁定时器、解绑全局事件、销毁插件对象等操作 

- destroyed:实例销毁之后 

父子组件执行顺序: 

- 挂载:父created -> 子created -> 子mounted> 父mounted 

- 更新:父beforeUpdate -> 子beforeUpdated -> 子updated -> 父亲updated 

- 销毁:父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

一旦进入到页面或者组件,会执行的生命周期,顺序:

 beforeCreate、 created、 beforeMount、 mounted

 在哪个阶段有$el,在哪个阶段有$data

    beforeCreate 啥也没有

    created  有data没有el

    beforeMount 有data没有el

    mounted 都有

如果加入了keep-alive会多俩个生命周期

    activated、deactivated

如果加入了keep-alive,第一次进入组件会执行哪些生命?

 beforeCreate、created、 beforeMount、 mounted、 activated

如果加入了keep-alive,第二次或者第N次进入组件会执行哪些生命周期?

只执行一个生命周期:activated

2. 说一说组件通信的方式?

Vue的组件通信一般分为三大类。1:父子组件通信,最常见的使用props和emit,父组件通过props将数据传递给子组件,子组件通过emit触发父组件中的方法来修改数据。其次还可以通过$ref、$parent和$child进行通信。2:祖孙组件之间的通信:这种通信一般也可以用props和emit只不过逐层传递会很麻烦,可以可以使用$listener和$attrs来进行通信。3:兄弟组件之间的通信:可以创建eventBus事件总线,通过$emit和$on的方式进行通信。其次还有全局数据通信,我们可以使用Vuex作为全局状态管理来实现。

3. 说一说computed和watch的区别?

1)、功能上:computed是计算属性,watch是监听一个值的变化,然后执行对应的回调。

 2)、是否调用缓存:computed中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取,而watch在每次监听的值发生变化的时候都会执行回调。 

3)、是否调用return:computed中的函数必须要用return返回,watch中的函数不是必须要用return。 

4)、computed默认第一次加载的时候就开始监听;watch默认第一次加载不做监听,如果需要第一次加载做监听,添加immediate属性,设置为true(immediate:true)

 5)、使用场景:computed----当一个属性受多个属性影响的时候,使用computed-----购物车商品结算。watch–当一条数据影响多条数据的时候,使用watch-----搜索框。

4. 说一说 v-if 和 v-show区别?

【概念】 

- v-if指令与v-show指令都可以根据值动态控制DOM元素显示隐藏,v-if和v-show属于Vue的内部常用的指令,指令的职责是当表达式的值改变时把某些特殊的行为应用到DOM上。 

- v-if指令用于条件性地渲染一块内容,这块内容只会在指令的表达式返回truthy值的时候被渲染。 

- v-show指令用法大致一样,不同的是带有v-show指令的元素始终会被渲染并保留在DOM中,v-show只是简单地切换元素的CSS property display。 

【区别】 

- 实现方式: v-if是动态的向DOM树内添加或者删除DOM元素,v-show是通过设置DOM元素的display样式属性控制显隐。 

- 编译过程: v-if切换有一个局部编译卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件,v-show只是简单的基于CSS切换。 

- 编译条件: v-if是惰性的,如果初始条件为假,则什么也不做,只有在条件第一次变为真时才开始局部编译, v-show是在任何条件下都被编译,然后被缓存,而且DOM元素保留。 

- 性能消耗: v-if有更高的切换消耗,v-show有更高的初始渲染消耗。 

- 使用场景: v-if适合条件不太可能改变的情况,v-show适合条件频繁切换的情况。

5. 说一说 vue 的 keep-alive ?

是什么:

vue系统自带的一个组件,功能:是来缓存组件的。===》提升性能

使用场景:

就是来缓存组件,提升项目的性能。具体实现比如:首页进入到详情页,如果用户在首页每次点击都是相同的,那么详情页就没必要请求N次了,直接缓存起来就可以了,当然如果点击的不是同一个,那么就直接请求。

1).效果方面:能在内存保持其中的组件状态,放置重复渲染DOM,减少加载时间,从而提高性能。

2).使用方面:有三个属性:include:只有匹配的组件才会被保存。exclude:只有匹配的组件才不会被保存。max:最多能保存的组件数。

3).结合Router使用:可以在相应组件下规定mate属性,并将keep-alive设置为true。

4).源码实现方面:可以结合Vue组件实例加载顺序讲解,VNode->实例化->_updata->真实Node,在实例化的时候会判断该组件是否被keep-alive保存过,是的话则直接拿其中的DOM进行渲染。

6. 说一说 Vue 中 $nextTick 作用与原理?

作用:Vue在更新DOM时是异步执行的,在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。所以修改完数据,立即获取DOM,获取的仍然是未修改的DOM,$nextTick中的代码会在当前渲染完成之后执行,就解决了异步渲染获取不到更新后DOM的问题了

原理:

1).$nextTick把回调函数放入callback等待执行。

2).将执行函数放到微任务或者宏任务中。

3).事件循环到了微任务或者宏任务,执行函数一次执行callback中的回调。本质是返回一个Promise

7. 说一说 Vue 列表为什么加 key?

Vue列表加key的目的是为diff算法添加标识,因为diff算法判断新旧VDOM是否相同的依据是节点的tag和key。如果tag和key相同则会进一步进行比较,使得尽可能多的节点进行复用。此外,key绑定的值一般是一个唯一的值,比如id。如果绑定数组的索引index,则起不到优化diff算法的作用,因为一旦数组内元素进行增删,后续节点的绑定的key也会发生变化,导致diff进行多余的更新操作。

1).虚拟DOM中key的作用:

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据[新数据]生成[新的虚拟DOM],随后Vue进行[新虚拟DOM] 与[旧虚拟DOM]的差异比较,比较规则如下:

2).对比规则:

(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:

a.若虚拟DOM中内容没变,直接使用之前的真实DOM

b.若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM

(2).旧虚拟DOM中未找到与新虚拟DOM相同的key:

创建新的真实DOM,随后渲染到到页面。

3).用index作为key可能会引发的问题:

a.若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低

b.如果结构中还包含输入类的DOM,会产生错误DOM更新 ==> 界面有问题。

4).开发中如何选择key?:

最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。

如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key也是没有问题的

8. 说一说vue-router 实现懒加载的方法?

【概念】 对于SPA单页应用,当打包构建时,JavaScript包会变得非常大,影响页面加载速度,将不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这就是路由的懒加载。 

【三种方法】 

1). Vue的异步组件:Vue允许以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。 

component:resolve => (require(['./home.vue'], resolve)); 

2). webpack提供的require.ensure:使用webpack的require.ensure,也可以实现按需加载,同样多个路由指定相同的chunkName也会合并打包成一个js文件。 

component: resolve => require.ensure([], () => resolve(require("@/views/example1.vue")), "Example") 

3). 动态import:在Webpack2中,可以使用动态import语法来定义代码分块点split point,官方也是推荐使用这种方法,如果使用的是Bable,需要添加syntax-dynamic-import插件, 才能使Babel可以正确的解析语法。 

const Home = () =>import('../components/home.vue') 

component: () => import("@/views/example.vue")

9. 说一说 HashRouter 和 HistoryRouter的区别和原理?

HashRouter 和 HistoryRouter 是前端路由的两种模式,前端路由适用于SPA,首次加载在浏览器上获取页面所有资源,后续跳转由前端实现。

Hash模式和History模式有以下不同:

 1).形式上,hash模式中URL存在'#'符号,而history模式的ULR则没有。 

2).history是H5新增,需要后端协助实现。 原理: Hash模式通过监听hashChange事件监听URL的hash值的修改实现对应跳转。 History模式通过H5提供的pushState,replaceState...等实现URL的修改和对应跳转,也可以通过监听popState事件来监听用户点击前进、后退按钮实现对应跳转。History模式实际上是利用浏览器的历史记录栈来实现。在项目过程中遇见采用History模式再刷新页面会导致404的问题,因为Hash模式#后面的hash值不会带入请求URL中,所以服务器觉得Hash模式下的URL是不变的;而History模式URL在/a /b 之间不断变化,必须要服务器对这些请求进行重定向,一直返回一个指定页面即可。

10. 说一说Vuex是什么,每个属性是干嘛的,如何使用?

Vuex是为Vue开发的管理状态模式,集中存储管理所有组件的状态。属性分别由state、getters、mutations、actions、module。 

(1)state用来定义所要存储的数据,通过$store.state调用数据 

(2)getters可以认为是store的计算属性,通过$store.getters调用属性

 (3)mutations用来存放改变state数据的同步方法,每个方法接收的参数都是state,用$store.commit来调用mutations中的同步方法 

(4)actions用来存放异步方法,接收的参数是context(mutations中的方法),通过$store.dispatch调用actions中的方法 

(5)module将store分割成模块,每个模块有自己的state、getters、mutations、actions、甚至嵌套子模块,从上至下进行同步分割 前四个属性除了用$store的方法调用,还能通过import { mapState/mapGetters/... } from 'vuex'引入,再用...mapState/mapGetter/...(['属性/方法名'])的形式映射进来调用。

11. 说一说Vue2.0 双向绑定的原理与缺陷?

原理:通过数据劫持defineProperty+发布订阅者模式,当vue实例初始化后observer会针对实例中的data中的每一个属性进行劫持并通过defineProperty()设置值后在get()中向发布者添加该属性的订阅者,这里在编译模板时就会初始化每一属性的watcher,在数据发生更新后调用set时会通知发布者notify通知对应的订阅者做出数据更新,同时将新的数据根性到视图上显示。缺陷:只能够监听初始化实例中的data数据,动态添加值不能响应,要使用对应的Vue.set()。

12. 说一说Vue3.0 实现数据双向绑定的方法 ?

【Vue3.0 数据双向绑定】

1). 在Vue2.0的基础上将Object.definedproperty换成了功能更强大的Proxy,原理相同。在vue实例初始化的时候(vm._init()执行的时候)调用Observe类的实例方法observe,传入数据(若是对象则发生递归),将其中每个数据进行一遍数据劫持(get实现依赖收集,set实现事件派发(这里的模式为发布订阅模式))。 

2). 相对vue2.0解决的问题:解决无法监听新增属性或删除属性的响应式问题、解决无法监听数组长度和index变化问题。 

【关于Proxy】 

1). Proxy是ES6新增的一个特性,实现的过程是在目标对象之前设置一层“拦截”,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。 

2). 用法:ES6原生提供Proxy构造函数,用来生成Proxy实例。var proxy=new Proxy(target,handler)target是用Proxy包装的被代理对象(可以是任何类型的对象,包含原生数组,函数,甚至是另一个代理)。handler是一个对象,其声明了代理target的一些操作,其属性是当执行一个操作时定义代理的行为的函数。

13. 说一下Diff算法?

【diff算法的理解】 

diff算法用来计算出Virtual DOM中改变的部分,然后针对该部分进行DOM操作,而不用重新渲染整个页面,渲染整个DOM结构的过程中开销是很大的,需要浏览器对DOM结构进行重绘与回流,而diff算法能够使得操作过程中只更新修改的那部分DOM结构而不更新整个DOM,这样能够最小化操作DOM结构,能够最大程度上减少浏览器重绘与回流的规模。 

【原理】 

diff算法:当数据发生改变时,set方法让调用Dep.notify通知所有订阅者Watcher数据发生更新,订阅者就会调用patch进行比较,然后将相应的部分渲染到真实DOM结构。 

虚拟DOM:diff算法的基础是Virtual DOM,Virtual DOM是一棵以JavaScript对象作为基础的树,每一个节点称为VNode,用对象属性来描述节点,实际上它是一层对真实DOM的抽象,最终可以通过渲染操作使这棵树映射到真实环境上,简单来说Virtual DOM就是一个Js对象,用以描述整个文档。 在浏览器中构建页面时需要使用DOM节点描述整个文档。 

【修改原则】 

vue优化优化时间复杂度O(n)是通过一定策略进行的,React中提到了两个假设,在Vue中同样适用: 

- 两个不同类型的元素将产生不同的树。 

- 通过渲染器附带key属性,开发者可以示意哪些子元素可能是稳定的。 

通俗点说就是: 

- 只进行统一层级的比较,如果跨层级的移动则视为创建和删除操作。 

- 如果是不同类型的元素,则认为是创建了新的元素,而不会递归比较他们的孩子。 

- 如果是列表元素等比较相似的内容,可以通过key来唯一确定是移动还是创建或删除操作。 

比较后会出现几种情况,然后进行相应的操作: 

- 此节点被添加或移除->添加或移除新的节点。 

- 属性被改变->旧属性改为新属性。 

- 文本内容被改变->旧内容改为新内容。 

- 节点tag或key是否改变->改变则移除后创建新元素。

14.vue优化

1). keep-alive 缓存组件

2). 路由懒加载

3). 内容使用

    v-if和v-show

    computed、watch、methods

4). Object.freeze :冻结对象

    纯展示类的接口数据,冻结就可以了

5). 使用ui组件按需引入

15. 说一说vue2和vue3区别

Vue.js是一个流行的JavaScript框架,用于构建用户界面。Vue.js有两个主要版本,即Vue 2和Vue 3。它们之间有一些重要的区别,包括以下几点:

1). 性能优化:Vue 3在性能方面进行了一些改进。它引入了响应式系统的重写,使用了Proxy代理对象,这使得Vue 3的性能比Vue 2更好。

2). 组合式API:Vue 3引入了组合式API,这是一种新的API风格,可以更好地组织和重用组件逻辑。相比之下,Vue 2使用的是选项式API。

3). TypeScript支持:Vue 3对TypeScript的支持更好。它提供了更好的类型推断和类型检查,使得在使用TypeScript时更容易开发和维护Vue应用程序。

4). Teleport组件:Vue 3引入了Teleport组件,它可以将组件的内容渲染到DOM中的任何位置,而不仅仅是组件所在的位置。这在处理模态框、弹出菜单等场景时非常有用。

5). Fragment组件:Vue 3引入了Fragment组件,它允许在不添加额外DOM元素的情况下渲染多个根元素。这在编写更具语义性的模板时非常有用。

6). 其他改进:Vue 3还引入了一些其他改进,如更好的Tree Shaking支持、更好的错误处理、更好的自定义指令等。

总结起来,Vue 3在性能、API设计、TypeScript支持等方面都有一些重要的改进。如果你正在开始一个新的项目,考虑使用Vue 3可能是一个不错的选择。

16. 在beforeMount中如何获取dom

在beforeMount中无法直接获取DOM

在模板中给需要获取的DOM元素添加ref属性,然后在beforeMount钩子函数中通过this.$refs来访问DOM元素。



在上述示例中,我们给div元素添加了ref属性,并在beforeMount钩子函数中通过this.$refs.myDiv来获取DOM元素。注意,这种方式只能在Vue组件中使用。

17. Vite使用过吗?说一说Vite与webpack区别

Vite是一个基于ES模块的构建工具,它专注于快速的冷启动和热更新。与Vite相比,Webpack是一个功能强大的打包工具,它可以处理各种类型的文件,并提供了丰富的插件生态系统。

Vite与Webpack的区别主要体现在以下几个方面:

1). 构建速度:Vite利用浏览器对ES模块的原生支持,采用了unbundle的机制,即在开发环境下不进行打包,而是直接使用ES模块的方式加载依赖,从而实现了快速的冷启动和热更新。相比之下,Webpack在开发环境下需要进行打包,因此启动速度相对较慢。

2). 开发体验:Vite支持按需编译,只编译当前正在编辑的文件,而不需要重新编译整个项目。这样可以提高开发效率。而Webpack需要对整个项目进行编译,无论是否修改了文件,都需要重新编译。

3). 构建结果:Vite在生产环境下会将所有的依赖打包成一个或多个静态资源文件,以提供更好的加载性能。而Webpack会将所有的依赖打包成一个或多个bundle文件,需要在浏览器中进行解析和加载。

总的来说,Vite在开发环境下具有更快的启动速度和热更新能力,而Webpack则更适合处理复杂的项目和构建需求。

18. pinia使用过吗?说一说vuex与pinia区别

Pinia在设计上支持更好的类型推断和类型安全。由于Vue 3中引入了TypeScript的原生支持,Pinia能够更好地与TypeScript集成,并提供更好的类型定义和类型检查。相比之下,Vuex也可以使用TypeScript,但是它的类型推断相对较弱,并且需要手动添加类型注解。

Vuex和Pinia是Vue.js的两个状态管理库,它们有一些区别:

1). Vue版本支持:Vuex是Vue.js官方推荐的状态管理库,适用于Vue 2.x版本。而Pinia是为Vue 3.x版本设计的状态管理库。

2). 性能和体积:Pinia在性能和体积方面进行了优化,相对于Vuex来说更加轻量级。Pinia使用了Vue 3的新特性,如Proxy和Reactivity API,以提供更高的性能和更小的包大小。

3). 语法和API:Vuex使用基于对象的API,通过定义state、mutations、actions和getters来管理状态。而Pinia使用类和装饰器来定义状态和操作,使代码更具可读性和可维护性。

4). TypeScript支持:Pinia对TypeScript提供了更好的支持,可以更方便地进行类型检查和代码提示。Vuex也支持TypeScript,但相对来说不如Pinia完善。

综上所述,选择Vuex还是Pinia取决于你的项目需求和使用的Vue版本。对于Vue 3项目,如果更注重性能和体积方面的优化,可以考虑使用Pinia。而对于Vue 2项目或更喜欢Vuex的丰富生态系统和广泛支持的开发者来说,Vuex仍然是一个强大而可靠的选择。

19. vue如何解决seo的问题

Vue可以通过使用vue-meta-info插件来解决SEO问题。该插件允许您在Vue组件中定义页面的元信息,例如标题、关键字和描述等。您可以在每个组件中使用metaInfo方法来定义元信息。

以下是一个示例,展示了如何在Vue页面中使用vue-meta-info插件来解决SEO问题:

// main.js
import Vue from 'vue'
import MetaInfo from 'vue-meta-info'
Vue.use(MetaInfo)
// 你的组件
export default {
  metaInfo() {
    return {
      title: 'Your Page Title',
      meta: [
        {
          name: 'keywords',
          content: 'keyword1, keyword2, keyword3'
        },
        {
          name: 'description',
          content: 'Your page description'
        }
      ]
    }
  }
}

在上面的示例中,您可以在metaInfo方法中返回一个对象,该对象包含了页面的元信息。您可以定义页面的标题、关键字和描述等。这些元信息将被插件自动注入到页面的头部。

通过使用vue-meta-info插件,您可以轻松地为每个页面定义不同的元信息,从而优化您的网站的SEO效果。

20. vue如何分包

在Vue项目中,可以通过修改vue.config.js文件来配置分包策略。以下是两个简单的配置示例:

1. 分包示例一:将所有的第三方库单独打包成一个vendor.js文件,将echarts库单独打包成一个echarts.js文件。

module.exports = {
  configureWebpack: {
    optimization: {
      splitChunks: {
        cacheGroups: {
          vendor: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendor',
            chunks: 'all',
          },
          echarts: {
            test: /[\\/]node_modules[\\/]echarts[\\/]/,            name: 'echarts',
            chunks: 'all',
            priority: 30,
          },
        },
      },
    },
  },
};

2. 分包示例二:将echarts库单独打包成一个echarts.js文件,其他第三方库按默认配置进行分包。

module.exports = {
  configureWebpack: {
    optimization: {
      splitChunks: {
        cacheGroups: {
          echarts: {
            test: /[\\/]node_modules[\\/]echarts[\\/]/,
            name: 'echarts',
            chunks: 'all',
            priority: 30,
          },
          default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true,
          },
        },
      },
    },
  },
};

这样配置后,打包时会将指定的库单独打包成一个文件,可以减小打包后的文件体积,提高页面加载速度。

21. Vue3自己封装过组件吗?如何设计一个modal组件

在Vue3中,可以使用Composition API来封装组件。下面是一个简单的示例,展示如何设计一个Modal组件:




//使用该Modal组件的示例:


22. vue3提升性能有哪些点

Vue 3提升性能的几个关键点如下:

1). 源码体积的优化:Vue 3移除了一些不常用的API,例如`inline-template`和`filter`,并使用了tree-shaking来优化源码体积。

2). Composition API:Vue 3引入了Composition API,它是基于函数的API,可以更灵活地组织组件的逻辑。相比于Vue 2.x使用的Options API,Composition API在大型项目中更容易拆分和重用。

3). Proxy代替Object.defineProperty:Vue 3使用Proxy来代替Vue 2.x中使用的Object.defineProperty。Proxy具有更强大的功能和更好的性能,可以更方便地监听对象的变化。

4). 编译器优化:Vue 3的编译器进行了优化,生成的代码更加高效,执行速度更快。

5). 静态树提升:Vue 3通过静态树提升(Static Tree Hoisting)来减少虚拟DOM的创建和比对,从而提高渲染性能。

6). 更好的响应式系统:Vue 3的响应式系统进行了重写,使用了Proxy来实现更高效的数据监听和更新。

7). 更好的组件更新策略:Vue 3引入了基于模板的更新策略,可以更准确地判断组件是否需要更新,从而减少不必要的渲染。

8). 缓存事件处理函数:Vue 3引入了缓存事件处理函数的机制,可以减少事件处理函数的创建和销毁,提高性能。

23. Vue 3.0 性能提升主要是通过哪几方面体现的?

Vue 3.0 的性能提升主要体现在以下几个方面:

1). 响应式系统的改进:Vue 3.0 使用了 Proxy 对象重写了响应式系统,相比于 Vue 2.x 中使用 defineProperty 的方式,Proxy 对象具有更好的性能。Proxy 对象可以拦截属性的访问、赋值、删除等操作,而不需要在初始化时遍历所有属性。此外,对于多层属性嵌套的情况,只有在访问某个属性时才会递归处理下一级的属性,避免了不必要的性能开销。

2). 缓存事件处理函数:Vue 3.0 引入了缓存事件处理函数的机制,可以减少事件处理函数的创建和销毁次数,提高性能。

3). 源码体积的优化:Vue 3.0 移除了一些不常用的 API,例如 inline-template、filter 等,通过使用 tree-shaking 技术,可以在打包时去除未使用的代码,减小源码体积,提高性能。

24. Vue 3.0 所采用的 Composition Api 与 Vue 2.x使用的Options Api 有什么区别?

Vue 3.0采用的Composition API与Vue 2.x使用的Options API有以下区别:

1). 组织代码的方式不同:
   - Options API:将相关的选项(如data、methods、computed等)放在一个对象中,导致代码在逻辑上分散。
   - Composition API:将相关的逻辑组织在一起,使代码更加模块化和可维护。

2). 数据和方法的声明方式不同:
   - Options API:通过在Vue实例中声明data、methods等选项来定义数据和方法。
   - Composition API:使用函数来定义数据和方法,并通过ref和reactive等函数进行响应式处理。

3). 生命周期钩子函数的使用方式不同:
   - Options API:通过在Vue实例中定义生命周期钩子函数来处理相应的逻辑。
   - Composition API:使用onMounted、onUpdated等函数来处理相应的逻辑。

4). 组件复用的方式不同:
   - Options API:通过mixins来实现组件的复用。
   - Composition API:通过函数的方式来实现组件的复用。

5). 对TypeScript的支持不同:
   - Options API:对TypeScript的支持较弱,需要额外的类型声明。
   - Composition API:对TypeScript的支持更好,可以更方便地进行类型推断和类型检查。

6). 性能优化方面的改进:
   - Composition API:通过使用reactive和computed等函数,可以更精确地控制组件的更新,提高性能。

下面是一个示例,展示了Vue 3.0中使用Composition API的代码:



25. Proxy 相对于 Object.defineProperty 有哪些优点?

Proxy 相对于 Object.defineProperty 的优点有:
1). 功能更强大:Proxy提供了更灵活的方式来操作对象,可以捕获和定制对象的各种操作,包括获取属性、设置属性、删除属性等。
2). 捕获多种操作:Proxy可以捕获多种不同的操作,例如get、set、delete、has、apply等,而Object.defineProperty只能监视属性值的变化。
3). 更透明的操作:使用Proxy可以在不干扰现有代码逻辑的情况下修改对象的行为。
4). 易于使用:Proxy提供了一个相对简单的API,使我们能够轻松创建代理对象,它的语法更直观。
5). 性能更好:Proxy的性能通常比Object.defineProperty更好,因为它是JavaScript引擎的一部分,可以进行更有效的优化。

26. Vue 3.0 在编译方面有哪些优化?

Vue 3.0 在编译方面进行了以下优化:

1). 响应式递归优化:Vue 3.0 在 Proxy API 中使用了响应式递归。当需要进行响应式递归时,才会进行递归操作,这样大大提升了性能。

2). 编译优化:Vue 3.0 对编译过程进行了优化,特别是在 patch 阶段。Vue 2.x 中的数据更新和重新渲染是以组件级别为颗粒度的,而 Vue 3.0 则对 patch 阶段进行了优化,减少了耗时。

3). 移除不常用的 API:Vue 3.0 移除了一些不常用的 API,例如 inline-template 和 filter 等。这样可以通过 tree-shaking 进行优化,减少打包体积。

4). Composition API:Vue 3.0 引入了 Composition API,与 Vue 2.x 使用的 Options API 相比,Composition API 更加灵活和可组合,可以更好地组织和复用代码。

这些优化措施使得 Vue 3.0 在性能和开发体验方面都有了显著的提升。

27.v-if和v-for优先级

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

是在源码中体现的:function genElement

​28.ref是什么?

来获取dom的

​29.scoped原理

1). 作用:让样式在本组件中生效,不影响其他组件。

2). 原理:给节点新增自定义属性,然后css根据属性选择器添加样式。

​30.Vue中如何做样式穿透

stylus样式穿透使用:>>>

sass和less使用:/deep/

通用使用:  :v-deep

31.​Vue组件传值

//1.父组件-->子组件:
    // 父组件:
        
        export default {
        components: {
            UserDetail
        }
    }
  //在子组件中使用props(可以是数组也可以是对象)接收即可。可以传多个属性。
      export default {
      props: ['myName']
         }

//2.子组件-->父组件:
        // 子组件
      
      export default {
          methods: {
              //子组件的事件
              changeParentName: function() {
                  this.$emit('handleChange', 'Jack')
              }
          }
      }
        //父组件
      
      methods: {
          changeName(name) {  
              this.name = name
          }
      }
//3.兄弟组件之间:
    bus.js

32.computed、methods、watch有什么区别?

1). computed vs methods区别

    computed是有缓存的

    methods没有缓存

2). computed vs watch区别

    watch是监听,数据或者路由发生了改变才可以响应(执行)

    computed计算某一个属性的改变,如果某一个值改变了,计算属性会监听到进行返回

    watch是当前监听到数据改变了,才会执行内部代码

33.​props和data优先级谁高?

props ===>  methods ===> data ===> computed ===>watch

​ 34.Vuex有哪些属性?

state、getters、mutations、actions、modules

state 类似于组件中data,存放数据

getters 类型于组件中computed

mutations 类似于组件中methods

actions 提交mutations的

modules 把以上4个属性再细分,让仓库更好管理

​35.Vuex是单向数据流还是双向数据流?

Vuex是单向数据流

​36.Vuex中的mutaitons和actions区别

mutaitons   :  都是同步事物

actions     :  可以包含任意异步操作

​37.Vuex如何做持久化存储

Vuex本身不是持久化存储

1. 使用localStorage自己写

2. 使用vuex-persist插件

38.Vue设置代理

//vue.config.js
module.exports = {
  publicPath:'./',
  devServer: {
    proxy: 'http://localhost:3000'
  }
}

39.Vue路径传值

1). 显式:  http://localhost:8080/about?a=1

//传值:
this.$router.push({
              path:'/about',
              query:{
                  a:1
              }
          })
 //接:
this.$route.query.a

2). 隐式:http://localhost:8080/about  

 //传值:
this.$router.push({
              name:'About',
              params:{
                  a:1
              }
          })
  //接收:
this.$route.params.a

40.路由导航守卫有哪些   

全局、路由独享、组件内

1). 全局

    beforeEach、beforeResolve、afterEach

2). 路由独享

    beforeEnter

3). 组件内

    beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

使用场景:判断是否登录,如果登录就next否则就跳转到登录页面

41.​Vue动态路由

//场景:详情页(文章、商品)
router.js配置:
    {
    path: "/list",
    name: "List",
    children:[
      {
        path:"/list/:id",
        name:'Details',
        component: () =>
          import("../views/Details.vue"),
      }
    ],
    component: () =>
      import("../views/List.vue"),
  }

​42. 双向绑定原理

vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。

通过Object.defineProperty()来实现数据劫持的。

1.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

2.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

3.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

​ 43.讲一下MVVM

M就是data的model层

V就是view视图层

VM就是理解为v-model原理实现,通过view更新model

​ 44.不校验URL

工具==》详情==》本地设置==》不校验合法域名  : 项目上线前URL一定要请求到(不勾选也可以请求到数据)

  五、ES6

1.var、let、const区别

var、let、const 共同点都是可以声明变量的

区别一:

    var 具有变量提升的机制

    let和const没有变量提升的机制

区别二:

    var 可以多次声明同一个变量

    let和const不可以多次声明同一个变量

区别三:

    var、let声明变量的

    const声明常量

    var和let声明的变量可以再次赋值,但是const不可以再次赋值了。

区别四:

    var声明的变量没有自身作用域

    let和const声明的变量有自身的作用域

​2.作用域

//考题一:let和const没有变量提升性
console.log( str );//undefined
var str = '你好';
console.log( num );//报错
let num = 10;
//考题二:
function demo(){
    var n = 2;
    if( true ){
        var n = 1;
    }
    console.log( n );//1
}
demo();
function demo(){
    let n = 2;
    if( true ){
        let n = 1;
    }
    console.log( n );//2
}
demo();
//考题三:可以修改
const obj = {
    a:1
}
obj.a = 11111;
console.log( obj )
const arr = ['a','b','c'];
arr[0]= 'aaaaa';
console.log( arr )

​3.将下列对象进行合并

//方式一:Object.assign
const a = {a:1,b:4};
const b = {b:2,c:3};
let obj1 = Object.assign(a,b);
console.log( obj1 );
//方式二:
let obj2 = {...a,...b};
console.log( obj2 );
//方式三:自己封装方法
function extend( target,  source ){
    for(var key in source){
        target[key] = source[key];
    }
    return target;
}
console.log( extend(a,b) );

4.箭头函数和普通函数有什么区别?

1). this指向的问题

    箭头函数中的this只在箭头函数定义时就决定的,而且不可修改的(call、apply、bind)

    箭头函数的this指向定义时候、外层第一个普通函数的this

2). 箭头函数不能new(不能当作构造函数)

3). 箭头函数prototype

4). 箭头函数arguments

5.Promise有几种状态

有三种状态:

pending(进行中)

fulfilled(已成功)

rejected(已失败)

​6.find和filter的区别

区别一:返回的内容不同

    filter 返回是新数组

    find   返回具体的内容

区别二:

    find :匹配到第一个即返回

    filter : 返回整体(没一个匹配到的都返回)

​ 7.some和every的区别

some  ==> 如果有一项匹配则返回true

every ==> 全部匹配才会返回true

六、TypeScript

1. ts和js有什么不同?

TypeScript和JavaScript有以下几个不同之处:

1). 类型系统:JavaScript的类型系统存在一些缺陷,容易导致类型错误。而TypeScript是JavaScript的超集,它引入了静态类型检查,可以在编译时发现并修复类型错误,提高代码的可靠性和可维护性。

2). 编译:JavaScript是一种解释型语言,代码在运行时逐行解释执行。而TypeScript需要先编译成JavaScript,然后才能在浏览器或其他JavaScript运行环境中执行。

3). 语法扩展:TypeScript在语法上对JavaScript进行了扩展,引入了一些新的特性和语法糖,例如类、接口、泛型等,使得代码更加结构化和可读性更高。

4). 工具支持:TypeScript提供了更强大的开发工具支持,包括代码补全、静态类型检查、重构等功能,可以提高开发效率和代码质量。

5). 生态系统:JavaScript是一门非常流行的编程语言,有着庞大的生态系统和丰富的第三方库。TypeScript可以无缝地与JavaScript进行互操作,可以使用JavaScript的库和工具,同时也有自己独特的类型定义文件和类型库。

6. 兼容性:由于TypeScript是JavaScript的超集,所以所有的JavaScript代码都可以作为TypeScript代码运行。这意味着现有的JavaScript项目可以逐步迁移到TypeScript,而不需要重写所有代码。

下面是一个简单的示例,展示了TypeScript和JavaScript的不同之处:

// TypeScript示例
function add(a: number, b: number): number {
  return a + b;
}
const result: number = add(1, 2);
console.log(result); // 输出:3

// JavaScript示例
function add(a, b) {
  return a + b;
}
const result = add(1, 2);
console.log(result); // 输出:3

2. 是否可以将多个.ts文件合并成一个.js文件?如果是,那么如何做?

可以将多个.ts文件合并成一个.js文件。可以使用tsc命令将多个.ts文件编译成一个.js文件。

以下是一个示例:
shell
tsc file1.ts file2.ts file3.ts --outFile bundle.js

上述命令将会把file1.ts、file2.ts和file3.ts这三个文件合并成一个bundle.js文件。

3. 内部模块和外部模块有什么区别?

内部模块和外部模块在TypeScript中有以下区别:

1). 定义方式:内部模块使用关键字`module`来定义,而外部模块使用`import`和`export`关键字来定义和导出模块。

2). 可见性:内部模块中的变量、函数、类等在模块外部是不可见的,除非使用`export`关键字导出它们。而外部模块中的变量、函数、类等默认是可见的,可以被其他模块引用和使用。

3). 命名空间:内部模块在TypeScript 1.5之后被称为命名空间(namespace),用于组织和管理相关的代码。而外部模块用于引入和使用其他模块的功能。

4). 文件结构:内部模块通常是将相关的代码放在同一个文件中,而外部模块通常是将不同的功能放在不同的文件中,并通过`import`和`export`关键字进行模块间的引用和导出。

以下是一个示例,演示了内部模块和外部模块的区别:

// 内部模块(命名空间)
module MyModule {
  export function greet(name: string) {
    console.log("Hello, " + name);
  }
}
MyModule.greet("Alice"); // 输出:Hello, Alice
// 外部模块
import { greet } from "./myModule";
greet("Bob"); // 输出:Hello, Bob

4. JavaScript不支持函数重载,但TypeScript是否支持函数重载?

TypeScript支持函数重载。函数重载是指在同一个作用域内,可以定义多个同名函数,但它们的参数类型或参数个数不同。编译器会根据传入的参数类型或参数个数来确定调用哪个函数。

以下是一个使用函数重载的示例:

function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
  return x + y;
}
console.log(add(1, 2)); // 输出:3
console.log(add("Hello", "World")); // 输出:HelloWorld

在上面的示例中,我们定义了两个同名的函数`add`,一个接受两个`number`类型的参数并返回`number`类型的结果,另一个接受两个`string`类型的参数并返回`string`类型的结果。根据传入的参数类型,编译器会自动选择调用对应的函数。

5. TypeScript是否支持所有面向对象的原则?

TypeScript支持面向对象的所有原则,包括封装、继承和多态。它提供了类的定义、创建实例对象、类的继承、静态关键字、抽象类和抽象方法、类属性权限修饰符以及存取器等特性来实现面向对象编程。

6. 如何检查TypeScript中的null和undefined ?

在TypeScript中,可以使用可选链操作符(?)来检查null和undefined值。可选链操作符允许我们在访问对象的属性或调用方法时,如果对象为null或undefined,则不会引发错误,而是返回undefined。

以下是几种在TypeScript中检查null和undefined的方法:

//1. 使用可选链操作符(?):
// 假设data可能为null或undefined,row也可能为null或undefined
// 假设data的完整结构为{row: {name: 'aaa'}}
function getData(data: any) {
  let name = data?.row?.name;
}

//2. 使用条件判断:
// 假设data可能为null或undefined,row也可能为null或undefined
// 假设data的完整结构为{row: {name: 'aaa'}}
function getData(data: any) {
  let name = data && data.row && data.row.name;
}

//3. 使用类型断言:
// 假设data可能为null或undefined,row也可能为null或undefined
// 假设data的完整结构为{row: {name: 'aaa'}}
function getData(data: any) {
  let name = (data as any)?.row?.name;
}

这些方法都可以用来检查TypeScript中的null和undefined值,并避免引发错误。

7. TS的“接口”和“type”语句有什么区别?

在TypeScript中,"接口"和"type"语句都用于定义自定义类型。它们有一些区别,如下所示:

1). 语法:接口使用"interface"关键字进行定义,而"type"语句使用"type"关键字进行定义。

2). 可以被扩展:接口可以通过继承其他接口来进行扩展,而"type"语句不能。

3). 可以被实现:接口可以被类实现,而"type"语句不能。

4). 可以被合并:当定义相同名称的接口时,它们会自动合并为一个接口。而"type"语句定义的类型不能被合并。

5). 可以用于描述函数:接口可以用于描述函数的参数和返回值类型,而"type"语句也可以用于描述函数类型,但语法略有不同。

下面是一个示例,演示了如何使用接口和"type"语句来定义自定义类型:

// 使用接口定义类型
interface Person {
  name: string;
  age: number;
}
// 使用"type"语句定义类型
type Animal = {
  name: string;
  age: number;
};
// 使用接口扩展
interface Employee extends Person {
  position: string;
}
// 使用"type"语句定义函数类型
type Add = (a: number, b: number) => number;
// 使用接口定义函数类型
interface Multiply {
  (a: number, b: number): number;
}
// 使用定义的类型
const person: Person = { name: "Alice", age: 25 };
const animal: Animal = { name: "Dog", age: 3 };
const employee: Employee = { name: "Bob", age: 30, position: "Manager" };
const add: Add = (a, b) => a + b;
const multiply: Multiply = (a, b) => a * b;

七、性能优化

1. 说一说前端性能优化手段?

前端资源比较庞大,包括HTML、CSS、JavaScript、Image、Flash、Media、Font、Doc等等,前端优化相对比较复杂,对于各种资源的优化都有不同的方式,按粒度大致可以分为两类:

一类是文件加载更快

1). 让传输的数据包更小(压缩文件/图片):图片压缩和文件压缩

2). 减少网络请求的次数:雪碧图/精灵图、节流防抖

3). 减少渲染的次数:缓存(HTTP缓存、本地缓存、Vue的keep-alive缓存等)

另一类是文件渲染更快

1). 提前渲染:ssr服务器端渲染

2). 避免渲染阻塞:CSS放在HTML的head中 JS放在HTML的body底部

3). 避免无用渲染:懒加载

4). 减少渲染次数:对dom查询进行缓存、将dom操作合并、使用减少重排的标签

在用户角度前端优化可以让页面加载得更快,对用户的操作响应得更及时,能够给用户提供更为友好的体验,在服务商角度前端优化能够减少页面请求数,减小请求所占带宽,能够节省服务器资源。

2. 说一说性能优化有哪些性能指标,如何量化?

1).性能评估 Chrome Performance选项卡 / Lighthouse 生成性能检测报告 

2).值得关注的性能指标:

(1)LCP (Largest Contentful Paint 最大内容绘制 )

(2)首屏渲染时间(也叫白屏时间)

(3)FCP (Fitst Contentful Paint 首先内容绘制 ) 

(4)可交互时间 (Time to Interactive TTI)

(5) Network请求时间(jax,js等) 3.浏览器开发者工具什么都能看得到,可以调用性能监测API 或建立 前端监控系统(无痕埋点)

3. 说一说服务端渲染?

【概念】 

1.)

- 传统服务端渲染 : JSP/PHP等等(前后端不分离的旧时代,模板引擎,后端拿到前端页面替换数据,前后端耦合) 

- 现代服务端渲染 : (Vue->Nuxt.js,React->Next.js,自己做SSR , 前后端分离的同时实现SSR) 

2). 服务端渲染(SSR)即是在服务器端渲染完整的HTML后返回给客户端(通常是浏览器)。 

3). 客户端渲染(CSR),也就是常见的单页面应用(SPA),由服务器端返回的初始 HTML 页面,由 JS 去异步加载数据,完成页面的渲染。由于ajax请求是异步 的,百度Google等搜索引擎抓取的页面是几乎空白的,可以右键查看网页源代码看是否完整的HTML结构判断是SSR还是CSR。 

4). 同构渲染,就是将SSR和CSR结合在一起,服务端渲染首屏后,激活应用,按照SPA方式运行,结合CSR和SSR各自优点。 

【服务端渲染优点】 

从上可知,服务端渲染的优点是: 

- 搜索引擎爬虫能爬取完整HTML,有利于做搜索引擎优化(SEO),提高网站流量和曝光率 

- 首屏渲染在服务端就完成,所以有更快的首屏加载速度 

- 只是首屏快,其他页面不像SPA一样是无感刷新,切换页面通过超链接进行页面跳转,体验稍差(传统PHP/JSP) 

【服务端渲染缺点】 

服务端渲染的缺点: 

- 由于是在服务端进行渲染,需要占用更多服务器端资源(更多的CPU和内存)。 

- 由于在服务端渲染,所以有些浏览器API无法使用,同样地 客户端一些生命周期在SSR也是不存在的。 

- 部署和开发要求稍高,前后端耦合。

4. XSS攻击是什么?

1).XSS是跨站脚本攻击(Cross Site Scripting)

2).攻击者可以通过向Web页面里面插入script代码,当用户浏览这个页面时,就会运行被插入的script代码,达到攻击者的目的。

3).危害:泄露用户的登录信息cookie;恶意跳转:直接在页面中插入window.location.href进行跳转。

4).防止XSS攻击的方法:

(1).对输入框内容进行过滤和转码。过滤掉用户输入的与事件有关的回调,如onclick,过滤掉用户输入的style标签,script标签。

(2).设置cookie的httponly,告诉浏览器不对客户端开放访问权限。

(3).对用户的输入做Html实体编码 

(4).不对html实体直接进行解码

5. CSRF攻击是什么?

1).概念:跨域请求伪造。

2). 目标站点仅使用cookie来验证,并且cookie是允许跨域的,这就使得恶意站点的页面能拿到用户的cookie. 

3). 目标站点后端没有验证请求的来源,导致任何带有效cookie的请求都被处理。 

4). 用户被诱导访问了恶意站点

预防CSRF攻击主要有以下策略:

-使用验证码,在表单中添加一个随机的数字或者字母验证码,强制要求用户和应用进行直接的交互,

-HTTP中Referer字段,检查是不是从正确的域名访问过来,他记录了HTTP请求的来源地址。

-使用token认证,在HTTP请求中添加token字段,并且在服务器建立一个拦截器验证这个token,如果token不对,就拒绝这个请求。

八、Git

1.git常用命令

以下是一些常用的git命令:

1). git init:在当前目录初始化一个新的git仓库。
2). git clone [url]:克隆一个远程git仓库到本地。
3). git add [file]:将文件添加到暂存区。
4). git commit -m "message":提交暂存区的文件到本地仓库,并添加提交信息。
5). git push:将本地仓库的提交推送到远程仓库。
6). git pull:从远程仓库拉取最新的代码到本地。
7). git branch:查看当前仓库的分支。
8). git checkout [branch]:切换到指定的分支。
9). git merge [branch]:将指定分支的代码合并到当前分支。
10). git status:查看当前仓库的状态。
11). git log:查看提交历史记录。

这些是一些常用的git命令,可以帮助你在不同的场景下进行git操作。

2.解决冲突

解决Git冲突的方法如下:

1). 首先,使用`git status`命令查看当前分支的状态,确认是否存在冲突的文件。

2). 打开冲突文件,可以看到类似以下的标记:
   <<<<<<< HEAD
   // 当前分支的代码
   =======
   // 合并分支的代码
   >>>>>>> branch_name

3). 根据需要,修改冲突文件,将冲突标记和不需要的代码删除,保留需要的代码。

4). 修改完成后,使用`git add `命令将修改后的文件添加到暂存区。

5). 使用`git commit -m "解决冲突"`命令提交解决冲突的修改。

6). 如果在解决冲突的过程中遇到困难,可以使用`git mergetool`命令打开合并工具来解决冲突。

7). 最后,使用`git push`命令将修改推送到远程仓库。

​3.GitFlow

GitFlow是一种在Git版本控制系统中使用的工作流程模型,它定义了一套规范的分支管理策略,旨在帮助团队更好地组织和协调开发工作。GitFlow的核心思想是将开发过程分为两个主要分支:主分支(master)和开发分支(develop),以及一些辅助分支,如功能分支(feature)、发布分支(release)和修复分支(hotfix)。

GitFlow的工作流程如下:
1). 主分支(master):主分支用于存储稳定的、可发布的代码。只有经过测试和验收的代码才能合并到主分支中。
2). 开发分支(develop):开发分支是主要的集成分支,用于整合各个功能分支的代码。所有的功能分支都从开发分支上创建,并最终合并回开发分支。
3). 功能分支(feature):功能分支用于开发新功能或解决某个特定问题。每个功能分支都是从开发分支上创建的,开发完成后再合并回开发分支。
4). 发布分支(release):发布分支用于准备发布新版本的代码。在发布分支上进行最后的测试、修复bug和版本号的更新等操作。发布分支最终会合并回主分支和开发分支。
5). 修复分支(hotfix):修复分支用于紧急修复生产环境中的bug。修复分支是从主分支上创建的,修复完成后会合并回主分支和开发分支。

使用GitFlow可以带来以下好处:
- 清晰的分支管理,使团队成员更容易理解和协作。
- 更好的版本控制,能够轻松地回滚到之前的版本。
- 更高效的开发流程,能够并行开发多个功能。
- 更稳定的发布过程,能够更好地控制发布的质量。

如果你想使用GitFlow,可以按照以下步骤进行安装:
1). 打开终端或命令提示符。
2). 输入以下命令来安装GitFlow插件:
shell
brew install git-flow-avh
3). 安装完成后,你可以使用以下命令来验证安装是否成功:
shell
git flow version
如果成功安装,你将看到GitFlow的版本信息。

你可能感兴趣的:(前端面试必备,前端,面试,职场和发展)