高频前端面试题

高频面试题

文章目录

一、HTML

1、你都做过哪些兼容性问题?

HTML兼容性:

h5新标签只能兼容到ie9,如果想要兼容ie低版本浏览器,需要引入html5shiv.js文件,其cdn写法如下:

<script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>

CSS兼容性:

1.1、媒体查询兼容性,ie9以下不支持媒体查询,需要引入response.js文件,其cdn写法如下:

<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>

1.2、CSS Hack:

1.2.1、属性前缀:例如 IE6能识别下划线和星号,IE7能识别星号,但不能识别下划线,IE6~IE10都认识"\9",但firefox前述三个都不能认识。

.red { 
	_color: red; /* ie6 */
	*color: red; /* ie7 */
	color: red\9; /* ie8/9/10 */
}

1.2.2、选择器前缀:例如 IE6能识别 * html .class{},IE7能识别 +html .class{}或者*:first-child+html .class{}。

*.red {} /* ie6 */
+.red {} /* ie7 */

1.2.3、条件注释:

针对所有IE(注:IE10+已经不再支持条件注释):

<!--[if IE]>IE浏览器显示的内容 <![endif]-->

针对IE6及以下版本:

<!--[if lt IE 6]>只在IE6-显示的内容 <![endif]-->

这类Hack不仅对CSS生效,对写在判断语句里面的所有代码都会生效。

1.3、厂商前缀:谷歌-webkit-、火狐-moz-、IE-ms-、欧朋-o-

1.4、其它兼容性:

1.4.1、ie老版本浮动造成的双边距问题:display:inline;

1.4.2、图片间隙:父盒子设置font-size: 0; 或者图片设置display: block;

1.4.3、块元素默认高度:overflow: hidden;

**JavaScript兼容性:**一般使用渐进增强和优雅降级的方式来解决兼容性问题。

// 优雅降级
var xhr = null;
if(XMLHttpRequest){
	xhr = new XMLHttpRequest();
}else{
	xhr = new ActiveXObject('Microsoft.XMLHTTP');
}

// 渐进增强
// 前边实现上传文件的基本功能
// 后边再判断如果支持拖拽事件,就实现拖拽上传

2、如何提高页面性能?

2.1、图片压缩、合并(精灵图)、使用字体图标代替小图片、使用base64、图片懒加载

2.2、css、js的压缩、封装复用

2.3、减少重排操作,例如使用transform书写动画效果,在for循环结束后再去操作dom等

2.3、使用CDN网络托管

2.4、数据懒加载、数据按需加载(上拉加载)、分页

2.5、路由懒加载

2.6、利用缓存来缓存文件

2.7、频繁触发的事件进行防抖和节流

2.8、异步加载

2.9、减少闭包,递归优化,使用高效的算法

2.10、webpack优化:去除无用代码treeShaking、组件按需加载、使用chunck、模板预编译等

2.11、字库用gb2312不要utf-8,一个汉字少一个字节


3、谈谈你对H5的理解?

Html5是Web中核心语言HTML的规范,是 HyperText Markup Language 5 的缩写,H5提供新的标签元素,使代码变的更有语义;提供了大量api,如本地存储、离线存储、webworker、websocket、filereader、地理定位、拖拽等;提供了更加酷炫的CSS3新特性,如过渡、变形、动画、阴影、渐变等。


4、浏览器从输入网址都看到网页都发生了什么?

4.1、域名解析成ip地址

4.2、客户端发送一个带有SYN标志的数据包给服务端(三次握手,第一次)

4.3、服务端收到后,回传一个带有SYN/ACK标志的数据包以示传达确认信息(三次握手,第二次)

4.4、客户端再回传一个带ACK标志的数据包,代表握手结束,连接成功(三次握手,第三次)

4.5、服务端处理数据并返回数据

4.6、客户端请求关闭连接(四次挥手,第一次)

4.7、服务端确认是否还有数据要传输(四次挥手,第二次)

4.8、服务端没有要传输的数据了,准备关闭连接(四次挥手,第三次)

4.9、客户端断开连接(四次挥手,第四次)

4.10、浏览器解析HTML,生成DOM树,解析CSS,生成CSS规则树

4.11、DOM树和CSS规则树合并成渲染树,开始渲染

4.12、执行JavaScript脚本


5、重绘和重排?

重排也叫回流,当元素因为规模尺寸,布局,隐藏等改变而需要重新构建时则成为重排。

重绘:一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局则叫重绘。

重绘不一定重排,但是重排一定重绘。


6、缓存?

6.1、浏览器缓存:就是把一个已经请求过的资源拷贝一份存储起来,当下次需要该资源时, 浏览器会根据缓存机制决定直接使用缓存资源还是再次向服务器发送请求。分为:

强制缓存:请求头设置cache-control:

​ max-age缓存的时间

​ no-cache:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。

​ no-store:直接禁止浏览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。

​ public:可以被所有的用户缓存,包括终端用户和CDN等中间代理服务器。

​ private:只能被终端用户的浏览器缓存,不允许CDN等中继缓存服务器对其缓存。

协商缓存:请求头设置last-modified/etag

1.Etag要优于Last-Modified。Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;

2.在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值;

3.在优先级上,服务器校验优先考虑Etag。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lP9m27us-1629198968567)(F:\offcn\course\第6阶段视频\第6-8天-面试题\offcn\高频面试题\4.png)]

6.2、H5缓存:

本地存储:localStorage永久存储、sessionStorage临时存储

离线缓存:在html标签上设置 manifest 属性 引入cache文件(CACHE缓存文件,NETWORK不缓存文件,FALLBACK当资源不可访问时,代替的文件)

6.3、更新缓存文件:1、更新manifest文件;2、通过javascript操作:window.applicationCache.update();3、清除浏览器缓存;4、带版本号,根据版本号判断。


7、状态码?

1字头:信息,服务器收到请求,需要请求者继续执行操作

2字头:成功,操作被成功接收并处理

3字头:重定向,需要进一步的操作以完成请求

4字头:客户端错误,请求包含语法错误或无法完成请求

5字头:服务器错误,服务器在处理请求的过程中发生了错误

101:切换协议。

200:请求成功。一般用于GET与POST请求

203:非授权信息。请求成功。但返回的meta信息不在原始的服务器,而是一个副本

204:无内容。服务器成功处理,但未返回内容。

301:永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。

302:临时移动。

304:未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。

305:使用代理。所请求的资源必须通过代理访问

307:临时重定向。

400:客户端请求的语法错误,服务器无法理解

404:服务器无法根据客户端的请求找到资源(网页)

405:客户端请求中的方法被禁止

500:服务器内部错误,无法完成请求

502:作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应

503:由于超载或系统维护,服务器暂时的无法处理客户端的请求。

505:服务器不支持请求的HTTP协议的版本,无法完成处理


8、!DOCTYPE html 是干什么的,有什么用?

1、声明文档类型是html5类型的文档。2、声明了则是标准模式,兼容ie高版本;不声明则是混杂模式,兼容ie低版本。


二、CSS

1、display:none; 和visibility:hidden;的区别是什么?

display:none; 彻底消失,释放空间。能引发页面的reflow回流(重排)。

visibility:hidden; 就是隐藏,但是位置没释放,好比opacity:0; 不引发页面回流。


2、CSS 优先级和权重值如何计算

内嵌样式>内部样式>外部样式>导入式

!important > 内嵌 1000 >Id 100 > class=[]=伪类 10 > tag=伪元素 1 > ( * + > ~) 0


3、如何触发BFC,以及BFC的作用

BFC:块级格式化上下文block formatting context,是一个独立渲染区域。规定了内部box如何布局,并且与这个区域外部毫不相干。

触发:float的值不是none;position的值不是static或者relative;display的值是inline-block、block、table-cell、flex、table-caption或者inline-flex;overflow的值不是visible。

作用:避免margin重叠;自适应两栏布局;清除浮动。


4、CSS盒模型

盒模型由:外边距margin、边框border、内边距padding、内容content四个部分组成

标准盒模型大小=border+padding+content

怪异盒模型大小=content

转怪异盒模型:box-sizing:border-box;

转标准盒模型:box-sizing:content-box;


5、如何水平垂直居中一个元素

5.1、弹性盒子

.box{
  display: flex;
  justify-content: center;
  align-items: center;
}

5.2、定位

.box{
  position: relative;
}
.box .sub{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /*margin-left: 负的宽度的一半*/
  /*margin-top: 负的高度的一半*/
}

6、css实现一个三角形
.triangle{
  width: 0;
  height: 0;
  border: 100px solid transparent;
  border-left-color: red;
}

7、如何实现左边固定宽,右边自适应布局

7.1、圣杯布局

<div id="container">
  <div id="center" class="column"></div>
  <div id="left" class="column"></div>
  <div id="right" class="column"></div>
</div>
#container {
  padding-left: 200px; 
  padding-right: 150px;
}
#container .column {
  float: left;
}
#center {
  width: 100%;
}
#left {
  width: 200px; 
  margin-left: -100%;
  position: relative;
  right: 200px;
}
#right {
  width: 150px; 
  margin-right: -150px; 
}

7.2、双飞翼布局

  <div id="container" class="column">
    <div id="center"></div>
  </div>
  <div id="left" class="column"></div>
  <div id="right" class="column"></div>
  #container {
    width: 100%;
  }

.column {
  float: left;
}
        
#center {
  margin-left: 200px;
  margin-right: 150px;
}
        
#left {
  width: 200px; 
  margin-left: -100%;
}
        
#right {
  width: 150px; 
  margin-left: -150px;
}  

7.3、等高布局(假等高)互补的内外边距

.parent{
  overflow: hidden;
}
.left, .right{
  margin-bottom: -10000px;
  padding-bottom: 10000px;
}

7.4、等高布局(真等高)弹性盒子

.parent{
  display: flex;
}
.child{
  flex: 1;
}

7.5、calc

  <div id="left" class="column"></div>
  <div id="center" class="column"></div>
  <div id="right" class="column"></div>
  .column{
    float: left;
  }
  #left{
    width: 100px;
  }
  #right{
    width: 200px;
  }
  #center{
    width: calc(100% - 100px - 200px);
  }

7.6、浮动

  <div id="left" class="column"></div>
  <div id="right" class="column"></div>
  <div id="center"></div>
  .column{
    float: left;
  }
  #left{
    width: 100px;
  }
  #right{
    width: 200px;
  }
  #center{
    margin-left: 100px;
    margin-right: 200px;
  }

8、如何实现6px字体
.font{
  font-size: 12px;
  transform: scale(.5);
}

9、移动端1px边框怎么设置
/* 方法1 */
.border{
  width: 100%;
  height: 1px;
  background: red;
}
/* 方法2 */
.border{
  border-image: url(border.png)
}
/* 方法3 */
.border{
  box-shadow: 0 0 0 1px #000;
}

