0


前端常见面试八股文

HTML篇

1、H5新增标签有哪些?

一、语义化标签

header、footer、nav、aside、section、article

语义化的意义?

1、更适合搜索引擎的爬虫爬取有效的信息,利于SEO。

2、对开发团队很友好,增加了标签的可读性,结构更加的清晰,便于团队的开发和维护。

二、多媒体标签

视频标签:video

属性:src、 Poster(加载等待画面的图片)、muted(静音)

音频标签: audio

属性:src、loop、controls(调出当前控件)

三、表单元素、控件

输入框input中新增 type,类型可为、email、url、data、number等

required、placeholder

2、src和href的区别

src和href都是对外部资源的引用,区别如下:

src: 表示对资源的引用,用在js脚本、img、frame等元素上,当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到该资源加载、编译、执行完成,所以js脚本会放在页面的底部,而不是头部。

href:表示超文本引用,指向一些网络资源,当浏览器识别它指向的文件时,就会并行下载资源,不会停止对当前文件的处理,用在a、link上

3、defer和async

参考博文:

https://blog.csdn.net/weixin_42561383/article/details/86564715

默认情况下,浏览器是同步加载js脚本的,即渲染引擎遇到 script标签就会停下来,等执行完脚本,再去继续向下渲染,如果是外部脚本,还必须加入脚本的下载时间。

加入脚本的很多而且体积很大的话,下载执行 就会占用很大的时间,照成浏览器堵塞,用户就会感觉卡死了,没有任何响应,体验极差,所以浏览器允许脚本的异步加载,方式如下:

<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

关键字就是defer和async,脚本会异步加载。渲染引擎遇到这行命令就会开始下载外部的脚本,但是并不会等待其脚本下载完成和执行完成,而是直接执行后面的命令。

defer和async的区别:

defer:需要等待整个页面正常DOM渲染完成后,才会执行下载好的js脚本,并且是按下载完后的顺序执行。

async:当外部的js脚本下载完成后,渲染引擎会暂停DOM渲染,开始执行下载好的JS脚本的内容,在执行完成后,再去渲染。

简单的说:

defer是需要等DOM元素都渲染完成后,才去执行下载完成的js脚本

async是当js脚本下载完成后,开始执行的时候中断DOM渲染,执行js脚本的内容,执行完成再去渲染DOM元素

没有

defer

async

属性,浏览器会立即下载并执行相应的脚本,并且在下载和执行时页面的处理会停止。可能形成页面堵塞

不用异步加载的话可以把外部引入的脚本放在

</body>

前即可

4、行内元素与块级元素

块级元素: 独占一行,可以设置宽高,设置margin和padding都有效,代表如下:

div、p、h1…h6、table、tr、ol、li、ul

行内元素元素: 可以排成一行,设置宽高无效,对margin设置左右方向有效,而上下无效,padding设置都无效,代表如下:

基本上都是文本标签

span、img、b、strong、font、br、a

5、title与h1、b与strong、i与em的区别

Strong表示的是重点内容,有语气加强的含义,,会重读,而展示强调的内容

b只是展示强调的内容

title属性没有明确意义只表示是个标题,H1则表示层次明确的标题,对页面信息的抓取也有很大的影响

i内容展示为斜体,em表示强调的文本

6、回流(重拍)与重绘

先了解下HTML文文件渲染的过程

image-20220106163008328

都发生在渲染DOM树(Render Tree)后的过程中,其中Layout是回流,Painting是重绘

回流:浏览器中进行布局的过程就是回流

重绘:浏览器进行 渲染就是重绘

触发回流的一些条件:

1、添加或者删除可见的DOM元素;

2、元素位置改变;

3、元素尺寸改变——边距、填充、边框、宽度和高度

4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;

5、页面渲染初始化;

6、浏览器窗口尺寸改变——resize事件发生时;

image-20220106163705728

触发重绘的一些条件:

1、改变背景色

2、改变透明度

简单的说,就是不影响布局的情况下,改变的就是重绘

image-20220106164151661

性能优化方法:

减少浏览器的回流行为达到性能优化。

1、用transform代替位移

2、用visibility:hidden透明度代替display:none

3、使用display:none技术,只引发两次回流和重绘;

4、使用cloneNode(true or false)和replaceChild技术,引发一次回流和重绘;

5、让元素脱离动画流,减少回流的Render tree的规模;

7、cookie、localStorage 和 sessionStorage的区别及应用实例

详解cookie

简单的理解是因为http协议的无状态性,所以需要cookie去把特别的信息保存下来,当然保存的是在浏览器中,下面简单的讲解下逻辑

1、浏览器向服务器发送请求,传输一些信息

2、服务器收到请求,在响应体中通过set-cookie返回字段,保存在浏览器中。

3、浏览器再次发送请求时会携带cookie,根据服务器中对比,做一个判断。

在前端的js代码中通过:

document.cookie = ‘name=value’

去设置cookie,其中cookie带有如下属性

max-age/expires:可设置cookie的有效时间,其中expires必须时GMT的时间格式,可用

new Date().toGMTString()

去对时间进行转换。在没有设置时间时,cookie会随着浏览器的关闭,而被删除

domain:可设置访问该cookie的域名

path:可设置访问此cookie的页面路径

Size:设置cookie的大小

http:cookie的httponly属性,如果为true。则只有在http请求头中会有此cookie的信息,而不能通过

document.cookie

来进行访问

删除cookie

只需要把cookie的有效时间设置为当前日期的前任何时间即可

下面是列子:

  <body>
    <form>
      用户名<input type="text" name="username1111" /> 密码<input
        type="password"
        name="pwd"
        id="pwd"
      />
      提交<input id="submit" type="submit" /> 记住密码<input type="checkbox" />
      <button onclick="hand1()" id="delete">删除cookie</button>
    </form>

    <script>
      let lineTime = new Date("2022-7-20 19:7:30").toGMTString();
      console.log("lineTime: ", lineTime);
      let submit = document.getElementById("submit");
      let username = document.querySelector('input[type="text"]');
      let pwd = document.querySelector('input[type="password"]');
      let check = document.querySelector('input[type="checkbox"]');

      let arr = document.cookie.split(";");
      console.log(arr);
      let arr1 = [];
      arr.forEach((item) => {
        arr1.push(item.split("="));
      });
      let cookie = {};
      for (let i = 0; i < arr1.length; i++) {
        let name = arr1[i][0];
        let value = arr1[i][1];
        cookie[name] = value;
      }
      if (document.cookie) {
        username.value = cookie.yourname;
        check.checked = true;
      }
      submit.addEventListener("click", (e) => {
        let value = username.value;
        let key = "yourname";
        if (check.checked && username.value !== "") {
          document.cookie = `${key}=${value};expires=${lineTime}`;
          e.preventDefault();
        }
      });

      function hand1() {
        let key = "yourname";
        document.cookie = `${key}=; expires=Thu, 01 Jan 1970 00:00:00 GMT`;
        console.log(document.cookie);
        e.preventDefault();
      }
    </script>

localStorage 和sessionStorage

前面得cookie作为一个存储手段进行举例,但是在H5中新增了两种存储方式,其中使用方法如下:

localStorage.key() 

:拿到指定key得值

localStorage.setItem('名','值')

:设置相关的储存值

localStorage.getItem('名')

:取出对应名的值

localStorage.removeItem('名')