10、px、em、rem、vh、vw分别是什么

px物理像素,绝对单位;em相对于父级字体大小,相对单位;rem相对于html的字体大小,相对单位;vh相对于屏幕高度的大小,相对单位;vw相对于屏幕宽度的大小,相对单位。


11、css可继承的属性有哪些

可继承的属性:文本类:text-indent、text-align、line-height、word-spacing、letter-spacing、text-transform、direction、color;

字体类:font、font-family、font-weight、font-size、font-style、font-variant、font-stretch、font-size-adjust;

其它类:visibility、caption-side、border-collapse、border-spacing、empty-cells、table-layout、list-style-type、list-style-image、list-style-position、list-style、quotes、cursor、page、page-break-inside、windows、orphans等


三、JavaScript

1、call、apply、bind的区别

这三个都是用来定义上下文的,call、apply会指定上下文并执行函数;而bind终身定 死上下文但是不执行函数,并返回新的函数。 其中call和apply传入参数的形式有别,call是单独罗列,逗号隔开参数;apply是数 组。 函数.call(上下文对象,参数,参数,参数); 函数.apply(上下文对象,[参数,参数,参数]);

var obj = {
 a: 10
}

function fun(b, c){
 console.log(this.a + b + c);
}

fun.call(obj, 3, 4);
fun.apply(obj, [3, 4]);
fun = fun.bind(obj); // 返回新的函数
fun(3,4);

2、数据类型有哪些

基本类型:数字number、字符串string、布尔boolean、undefined、null、symbol

引用类型:数组array、函数function、对象object


3、如何检测数据类型

typeof 能够检测:数字、字符串、布尔、undefined、symbol、function

instanceof 能够检测:数组

Object.prototype.toString.call() 万能法


4、各语句的区别

4.1、for和for…in和for…of的区别

for循环,遍历整个数组

for…in加强循环,不光可以遍历数组,还可以遍历对象和其原型上的方法

for…of遍历数组和可枚举的对象

4.2、switch和if的区别

switch用于判断精准的值

if用于判断值的范围

4.3、while和do…while的区别

while当符合条件时则执行

do…while先执行一次,然后再判断是否符合条件,比while要多执行一次

4.4、break和continue的区别

break是跳出当前循环并终止循环

continue是跳出当前循环并执行下一次循环


5、闭包

闭包就是函数能够记忆住当初定义时候的作用域,不管函数到哪里执行了,永远都能够 记住那个作用域,并且会遮蔽新作用域的变量。可预测状态容器;实现模块化,实现变量的私有封装;可以实现迭代器。 闭包缺点:1.闭包有一个非常严重的问题,那就是内存浪费问题,这个内存浪费不仅仅 因为它常驻内存,更重要的是,对闭包的使用不当的话会造成无效内存的产生;2.性能问题 使用闭包时,会涉及到跨作用域访问,每次访问都会导致性能损失。 因此在脚本中,最好小心使用闭包,它同时会涉及到内存和速度问题。不过我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。

function foo(){
    var a = 0;
    function f(){
      a++;
      return a;
    }
    return f;
}
var res = foo();
res(); 
res();
res();
console.log(res()); // 4 a的值被存储在内存中不会被释放掉

6、原型和原型链

原型:每一个引用类型都有一个隐式原型__ proto __ ,每一个函数都有一个显示原型prototype,该属性指向它的原型对象。

原型链:某个对象的原型又有自己的原型,直到某个对象的原型为null为止,组成这条的最后一环,这种一级一级的链就是原型链。


7、继承

7.1、原型链继承

 /**
 * 缺点:引用类型的属性被所有实例共享,
 * 在创建Child 的实例时, 不能向Person传参
 */
   function Person() {
    this.name = "xiaopao";
   }
   Person.prototype.getName = function () {
    console.log(this.name);
   };
   function Child() {}
   Child.prototype = new Person();

7.2、借用构造函数继承(经典继承)

/*
优点:
1.避免了引用类型的属性被所有实例共享
2.可以在Child中向Parent传参
缺点:
1.只是子类的实例,不是父类的实例
2.方法都在构造函数中定义,每次创建实例都会创建一遍方法
*/
   function Child() {
    Person.call(this);
   }

7.3、组合继承

/*
优点:融合原型链继承和构造函数的优点,是JavaScript中最常用的继承模式
缺点:调用了两次父类构造函数
组合继承最大的问题是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子
类型原型的时候,另一次是在子类型构造函数内部)
*/
   function Child(name, age) {
    Parent.call(this, name); // 第二次调用 Parent()
    this.age = age;
   }
   Child.prototype = new Parent(); // 第一次调用 Parent()

7.4、原型式继承

 // 缺点: 包含引用类型的属性值始终都会共享相应的值, 这点跟原型链继承一样
   function CreateObj(o) {
    function F() {}
    F.prototype = o;
    return new F();
   }
   var person = {
    name: "xiaopao",
    friend: ["daisy", "kelly"],
   };
   var person1 = CreateObj(person);

7.5、寄生式继承 可以理解为在原型式继承的基础上增加一些函数或属性

 // 缺点:跟借用构造函数一样,每次创建对象都会创建一遍方法

   var ob = {
    name: "xiaopao",
    friends: ["lulu", "huahua"],
   };

   function CreateObj(o) {
    function F() {} // 创建一个构造函数F
    F.prototype = o;
    return new F();
   }

   // 上面CreateObj函数 在ECMAScript5 有了一新的规范写法,Object.create(ob) 效果是一样的 , 看下面代码
   var ob1 = CreateObj(ob);
   console.log(ob1.name); // xiaopao

7.6、寄生组合式继承

// 优点:完美继承
// 缺点:代码繁多,使用起来十分麻烦
   function Parent(name) {
    this.name = name;
    this.colors = ["red", "blue", "green"];
   }

   Parent.prototype.sayName = function () {
    console.log(this.name);
   };

   function Child(name, age) {
    Parent.call(this, name);
    this.age = age;
   }

   function CreateObj(o) {
    function F() {}
    F.prototype = o;
    return new F();
   }


   // Child.prototype = new Parent(); // 这里换成下面
   function prototype(child, parent) {
    var prototype = CreateObj(parent.prototype);
    prototype.constructor = child;
    child.prototype = prototype;
   }
   prototype(Child, Parent);


   var child1 = new Child("xiaopao", 18);

   console.log(child1);

7.7、es6 继承

 class Child extends Parent {}

8、递归和递归优化

递归就是函数自己调用自己。但是又不能无限的调用自己,需要有一个出口,否则会成为死循环。函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

// 循环求1-5的所有数的和
var sum = 0;
for(var i = 1; i <= 5; i++){
	sum += i;
}
console.log(sum) // 15

//递归实现1-5的所有数的和
function sum(n){
	if(n === 1){
		return 1;
	}
	return n + sum(n-1);
}
console.log(sum(5)); //15

尾递归优化是解决递归调用栈溢出的方法。尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

// 上例递归进行尾递归优化
function sum(n, m = 0){
	if(n === 1){
		return 1 + m;
	}
	return sum(n-1, n + m);
}
console.log(sum(5)); //15

// 或者while优化
function sum(n, m = 0){
	while(n >= 1){
		return sum(n - 1, n + m);
	}
	return m;
}
console.log(sum(5)); // 15


9、ajax工作原理和封装

1.创建XMLHttpRequest对象。 2.设置请求方式。open() 3.调用回调函数。onreadystatechange 4.发送请求。send()

 function ajax(options) {
    const { type, dataType, data, timeout, url, success, error } = options;
    var params = formatParams(data);
    var xhr;
    //考虑兼容性
    if (window.XMLHttpRequest) {
      xhr = new XMLHttpRequest();
    } else if (window.ActiveObject) {
      //兼容IE6以下版本
      xhr = new ActiveXobject("Microsoft.XMLHTTP");
    }
    //启动并发送一个请求
    if (type == "GET") {
      xhr.open("GET", url + "?" + params, true);
      xhr.send();
    } else if (type == "POST") {
      xhr.open("post", url, true);
      //设置表单提交时的内容类型
      //Content‐type数据请求的格式
      xhr.setRequestHeader(
        "Content‐type",
        "application/x‐www‐form‐urlencoded"
      );
      xhr.send(params);
    }

    // 设置有效时间
    setTimeout(function () {
      if (xhr.readySate != 4) {
        xhr.abort();
      }
    }, timeout);

    // 接收
    // options.success成功之后的回调函数 options.error失败后的回调函数
    //xhr.responseText,xhr.responseXML 获得字符串形式的响应数据或者XML形式的响应数据
    xhr.onreadystatechange = function () {
      if (xhr.readyState == 4) {
        var status = xhr.status;
        if ((status >= 200 && status < 300) || status == 304) {
          success && success(xhr.responseText, xhr.responseXML);
        } else {
          error && error(status);
        }
      }
    };
  }

  //格式化请求参数
  function formatParams(data) {
    var arr = [];
    for (var name in data) {
      arr.push(
        encodeURIComponent(name) + "=" + encodeURIComponent(data[name])
      );
    }
    arr.push(("v=" + Math.random()).replace(".", ""));
    return arr.join("&");
  }

10、跨域

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。其 实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。同源策略SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR 等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地 址,也非同源。

方法1:跨域资源共享CORS跨域,就是服务端在HTTP返回头上加上“AccessControll-Allow-Origin:*”。 “Access-Controll-Allow-METHODS:GET, POST” DELETE、PATCH请求类型会发出OPTIONS预检请求。

方法2:代理跨域,webpack-dev-server里面的proxy配置项。config中的 ProxyTable

方法3:JSONP,利用页面srcipt没有跨域限制的漏洞,用script的src引入它,然后页 面内定义回调函数,jQuery中$.ajax({dataType: ‘jsonp’})。

方法4: iframe跨域,配合window.name或者 location.hash或者document.domain 一起使用

方法5:nginx反向代理接口跨域,通过nginx配置一个代理服务器(域名与domain1 相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中 domain信息,方便当前域cookie写入,实现跨域登录。

方法6:jquery的ajax跨域,dataType:‘jsonp’


11、事件流和事件委托

事件流一般分三个阶段:1、捕获阶段(由外向内) 2、目标阶段 (执行阶段) 3、冒泡阶段(由内向外)

阻止事件冒泡e.stopPropagation() 阻止默认动作e.preventDefault()

事件委托:就是把事件委托给父级,利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。


12、事件循环

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mVVPC7Iq-1629198968570)(F:\offcn\course\第6阶段视频\第6-8天-面试题\offcn\高频面试题\2.png)]

同步任务进入主线程,异步任务进入Event Table并注册函数 当指定的事情完成时,Event Table会将这个函数移入Event Queue。 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执 行。 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3oRvsoTe-1629198968574)(F:\offcn\course\第6阶段视频\第6-8天-面试题\offcn\高频面试题\3.png)]

      console.log("script start");

      setTimeout(function () {
        console.log("setTimeout");
      }, 0);

      Promise.resolve()
        .then(function () {
          console.log("promise1");
        })
        .then(function () {
          console.log("promise2");
        });

      console.log("script end");
      /* 执行结果为:script start, script end, promise1, promise2, setTimeout因为Promise是微任务,主线程会在同步任务做完后先清空微任务队列,再执行宏任务队列 */

微任务是由JavaScript自身发起,包括:process.nextTick、promise、MutationObserver

宏任务是由宿主发起的,如浏览器、node。包括:setTimeout、setInterval、setImmediate、postMessage


13、防抖和节流
// 节流:在计时器内部清除计时器,有节奏的执行事件
        function throttle(callback, delay = 1000){
            let timer = null;
            function f(){
                if(!timer){
                    timer = setTimeout(() => {
                        callback && callback.call(this);
                        clearTimeout(timer);
                        timer = null;
                    }, delay);
                }
            }
            return f;
        }
      
// 防抖:在计时器前边清除计时器,只执行最后一次事件,能够无限延长执行时间
        function debounce(callback, delay = 1000) {
            let timer = null;
            function f() {
                clearTimeout(timer);
                timer = setTimeout(() => {
                    callback && callback.call(this);
                }, delay);
            }
            return f;
        }

14、深克隆和浅克隆

浅克隆:同值也同址。浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。如:Object.assign;=等号赋值;slice截取。

深克隆:同值不同址。深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。如:JSON.parse(JSON.stringify());

  function deepClone(target) {
    // 定义一个变量
    let result;
    // 如果当前需要深拷贝的是一个对象的话
    if (typeof target === "object") {
      // 如果是一个数组的话
      if (Array.isArray(target)) {
        result = []; // 将result赋值为一个数组,并且执行遍历
        for (let i in target) {
          // 递归克隆数组中的每一项
          result.push(deepClone(target[i]));
        }
        // 判断如果当前的值是null的话;直接赋值为null
      } else if (target === null) {
        result = null;
        // 判断如果当前的值是一个RegExp对象的话,直接赋值
      } else if (target.constructor === RegExp) {
        result = target;
      } else {
        // 否则是普通对象,直接for in循环,递归赋值对象的所有值
        result = {};
        for (let i in target) {
          result[i] = deepClone(target[i]);
        }
      }
      // 如果不是对象的话,就是基本数据类型,那么直接赋值
    } else {
      result = target;
    }
    // 返回最终结果
    return result;
  }

15、cookie、sessionStorage和localStorage的区别

15.1、cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
15.2、存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
15.3、数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
15.4、作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的
15.5、web Storage支持事件通知机制,可以将数据更新的通知发送给监听者
15.6、web Storage的api接口使用更方便


16、get和post请求的区别

GET在浏览器回退时是无害的,而POST会再次提交请求。

GET产生的URL地址可以被Bookmark,而POST不可以。

GET请求会被浏览器主动cache,而POST不会,除非手动设置。

GET请求只能进行url编码,而POST支持多种编码方式。

GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

GET请求在URL中传送的参数是有长度限制的,而POST么有。

对参数的数据类型,GET只接受ASCII字符,而POST没有限制。

GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

GET参数通过URL传递,POST放在Request body中


17、new操作符都做了哪些事情

构造函数中没有显示的创建Object对象,实际上后台自动创建了一个空对象,直接给this对象赋值属性和方法,this即指向创建的对象。没有return返回值,后台自动返回了该对象,该对象继承构造函数的原型

// 模拟构造函数实现
 var Book = function(name) {
   this.name = name;
 };
 //正常用法
 var js = new Book('js');
 //使用代码模拟,在非IE浏览器中测试,IE浏览器不支持
 var javascript = {};
 javascript.__proto__ = Book.prototype;
 Book.call(javascript, 'js');

18、XSS攻击和CSRF攻击

XSS:跨站脚本攻击Cross site script,因叫css容易让人误会所以改成了xss。比如一个JSON数据:

      var obj = [
        {
          id: 1,
          name: "<script>alert('哈哈哈')</script>",
          age: 12,
        }
      ];

在不该出现script代码的地方出现了,引发一些潜在的危险。 XSS漏洞,能让人们在网页里面插入一段有功能的语句。 XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害, 而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个 页面的时候就会运行这些脚本。 防范: ① 用正则表达式阻止用户提交带有<、eval、script等危险字眼的语句 ② 显示的时候不要直接用innerHTML,而是用innerText,或者将<转义。

CSRF 的全称是“跨站请求伪造”,而 XSS 的全称是“跨站脚本”。看起来有点相 似,它们都是属于跨站攻击——不攻击服务器端而攻击正常访问网站的用户,但前面说 了,它们的攻击类型是不同维度上的分类。CSRF 顾名思义,是伪造请求,冒充用户在站内 的正常操作。我们知道,绝大多数网站是通过 cookie 等方式辨识用户身份(包括使用服务 器端 Session 的网站,因为 Session ID 也是大多保存在 cookie 里面的),再予以授权的。 所以要伪造用户的正常操作,最好的方法是通过 XSS 或链接欺骗等途径,让用户在本机(即 拥有身份 cookie 的浏览器端)发起用户所不知道的请求。 就是说,如果用户不老老实实写姓名,写了一个个


19、垃圾回收机制

一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根本访问不到它们,这几个对象也是垃圾,也要被清除。 垃圾回收的方法主要有两种:一种是标记清除,即用完之后的变量 赋值成null,另一种 是引用计数,将使用完的对象的引用计数,如果为0则回收


20、常用DOM操作

createElement 创建

appendChild末尾添加

insertBefore 前边插入

cloneNode(true) 克隆

removeChild() 移除

parentNode父节点

childNodes // 全部子节点

firstChild // 第一个子节点

lastChild // 最后一个子节点

previousSibling // 上一个兄弟节点

nextSibling // 下一个兄弟节点

获取dom节点:document.getElementById() 、document.getElementsByTagName() 、document.getElementsByClassName() 、document.getElementsByName() 、document.querySelector() 、document.querySelectorAll()


21、AMD、CMD、ES6、CommonJS的区别

CommonJS:模块引用(require) 模块输出(exports) 模块标识(module) ES6:模块引用(import) 模块输出(export) 前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持。 前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线 程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同 步导入会对渲染有很大影响。 前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以 如果想更新值,必须重新导入一次。但是后者采用实时绑定的方式,导入导出的值都 指向同一个内存地址,所以导入值会跟随导出值变化 AMD、CMD都使用define定义模块,require引入模块,区别在于AMD是前置依赖, CMD是就近依赖

      // AMD   依赖必须一开始就声明
      define(["./a", "./b"], function (require, factory) {
        // do something...
      });

      // CMD  
      define(function(require, factory) {
        var a = require('./a'); // 依赖就近书写
        // do something...
      });

四、ES6

1、let、const、var的区别

var声明变量存在变量提升,let和const不存在变量提升

let、const都是块级局部变量 ,存在暂时性死区

const 的特性和 let 完全一样,不同的只是const声明时候必须赋值,赋值基本类型时,只能进行一次赋值,即声明后不能再修改,赋值引用类型时,内存地址不能修改

同一作用域下let和const不能声明同名变量,而var可以


2、箭头函数和普通函数的区别

2.1、箭头函数的this是定义时决定的,普通函数是看调用方法。

2.2、箭头函数不能成为构造函数

2.3、箭头函数不能使用async/await

2.4、箭头函数不能使用Generator函数,不能使用yeild关键字

2.5、箭头函数不能使用call、apply、bind来修改this指向

2.6、箭头函数不绑定arguments

2.7、箭头函数不具有prototype原型对象,不具有super


3、promise的实现原理和封装

promise一共有三种状态,分别是pedding初始状态 、resolved成功的状态、 rejected失败的状态。传入两个参数,一个是resolve,执行then的方法,一个是reject,执行catch的方法或者then的第二个参数的回调。promise一旦状态改变就不可在修改。promise的链式调用实际上是返回的一个新的promise,而非return this。

// 简版promise
function Promise(executor){ //executor执行器
    let self = this;
    self.status = 'pending'; //等待态
    self.value  = undefined; // 表示当前成功的值
    self.reason = undefined; // 表示是失败的值
    function resolve(value){ // 成功的方法
        if(self.status === 'pending'){
            self.status = 'resolved';
            self.value = value;
        }
    }
    function reject(reason){ //失败的方法
        if(self.status === 'pending'){
            self.status = 'rejected';
            self.reason = reason;
        }
    }
    executor(resolve,reject);
}