:移除掉相关的值

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <input type="text" />
    <button onclick="hand1()" class="hand">搜索</button>
    <ul id="one"></ul>

    <script>
      if (sessionStorage.length > 0) {
        for (let i = 0; i < sessionStorage.length; i++) {
          let key = sessionStorage.key(i);
          let newLi = document.createElement("li");
          newLi.innerHTML = `${sessionStorage.getItem(key)}`;
          let ul = document.getElementById("one");
          ul.append(newLi);
          let close = document.createElement("span");
          close.innerHTML = `删除`;
          newLi.appendChild(close);
          close.addEventListener("click", (e) => {
            sessionStorage.removeItem(key);
            newLi.parentNode.removeChild(newLi);
          });
        }
      }

      let search = document.querySelector('input[type="text"]');
      let hand = document.getElementsByClassName("hand")[0];
      let temp = 0;
      function hand1() {
        if (search.value) {
          console.log(111);
          temp++;
          sessionStorage.setItem(`${temp}`, search.value);
          let newLi = document.createElement("li");
          newLi.innerHTML = `${sessionStorage.getItem(temp)}`;
          let ul = document.getElementById("one");
          ul.append(newLi);
          let close = document.createElement("span");
          close.innerHTML = `删除`;
          newLi.appendChild(close);
          close.addEventListener("click", (e) => {
            console.log(temp);
            sessionStorage.removeItem(temp);
            newLi.parentNode.removeChild(newLi);
          });
        }
      }
    </script>
  </body>
</html>

sessionStorage使用的方法和localStorage一致。

三者的区别

cookie具有时效性,可以手动的去设置需要保存的时间,内存大小大概为4KB,内容保存在客户端上,删除的话把过期时间设置为前一天即可。

localStorage,除非手动去删除,不然一直会保存,内存大小为5MB,内容保存在服务器上

sessionStorage,临时会话保存,相关页面关闭掉就自动删除,内容大小为5MB,内容保存在服务器上’

建议理解三者的基本使用再去理解这面试题,因为工作中也能用到

8、同源策略与跨域问题

什么是同源策略

借用阮一峰老师的描述:

image-20220205201415594

简单地说就是为了网页之间的安全性,但是在平常的工作中,我们会遇到别的资源请求问题,也就是跨域问,下面给出几种常见的跨域解决问题的方案

跨域解决方案

参考B站技术蛋老师的讲解

1、JSONP

json with padding,也就是JSON的填充方法。在服务器与客户端之间的格式我们常用的是JSON格式。

我们用script标签获取数据,其中的数据会被执行,所以我们一般会给数据外包一个js函数,然后再外包一层作为传输用的json格式。

我们知道通过script标签请求的资源中,并不会受到同源策略的限制,而jsonp方法就是通过这个方式去实现别的资源共享,简单原理如下:

原理可分为 客户端与服务器

客户端:客户端上去写一个函数,专门用来处理跨域获取服务器JSON数据的方法,在传入的url上写额外的参数,传到服务器,同时客户端收到服务器传过来的数据后,调用提前设定好的函数,执行服务器传过来的数据。

服务器:数据源是在服务器上存在的,而我们需要再然后服务器对额外的参数进行判断,把数据外包一个js函数,通过JSON的方式传给客户端。

image-20220205203014701

image-20220205203255464

2、CORS

当浏览器向服务器发起请求的时候,会在请求头中添加origin(协议+主机+端口),也就是表明自己的协议+主机+端口,当服务器接收到这个origin的时候,就得添加头部Access-Control-Allow-Origin到响应里面,浏览器看到服务器传回来的Access-Control-Allow-Origin,就可以进行判断是否进行跨域请求

image-20220205205058169

image-20220205205425242

核心其实就是设置Access-Control-Allow-Origin

9、检测数据类型的方法

1、typeof

typeof在检测null、object、array、data的结果中都是object,所以无法用来区分这几个类型的区别

 <script>
      let a = [
        "123",
        123,
        false,
        true,
        Symbol(1),
        new Date(),
        null,
        undefined,
        function () {},
        {},
        []
      ];
      a.forEach((item) => {
        console.log(item, "检测出的值:", typeof item);
      });
    </script>

image-20220223220519006

2、instanceof

缺点是只能检测该对象是否存在目标对象的原型上

[对象] instanceof [构造函数]

  <script>
      function Foo() {}
      var f1 = new Foo();
      var d = new Number(1);

      console.log(f1 instanceof Foo); // true
      console.log(d instanceof Number); //true
      console.log(123 instanceof Number); //false   -->不能判断字面量的基本数据类型
    </script>
console.log(Number instanceof Number)  // false
console.log(String instanceof String)  // false
console.log(Fun instanceof Fun)        // false,这里Fun指的是函数
console.log(null instanceof Object)   // false,null不具有任何对象的特性,也没有__proto__属性

instanceof 用于判断对象类型,但以上情况的结果都为false,请注意。

instanceof 的原理

因为会一直往原型链上查找

L代表instanceof左边,R代表右边

function hand(L, R) {
        let L = L._propto_;
        while (true) {
          if (L === null) {
            return false;
          }
          if (L === R.propotype) {
            return true;
          }
          L = L._propto_;
        }
      }

3、constructor

constructor 不能判断null、undefind,因为他们不是构造对象

  <script>
      let a = [
        "123",
        123,
        false,
        true,
        Symbol(1),
        new Date(),
        function () {},
        {},
        [],
        null,
        undefined,
      ];
      try {
        a.forEach((item) => {
          console.log(item, "检测出的值:", item.constructor.name);
        });
      } catch (e) {
        console.log(e);
      }
    </script>

image-20220223221351705

4、Object.prototype.toString.call

可以检测出所有的数据类型

<script>
      let a = [
        "123",
        123,
        false,
        true,
        Symbol(1),
        new Date(),
        function () {},
        {},
        [],
        null,
        undefined,
      ];
      try {
        a.forEach((item) => {
          console.log(
            item,
            "检测出的值:",
            Object.prototype.toString.call(item)
          );
        });
      } catch (e) {
        console.log(e);
      }
    </script>

image-20220223222504029

在使用Array.prototype.toString.call的时候,遇到null、undefined会出现报错

10、阻止默认事件

在面试中我们会被问到事件冒泡、默认事件等概念,下面做一些简单的了解:

事件冒泡

1.原理:元素自身的事件被触发后,如果父元素有相同的事件,如click事件,那么元素本身的触发状态就会传递,也就是冒到父元素,父元素的相同事件也会一级一级根据嵌套关系向外触发,直到document/window,冒泡过程结束。

2.使用范围:冒泡事件只是针对于click相关的事件,还有click的分支事件:mouseup、mousedown

默认事件:

比如点击a标签会默认打开、input提交表单按钮,都是默认事件

阻止默认事件但是不阻止事件冒泡

event.preventDefault()

阻止事件冒泡阻止默认事件

event.stopPropagation();

在jquery中阻止事件冒泡和默认行为,在原生js中只阻止事件冒泡

return false

11、文档流,文本流清除浮动

文档流:

我理解的是页面中的块级元素等DOM元素按照默认的方式去进行排列

文本流:

网页页面上的一些文字的排列

清除浮动的方法及原理:

原理:其实就是让目标元素脱离文档流即可,手段有

float:left

position中的绝对定位和固定定位

方法:

1、用伪类去清除浮动

2、在浮动元素上添加一个空盒子,写上clear:both的样式

3、双伪类清除浮动

12、手写call、apply、bind

1、call

想要手写出就得知道它是怎么使用的,函数.call(绑定this指向,参数一,参数二,…)