Promise.prototype.then = function(onFufiled,onRejected){
    let self = this;
    if(self.status === 'resolved'){
        onFufiled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
}

4、forEach、for in、for of三者区别

forEach更多的用来遍历数组
for in 一般用来遍历对象或json,可以遍历对象的原型
for of一遍用来遍历数组对象和可枚举对象,不能遍历原型
for in循环出的是key,for of循环出的是value


5、set、map分别是什么

set对象:允许你存储任何类型的唯一值,无论是原始值或者是对象引用。Set是值得集合,不能通过get方法获取值,因为set只有值。能通过迭代器进行for…of遍历。Set的值是唯一的可以做数组去重。

var set = new Set(['1',undefined,{},2,[3,4]]);
set.size; // 5
set.add(5);
set.delete('1'); // true
set.has('2'); // false
set.keys(); // SetIterator
set.values(); // SetIterator
set.entries(); // SetIterator
// set对象可遍历
set.forEach();

map对象:Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。

var myMap = new Map();
var keyObj = {};
myMap.set(keyObj, "和键 keyObj 关联的值");
myMap.get(keyObj); // "和键 keyObj 关联的值"
myMap.size // 1
myMap.has(keyObj); // true
myMap.delete(keyObj); // true
myMap.clear(); 
myMap.keys(); // MapIterator	键组成的对象
myMap.values(); // MapIterator  值组成的对象
myMap.entries(); // MapIterator	键值对组成的对象
// map对象可遍历
myMap.forEach((value, key) => {
  console.log(value, key)
})
for(var [key, value] of myMap){
	console.log(key, value); //  {}, "和键 keyObj 关联的值"
} 

6、symbol的理解

ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名

let sy = Symbol("KK");
console.log(sy);   // Symbol(KK)
typeof(sy);        // "symbol"
 
// 相同参数 Symbol() 返回的值不相等
let sy1 = Symbol("kk"); 
sy === sy1;       // false

// 写法1
let syObject = {};
syObject[sy] = "kk";
console.log(syObject);    // {Symbol(key1): "kk"}
 
// 写法2
let syObject = {
  [sy]: "kk"
};
console.log(syObject);    // {Symbol(key1): "kk"}
 
// 写法3
let syObject = {};
Object.defineProperty(syObject, sy, {value: "kk"});
console.log(syObject);   // {Symbol(key1): "kk"}

Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。

for (let i in syObject) {
  console.log(i);
}    // 无输出
 
Object.keys(syObject);                     // []
Object.getOwnPropertySymbols(syObject);    // [Symbol(key1)]
Reflect.ownKeys(syObject);                 // [Symbol(key1)]

7、新增哪些数组方法

forEach:forEach()会遍历数组, 循环体内没有返回值,forEach()循环不会改变原来数组的内容, forEach()有三个参数, 第一个参数是当前元素, 第二个参数是当前元素的索引, 第三个参数是当前元素所属的数组

map:map()的主要作用, 其实是创建一个新的数组

filter:filter()主要是过滤的, 用来过滤数组中不满足条件的元素, 把满足条件的元素放到新的数组里, 并且不会改变原数组

every:会遍历数组, 在循环体内写条件, 如果每一项都是true, 就会返回true, 只要有一个是false, 就会返回false

some:遍历数组的每一项, 然后根据循环体内的条件去判断, 只要有一个是true, 就会停止循环

reduce:接收一个函数作为累加器, 数组中每个值(从左到右)开始缩减, 最终为一个值

Array.from():用于将两类对象变成数组。一类是类数组对象,一类是可遍历对象。

Array.of():用于将一组值,转换为数组

find() 和 findIndex():用于找出第一个符合条件的数组成员和下标。

fill():方法使用给定值填充一个数组

includes():检查是否包含某个值


8、新增哪些字符串方法

startsWith():返回布尔值,表示参数字符串是否在原字符串的头部

endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部

includes():返回布尔值,表示是否找到了参数字符串

repeat():返回一个新字符串,表示将原字符串重复n次

padStart():开始位置填充

padEnd():结束位置填充

trimStart():消除字符串头部的空格

trimEnd():消除字符串尾部的空格

matchAll():返回一个正则表达式在当前字符串的所有匹配


9、新增哪些对象方法

Object.is():判断两个值是否相等

Object.assign():用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

Object.getOwnPropertyDescriptors():返回指定对象所有自身属性(非继承属性)的描述对象

Object.setPrototypeOf(),Object.getPrototypeOf():用来读取或设置当前对象的prototype对象

Object.keys():用于返回一个数组,成员的参数是对象自身的所有可遍历属性的键名

Object.values():返回值是一个数组,成员是对象自身的(不含继承的)所有可遍历属性的值。

Object.entries():返回一个数组,成员是对象自身(不含继承的)所有可遍历属性的键值对数组,Symbol 属性的值会被过滤。

Object.fromEntries():是Object.entries 的逆操作,将一个键值对数组转为对象。


10、async…await

async…await是Generator函数的语法糖,将*改成async,将yield换成await。 是对Generator函数的改进, 返回promise。 异步写法同步化,遇到await先返回,执行完异步再执行接下来的. 内置执行器, 无需next()


五、Vue

1、生命周期都有哪些,以及在这些生命周期中都做过哪些事情

beforeCreate创建之前;无法获取响应数据

created创建之后,可以在这加个loading事件和进行数据请求

beforeMount挂载前 ,在这结束loading,还做一些初始数据的获取,实现函数自执行

mounted挂载后 ,在这发起后端请求,拿回数据,配合路由钩子做一些事情

beforeUpdate数据更新之前

updated数据更新完成之后

beforeDestroy销毁之前 ,你确认删除XX吗?或者确认退出吗?

destroyed销毁之后 ,当前组件已被删除,清空相关内容,在这获取不到dom了


2、组件通信

父传子:props、 a t t r s / attrs/ attrs/listeners、 c h i l d r e n 、 children、 childrenroot、provide/inject、$refs

子传父: e m i t 、 emit、 emitparent、

同级传:eventBus、vuex


3、页面通信

url拼接参数:"/a?a1=a1",接收页面:this.$route.query.a1

query传参:{path: ‘a’, query: {a2:‘a2’}},接收页面:this.$route.query.a2

params传参:{name: ‘a’, params: {a3:‘a3’}},接收页面:this.$route.params.a3

动态路由传参:/path/a4 ,接收页面:this.$route.params.id,路由:path: “/a/:id”


4、$set是干什么的

当数据变化但没有更新视图时使用,例如对象新增加的属性,数组新增加的成员

this.$set(obj,"key","value")

5、$nextTick是干什么的

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

例如:在created生命周期中想要操作dom就可以使用

this.$nextTick(()=>{ … })可以在mounted之前的生命周期中操作dom


6、mixin是干什么的

提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

var mixin = {
  data: function () {
    return {
      message: 'hello',
      foo: 'abc'
    }
  }
}

new Vue({
  mixins: [mixin],
  data: function () {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created: function () {
    console.log(this.$data)
    // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})

7、简单说说MVVM的理解

MVVM 是 Model-View-ViewModel 的缩写。 Model代表数据模型,也可以在 Model中定义数据修改和操作的业务逻辑。 View 代表UI 组件,它负责将数据模型转化成 UI 展现出来。 ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理 解就是一个同步View 和 Model的对象,连接Model和View。 在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也 会立即反应到View 上。 ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起 来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关 注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维 护完全由 MVVM 来统一管理。


8、watch和computed的区别

Watch只能监听data中的数据变化,computed不需要,watch可以进行异步操作, computed不可以,computed不修改原始数据,通过return返回处理的数据,可以包含大 量的逻辑运算


9、v-if和v-show的区别

9.1、v-show 只是简单的控制元素的 display 属性,而 v-if 才是条件渲染(条件为真,元 素将会被渲染,条件为假,元素会被销毁);

9.2、v-show 有更高的首次渲染开销,而 v-if 的首次渲染开销要小的多;

9.3、v-if 有更高的切换开销,v-show 切换开销小;

9.4、v-if 有配套的 v-else-if 和 v-else,而 v-show 没有

9.5、v-if 可以搭配 template 使用,而 v-show 不行


10、为什么不能v-for和v-if一起使用

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

永远不要把 v-if 和 v-for 同时用在一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断)

如果避免出现这种情况,则在外层嵌套 template (页面渲染不生成dom节点),再这一层进行 v-if 判断,然后再内部进行 v-for 循环

<template v-if="isShow">
	<p v-for="item in items">
</template>

如果条件出现再循环内部,可通过计算属性 computed 提前过滤掉那些不需要显示的项

computed:{
	items:function(){
		return this.list.filter(function(item){
			return item.isShow
		})
	}
}

11、key的作用是什么,值写index和id哪个更好

key是为每个vnode指定唯一的id,在同级vnode的Diff过程中,可以根据key快速的进 行对比,来判断是否为相同节点, 利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,指定key后,可以 保证渲染的准确性(尽可能的复用 DOM 元素。)赋值时应优先使用id。


12、过滤器怎么使用
// 全局使用
Vue.filter('globalFilter', function(){
    // ...
})

// 局部使用
filters: {
    formatMoney(num) {
		// ...
    },
}
<p>过滤器{{ money | formatMoney }}</p>

13、vuex五大核心分别是干什么的

state:Vuex中的基本数据,辅助函数mapState

getters:即从store的state中派生出的状态,有点类似计算属性,辅助函数mapGetters

mutations:是更改Vuex中的store中的状态的唯一方法,是同步的,辅助函数mapMutations

actions:Action 提交的是 mutation,而不是直接变更状态。 Action 可以包含任意异步操作。辅助函数mapActions

Modules:Vuex 允许我们将 store 分割到模块(module)。每个模块拥有自己的 state、mutations、actions、getters、甚至是嵌套子模块——从上至下进行类似的分割


14、如何调用mutations和actions的方法
调用mutations:$store.commit('mutations中定义的方法')

调用actions:$store.dispatch('actions中定义的方法')

actions调用mutations中的方法:

fn(context){

    context.commit('mutations中定义的方法');

}

15、vue-router常写属性都有什么

router-link常用属性:

to表示目标路由的链接

replace设置replace属性的话,当点击时,会调用roter.replace()而不是router.push(),所以导航后不会留下history记录,也就是不能回退到上一个页面

append设置append属性后,则在当前路径前添加基路径,例如,我们从/a导航到一个相对路径b,如果没有配置append,则路径为/b,如果配了,则为/a/b

tag 有时候想要渲染成某种标签,例如

  • 。于是我们使用tag prop 类指定何种标签,同样它还是会监听点击,触发导航。
  • active-class 设置链接激活时使用的css类名。默认值可以通过路由的构造选项linkActiveClass来全局配置, 默认值为 ‘router-link-active‘

    exact “是否激活”,默认是false 。

    vue-router常用属性:

    path路由路径

    name路由名字

    component导入路由组件

    redirect路由重定向

    mode路由模式

    children子路由

    meta路由元信息


    16、路由守卫都有哪些以及都做过哪些事情,三个参数分别是干什么的

    全局守卫:beforeEach(登录拦截)、afterEach

    路由独享守卫:beforeEnter(部分路由的登录拦截)

    组件内守卫:beforeRouteEnter(权限管理)、beforeRouteUpdate、beforeRouteLeave

    路由全局解析守卫:beforeResolve(这里根据单页面name的指向不同,去访问的接口域名也不同)

    三个参数:to:去哪,from:从哪来,next:下一步

    当从a页面离开进入b页面时触发的生命周期
        1.beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
        2.beforeEach: 路由全局前置守卫,可用于登录验证、全局路由loading等。
        3.beforeEnter: 路由独享守卫
        4.beforeRouteEnter: 路由的组件进入路由前钩子。
        5.beforeResolve:路由全局解析守卫
        6.afterEach:路由全局后置钩子
        7.beforeCreate:组件生命周期,不能访问this。
        8.created:组件生命周期,可以访问this,不能访问dom。
        9.beforeMount:组件生命周期
        10.deactivated: 离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。
        11.mounted:访问/操作dom。
        12.activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。
        13.执行beforeRouteEnter回调函数next。
    

    17、hash和history模式的区别

    hash模式就是url后面写#锚点,由于 hash 值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件(hashchange只能改变 # 后面的url片段);更关键的一点是,因为hash发生变化的url都会被浏览器记录下来,从而你会发现浏览器的前进后退都可以用了,所以人们在 html5 的 history 出现前,基本都是使用 hash 来实现前端路由的

    history模式:hash 能兼容到IE8, history 只能兼容到 IE10;hash 本来是拿来做页面定位的,如果拿来做路由的话,原来的锚点功能就不能用了。其次,hash 的传参是基于 url 的,如果要传递复杂的数据,会有体积的限制,而 history 模式不仅可以在url里放参数,还可以将数据存放在一个特定的对象中。history api可以分为两大部分:切换(back、forward、go)和修改(pushState,replaceState)。history模式的问题:就怕刷新,


    18、说说常用指令有哪些,如何自定义指令

    v-if :如果是真则渲染节点,否则不渲染节点

    v-if、v-else 和 v-else-if :类似js的if…else判断语句

    v-show :通过display:none;控制元素显示隐藏

    v-for :循环,v-for的优先级高于v-if,不推荐一起使用

    v-bind :绑定属性,

    v-on :绑定事件,

    .stop 阻止事件继续传播

    .prevent 事件不再重载页面

    .capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理

    .self 只当在 event.target 是当前元素自身时触发处理函数

    .once 事件将只会触发一次

    .passive 告诉浏览器你不想阻止事件的默认行为

    v-model :数据双向绑定

    .lazy 默认情况下,v-model同步输入框的值和数据。可以通过这个修饰符,转变为在change事件再同步。

    .number 自动将用户的输入值转化为数值类型

    .trim 自动过滤用户输入的首尾空格

    v-text 和 v-html :用来更新textContent和输出真正的html结构

    v-pre :主要用来跳过这个元素和它的子元素编译过程。

    v-cloak :保持在元素上直到关联实例结束时进行编译。

    v-once :关联的实例,只会渲染一次。之后的重新渲染,实例极其所有的子节点将被视为静态内容跳过,这可以用于优化更新性能。

    // 自定义指令 v-focus
      directives: {
        focus: {
          // 指令的定义
          inserted: function(el) {
            el.focus();
          },
        },
      },
    

    19、vue插槽如何使用
    // 比如新建一个<base-layout> 组件
    <div class="container">
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
    // 使用插槽
    <base-layout>
      <template v-slot:header> // 或者 <template #header>
        <h1>Here might be a page title</h1>
      </template>
    
      <template v-slot:default>
        <p>A paragraph for the main content.</p>
        <p>And another one.</p>
      </template>
    
      <template v-slot:footer>
        <p>Here's some contact info</p>
      </template>
    </base-layout>
    
    // 作用域插槽,比如新建一个<current-user>组件
    <span>
      <slot v-bind:user="user">
        {{ user.lastName }}
      </slot>
    </span>
    // 使用插槽
    <current-user>
      <template v-slot:default="slotProps">
        {{ slotProps.user.firstName }}
      </template>
    </current-user>
    // 或者缩写
    <current-user v-slot="slotProps">
      {{ slotProps.user.firstName }}
    </current-user>
    
    
    

    20、vue单页应用优缺点

    优点:Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组 件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、 快速、模块友好。 只关心数据,不关心DOM。插件众多。维护状态方便。

    缺点:不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(如果要支持 SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏 览器的导航按钮需要自行实现前进、后退。


    21、为什么做SSR,如何实现

    更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。

    更快的内容到达时间,特别是对于缓慢的网络情况或运行缓慢的设备。通常 可以产生更好的用户体验。

    开发条件所限。浏览器特定的代码,只能在某些生命周期钩子函数中使用; 一些外部扩展库可能需要特殊处理,才能在服务器渲染应用程序中运行。

    涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完 全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js server 运行环境

    更多的服务器端负载

    通过后端返回html结构,在前端进行渲染展示,可使用Nuxt实现。


    22、如何实现路由懒加载

    为给客户更好的客户体验,首屏组件加载速度更快一些,解决白屏问题。

    可以将页面进行划分,需要的时候加载页面,可以有效的分担首页所承担的加载压力

    减少首页加载用时。

    component: () => import('./Foo.vue')
    // 或者
    component: resolve => require(['@/components/home'],resolve)
    

    23、less如何设置全局样式
    // vue.config.js 中
    function addStyleResource(rule) {
      rule
        .use("style-resource")
        .loader("style-resources-loader")
        .options({
          patterns: [path.resolve(__dirname, "./src/assets/less/global.less")],
        });
    }
    // module.export 中 使用less
      css: {
        loaderOptions: {
          less: {
            lessOptions: {
              javascriptEnabled: true,
              globalVars: {
                primary: "#fff",
              },
            },
          },
        },
      },
    // less全局变量
      chainWebpack: (config) => {
        const types = ["vue-modules", "vue", "normal-modules", "normal"];
        types.forEach((type) =>
          addStyleResource(config.module.rule("less").oneOf(type))
        );
      },
    

    24、scoped的作用是什么

    scoped的意思是下面的样式的作用域就是当前这个组件,不会影响全局的样式


    25、 r o u t e r 和 router和 routerroute的区别

    r o u t e r 是 路 由 跳 转 , router是路由跳转, routerroute是路由信息


    26、data:{}和data(){return {}} 的区别

    因为不使用return包裹的数据会在项目的全局可见,会造成变量污染,使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件。起到保护源数据的效果。


    27、axios的配置、封装、拦截和跨域
    // 跨域:在config文件夹中的index.js中的devServe中填写
    proxyTable:{ // cli3.0+  是在vue.config.js中配置proxy
    	'/api':{
    		Target:’代理服务器的目标地址’,
    		changeOrigin: true,
    		PathRewrite: {“^/api”:” ”}
    	}
    }
    
    // 封装和拦截
        import axios from "axios";
    
        const http = axios.create({
          baseURL: "/api",
          timeout: 5000,
          headers: {
            "Context‐Type": "application/json",
          },
        });
        // 请求拦截
        http.interceptors.request.use(
          (res) => {
            // const token = sessionStorage.getItem('token') ? sessionStorag.getItem('token') : '';
            // if(token){给headers添加token}
            return res;
          },
          (err) => {
            return err;
          }
        );
        // 响应拦截
        http.interceptors.response.use(
          (res) => {
            // const code = res.code
            // if(code === 404){router.replace()}
            // if(code === 200){router.replace()}
            return res;
          },
          (err) => {
            return err;
          }
        );
    
        function get(url, params = {}) {
          return new Promise((resolve, reject) => {
            http
              .get(url, params)
              .then((res) => resolve(res))
              .catch((err) => reject(err));
          });
        }
    
        function post(url, params = {}) {
          return new Promise((resolve, reject) => {
            http
              .post(url, params)
              .then((res) => resolve(res))
              .catch((err) => reject(err));
          });
        }
    
        export { get, post };
    

    28、cli各版本构建项目的命令和启动命令

    创建项目:
    cli2.0:vue init webpack 项目名
    cli3.0+:vue create 项目名
    启动项目:
    cli2.0:npm run dev
    cli3.0+:npm run serve


    29、简单谈谈你对vue3.0的理解

    29.1、vue3.0和vue2.0最大的区别就是api从原来的options API 变成了 composition API + options API ,编写代码更灵活、复用率更高。

    29.2、vue3.0比vue2.0快2倍,Tree-shaking 更友好

    29.3、vue3.0支持TypeScript以及PWA

    29.4、数据双向绑定从Object.defineProperty变成了 new Proxy,不用再使用$set了

    29.5、其他方面的更改:支持自定义渲染器、支持 Fragment和 Protal组件等。


    30、简单说说双向绑定的原理
        // 发布订阅模式实现的数据依赖采集器
        class Dep {
          constructor() {
            this.subs = [];
          }
          addSub(sub) {
            this.subs.push(sub);
          }
          notify() {
            this.subs.forEach((sub) => sub.update());
          }
        }
        Dep.depTargets = null;
    
        class Watcher {
          constructor(data, getter) {
            this.getter = getter;
            this.value = this.get();
          }
          get() {
            Dep.depTargets = this;
            let value = this.getter();
            Dep.depTargets = null;
            return value;
          }
          update() {
            this.value = this.get();
          }
        }
    
        const typeTo = (val) => Object.prototype.toString.call(val);
    
    	// 观察者模式监听所有属性的变化
        function defineReactive(obj, key, value) {
          let dep = new Dep();
          Object.defineProperty(obj, key, {
            set(newValue) {
              if (newValue === value) return;
              value = newValue;
              dep.notify();
            },
    
            get() {
              const topTarget = Dep.depTargets;
              dep.addSub(topTarget);
              return value;
            },
          });
        }
    
        function walk(obj) {
          Object.keys(obj).forEach((key) => {
            if (typeTo(obj[key]) === "[object Object]") {
              walk(obj[key]);
            }
            defineReactive(obj, key, obj[key]);
          });
        }
    
        function observe(value) {
          if (typeTo(value) !== "[object Object]") return null;
          walk(value);
        }
    
        observe(this.data);
        new Watcher(this.data, () => {
          this.$mounte(this.el);
        });
    

    31、做过哪些vue的性能优化

    31.1、长列表性能优化:可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。

    export default {
      data: () => ({
        books: []
      }),
      async created() {
        const books = await axios.get("/api/books");
        this.books = Object.freeze(books);
      }
    };
    

    31.2、优化无限列表性能:如果应用存在非常长或者无限滚动的列表,那么需要采用“窗口化”的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间。window.requestAnimationFrame方法可以设置延迟加载的功能

    	setTimeout(() => {
            // 插入十万条数据
            const total = 100000;
            // 一次20条,可根据性能问题自己调整
            const MAX_ONCE = 20;
            // 渲染数据需要的次数
            const loopCount = total / MAX_ONCE;
            let countOfRender = 0;
            let el = document.querySelector("ul");
            function add() {
              // 优化,不允许插入数据引起回流
              const fragment = document.createDocumentFragment();
              for (let i = 0; i < MAX_ONCE; i++) {
                const li = document.createElement("li");
                li.innerText = `${i} + ${Math.floor(Math.random() * total)}`;
                fragment.appendChild(li);
              }
              el.appendChild(fragment);
              countOfRender += 1;
              loop();
            }
    
            function loop() {
              if (countOfRender < loopCount) {
                window.requestAnimationFrame(add);
              }
            }
            loop();
          }, 0);
    

    32、vue的diff算法和虚拟dom

    虚拟DOM就是为了解决浏览器性能问题而被设计出来的。若一次操作中有N次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这N次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一次性添加到DOM树上,再进行后续操作,避免大量无谓的计算量。所以,用JS对象模拟DOM节点的好处是,页面的更新可以先全部反映在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成后,再将最终的JS对象映射成真实的DOM,交由浏览器去绘制。

    vue的diff算法:只进行同层级比较,忽略跨级操作,从两头向中间进行对比。

    //diff时调用patch函数,patch接收两个参数vnode,oldVnode,分别代表新旧节点。
    function patch (oldVnode, vnode) {
        if (sameVnode(oldVnode, vnode)) {
            patchVnode(oldVnode, vnode)
        } else {
            const oEl = oldVnode.el
            let parentEle = api.parentNode(oEl)
            createEle(vnode)
            if (parentEle !== null) {
                api.insertBefore(parentEle, vnode.el, api.nextSibling(oEl))
                api.removeChild(parentEle, oldVnode.el)
                oldVnode = null
            }
        }
        return vnode
    }
    // patch函数内第一个if判断sameVnode(oldVnode, vnode)就是判断这两个节点是否为同一类型节点
    function sameVnode(oldVnode, vnode){
      //两节点key值相同,并且sel属性值相同,即认为两节点属同一类型,可进行下一步比较
        return vnode.key === oldVnode.key && vnode.sel === oldVnode.sel
    }
    //也就是说,即便同一个节点元素比如div,他的className不同,Vue就认为是两个不同类型的节点,执行删除旧节点、插入新节点操作。这与react diff实现是不同的,react对于同一个节点元素认为是同一类型节点,只更新其节点上的属性。
    
    //对于同类型节点调用patchVnode(oldVnode, vnode)进一步比较
    patchVnode (oldVnode, vnode) {
        const el = vnode.el = oldVnode.el  //让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。
        let i, oldCh = oldVnode.children, ch = vnode.children
        if (oldVnode === vnode) return  //新旧节点引用一致,认为没有变化
        //文本节点的比较
        if (oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text) {
            api.setTextContent(el, vnode.text)
        }else {
            updateEle(el, vnode, oldVnode)
            //对于拥有子节点(两者的子节点不同)的两个节点,调用updateChildren
            if (oldCh && ch && oldCh !== ch) {
                updateChildren(el, oldCh, ch)
            }else if (ch){  //只有新节点有子节点,添加新的子节点
                createEle(vnode) //create el's children dom
            }else if (oldCh){  //只有旧节点内存在子节点,执行删除子节点操作
                api.removeChildren(el)
            }
        }
    }
    // 更新vnode:updateChildren
    function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
        var oldStartIdx = 0;
        var newStartIdx = 0;
        var oldEndIdx = oldCh.length - 1;
        var oldStartVnode = oldCh[0];
        var oldEndVnode = oldCh[oldEndIdx];
        var newEndIdx = newCh.length - 1;
        var newStartVnode = newCh[0];
        var newEndVnode = newCh[newEndIdx];
        var oldKeyToIdx, idxInOld, vnodeToMove, refElm;
    
        // removeOnly is a special flag used only by <transition-group>
        // to ensure removed elements stay in correct relative positions
        // during leaving transitions
        var canMove = !removeOnly;
    
        {
          checkDuplicateKeys(newCh);
        }
        // 如果索引正常
        while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
            // 当前的开始旧节点没有定义,进入下一个节点
          if (isUndef(oldStartVnode)) {
            oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
            // 当前的结束旧节点没有定义,进入上一个节点
          } else if (isUndef(oldEndVnode)) {
            oldEndVnode = oldCh[--oldEndIdx];
            // 如果旧的开始节点与新的开始节点相同,则开始更新该节点,然后进入下一个节点
          } else if (sameVnode(oldStartVnode, newStartVnode)) {
         // 更新节点
            patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
            oldStartVnode = oldCh[++oldStartIdx];
            newStartVnode = newCh[++newStartIdx];
            // 如果旧的结束节点与新的结束节点相同,则开始更新该节点,然后进入下一个节点
          } else if (sameVnode(oldEndVnode, newEndVnode)) {
            patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
            oldEndVnode = oldCh[--oldEndIdx];
            newEndVnode = newCh[--newEndIdx];
            // 如果旧的开始节点与新的结束节点相同,更新节点后把旧的开始节点移置节点末尾
          } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
            patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
            canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
            oldStartVnode = oldCh[++oldStartIdx];
            newEndVnode = newCh[--newEndIdx];
            // 如果旧的结束节点与新的开始节点相同,更新节点后把旧的结束节点移置节点开头
          } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
            patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
            canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
            oldEndVnode = oldCh[--oldEndIdx];
            newStartVnode = newCh[++newStartIdx];
          } else {
              // 如果旧的节点没有定义key,则创建key
            if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); }
            idxInOld = isDef(newStartVnode.key)
              ? oldKeyToIdx[newStartVnode.key]
              : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
            // 如果没有定义index,则创建新的新的节点元素
            if (isUndef(idxInOld)) { // New element
              createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm);
            } else {
              vnodeToMove = oldCh[idxInOld];
              if (sameVnode(vnodeToMove, newStartVnode)) {
                patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue);
                oldCh[idxInOld] = undefined;
                canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
              } else {
                // same key but different element. treat as new element
                createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm);
              }
            }
            newStartVnode = newCh[++newStartIdx];
          }
        }
        // 如果旧节点的开始index大于结束index,则创建新的节点  如果新的开始节点index大于新的结束节点则删除旧的节点
        if (oldStartIdx > oldEndIdx) {
          refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;
          addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
        } else if (newStartIdx > newEndIdx) {
          removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
        }
      }
    

    33、vuex页面刷新后数据丢失?和history模式刷新404问题?
    //   在页面加载时读取sessionStorage里的状态信息
    if ( sessionStorage.getItem('state') ) {
      this.$store.replaceState( Object.assign( {}, this.$store.state,
      JSON.parse(sessionStorage.getItem('state') ) ) )
    }
    
    //   页面刷新时将state数据存储到sessionStorage中
    window.addEventListener('beforeunload',()=>{
      sessionStorage.setItem('state',JSON.stringify(this.$store.state) )
    })
    
    // history模式刷新404问题   在vue.config.js中配置
    module.exports = {
        publicPath: '/',   //这个必须,引入静态资源需要从根路径引入,否则会找不到静态资源
        devServer: {
            // history模式下的url会请求到服务器端,但是服务器端并没有这一个资源文件,就会返回404,所以需要配置这一项
            historyApiFallback: {
                index: '/index.html' //与output的publicPath
            },
          },
    }
    

    34、Vue 开发中如何使用全局状态常量?你都用这个状态常量做什么事情

    比如最常见的全局状态常量就是process.env.NODE_ENV

    它的值可能是:production、development

    就是webpack.config.js中的mode。

     {
     	mode: "",
     	entry: "",
     	output: {}
     }
    
     // 比如现在是开发模式,我就显示一个某某功能按钮。
    <button v‐if="process.env.NODE_ENV === 'develpment'">
    测试按钮
    </button>
    npm run serve 的时候看的见这个按钮
    npm run build 的时候看不见这个按钮
    

    35、动态路由

    通过addRoutes() 动态添加路由信息


    六、React

    1、调用 setState 之后发生了什么?

    在代码中调用 setState 函数之后,React 会将传入的参数对象与组件当前的状态合 并,然后触发所谓的调和过程(Reconciliation)。经过调和过程,React 会以相对高效的 方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面。在 React 得到元素 树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重 渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改 变,这就保证了按需更新,而不是全部重新渲染。


    2、react 生命周期函数

    初始化阶段:

    getDefaultProps:获取实例的默认属性

    getInitialState:获取每个实例的初始化状态

    componentWillMount:组件即将被装载、渲染到页面上

    render:组件在这里生成虚拟的 DOM 节点

    componentDidMount:组件真正在被装载之后

    运行中状态:

    componentWillReceiveProps:组件将要接收到属性的时候调用

    shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回 false, 接收数据后不更新,阻止 render 调用,后面的函数不会被继续执行了)

    componentWillUpdate:组件即将更新不能修改属性和状态

    render:组件重新描绘 componentDidUpdate:组件已经更新

    销毁阶段:

    componentWillUnmount:组件即将销毁


    3、为什么虚拟 dom 会提高性能?

    虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没 有必要的 dom 操作,从而提高性能。 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进 行比较,记录两棵树差异把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视 图就更新了。


    4、react diff 原理

    把树形结构按照层级分解,只比较同级元素。 给列表结构的每个单元添加唯一的 key 属性,方便比较。 React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字) 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一 个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制. 选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。


    5、React 中 refs 的作用是什么?

    Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。我们可以 为元素添加 ref 属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函 数的第一个参数返回


    6、展示组件和容器组件之间有何不同?

    展示组件关心组件看起来是什么。展示专门通过 props 接受数据和回调,并且几乎不 会有自身的状态,但当展示组件拥有自身的状态时,通常也只关心 UI 状态而不是数据的状 态。 容器组件则更关心组件是如何运作的。容器组件会为展示组件或者其它容器组件提供数 据和行为(behavior),它们会调用actions,并将其作为回调提供给展示组件。容器组件经 常是有状态的,因为它们是(其它组件的)数据源。


    7、类组件和函数式组件之间有何不同?

    类组件不仅允许你使用更多额外的功能,如组件自身的状态和生命周期钩子,也能使组 件直接访问 store 并维持状态 当组件仅是接收 props,并将组件自身渲染到页面时,该组件就是一个 ‘无状态组件 (stateless component)’,可以使用一个纯函数来创建这样的组件。这种组件也被称为哑组 件(dumb components)或展示组件


    8、createElement 和 cloneElement 有什么区别?

    React.createElement():JSX 语法就是用 React.createElement()来构建 React 元素 的。它接受三个参数,第一个参数可以是一个标签名。如 div、span,或者 React 组件。 第二个参数为传入的属性。第三个以及之后的参数,皆作为组件的子组件。

    React.createElement(
    	type,
    	[props],
    	[...children]
    )
    

    React.cloneElement()与 React.createElement()相似,不同的是它传入的第一个参数 是一个 React 元素,而不是标签名或组件。新添加的属性会并入原有的属性,传入到返回 的新元素中,而旧的子元素将被替换。

    React.cloneElement(
    	element,
    	[props],
    	[...children]
    )
    

    9、简述 flux 思想

    Flux 的最大特点,就是数据的"单向流动"。 用户访问 View ,View 发出用户的 Action, Dispatcher 收到 Action,要求 Store 进行相应的更新, Store 更新后,发出一个"change"事件, View 收到"change"事件后,更新页面


    10、了解 redux 么,说一下 redux

    redux 是一个应用数据流框架,主要是解决了组件间状态共享的问题,原理是集中式管 理,主要有三个核心方法,action,store,reducer,工作流程是 view 调用 store 的 dispatch 接收 action 传入 store,reducer 进行 state 操作,view 通过 store 提供的 getState 获取最新的数据,flux 也是用来进行数据操作的,有四个组成部分 action, dispatch,view,store,工作流程是 view 发出一个 action,派发器接收 action,让 store 进行数据更新,更新完成以后 store 发出 change,view 接受 change 更新视图。 Redux 和 Flux 很像。主要区别在于 Flux 有多个可以改变应用状态的 store,在 Flux 中 dispatcher 被用来传递数据到注册的回调事件,但是在 redux 中只能定义一个可更新状态 的 store,redux 把 store 和 Dispatcher 合并,结构更加简单清晰

    新增 state,对状态的管理更加明确,通过 redux,流程更加规范了,减少手动编码量, 提高了编码效率,同时缺点时当数据更新时有时候组件不需要,但是也要重新绘制,有些影 响效率。一般情况下,我们在构建多交互,多数据流的复杂项目应用时才会使用它们


    11、什么是JSX?

    JSX即JavaScript XML。一种在React组件内部构建标签的类XML语法。JSX为react.js 开发的一套语法糖,也是react.js的使用基础。React在不使用JSX的情况下一样可以工作, 然而使用JSX可以提高组件的可读性,因此推荐使用JSX。

    优点:

    1.允许使用熟悉的语法来定义 HTML 元素树;

    2.提供更加语义化且移动的标签;

    3.程序结构更容易被直观化;

    4.抽象了 React Element 的创建过程;

    5.可以随时掌控 HTML 标签以及生成这些标签的代码;

    6.是原生的 JavaScript。


    12、React Hooks是什么

    Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允 许你在 React 函数组件中添加 state 的 Hook。如果你在编写函数组件并意识到需要向其添 加一些 state,以前的做法是必须将其它转化为 class。现在你可以在现有的函数组件中使 用 Hook。

    hooks优势:无需复杂的DOM结构,简洁易懂

    他允许你在不写class的情况下操作state 和react的其他特性。hooks 只是多了一种写 组件的方法,使编写一个组件更简单更方便,同时可以自定义hook把公共的逻辑提取出 来,让逻辑在多个组件之间共享。


    13、class 组件有什么不足

    1.生命周期臃肿,逻辑耦合;

    2.逻辑难以复用;通过继承解决,不支持多继承;通过高阶组件解决,会增加额外的组 件嵌套;通过渲染属性解决,也会增加额外组件嵌套,层级臃肿

    3.class存在this指向问题;匿名函数解决,每次创建新的函数,子组件重复不必要渲 染;bind解决,需要写很多跟逻辑状态无关的代码


    七、小程序

    1、简单描述下微信小程序的相关文件类型

    微信小程序项目结构主要有四个文件类型

    WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、 事件系统,可以构建出页面的结构。内部主要是微信自己定义的一套组件

    WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式

    js 逻辑处理,网络请求

    json 小程序设置,如页面注册,页面标题及tabBar

    主要文件

    app.json 必须要有这个文件,如果没有这个文件,项目无法运行,因为微信框架把这 个作为配置文件入口,整个小程序的全局配置。包括页面注册,网络设置,以及小程序的 window 背景色,配置导航条样式,配置默认标题,底部导航等

    app.js 必须要有这个文件,没有也是会报错!但是这个文件创建一下就行 什么都不需 要写以后我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量

    app.wxss 可选,全局样式


    2、简述微信小程序原理

    微信小程序采用 JavaScript、WXML、WXSS 三种技术进行开发,本质就是一个单页面 应用,所有的页面渲染和事件处理,都在一个页面内进行,但又可以通过微信客户端调用原 生的各种接口微信的架构,是数据驱动的架构模式,它的 UI 和数据是分离的,所有的页面 更新,都需要通过对数据的更改来实现

    小程序分为两个部分 webview 和 appService 。其中 webview 主要用来展现 UI , appService 有来处理业务逻辑、数据及接口调用。它们在两个进程中运行,通过系统层 JSBridge 实现通信,实现 UI 的渲染、事件的处理


    3、小程序的双向绑定和vue哪里不一样

    小程序直接 this.data 的属性是不可以同步到视图的,必须调用: this.setData({ // 这里设置 })


    4、小程序页面间有哪些传递数据的方法

    4.1、使用全局变量实现数据传递,在 app.js 文件中定义全局变量 globalData, 将需要存 储的信息存放在里面

    4.2、使用 wx.navigateTo 与 wx.redirectTo 的时候,可以将部分数据放在 url 里面,并 在新页面 onLoad 的时候接收

    4.3、使用本地存储 Storage


    5、小程序的生命周期函数

    onLoad 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打 开当前页面路径中的参数

    onShow() 页面显示/切入前台时触发

    onReady() 页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥 当,可以和视图层进行交互

    onHide() 页面隐藏/切入后台时触发。 如 navigateTo 或底部 tab 切换到其他页面, 小程序切入后台等

    onUnload() 页面卸载时触发。如 redirectTo 或 navigateBack 到其他页面时


    6、小程序组件通信

    父传子:组件上写自定义属性,子组件通过properties接收

    //父组件
    <banner  step="{{step}}"></banner>
    
    //子组件
    Component({
      properties: {
        step:{
          type:Number
        }
      }
    )},
    

    子传父:通过自定义事件triggerEvent

    //子组件
     realNameConfirm(){
    	 let step=2;
         this.triggerEvent('realNameConfirm', step)     //通过triggerEvent将参数传给父组件
    }
    
    //父组件
    <realName  bind:realNameConfirm="realNameConfirm" ></realName> 
    

    调取子组件的实例selectComponent

    <banner  id="banner"></banner>
    this.selectComponent('#banner'); 
    

    八、常见编程题

    1、数组去重
    // indexOf
          function unique(array) {
            var newArr = []; 
            for (var i = 0; i < array.length; i++) {
              if (newArr.indexOf(array[i]) === -1) {
                newArr.push(arr[i]);
              }
            }
            return newArr;
          }
     
    // 双层循环
          function unique(array) {
            var r = [];
            for (var i = 0, l = array.length; i < l; i++) {
              for (var j = i + 1; j < l; j++) if (array[i] === array[j]) j = ++i;
              r.push(array[i]);
            }
            return r;
          }
    
    // hash表
          function unique(array) {
            var hash = {}; 
            var arr1 = []; 
            for (var i = 0; i < array.length; i++) {
              if (!hash[array[i]]) {
                hash[array[i]] = true;
                arr1.push(array[i]);
              }
            }
            return arr1;
          }
          
    // set
    	  function unique(array) {
            return Array.from(new Set(array))
          }
          
    // 排序后,相邻相同去重     
          function unique(array) {
            array.sort();
            var re = [array[0]];
            for (var i = 1; i < array.length; i++) {
              if (array[i] !== re[re.length - 1]) {
                re.push(array[i]);
              }
            }
            return re;
          }
    

    2、数组排序
    // 冒泡排序
          function bubbleSort(array) {
            for (let i = 0; i < array.length; i++) {
              for (let j = i + 1; j < array.length; j++) {
                if (array[i] > array[j]) {
                  const temp = array[i];
                  array[i] = array[j];
                  array[j] = temp;
                }
              }
            }
            return array;
          }
          
    // 选择排序
          function selectionSort(array) {
            for (let i = 0; i < array.length; i++) {
              let minIndex = i;
              for (let j = i + 1; j < array.length; j++) {
                if (array[j] < array[minIndex]) {
                  minIndex = j;
                }
              }
              const temp = array[i];
              array[i] = array[minIndex];
              array[minIndex] = temp;
            }
            return array;
          }
          
    // 插入排序
          function insertionSort(array) {
            let temp = 0;
            for (let i = 1; i < array.length; i++) {
              let j = i;
              temp = array[i];
              while (j > 0 && temp < array[j - 1]) {
                array[j] = array[j - 1];
                j--;
              }
              array[j] = temp;
            }
            return array;
          }
          
    // 归并去重
    	function mergeSort(array, lo, hi) {
            if (lo >= hi) return;
    
            const mid = Math.floor(lo + (hi - lo) / 2);
            mergeSort(array, lo, mid);
            mergeSort(array, mid + 1, hi);
    
            merge(array, lo, mid, hi);
          }
          function merge(array, lo, mid, hi) {
            let i = lo;
            let j = mid + 1;
            const aux = [];
    
            for (let k = lo; k <= hi; k++) {
              aux[k] = array[k];
            }
    
            for (let k = lo; k <= hi; k++) {
              if (i > mid) {
                array[k] = aux[j++];
              } else if (j > hi) {
                array[k] = aux[i++];
              } else if (aux[j] < aux[i]) {
                array[k] = aux[j++];
              } else {
                array[k] = aux[i++];
              }
            }
          }
    

    3、统计字符串中出现次数最多的字符
    var str = '12342535646757765453423';
    var res = str.split('').reduce((res, cur) => {
        res[cur] ? res[cur] ++ : res[cur] = 1;
        return res;
    }, {})
    console.log(res); // {1: 1, 2: 3, 3: 4, 4: 4, 5: 5, 6: 3, 7: 3}
    

    4、手写数组filter方法
    Array.prototype.guolv = function(fn){
    	var arr = [];
    	for(let i = 0 ; i < this.length ; i++){
    		if(fn(this[i])) arr.push(this[i]);
    	}
    	return arr;
    }
    

    5、手写数组map方法
    // for循环实现
    Array.prototype.myMap = function () {
    	var arr = this
    	var [fn, thisValue] = Array.prototype.slice.call(arguments)
    	var result = []
    	for (var i = 0; i < arr.length; i++) {
    		result.push(fn.call(thisValue, arr[i], i, arr))
    	}
    	return result
    }
    
    // forEach实现(reduce类似)
    Array.prototype.myMap = function (fn, thisValue) {
    	var result = []
    	this.forEach((v, i, arr) => {
    		result.push(fn.call(thisValue, v, i, arr))
    	})
    	return result
    }
    

    6、函数柯里化
          // 标准版
          function currying(fn) {
            let args = [];
            return function _c(...newArgs) {
              if (newArgs.length) {
                args = [...args, ...newArgs];
                return _c;
              } else {
                return fn.apply(this, args);
              }
            };
          }
    
          let addCurry = currying(add);
          function add() {
            return [...arguments].reduce((prev, curr) => prev + curr)
          }
          // 注意调用方式的变化
          console.log(addCurry(1)(2)(3)(4, 5)());
          
          // 简单版
          function sum(n) {
            function fn(m) {
              n += m;
              return fn;
            }
            fn.toString = function () {
              return n;
            };
            return fn;
          }
    

    7、数组扁平化
    // 二维数组转一维数组
    let arr = [[0, 1], [2, 3], [4, 5]]      
    let newArr = arr.reduce((pre,cur) => {
        return pre.concat(cur)              // 合并pre 与 cur, 并返回一个新数组
    },[])
    console.log(newArr);
    
    // 多维数组转一维数组
    var arr = [[[[1, 2, 3], 4, 5], 6], 7, [[[8]], 9, 10]];
    
          function flatten(arr) {
            var result = [];
            function iterator(arr) {
              for (let i = 0; i < arr.length; i++) {
                if (Array.isArray(arr[i])) {
                  iterator(arr[i]);
                } else {
                  result.push(arr[i]);
                }
              }
            }
            iterator(arr);
            return result;
          }
    
          console.log(flatten(arr));
    

    8、手写bind方法
    	 Function.prototype.bind = function () {
            // 保存原函数
            var self = this;
            // 取出第一个参数作为上下文, 相当于[].shift.call(arguments)
            var context = Array.prototype.shift.call(arguments);
            // 取剩余的参数作为arg; 因为arguments是伪数组, 所以要转化为数组才能使用数组方法
            var arg = Array.prototype.slice.call(arguments);
            // 返回一个新函数
            return function () {
              // 绑定上下文并传参
              self.apply(
                context,
                Array.prototype.concat.call(
                  arg,
                  Array.prototype.slice.call(arguments)
                )
              );
            };
          };
    

    9、数组交集、差集
    //交集
    function intersection(arr1, arr2){
    	return arr1.filter(item => arr2.includes(item));
    }
    
    //差集
    function differencesection(arr1, arr2){
    	return arr1.filter(item => !arr2.includes(item));
    }
    
    

    10、连续重复最长的项
          function maxRepeat(arr) {
            var i = 0;
            var j = 1;
    
            var maxCount = 0;
            var maxChar = "";
    
            while (i < arr.length) {
              if (arr[i] != arr[j]) {
                if (j - i > maxCount) {
                  maxCount = j - i;
                  maxChar = arr[i];
                }
                i = j;
              }
              j++;
            }
    
            return {
              maxCount,
              maxChar,
            };
          }
    
          console.log(maxRepeat([1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4]));
    

    11、手写queryString
    	// {"a":1,"b":2} => "a=1&b=2"
    
          var result = [];
          for (var k in o) {
            result.push(k + "=" + o[k]);
          }
          result.join("&");
    
          // "a=1&b=2" => {"a":1,"b":2}
    
          var str = "a=1&b=2";
          var arr = str.split("&");
          var obj = {};
          for (var i = 0; i < arr.length; i++) {
            var k = arr[i].split("=")[0];
            var v = arr[i].split("=")[1];
            obj[k] = v;
          }
    

    12、手写splice方法
          Array.prototype.removeIndex = function (index) {
            let length = this.length;
            for (let i = index; i < length; i++) {
              this[i] = this[i + 1];
            }
            this[length - 1] = null;
            this.length--;
          };
    

    九、其他

    1、webpack的基本配置

    1.1、entry(项目入口,打包的入口文件,一个字符串或者一个对象):entry:"./src/index.js"

    1.2、output(出口文件,配置打包的结果,一个对象):

    output:{
      filename: "build.js"
    }
    

    1.3、module(模块的处理):loader的配置主要在module.rules中进行,例如:

    module: {
      rules:[{
        test: /(\.jsx|\.js)/,  //表示匹配规则,是一个正则表达式
        use:{        //表示针对匹配文件将使用处理的loader
          loader: "babel-loader",  
          options: {
            presets: ["es2015", "react"]
          }
        }
      }]
    }
    

    1.4、plugin(loader不能做的处理都能交给plugin来做),例如:

    const CleanWebpackPlugin = require("clean-webpack-plugin")
    
    {
      plugin:[
        new webpack.DefinePlugin({
          "process.env" : {
            NODE_ENV: JSON.stringify("production")
          }
        }),
        new CleanWebpackPlugin(["js"], {
          root: __dirname + "/stu/",
          verbose: true,
          dry: false
        })
      ]
    }
    

    1.5、其它常用属性:

    devtool:打包后的代码和原始代码存在较大的差异,此选项控制是否生成以及如何生成sourcemap

    devserver:通过配置devserver选项,可以开启一个本地服务器(通常在这里配置跨域)

      devServer: {
        proxy: {
          '/api': {
            target: 'http://www.baidu.com/',
            pathRewrite: {'^/api' : ''},
            changeOrigin: true,     // target是域名的话,需要这个参数,
            secure: false,          // 设置支持https协议的代理
          }
        }
      }
    

    watch:启用watch模式后,webpack将持续监听热河已经解析文件的更改,开发是开启会很方便

    watchoption:用来定制watch模式的选项

    performance:打包后命令行如何展示性能提示,如果超过某个大小是警告还是报错

    1.6、webpack打包优化常用配置

    1.6.1、减小打包体积:

    使用CommonsChunkPlugin分离第三方包

    entry: {
    	vendor: ['babel-polyfill', "axios", "marked", "react", "react-dom", "react-router-dom"], // 第三方文件
    	app: './src/main.js'
    },
    plugins: [
    	new webpack.optimize.CommonsChunkPlugin({
    		name: "vendor", // 当加载 vendor 中的资源的时候,把这些资源都合并到 vendor.js 文件中
    		filename: "js/vendor.js",
    		minChunks: Infinity,
    	})
    ],
    

    或者使用externals进行cdn网络托管:

      // index.html页面中引入
      <script src="https://cdn.bootcss.com/react/15.0.0/react-with-addons.min.js"></script>
      <script src="https://cdn.bootcss.com/react/15.0.0/react-dom.min.js"></script>
      <script src="https://cdn.bootcss.com/react-router/3.0.0/ReactRouter.min.js"></script>
      <script src="https://cdn.bootcss.com/redux/3.6.0/redux.min.js"></script>
      <script src="https://cdn.bootcss.com/react-redux/5.0.1/react-redux.min.js"></script>
      <script src="https://cdn.bootcss.com/history/4.5.0/history.min.js"></script>
    // webpack中配置
    externals: {
    	'react': 'React',
    	'react-dom': 'ReactDOM',
    	'react-router': 'ReactRouter',
    	'redux': 'Redux',
    	'history': 'History'
    },
    
    // vue.config.js
      configureWebpack:{
        externals: {
          'vue': 'Vue',
          'element-ui': 'ELEMENT',
          'vue-router': 'VueRouter'
        },
      }
    

    1.6.2、小图片优化,使用url-loader,将小图转成base64,防止小图太多请求次数太多

    npm install -D url-loader
    module: {
    	rules: [{
    		test: /\.(png|svg|jpg|gif)$/,
    		use: [{
    		loader: 'url-loader', // 优化小图片过多造成请求数太多
    		options: {
    			limit: 8192, // 如果图片小于 8192 bytes 就直接 base64 内置到模板,否则才拷贝
    			outputPath: 'img/'
    		} 
    	}]
    },
    

    1.6.3、压缩css、js和html:

     // 压缩css
     rules: [
     	{
     		test: /\.css$/,
     		use: ExtractTextPlugin.extract({
     		fallback: "style-loader",
     		//这个地方配置一个对象,添加一个属性进行压缩css文件
     		use: {
     			loader: 'css-loader',
     			options: {
     				minimize: true   // 配置minimize 值为true,压缩css 文件
     			}
     		}
     	})
     },
     
    // 压缩js
    /*下载*/ npm install -D uglifyjs-webpack-plugin
    /*引入*/ const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
    /*配置*/
    plugins: [
    	new UglifyJsPlugin(), // 压缩 JavaScript
    ],
    
    //压缩html
    /*下载*/ npm   install  -D   html-webpack-plugin
    /*引入*/ const HtmlWebpackPlugin = require('html-webpack-plugin')
    /*配置*/
    plugins: [
    	new HtmlWebpackPlugin({
    		template: './index.html', // 把 index.html 也打包到 dist 目录中
            // 压缩 html,默认 false 不压缩
            minify: {
            	collapseWhitespace: true, // 去除回车换行符以及多余空格
                removeComments: true, // 删除注释
            }
        }), 
    ],
    

    2、git的常用命令

    2.1、首次拉取项目:git clone 项目地址

    2.2、https方式的需要输入用户名和密码,需要长期保存信息的话则输入:git config --global credential.helper winstore

    ssh方式的需要生成ssh密钥然后在github上设置,生成密钥:ssh-keygen -t rsa -C

    2.3、查看分支:git branch -a

    2.4、切换分支:git checkout 分支名

    2.5、拉取项目:git pull origin 分支名

    2.6、添加管理:git add .

    2.7、提交代码:git commit -m 说明

    2.8、查看提交:git log

    2.9、推送提交:git push -u origin 分支名

    2.10、版本回退:git reset --hard 目标版本号 或者 git revert -n 版本号

    或者git reset --hard HEAD^ 退回到前一个版本

    2.11、回退后push会报错,需要强制推送:git push -f

    2.12、合并分支:git merge 分支名


    3、项目的工作流程

    3.1、产品经理出原型图、流程图

    3.2、UI设计根据原型图出设计稿

    3.3、项目经理出开发规范、前后端任务分配

    3.4、前后端任务细分(根据功能、模块进行任务划分)、估计工期

    3.5、前端进行静态页面开发、后端进行数据库搭建

    3.6、后端写接口、接口联调、前端调接口渲染

    3.7、提交、测试、修改bug

    3.8、优化、打包、发布


    4、项目中你都负责哪块

    4.1、后台管理系统:主要使用cli脚手架进行项目搭建,使用elementUI进行页面的快速开发,负责的模块主要有登录页:登录验证、登录拦截、登录过期、登录异常处理;首页:echarts制表,有饼状图、柱状图、折线图,图表一键存为图片,动态渲染图表等;系统管理:系统设置,设置管理系统的版式、样式、风格;角色设置,设置角色的权限、个人信息、密码修改等;订单管理、文章管理、公告管理:进行相关的增删改查等操作。

    4.2、商城:主要有首页(搜素模块、轮播组件、九宫格组件、列表组件、上拉加载更多组件、loading组件)、详细页(自定义标题栏组件、模态框组件、消息提示框组件)、购物车页(商品数量增加,总金额实时计算)、登录注册页(表单验证、本地存储、数据加密、防XSS、CSRF攻击)、个人中心(我的收藏、我的设置、我的订单)等等


    5、平时是如何进行自己的技能提升的

    比如:去阮一峰个人网站、廖雪峰微博、csdn社区、哔哩哔哩等视频网站上进行学习,也有关注一些公众号,经常看一些大神发的帖子和文章。


    6、你还有什么想要了解的

    6.1、贵公司现在正在开发什么项目,周期是多久,用的是什么技术?

    6.2、公司项目中的技术是否总是一成不变的,是否会使用新技术?比如什么时候会考虑使用vue3?

    6.3、公司是否注重员工平时的技能提升,是否会有一些技能上的分享或者培训?

    6.4、人员配比是什么样的,有几个前端几个后端,平时是如何沟通的(用的git、svn、还是其它可视化工具)?


    7、你都封装过哪些组件

    7.1、展示组件:container、row、col、header、footer等

    7.2、列表组件:整体是一个左图右文、固定宽+自适应的布局,内容部分放入了插槽,提高了组件的扩展性。

    7.3、菜单组件:因为菜单层级是未知的,有的可能有二级,有的可能有三级或者更多层级,所以这块是通过一个递归组件来实现的,组件自己调用自己,最后暴露一个menuData属性,通过props来接收,父组件通过在子组件上写menuData属性来传值。

    7.4、loading组件、加载更多组件、分页组件等等。


  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值