<script>
      function person(a, b, c, d) {
        console.log(this.name);
        console.log(a, b, c, d);
      }
      let egg = {
        name: "测试",
      };

      //js创建新函数是在函数原型上去添加
      Function.prototype.newCall = function (params) {
        //先做判断,如果绑定的调用的this不是函数,则抛出异常错误
        if (typeof this !== "function") {
          throw new TypeError("不是函数");
        }

        //防止传入null、undefinde,和window进行绑定
        params = params || window;

        //先获取传入的参数
        let arr = [...arguments].slice(1);

        //把this指向指向形参的函数,保存this
        params.fn = this;

        //带入的参数传入函数中
        let result = params.fn(...arr);

        //删除用完的函数

        delete params.fn;

        return result;
      };

      person.newCall(egg,'1','2','3','4')
    </script>

image-20220225111457351

2、apply

apply和call的写法基本一至,就是二者接受的参数不同,面试apply的写法

函数.apply(this指向对象,[参数1,参数2,参数3…])

 <script>
      function person(a, b, c, d) {
        console.log(this.name);
        console.log(a, b, c, d);
      }
      egg = {
        name: "tom",
      };

      Function.prototype.newApply = function (context) {
        if (typeof this !== "function") {
          throw TypeError("不是一个函数");
        }
        context = context || window;
        context.fn = this;
        let result
        if (arguments[1]) {
          result = context.fn(...arguments[1]);
        } else {
          result = context.fn();
        }
        
        delete context.fn
        return result
      };

      person.newApply(egg,[1,2,3,4])
    </script>

image-20220227125422710

3、bind

bind与前两个的区别如下

1、bind返回的是一个函数

2、如果是new出来的 ,返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化

<script>
      function person(a, b, c, d) {
        console.log(this.name);
        console.log(a, b, c, d);
      }

      Function.prototype.newBind = function (content) {
        if (typeof this !== "function") {
          throw TypeError("不是一个函数");
        }
        let arr = Array.prototype.slice.call(arguments, 1);
        const that = this;
        return function fn() {
          if (this instanceof fn) {
            return new that(...arr, ...arguments);
          } else {
            return that.apply(content, arr.concat(...arguments));
          }
        };
      };

      egg = {
        name: "tom",
      };

      person.newBind(egg, 1, 2, 3)(4);
    </script>

image-20220227151331533

13、new的过程中发生了什么

其实new的过程中就是this指向改变、新创建一个实列的过程,如下

   <script>
      function Mother(tip){
        this.tip=tip
      }
      let son =new Mother()

      // 1、创建一个son空对象
      // 2、使空对象的隐式原型指向原函数的显示原型
      // son_proto_=Mother.prototype
      // 3、原函数进行this的绑定,指向空对象
      // let result =Mother.call(son,tip)
      // 4、判断函数结果是不是null、undefined,是就返回之前新对象,不是就返回结果
      // return result instanceof Object ? Object: result
    </script>

14、js的继承方式

1、原型链继承

关键点就是: 继承函数.prototype =new 被继承函数

  <script>
      function person() {
        this.obj = {
          age: 10,
        };
        this.speack=function(){
          console.log('父元素');
        }
      }
      person.prototype.name = "tom";
      //子元素
      function son() {
        this.name = "son1";
      }
      son.prototype = new person(); //原型链继承
      let p1 = new son();
      console.log(p1.obj.age); //10
      console.log(p1 instanceof person); //true
      console.log(p1.name); //son1
    </script>

缺点:

1、继承单一

2、子类无法向父类的构造函数传参

3、所有新实列都会共享父类实列的属性(原型上的属性也是共享的,一个实列修改了原型属性,另一个也会被修改)

2、原型继承

<script>
   var person ={
      arr: ['a','b','c','d']
   }
   var p1 =Object.create(person)
   p1.arr.push('aaa')
   console.log(p1);  //{}
   console.log(person); 

    </script>

image-20220228112350530

缺点:子类实列共享了父类构造函数的引用属性,不能传参

3、构造函数继承

<script>
      var person = function (name) {
        this.name = name;
      };
      person.prototype.age=10
      var son = function () {
        person.call(this, "tom");
      };
      var p1 =new son()
        console.log(p1.name);//tom
        console.log(p1.age);undefined
        
    </script>

重点:借用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行)

优点:1、可以继承多个构造函数属性

​ 2、在子实列中可以向父实列传参

缺点:1、只能继承父实列的构造函数属性

​ 2、每新实列都会有父类构造函数的副本,臃肿

4、组合继承

  <script>
      var person = function (name) {
        this.name = name;
      };
      person.prototype.age=10
      var son = function () {
        person.call(this, "tom"); //构造函数继承
      };
      son.prototype=new person() //原型链继承
      var p1 =new son()
        console.log(p1.name);//tom
        console.log(p1.age);undefined
        
    </script>

重点:结合了两种模式的优点,传参和复用
特点:1、可以继承父类原型上的属性,可以传参,可复用。
   2、每个新实例引入的构造函数属性是私有的。
缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。

5、寄生组合继承

 <script>
      var person = function (name) {
        this.name = name;
      };
      person.prototype.age = 10;
      var son = function () {
        person.call(this, "tom"); //构造函数继承
      };
      son.prototype = Object.create(person.prototype); //寄生组合继承
      son.prototype.constructor = son;
      var p1 = new son();
      console.log(p1.name); //tom
      console.log(p1.age); //10
    
    </script>

优点

  • 只调用了一次父类构造函数,只创建了一份父类属性
  • 子类可以用到父类原型链上的属性和方法

6、extends继承

  <script>
      class person{
        constructor(){
          this.name ='tom'
        this.speack =()=>{
          console.log('我是爹');
        }
        }
      }

     class son extends person{
       constructor(){
       
         super()
          this.age=12
       }
     }
    
    let s1 =new son()

    console.log(s1.age,s1.name);
    </script>

ES6出来的

​ 子类只要继承父类,可以不写 constructor ,一旦写了,则在 constructor 中的第一句话必须是 super 。

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

1、call、apply、bind不会改变箭头函数this值,会改变普通函数this值

2、箭头函数没有原型属性

3、箭头函数不绑定arguments,取而代之用rest参数… 解决

4、箭头函数不能作为构造函数使用,不能使用new

5、箭头函数中的 this 和调用时的上下文无关,而是取决于定义时的上下文

16、移动端1px像素解决办法

先理解概念,物理像素、逻辑像素

物理像素:是不同手机型号出厂时携带的像素,也称为硬件像素

逻辑像素:css中记录的像素

问题描述:

在开发的时候ui设计师要求的1px是设备的物理像素,而css中的是逻辑像素,它们之间并不是直接等于的关系,存在着比列关系,通常可以用 javascript 中的

window.devicePixelRatio

来获取,也可以用媒体查询的

-webkit-min-device-pixel-ratio

来获取。当然,比例多少与设备相关。

解决办法:

1、媒体查询利用设备像素比列缩放,设置小数像素

css可以这样设置:

.border { border: 1px solid #999 }
@media screen and (-webkit-min-device-pixel-ratio: 2) {
    .border { border: 0.5px solid #999 }
}
@media screen and (-webkit-min-device-pixel-ratio: 3) {
    .border { border: 0.333333px solid #999 }
}

js可以这样写:

<body><div id="main" style="border: 1px solid #000000;"></div></body>
<script type="text/javascript">
    if (window.devicePixelRatio && devicePixelRatio >= 2) {
        var main = document.getElementById('main');
        main.style.border = '.5px solid #000000';
    }
</script>

2、媒体查询 + transfrom 对方案1的优化

/* 2倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 2.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.5);
        transform: scaleY(0.5);
    }
}
/* 3倍屏 */
@media only screen and (-webkit-min-device-pixel-ratio: 3.0) {
    .border-bottom::after {
        -webkit-transform: scaleY(0.33);
        transform: scaleY(0.33);
    }
}

17、伪类与伪元素的区别

首先在知道这个概念之前先了解什么是伪类?伪元素?

1、伪元素

1、伪元素在DOM树中创建了一些抽象元素,这些抽象元素并不存在与文档语言中(逻辑上存在,不存在文档树中)

2、伪元素由两个::开头,接的是伪元素得名称(使用两个::是为了区分伪类和伪元素,在CSS2中依然可以用一个:的语法,但是在CSS3中必须使用两个冒号::)

3、一个选择器只能使用一个伪元素,并且伪元素必须处于选择器语句的最后

伪元素的种类:

image-20220301105558373

2、伪类

1、伪类由一个冒号开头:

2、获取不存在与DOM树中的信息。比如

<a>

标签的

:link

visited

等,这些信息不存在与DOM树结构中,只能通过CSS选择器来获取

伪类的种类:

image-20220301110437341

18、移动端、浏览器字体小于12px的解决方案

 font-size: 12px;

是浏览器字体设置最小的时候,12以下的数值不生效了,要是想实现小于12px,方法如下:

 transform: scale(0.5); 

这时候可能会遇到字体缩放和布局一起缩小了,只需要将字体与容器样式分开写就好了

<style>
      *,
      body {
        padding: 0;
        margin: 0;
      }
      .one {
        font-size: 20px;
        transform: scale(0.5);
      }
      .father {
        width: 100px;
        height: 100ox;
        background-color: red;
      }
    </style>
  </head>
  <body>
    <div class="father"><p class="one">这是一段话</p></div>
  </body>

19、js浮点数精度计算问题解决

一般运算中,我们会保留后两位小数,这里做点知识扩展

1、**.toFixed()**

语法:

数字.toFixed(2)

括号中的数字是保留的位数,该方法会四舍五入,返回的值类型是string型

2、Math.round()

语法:

Math.round(x) 

x是数值

返回值: 给指定的数字值四舍五入到最接近的整数

解决精度运算问题有如下方式:

1、 扩大倍数法:有多少位小数就扩大10的n次方

document.write((0.01*100+0.09*100)/100); //输出结果为0.1

2、四舍五入法:

document.write((0.01+0.09).toFixed(2)); //保留2位小数,输出结果为0.10

document.write(Math.round((0.01+0.09)*100)/100); //输出结果为0.1

20、移动端的300毫秒延迟问题

问题描述:

移动端浏览器在派发点击事件的时候,通常会出现300ms左右的延迟。也就是说,当我们点击页面的时候移动端浏览器并不是立即作出反应,而是会等上一小会儿才会出现点击的效果。

方案一:禁用缩放

<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">

方案二:更改默认的视口宽度

<meta name="viewport" content="width=device-width">

方案三:fastclick,解决移动端300ms延迟

FastClick 是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。FastClick的实现原理是在检测到touchend事件的时候,会通过DOM自定义事件立即出发模拟一个click事件,并把浏览器在300ms之后的click事件阻止掉。

直接安装依赖按照官网的教程使用就好了

21、HTTP缓存

面试中我们常会被到,你知道http缓存的方式有几种吗?这样的问题,下面我简单的介绍下,为什么会有HTTP缓存、缓存的方式

1、存在的意义

我们每一次的网络请求中,在请求成功的前提下,服务器都会给我们返回对应的资源,浏览器进行下载,但是并不是所以的资源都会被下载,比如当某些资源未改变时,浏览器会在本地缓存中拿到它们,从而避免了重复下载的过程,这也算是一种性能上的优化

可以被缓存的资源:JS文件、CSS文件、图片、字体包等等

2、缓存的类型

我们可以知道,前端发起HTTP请求的时候,会在请求头上携带相关的信息,浏览器就会根据我们携带的信息去判断,本次请求的该资源是否有本地缓存

强缓存

以前的使用中,我们一直用的是expires,也就是当服务器返回响应时,在响应头中将过期的时间写入expires字段中,自己随便找个网站F12打开调试台,找一条网络请求即可

image-20220508173343035

可以看到expires中写入了过期的时间范围,那整个流程是怎么样的呢?

当首次资源发送时:

image-20220508173625548

再次发送资源时,浏览器会把expires中的时间戳和本地时间去进行对比,如果本地时间小于expires中的时间,则在缓存中拿到这个资源,这里的时间戳由于是服务器上传的,所以需要保证浏览器和服务器上的时间一致

image-20220508174130577

expires是通过拿到时间戳去进行比对的,在后面又增加了

Cache-Control

中的

max-age

字段也允许我们通过设定时间长度来达到同样的目的。

max-age可以看作是对expires的补充,在日常工作中,我们可能用max-age较多,但是如果要实现向下兼容,expires也是必不可少的

cache-control: max-age=3600, s-maxage=31536000

这里可以看到max-age的值,它并不是一个时间戳,而是一个时间长度,单位是秒,意思是在3600秒内,该资源是有效的。max-age的机制就是对资源的判定有效不再受到服务器时间的限制,客户端会记录到请求资源的时间点,以此时间点为起点,从而确保两个时间点都来自于客户端,相对来说更加的精确,这里就不给出请求图了,和上面原理一致

这里介绍下cache-control中的值

max-age:缓存保存的时间

no-cache:绕开了浏览器,每一次发起请求都不会再去询问浏览器的缓存情况,而是直接向服务端去确认该缓存是否过期

no-store:不使用任何的缓存策略,连服务器端的缓存确认都绕开了,只允许直接向服务端发起请求,并下载完整的响应

public:资源能被本地缓存、服务器代理

private:资源只能给本地缓存,其他服务器不能缓存

协商缓存

浏览器向服务器询问是否需要重新下载资源,还是从本地上获取到缓存的资源,值得一提的是,当服务器提示资源未改动,资源就会被重定向到浏览器缓存,对应的HTTP状态码是304

协商缓存的实现

Last-Modified:时间戳,当我们第一次请求资源时,会在响应头中返回

If-Modified-Since:时间戳,就是服务器返回的Last-Modified的值

过程就是,请求头会携带If-Modified-Since,服务器会进行判断,若If-Modified-Since的时间戳是和last-modified不一致,服务器就会完整的返回响应内容,并且返回新的last-modified值,如果一致,就返回304,响应头也不会添加last-modified字段

第一次请求:

image-20220508183133159

再次请求

image-20220508183445799

但是last-modified有自己的缺陷

1、last-modified的值只精确到秒级

2、如果文件每隔一段时间重复生成,但内容是一致的,last-modified会每次返回资源文件,即使内容一致

所以etag对其进行了补充

Etag是服务器对每个资源文件生成的唯一 标识字符串 ,这个字符串基于文件内容编码,只要文件内容不同,对应的Etag也会不同

请求方式和上图差不多,服务器返回Etag,下次请求请求头会带上名为if-None-Match的字符串为服务器做对比

这里其实可以看到,Etag会让服务器多做事,也就是会影响到服务器的性能,所以我们使用的时候需要进行考虑,Etag的感知文件变化上比Last-modified更为准确,所以优先级也更高,二者同时出现时,以Etag为准

上诉只是对HTTP缓存的一些简单介绍,基础面试知识点可以应付了,HTTP缓存有很多知识,感兴趣可以多百度看下

CSS篇

1、举出让元素居中的方法

个人总结出两种情况,即需要居中的元素带宽高、不带宽高,如下:

带宽高

 <style>
      .father {
        width: 500px;
        height: 500px;
        background-color: red;
        position: relative;
      }
      .son {
        width: 100px;
        height: 100px;
        background-color: blue;
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        margin: auto;
      }
    </style>
    
    <body>
    <div class="father">
      <div class="son"></div>
    </div>
  </body>
 <style>
      .father {
        width: 500px;
        height: 500px;
        background-color: red;
        position: relative;
      }
      .son {
        width: 100px;
        height: 100px;
        background-color: blue;
        position: absolute;
        top: 50%;
        left: 50%;
        margin-top: -50px;
        margin-left: -50px;
      }
    </style>
    
    
     <body>
    <div class="father">
      <div class="son"></div>
    </div>
  </body>

image-20220112201105601

不带宽高

 <style>
      .father {
        width: 500px;
        height: 500px;
        background-color: red;
        position: relative;
      }
      .son {
        background-color: blue;
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%,-50%);
      }
    </style>
    
     <body>
    <div class="father">
      <div class="son">son</div>
    </div>
  </body>
<style>
      .father {
        width: 500px;
        height: 500px;
        background-color: red;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .son {
        background-color: blue;
      }
    </style>
    
    
     <body>
    <div class="father">
      <div class="son">son</div>
    </div>
  </body>

2、常见的布局方式

一、左边(右边)固定,右边(左边)自适应

float布局
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .left {
        width: 200px;
        height: 200px;
        background-color: red;
        float: left;
      }
      .right {
        height: 200px;
        background-color: blue;
      }
    </style>
  </head>
  <body>
    <div class="left"></div>
    <div class="right"></div>
  </body>
</html>
绝对定位
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .left {
        width: 200px;
        height: 200px;
        background-color: red;
        position: absolute;
      }
      .right {
        height: 200px;
        background-color: blue;
      }
    </style>
  </head>
  <body>
    <div class="left"></div>
    <div class="right"></div>
  </body>
</html>
弹性布局
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .box {
        display: flex;
      }
      .left {
        width: 200px;
        height: 200px;
        background-color: red;
      }
      .right {
        flex: 1;
        background-color: blue;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="left"></div>
      <div class="right"></div>
    </div>
  </body>
</html>

弹性布局若是要实现右边固定的效果,则加上如下代码即可

.box {
        display: flex;
        flex-direction: row-reverse;
      }

二、左中右(左右不变,中间自适应)

float布局
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .left {
        width: 200px;
        height: 200px;
        background-color: red;
        float: left;
      }
      .right {
        width: 200px;
        height: 200px;
        background-color: red;
        float: right;
      }
      .center {
        background-color: blue;
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div class="left"></div>
    <div class="right"></div>
    <div class="center"></div>
  </body>
</html>
绝对定位
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .left {
        width: 200px;
        height: 200px;
        background-color: red;
        position: absolute;
        left: 0;
      }
      .right {
        width: 200px;
        height: 200px;
        background-color: red;
        position: absolute;
        right: 0;
      }
      .center {
        background-color: blue;
        height: 200px;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div class="left"></div>

    <div class="right"></div>
    <div class="center"></div>
  </body>
</html>
弹性布局
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .box {
        display: flex;
      }
      .left {
        width: 200px;
        height: 200px;
        background-color: red;
      }
      .right {
        width: 200px;
        height: 200px;
        background-color: red;
      }
      .center {
        background-color: blue;
        flex: 1;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="left"></div>
      <div class="center"></div>
      <div class="right"></div>
    </div>
  </body>
</html>

image-20220112210301518

三、上中下(上下不变,中间自适应)

在写之前,首先说明一点,关于元素的宽度,若是没给出,在浏览器中会自动给出宽度, 但是关于高度并不会自动给出高度,所以上面的弹性布局列子中,没给出高度但是还是显示出了盒子是因为盒子的内容已经被撑开了,但是高度并不会自动分配

绝对定位
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .top {
        height: 200px;
        width: 100%;
        background-color: red;
        position: absolute;
        top: 0;
      }
      .bottom {
        height: 200px;
        width: 100%;
        background-color: red;
        position: absolute;
        bottom: 0;
      }
      .center {
        position: absolute;
        background-color: blue;
        top: 200px;
        bottom: 200px;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div class="top"></div>
    <div class="center"></div>
    <div class="bottom"></div>
  </body>
</html>
弹性布局
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      html,
      body {
        height: 100%;
      }
      .box {
        height: 100%;
        display: flex;
        flex-direction: column;
      }
      .top {
        width: 100%;
        height: 200px;
        background-color: red;
      }
      .bottom {
        width: 100%;
        height: 200px;
        background-color: red;
      }
      .center {
        flex: 1;
        background-color: blue;
      }
    </style>
  </head>
  <body>
    <div class="box">
      <div class="top"></div>
      <div class="center"></div>
      <div class="bottom"></div>
    </div>
  </body>
</html>

额外补充点:

文档流和文本流

个人对文档流的理解就是盒模型中的概念。
文档流就是在浏览器中的规则,块状元素的规则是从上到下排序的,行内元素是从左到右排序的

文本流:适用于文字之间的规则

其中我们在上面的列子中float、绝对定位、固定定位、display:flex,都会让原本的元素脱离文档流,在了解相关的规则后,我们可以按照自己的想法去布局。

vue篇

1、你怎么理解vue?它是属于什么模式?

vue.js是一款渐进式、多途径、高性能JavaScript框架,易用、灵活、高效

属于MVVM(Model-View-ViewModel)模式,也是就model(模型层)、ViewModel(视图驱动层)、view(视图层),数据在传递的时候双向传递

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

相同点:v-if与v-show都可以动态控制dom元素显示隐藏

不同点:v-if显示隐藏是将dom元素整个添加或删除,而v-show隐藏则是为该元素添加css–display:none,dom元素还在。

性能角度上:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;

使用场景:v-if适合运营条件不大可能改变;v-show适合频繁切换。

3、vue响应式原理和双向绑定原理

在了解该知识点前,先做一个只是铺垫,详细介绍下Object.defineProperty的用法

Object.defineProperty详解

Object.defineProperty是es5的方法,作用是直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象(只能用在对象上,不能用在数组上)

1、语法
Object.defineProperty(obj, prop, descriptor)
2、参数
  • obj:必需。目标对象
  • prop:必需。需定义或修改的属性的名字
  • descriptor:必需。目标属性所拥有的特性
3、属性用法

修改某个属性的值时,给这个属性添加一些特性。

let person = {}; 
Object.defineProperty(person, 'name', 
    writable: true || false,
    configurable: true || false,
    enumerable: true || false,
    value:'gjf'
});

属性详解:

writable:是否可以被重写,true可以重写,false不能重写,默认为false。
enumerable:是否可以被枚举(使用for…in或Object.keys())。设置为true可以被枚举;设置为false,不能被枚举。默认为false。
value:值可以使任意类型的值,默认为undefined
configurable:是否可以删除目标属性或是否可以再次修改属性的特性(writable, configurable, enumerable)。设置为true可以被删除或可以重新设置特性;设置为false,不能被可以被删除或不可以重新设置特性。默认为false。

vue响应式原理(vue双向绑定的原理)
vue2.x中

object.defineProperty是属性级别的拦截,会在set、get的方法中进行拦截操作

当把一个普通的js对象传入vue实列作为data选项时,vue会遍历此对象的所有的property,并使用

object.defineProperty

,把这些property全部转换为getter/setter。然后watcher会监听setter的变化,每当setter变化后,就会进行视图层的重新渲染,达到响应式效果

在这里主要我改动了data中的某一个值,那么整个响应式的状态又会重新开始进行setter、监听,相对来说浪费资源

<body>
    <div id="app">
        hellow
    </div>

    <script>
        let data ={
            name: 'tom',
            age:123
        }
        let vm ={}
        var id= document.getElementById('app')
        Object.keys(data).forEach((k)=>{
             Object.defineProperty(vm,k,{
                 get:function(){
                     id.textContent=data[k]
                     return data[k]
                 },
                 set:function(e){
                   data[k]=e
                   id.textContent=data[k]
                 }
             })
        })
    </script>

若是data中有多个值,则会进行循环输出,来监听每一个值的变化

vue3.x中

Proxy(代理)

对象级别的拦截

改变语法原有的规则,可以自定义其规则(元编程)

target:代理处理的目标对象

handler: 代理处理的方法

let obj =new Proxy(target,handler)

自定义对象属性的获取、赋值、枚举、函数调用等功能

  <body>
    <div id="app">
        hellow
    </div>

    <script>
        let data ={
            name: 'tom',
            age:123
        }
        let vm ={}
        var id= document.getElementById('app')
        

        const vs =new Proxy(data,{
            get(target,key){
                id.textContent=target[key]
            },
            set(target,key,newValue){
                if(target[key] === newValue){
                    return
                }
                target[key]=newValue
                id.textContent=target[key]
            }
        })
    </script>

这里去监听每一个属性的时候不需要用到循环的方法,简单的说就是谁改变就去监听谁,在原用的基础上提升了性能

4、虚拟DOM和diff算法

参考b站 https://www.bilibili.com/video/BV1dV411a7mT/

1、认识虚拟DOM

没有虚拟DOM之前: 数据改变=>操作DOM=>视图更新

使用虚拟DOM后:数据改变=>虚拟DOM(计算出变更)=>操作DOM=>视图更新

虚拟DOM:用js模拟DOM结构

如下图,左边是实际DOM元素,而右边是js模拟的DOM结构

image-20220215160130741

2、虚拟DOM的好处

每当dom改变的时候,不会全部都进行DOM改变,而是被改动的地方进行改变

image-20220215161655267

这里就是用js代码去模拟DOM结构

用虚拟DOM计算出最小的变化,然后再去更新变化的实际DOM

3、认识diff算法基本概念

是两个虚拟DOM之间的一种比较

1、虚拟DOM之间会平级别对比 ,深度遍历进行对比

2、对比的同时会通过key值进行判断,判断元素是仅仅位置发生改变还是需要整个替换或删除

3、如果不是元素发生改变的话,再对内容进行对比,如果是内容发生改变的话,就直接修改内容

详细的算法介绍就需要另外研究了

5、vue的生命周期与不同阶段的作用

vue的生命周期有:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed

作用

beforeCreate:可以加载loading事件,在加载实列的时候被触发

created:初始化完成时的事件在这里,这里也结束loading事件,异步请求也适合在这里被调用

mounted:可以挂载元素,过去DOM节点

updated:对数据统一处理,这里写上对应的函数即可

beforeDestroy:可以确定一个停止事件的确认框

nextTick:更新数据后,立即操作dom

6、vue组件之间的传值方式

1、父子组件传值方式

父组件向子组件传值

1、传值前现在父组件中导入需要的子组件,然后在父组件中传入值、

2、子组件需要在props:{}中定义传过来的值,以及类型,然后就可以在其他地方使用

父组件

image-20220215203707391

子组件:
image-20220215203805971

子组件向父组件传值

就是在子组件中通过$emit(传向父组件事件,传向父组件值)向父组件传入值,父组件接受改变即可

子组件:

<template>
  <div>
    {{ sendData }}

    <button @click="hand">点击</button>
  </div>
</template>

<script>
export default {
  name: "Son",
  props: ["sendData"],
  data() {
    return {
      city: "上海",
    };
  },
  methods: {
    hand: function () {
      console.log(this.city);
      this.$emit("change", this.city);
    },
  },

 
};

父组件:

<template>
  <div class="home">
    <Son :sendData="msg" @change="changeFather" />
  </div>
</template>

<script>
// @ is an alias to /src
import Son from "@/components/son.vue";

export default {
  name: "Home",
  components: {
    Son,
  },
  data() {
    return {
      msg: "北京",
    };
  },
  methods: {
    changeFather: function (city) {
      console.log(this.msg);
      this.msg = city;
    },
  },
};
</script>

2、兄弟组件传值方式

1、bus总线方法

1、定义一个bus.js ,内容如下

import Vue from "vue";
const Bus = new Vue();
export default Bus;

2、然后在main.js中添加如下代码

import bus from "./components/bus";
Vue.use(bus);
Vue.prototype.$bus = bus;

3、在需要传值的兄弟组件中,通过

    b
   
   
    u
   
   
    s
   
   
    .
   
  
  
   bus.
  
 
bus.emit(传入方法,传入值),方式传入
<template>
  <div>
    <p>child1</p>
    <button @click="hand1">点击</button>
  </div>
</template>

<script>
export default {
  name: "Child1",
  data() {
    return {
      msg: "666",
    };
  },
  methods: {
      hand1: function () {
          this.$bus.$emit('get',this.msg)
      }
  }
};
</script>

<style></style>

4、在接收的组件中在mouted生命周期中,用

    b
   
   
    u
   
   
    s
   
   
    .
   
  
  
   bus.
  
 
bus.on(传过来的方法名,回调函数),来进行接收
<template>
  <div>
    <p>child2</p>
    <p>{{ cmsg }}</p>
  </div>
</template>

<script>
export default {
  name: "Child2",
  data() {
    return {
      cmsg: "",
    };
  },
  mounted: function () {
    this.$bus.$on("get", (data) => {
      console.log(data);
      this.cmsg = data;
    });
  },
};
</script>

<style></style>
2、 可以子组件先传入父组件,然后再由父组件再传给另一个子组件

7、methods、computed 和 watch 的区别和运用的场景?

1、methods、computed的区别

在页面进行渲染的时候,methos中所有的方法都会被重新调用,而computed,只会在其被依赖项改变的时候才会重新计算

2、computed和watch的区别

computed是计算属性,有如下特点:

1、计算属性定义的属性可以不用在data中定义,在DOM中直接使用。

2、支持缓存,只有在数据依赖项发生改变的时候,才会重新计算。

3、不支持异步操作,在computed中异步操作无效,无法监听数据的变化

使用方法如下:

image-20220216110741938

watch是监听属性,特点如下:

1、不支持缓存,只要数据变化,就会直接触发响应的操作

2、在watch内支持异步操作

3、监听的函数接受两个参数,第一个是参数最新的值,第二个是参数输入之前的值

使用方法如下:

image-20220216111608863

image-20220216111619725

总结:

  • 如果一个数据需要经过复杂计算就用 computed
  • 如果一个数据需要被监听并且对数据做一些操作就用 watch

8、在哪个生命周期内调用异步请求?

在created、beforeMount、mounted都可以发送异步请求,因为可以将服务端端返回的数据进行赋值。但是最常用的是在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求

有两个优点:
第一点:能更快获取到服务端数据,减少页面 loading 时间;
第二点:放在 created 中有助于一致性,因为ssr 不支持 beforeMount 、mounted 钩子函数。

9、对 keep-alive 的了解?

img

10、组件中 data 为什么是一个函数?

因为vue的组件可能在不同的页面中会被调用,是一个函数的话每一次的调用就会执行data函数并返回新的数据,这样就可以避免多处调用之间的数据污染

11、Vue 中的 key 有什么作用?

我们可以知道,通常我们在使用v-for的时候会去标明key值,但是不建议去使用其数组的下标index作为key值。

在上面中已经简单的介绍的虚拟DOM、diff算法,key值主要是为了diff算法服务的,给每一个循环出的item绑定一个key值,做唯一标识

好处如下:

1、判断新旧VDOM节点在逻辑上是不是同一个对象

2、准确。因为key的特性是唯一性。所以如果不加

key,

采用index的标记法,那么vue会选择复用同类型节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug.

3、快速。在元素list一直变化的情况下,key值设置唯一时,能很精确找到/找不到变更元素,若使用index标记,要遍历vnode,时间长。

  • Vue的策略是不对dom直接更新,而是先比较oldDOM和VDOM 之间的区别,通过比较差别并且复用没有改变的dom,来快速的构建新的dom并渲染到页面

12、vue中如何动态的设置class、style?

//动态class对象
<div :class="{ 'isActive': true, 'red': isRed }"></div>
//动态style对象
<div :style="{ color: bgColor, fontSize: '18px' }"></div>
//动态class数组
<div :class="['is-active', isRed ? 'red' : '' ]"></div>
//动态style数组
<div :style="[{ color: bgColor, fontSize: '18px' }, { fontWeight: '500' }]"></div>

13、为什么v-if和v-for不建议用在同一标签?

因为v-for的优先级高于v-if,程序会先执行v-for的操作,然后再去执行v-if,每次都会先去循环再进行条件判断,就会带来性能上的浪费

14、不需要响应式的数据应该怎么处理?

// 方法一:将数据定义在data之外
data () {
    this.list1 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list2 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list3 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list4 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    this.list5 = { xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx }
    return {}
 }
    
// 方法二:Object.freeze()
data () {
    return {
        list1: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list2: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list3: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list4: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
        list5: Object.freeze({xxxxxxxxxxxxxxxxxxxxxxxx}),
    }
 }

image-20220216171614716

15、watch有哪些属性,分别有什么用?

1、正常监听

<div id="app">
<input tyoe="text" v-model="brand">
</div>

new Vue({
    el:"#app",
    data:{
        brand:'nike',
    },
    watch:{
        cityname(newbrand,oldbrand){
            //...
        }
    }
})

2、immediate

watch时有一个特点,就是当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。

<body>
<div id="app">
    <input type="text" v-model="num">
</div>
<script src="vue.js"></script>
<script>
    new Vue({
        el: '#app',
        data: {
            num: 1
        },
        watch: {
            num: {
                // 数据发生变化就会调用这个函数  
                handler(newVal, oldVal) {
                    console.log('oldVal:', oldVal)
                    console.log('newVal:', newVal)
                },
                // 立即处理 进入页面就触发
                immediate: true
            }
        }
    })
</script>
</body>
普通的监听时候写的请示写的函数其实就是在写这个handler方法。
immediate表示在watch中首次绑定的时候,是否执行handler,值为true则表示在watch中声明的时候,就立即执行handler方法,值为false,则和一般使用watch一样,在数据发生变化的时候才执行handler。

3、deep

深度监听,当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。

<div id="app">
<input tyoe="text" v-model="brand">
</div>

new Vue({
    el:"#app",
    data:{
        brand:(name:'nike',id:"no1"),
    },
    watch:{
        cityname:{
            handler:(newbrand,oldbrand){
            //...
        },
        deep:true,
        immediate:true
        }
    }
})

vue-router篇

1、this.

$router

和this.

$route

的区别

this.$router:是全局路由器的router的实列,可以在任何组件内进行访问,就是跳转路由的方法,里面有很多的方法比如


      this.$router.push()
      this.$router.replace()
      this.$router.go()

his.$route:包含当前激活的路由状态信息、url解析得到的信息

**1.$route.path**
      字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"。
**2.$route.params**
      一个 key/value 对象,包含了 动态片段 和 全匹配片段,
      如果没有路由参数,就是一个空对象。
**3.$route.query**
      一个 key/value 对象,表示 URL 查询参数。
      例如,对于路径 /foo?user=1,则有 $route.query.user == 1,
      如果没有查询参数,则是个空对象。
**4.$route.hash**
      当前路由的 hash 值 (不带 #) ,如果没有 hash 值,则为空字符串。锚点
**5.$route.fullPath**
      完成解析后的 URL,包含查询参数和 hash 的完整路径。
**6.$route.matched**
      数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。
**7.$route.name    当前路径名字**
**8.$route.meta  路由元信息

2、active-class 是哪个组件的属性?

active-class是vue-router模块的router-link组件中的属性,用来做选中样式的切换

用法:
1、直接在路由js文件中配置linkActiveClass

export default new Router({
  linkActiveClass: 'active'
})

2、在router-link中写入active-class

<router-link to="/home" class="menu-home" active-class="active">首页</router-link>

这里这个路由的样式可以直接在style中去写对应的类名就好了

如果两个路由都是用一样的样式,比如如下情况:

<div class="menu-btn">
  <router-link to="/" class="menu-home" active-class="active">
    首页
  </router-link>
</div>
<div class="menu-btn">
  <router-link to="/my" class="menu-my" active-class="active">
    我的
  </router-link>
</div>

这样跳转后两个router-link都会有显示的样式,可能是因为 to=“/” 引起的,active-class选择样式时根据路由中的路径去匹配,然后显示,例如在my页面中,路由为localhost:8080/#/my,那么to=“/”和to=”/my"都可以匹配到,所有都会激活选中样式

解决办法

a.直接在路由js文件中配置linkActiveClass

export default new Router({
  linkExactActiveClass: 'active',
})

b.在router-link中写入exact

<router-link to="/" class="menu-home" active-class="active" exact>首页</router-link>

3、路由传参的方式与动态路由匹配

介绍前我们先了路由的两中形式

  • 编程式的导航 router.push
  • 声明式的导航 <router-link>

对于参数的传递主要是用对象的方式去写的,可分为命名路由、查询参数

命名路由

使用前提:在注册路由的地方需要给路由命名

 {
    path: "/",
    name: "Home",
    component: Home,
  },

命名路由搭配着params来进行传递,如下

this.$router.push({ name: 'Home', params: { userId: 123 }})
<router-link :to="{ name: 'Home', params: { userId: 1111}}">click to news page</router-link>

在目标页面接收

this。$route.params.userId

查询参数

查询参数其实就是在路由地址后面带上参数和传统的url参数一致的,也就是传递的参数会出现在url地址栏上

注意:name、path可以和query搭配,params只能和name搭配

用法:

this.$router.push({ path: 'Home', query: { userId: 123 }});
<router-link :to="{ path: 'Home', query: { userId: 1111}}">click to news page</router-link>

接收:

this.$route.query.userId

动态路由匹配

常见场景:当我们有一个通用组件,对应不同ID共和不相同的客户,都要使用这个组件来渲染,我们就可以使用路由中的 动态路参数来达到这个效果,一个路径参数使用:进行标记,每当匹配到一个路由,参数值就会被设置

可以用

this.$route.params

去接收参数的值

注意:当使用该方法时,原组件的实列会被复用,但是组件的生命周期钩子不会被调用,简单列子如下

写跳转的组件:

  <div>
    <p><router-link to="/about/1">user1</router-link></p>
    <p><router-link to="/about/2">user2</router-link></p>
    <p><router-link to="/about/3">user3</router-link></p>
  </div>

路由写法:

  {
    path: "/about/:id",
    name: "About",

    component: () => import("../views/About.vue"),
  },

在目标组件用

 {{ this.$route.params.id }}

进行参数接收即可

关于在watch中$route(to, from)不生效

这里我想监听组件的路由变化,使用watch去监听$route(to, from)发现不生效,解决办法参考这篇文章

https://blog.csdn.net/u010227042/article/details/107961248

路由组件传参

当组件中使用

$route

会与路由紧密耦合,这限制了组件的灵活性,因为它只能用于特定的 URL。虽然这不一定是件坏事,但我们可以通过

props

配置来解除这种行为:

1、如果 props 被设置为 true,route.params 将会被设置为组件属性

这里用动态路由匹配做列子,同样传入的是id

在目标跳转的路由中:

  {
    path: "/about/:id",
    name: "About",
    props: true,
    component: () => import("../views/About.vue"),
  },

目标路由中props接收然后直接用:

export default {
  name: "About",
  props: ["id"],
};
2、对象模式
  {
        //对象模式
        path:'/about/chilrenRoute2/:id',
        component:chilrenRoute2,
        props: { userName: 'userName'}
      }
  export default {
    props:['userName']
  }

获取:

  <p>  通过 props传递——对象 , 获取路由组件传递参数值 /about/chilrenRoute2/:id 路由的id值:  {{ this.userName }}</p>
3、函数模式

函数模式的路由配置中,props属性是函数,这个函数返回一个对象。
在函数模式中,可以有一个参数,这个参数就是route对象,里面存储的是路由的相关携带信息。

     {
        //函数模式
        path:'/about/chilrenRoute2/:id',
        component:chilrenRoute2,
        props: function (route){

          //route 是 $route对象
            return {
              userName:'userName',
              querys: route.params.id
            }

        }
      }

js代码:

  export default {
    // props:['id']
    // props:['userName']
    props:['userName','querys']
  }

获取:

  <p>  通过 props传递——函数模式 , 获取路由组件传递参数值 /about/chilrenRoute2/:id 路由的id值:  {{ this.userName }} ,  {{ this.querys }} ,  {{ $route.params }}</p>

4、vue-router 有哪几种导航钩子(导航守卫)?

1、全局守卫:

router.beforeEach

2、全局解析守卫:

router.beforeResolve

3、全局后置钩子:

router.afterEach

4、路由独享的守卫:

beforeEnter

5、组件内的守卫:

beforeRouteEnter、beforeRouteUpdate (2.2 新增)、beforeRouteLeave
beforeRouteEnter
  // 在渲染该组件的对应路由被 confirm 前调用
  // 不!能!获取组件实例 `this`
  // 因为当守卫执行前,组件实例还没被创建
  虽然无法直接获取组件实力
  但是我们可以通过next参数的回调函数获取到当前实例进行操作
  beforeRouteEnter: (to, from, next) => {
    next((vm) => {
      //vm就是当前组件实例
    });
  }
beforeRouteUpdate
  // 在当前路由改变,但是该组件被复用时调用
  // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
  // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
  // 可以访问组件实例 `this`
beforeRouteLeave
  // 导航离开该组件的对应路由时调用
  // 可以访问组件实例 `this`

详细介绍:

https://zhuanlan.zhihu.com/p/54112006

5、vue-router历史模式和hash模式的区别?

hash模式:

url里面带有#号,开发中默认的就是hash模式,hash虽然出现在URL中,但是不会被包括在HTTP请求中,所以改变hash不会重新刷新页面

路由的哈希模式就是利用了window.onhashchange事件,也就是url中的hash值(#号后面的值)如果有变化,就会自动调用hashchange的监听事件,在hashchange的监听事件内可以得到改变后的url,这样就能找到对应页面进行加载

window.addEventListener('hashchange', () => {
   // 把改变后的url地址栏的url赋值给data的响应式数据current,调用router-view去加载对应的页面
   this.data.current = window.location.hash.substr(1)
})

历史模式:

利用了H5新增的pushState()、replaceState()方法。当这两个方法执行时,只能改变当前地址栏的URL,但是浏览器不会像后端发起请求,也不会触发popstate事件的执行。

也就是说,完成URL跳转而无需重新加载页面,由于vue是单页面的形式,当后台没有正确配置的时候,需要我们自己去配置404页面

image-20220218151924017

6、动态路由添加

最好和vuex做一个案列理解

7、vue-router怎么重定向的?

用redirect即可定位到相对位置

const routes = [
  {
    path: '/users/:id/posts',
    redirect: to => {
      // 方法接收目标路由作为参数
      // return 重定向的字符串路径/路径对象
    },
  },
]

8、vue-router实现路由懒加载

路由懒加载也就是延迟加载没在需要的时候记加载,随用随载,也是前端优化的一种手段

懒加载的实现方法:

1、ES6标准语法import()
const Foo = () => import('../components/Foo')
const Aoo = () => import('../components/Aoo')

export default new Router({
    routes: [
        {
            path: '/Foo',
            name: 'Foo',
            component: Foo
        },
        {
            path: '/Aoo',
            name: 'Aoo',
            component: Aoo
        }
    ]
})

就是定义一个变量,变量就是路由文件,然后在路由中随用随导入

2、Vue异步加载技术
     1:vue-router配置路由,使用vue的异步组件技术,可以实现懒加载,此时一个组件会生成一个js文件。
     2:component: resolve => require(['放入需要加载的路由地址'], resolve)
  {
      path: '/problem',
      name: 'problem',
      component: resolve => require(['../pages/home/problemList'], resolve)
    }

9、Vue-router跳转和location.href有什么区别?

1、vue-router使用pushState进行路由更新,静态跳转,页面不会重新加载;location.href会触发浏览器,页面重新加载一次

2、vue-router使用diff算法,实现按需加载,减少dom操作

3、vue-router是路由跳转或同一个页面跳转;location.href是不同页面间跳转;

4、vue-router是异步加载this.$nextTick(()=>{获取url});location.href是同步加载

10、怎么配置404页面?

在router.js中 路由是从上到下执行的 只需要在最后一行把path写成 * 并且指定一个404.vue页面即可

如何触发404页面,比如你的域名是http://localhost:8080/,当你进入一个没有声明/匹配的路由页面时就会跳到404页面,
比如访问了http://localhost:8080/无此页面,就会跳到404页面,如果没有声明一个404页面,那就会跳到一个空白页面

   {
            path: '*',
            name: '/404',
            component: resolve => require(['@/components/404.vue'], resolve),
        },

11、切换路由时需要保存草稿的功能,怎么实现?

1、用组件内的守卫中的:beforeRouteLeave去实现

keep-alivebeforeRouteLeave (to, from, next) {
  if(用户已经输入信息){
    //出现弹窗提醒保存草稿,或者自动后台为其保存
    
  }else{
    next(true);//用户离开
  }

}

2、使用keep-alive去缓存组件

12、切换到新路由时,页面要滚动到顶部或保持原先的滚动位置怎么做呢?

1、使用afterEach

router.afterEach((to,from,next) => {
  window.scrollTo(0,0);
});

2、该功能只能在H5 history下去使用

注意: 这个功能只在 html5 history 模式下可用。
const router = new vueRouter({
 routes: [...],
 scrollBehavior (to, from, savedPosition) {
  // return 期望滚动到哪个的位置
 }
})
scrollBehavior (to, from, savedPosition) {
 if (savedPosition) {
  return savedPosition
 } else {
  return { x: 0, y: 0 }
 }
}

13、说说vue-router完整的导航解析流程是什么?(选)

1.导航被触发
2.在即将离开的组件里调用beforeRouteLeave守卫
3.调用全局前置守卫beforeEach守卫
4.在重用的组件里调用beforeRouteUpdate守卫 / 调用路由配置的beforeEnter守卫
5.解析异步路由组件
6.在被激活的组件里调用beforeRouteEnter
7.调用全局的beforeResolve守卫
8.导航被确认
9.调用全局的 afterEach 钩子
10.触发DOM更新
11.用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

VUEX

要是看不懂官网的列子就看这篇博客入门就好了

https://www.jianshu.com/p/a804606ad8e9

关于mapState、mapAction等的了解可以看看这篇博客

https://www.cnblogs.com/m2maomao/p/9954640.html

标签: 前端 面试 html

本文转载自: https://blog.csdn.net/qq_43376559/article/details/122793036
版权归原作者 赵铭or 所有, 如有侵权,请联系我们删除。

“前端常见面试八股文”的评论:

还没有评论