前端面试题(waillyer)

一、 javascript

es6相关移步 es6常用特性

1、创建对象有几种方法

在这里插入图片描述

2、原型关系

每个class(class 实际是函数,是语法糖)都有显示原型 prototype
每个实例都有隐式原型 proto
实例的 proto 指向对应class 的 prototype

在这里插入图片描述
原型、构造函数、实例、原型链

原型链:从一个实例对象往上找构造这个实例的相关联的对象,然后这个关联的对象再往上找它又有创造它的上一级的原型对象,以此类推,一直到 Object.prototype 原型对象终止,这个链条就断了,也就是说 Object.prototype 是整个原型链的顶端。通过什么往上找?就是通过 prototype 和 proto.
原型:构造函数都有一个 prototype 属性,这是在声明一个函数时js 自动增加的,这个 prototype 指的就是原型对象。原型对象怎么区分被哪个构造函数引用?就是通过 constructor(构造器),原型对象中会有一个构造器,这个构造器会默认声明的那个函数。
构造函数:任何一个函数只要被 new 去操作(使用)就是构造函数,构造函数也是函数
实例:只要是对象就是一个实例
构造函数可以使用new 生成实例
在这里插入图片描述

3、数组使用

在这里插入图片描述

4、数组去重的几种方法

一、利用ES6 Set去重(ES6中最常用)

function unique (arr) {
  return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
 //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]

不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。

二、利用for嵌套for,然后splice去重(ES5中最常用)

function unique(arr){            
        for(var i=0; i<arr.length; i++){
            for(var j=i+1; j<arr.length; j++){
                if(arr[i]==arr[j]){         //第一个等同于第二个,splice方法删除第二个
                    arr.splice(j,1);
                    j--;
                }
            }
        }
return arr;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
    //[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}]     //NaN和{}没有去重,两个null直接消失了

双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。

三、利用indexOf去重

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array = [];
    for (var i = 0; i < arr.length; i++) {
        if (array .indexOf(arr[i]) === -1) {
            array .push(arr[i])
        }
    }
    return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
   // [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}]  //NaN、{}没有去重

新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。

四、利用sort()

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return;
    }
    arr = arr.sort()
    var arrry= [arr[0]];
    for (var i = 1; i < arr.length; i++) {
        if (arr[i] !== arr[i-1]) {
            arrry.push(arr[i]);
        }
    }
    return arrry;
}
     var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
        console.log(unique(arr))
// [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined]      //NaN、{}没有去重

利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对。

五、利用对象的属性不能相同的特点进行去重(这种数组去重的方法有问题,不建议用,有待改进)

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var arrry= [];
     var  obj = {};
    for (var i = 0; i < arr.length; i++) {
        if (!obj[arr[i]]) {
            arrry.push(arr[i])
            obj[arr[i]] = 1
        } else {
            obj[arr[i]]++
        }
    }
    return arrry;
}
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
        console.log(unique(arr))
//[1, "true", 15, false, undefined, null, NaN, 0, "a", {…}]    //两个true直接去掉了,NaN和{}去重

六、利用includes

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    var array =[];
    for(var i = 0; i < arr.length; i++) {
            if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
                    array.push(arr[i]);
              }
    }
    return array
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
    //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]     //{}没有去重

七、利用hasOwnProperty

function unique(arr) {
    var obj = {};
    return arr.filter(function(item, index, arr){
        return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
    })
}
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
        console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}]   //所有的都去重了

利用hasOwnProperty 判断是否存在对象属性

八、利用filter

function unique(arr) {
  return arr.filter(function(item, index, arr) {
    //当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
    return arr.indexOf(item, 0) === index;
  });
}
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
        console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]

九、利用递归去重

function unique(arr) {
        var array= arr;
        var len = array.length;

    array.sort(function(a,b){   //排序后更加方便去重
        return a - b;
    })

    function loop(index){
        if(index >= 1){
            if(array[index] === array[index-1]){
                array.splice(index,1);
            }
            loop(index - 1);    //递归loop,然后数组去重
        }
    }
    loop(len-1);
    return array;
}
 var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

十、利用Map数据结构去重

function arrayNonRepeatfy(arr) {
  let map = new Map();
  let array = new Array();  // 数组用于返回结果
  for (let i = 0; i < arr.length; i++) {
    if(map .has(arr[i])) {  // 如果有该key值
      map .set(arr[i], true); 
    } else { 
      map .set(arr[i], false);   // 如果没有该key值
      array .push(arr[i]);
    }
  } 
  return array ;
}
 var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果。

十一、利用reduce+includes

function unique(arr){
    return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]);
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr));
// [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]

十二、[…new Set(arr)]

[...new Set(arr)] 
//代码就是这么少----(其实,严格来说并不算是一种,相对于第一种方法来说只是简化了代码)

5、数组拷贝

在JS中,数据类型分为基本数据类型和引用数据类型两种,对于基本数据类型来说,它的值直接存储在栈内存中,而对于引用类型来说,它在栈内存中仅仅存储了一个引用,而真正的数据存储在堆内存中

一、 浅拷贝

对于浅拷贝而言,就是只拷贝对象的引用,而不深层次的拷贝对象的值,多个对象指向堆内存中的同一对象,任何一个修改都会使得所有对象的值修改,因为它们公用一条数据

二、深拷贝

我们在实际的项目中,肯定不能让每个对象的值都指向同一个堆内存,这样的话不便于我们做操作,所以自然而然的诞生了深拷贝
深拷贝作用在引用类型上!例如:Object,Array
深拷贝不会拷贝引用类型的引用,而是将引用类型的值全部拷贝一份,形成一个新的引用类型,这样就不会发生引用错乱的问题,使得我们可以多次使用同样的数据,而不用担心数据之间会起冲突

三、深拷贝的实现

1、首先看一下乞丐版的深拷贝吧!JSON.stringify()以及JSON.parse()
 var obj1 = {
    a: 1,
    b: 2,
    c: 3
}
var objString = JSON.stringify(obj1);
var obj2 = JSON.parse(objString);
obj2.a = 5;
console.log(obj1.a);  // 1
console.log(obj2.a); // 5

可以看到没有发生引用问题,修改obj2的数据,并不会对obj1造成任何影响
但是为什么说它是乞丐版的呢?
那是因为 使用JSON.stringify()以及JSON.parse()它是不可以拷贝 undefined , function, RegExp 等等类型的

2、接着来看第二种方式 Object.assign(target, source)
 var obj1 = {
    a: 1,
    b: 2,
    c: 3
}
var obj2 = Object.assign({}, obj1);
obj2.b = 5;
console.log(obj1.b); // 2
console.log(obj2.b); // 5

第二种方式实现的看起来也没有任何的问题,但是这是一层对象,如果是有多层嵌套呢

 var obj1 = {
    a: 1,
    b: 2,
    c: ['a','b','c']
}
var obj2 = Object.assign({}, obj1);
obj2.c[1] = 5;
console.log(obj1.c); // ["a", 5, "c"]
console.log(obj2.c); // ["a", 5, "c"]

可以看到对于一层对象来说是没有任何问题的,但是如果对象的属性对应的是其它的引用类型的话,还是只拷贝了引用,修改的话还是会有问题

第三种方式 递归拷贝
// 定义一个深拷贝函数  接收目标target参数
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;
}

例如

    let obj1 = {
        a: {
            c: /a/,
            d: undefined,
            b: null
        },
        b: function () {
            console.log(this.a)
        },
        c: [
            {
                a: 'c',
                b: /b/,
                c: undefined
            },
            'a',
            3
        ]
    }
    let obj2 = deepClone(obj1);
        console.log(obj2);

6、Ajax

一、定义

1、什么是Ajax
Ajax:即异步 JavaScript 和XML。Ajax是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,Ajax可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。而传统的网页(不使用 Ajax)如果需要更新内容,必需重载整个网页面。

2、同步与异步的区别
同步提交:当用户发送请求时,当前页面不可以使用,服务器响应页面到客户端,响应完成,用户才可以使用页面。

异步提交:当用户发送请求时,当前页面还可以继续使用,当异步请求的数据响应给页面,页面把数据显示出来 。

3、ajax的工作原理
客户端发送请求,请求交给xhr,xhr把请求提交给服务,服务器进行业务处理,服务器响应数据交给xhr对象,xhr对象接收数据,由javascript把数据写到页面上,如下图所示:

在这里插入图片描述

二、实现AJAX的基本步骤

要完整实现一个AJAX异步调用和局部刷新,通常需要以下几个步骤:
创建XMLHttpRequest对象,即创建一个异步调用对象.
创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
设置响应HTTP请求状态变化的函数.
发送HTTP请求.
获取异步调用返回的数据.
使用JavaScript和DOM实现局部刷新.

//第一步,创建XMLHttpRequest对象
var xmlHttp = new XMLHttpRequest();
function CommentAll() {
    //第二步,注册回调函数
    xmlHttp.onreadystatechange =callback1;
    //{
    //    if (xmlHttp.readyState == 4)
    //        if (xmlHttp.status == 200) {
    //            var responseText = xmlHttp.responseText;

    //        }
    //}
    //第三步,配置请求信息,open(),get
    //get请求下参数加在url后,.ashx?methodName = GetAllComment&str1=str1&str2=str2
    xmlHttp.open("post", "/ashx/myzhuye/Detail.ashx?methodName=GetAllComment", true);

    //post请求下需要配置请求头信息
    //xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

    //第四步,发送请求,post请求下,要传递的参数放这
    xmlHttp.send("methodName = GetAllComment&str1=str1&str2=str2");//"

}
//第五步,创建回调函数
function callback1() {
    if (xmlHttp.readyState == 4)
        if (xmlHttp.status == 200) {
            //取得返回的数据
            var data = xmlHttp.responseText;
            //json字符串转为json格式
            data = eval(data);
            $.each(data,
                function(i, v) {
                    alert(v);
                });       
        }
}

二、 css/html

1、img标签中alt属性与title属性区别

title是html5中,用于元素信息提示,并非必须使用
而alt是为了当图片不显示的时候,图片的替换文字,长度必须少于100个英文字符,属性是搜索引擎判断图片与文字是否相关的重要依据(在 IE 浏览器下会在没有 title 时把 alt
当成 tool tip 显示)

2、浏览器的内核

IE: trident 内核
Firefox:gecko 内核
Safari:webkit 内核
Opera:以前是 presto 内核,Opera 现已改用 Google Chrome 的 Blink 内核
Chrome:Blink(基于 webkit,Google 与 Opera Software 共同开发)

3、 HTML 文件里开头Doctype

声明位于文档中的最前面的位置,处于 标签之前。此标签可告知浏览器文档使用哪种 HTML 或 XHTML 规范。(重点:告诉浏览器按照何种规范解析页面)
盒模型:
在 W3C 标准中,如果设置一个元素的宽度和高度,指的是元素内容的宽度和高度,而在 Quirks 模式下,IE 的宽度和高度还包含了 padding 和 border。
设置行内元素的高宽:在 Standards 模式下,给等行内元素设置 wdith 和 height 都不会生效,而在 quirks 模式下,则会生效。
设置百分比的高度:在 standards 模式下,一个元素的高度是由其包含的内容来决定的,如果父元素没有设置百分比的高度,子元素设置一个百分比的高度是无效的用
margin:0 auto 设置水平居中:使用 margin:0 auto 在 standards 模式下可以使元素水平居中,但在 quirks 模式下却会失效。

div+css 的布局较 table 布局有什么优点?

改版的时候更方便 只要改 css 文件。
页面加载速度更快、结构化清晰、页面显示简洁。
表现与结构相分离。
易于优化(seo)搜索引擎更友好,排名更容易靠前。

strong 与 em 的异同

strong:粗体强调标签,强调,表示内容的重要性
em:斜体强调标签,更强烈强调,表示内容的强调点

为什么利用多个域名来存储网站资源会更有效?

CDN 缓存更方便
突破浏览器并发限制
节约 cookie 带宽
节约主域名的连接数,优化页面响应速度
防止不必要的安全问题

请谈一下你对网页标准和标准制定机构重要性的理解。

网页标准和标准制定机构都是为了能让 web 发展的更‘健康’,开发者遵循统一的标准,降低开发难度,开发成本,SEO 也会更好做,也不会因为滥用代码导致各种 BUG、安全问题,最终提高网站易用性。

请描述一下 cookies,sessionStorage 和 localStorage 的区别?

sessionStorage 用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此 sessionStorage 不是一种持久
的本地存储,仅仅是会话级别的存储。而 localStorage 用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
web storage 和 cookie 的区别
Web Storage 的概念和 cookie 相似,区别是它是为了更大容量存储设计的。Cookie 的大小是受限的,并且每次你请求一个新的页面的时候 Cookie 都会被发送过去,这样无形中浪费
了带宽,另外 cookie 还需要指定作用域,不可以跨域调用。
除此之外,Web Storage 拥setItem,getItem,removeItem,clear 等方法,不像 cookie需要前端开发者自己封装 setCookie,getCookie。但是 Cookie 也是不可以或缺的:Cookie的作用是与服务器进行交互,作为 HTTP 规范的一部分而存在 ,而 Web Storage 仅仅是为了在本地“存储”数据而生。

src 与 href 的区别。

src 用于替换当前元素,href 用于在当前文档和引用资源之间确立联系。
src 是 source 的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求 src 资源时会将其指向的资源下载并应用到文档内,例如 js 脚本,img 图片和 frame 等元素。

当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js 脚本放在底部而不是头部。
href 是 Hypertext Reference 的缩写,指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,如果我们在文档中添加

那么浏览器会识别该文档为 css 文件,就会并行下载资源并且不会停止对当前文档的处理。 这也是为什么建议使用 link 方式来加载 css,而不是使用@import 方式。

css隐藏但是占位

css visibility属性
使用该属性 visibility:hidden/show,可以使隐藏的div任占空间

三、vue

1、描述 Vue 组件生命周期(有父子组件的情况)

答案:单组件生命周期,生命周期可分为

挂载阶段
beforeCreate:此阶段为实例初始化之后,此时数据观察和事件机制还没有形成,不能获取到dom节点;
created:此阶段的vue实例已经创建,仍不能获取DOM 节点.把vue 的一个实例给初始化了,只是存在于 js 内存的一个变量而已,这个时候并没有开始渲染;
beforeMount:在这一阶段,我们虽然还不能获取到具体 DOM 元素,但 vue 挂载的根节点已经创建,下面 vue 对DOM 的操作将围绕这个根元素继续进行,beforeMount 这个阶段是过渡性的,一般一个项目只能用到一两次;
mounted:组件真正绘制完成了,页面已经渲染完了,数据和DOM 都已被渲染出来,一般我们的异步请求都写在这里)
更新阶段
beforeUpdate: 这一阶段,vue遵循数据驱动DOM 的原则,beforeUpdate 函数在数据更新后没有立即更新数据,但是DOM 数据会改变,这是双向数据绑定的作用;
updated:这一阶段,DOM 会和更改过的内容同步)
销毁阶段
beforeDestroy:在上一阶段vue已经成功通过数据驱动DOM 的修改,当我们不再需要 vue 操纵 DOM 时,就要销毁 vue,也就是清除vue 实例与 DOM 的关联,调用destroy方法可以销毁当前组件。在销毁前,会触发 beforeDestroy 钩子函数;
destroyed:在销毁后,会触发destroyed 钩子函数)
beforeDestroy要做的事:

自定义事件解除绑定:(eventBus 等)
销毁定时任务:(setTimeout,setInterval等)
绑定的window 或 document 事件要销毁
总之就是该销毁的要在这里销毁,不要让他们留在内存中

具体参考:https://www.cnblogs.com/queenya/p/13416654.html

多组件生命周期:

挂载阶段(加载渲染过程):
父 beforeCreate --> 父 created --> 父 beforeMount --> 子 beforeCreate --> 子 created --> 子 beforeMount --> 子 mounted --> 父 mounted
更新阶段:
父 beforeUpdate --> 子 beforeUpdate --> 子 updated --> 父 updated
销毁阶段:
父 beforeDestroy --> 子 beforeDestroy --> 子 destroyed --> 父 destroyed

2、组件传值的几种方式?

1、父子组件传值

通过props向下传递
子组件使用

 props:{
    msgChild:String
  },

###2、子组件向父组件传值
通过$emit()向上传递

this.$emit('pushId',99)

父组件通过

 <Menu :msgChild="msg1" @pushId="getChildrenId" />
 import Menu from "@/components/menu/Menu";
 export default {
  components: {
    Menu,
  },
  methods: {
    getChildrenId(id){
      alert('子组件传递的值'+id)
    },
}

方法三、子组件向父组件传值

子组件通过`$emit(‘func’,‘我是子组件传递的消息1!’)

父组件通过on监听

   <Menu :msgChild="msg1" @pushId="getChildrenId"  ref="son"/>
   mounted(){
            this.$refs['son'].$on('func',(msg)=>{
                console.log(msg);
            })
        },

方法四、子组件之间传值

子组件之间传值,可以通过父组件做为桥梁,传值
但过于繁琐,不推荐,我们可以新建中间文件

新建文件
bus.js

import Vue from 'vue'
export  default new Vue()


子组件a

import Bus from '@/bus.js'

export default {
  methods: {
    readmsg(){
      Bus.$emit('pushData','传递给其他子组件的值')
    },
    }
}

子组件b

import Bus from '@/bus.js'
export default {
mounted() {

    Bus.$on('pushData',(msg)=>{
      alert(msg)
    })
    }
}

方法五、EventBus传参

1、在main.js种挂载全局EventBus
效果和方法四是一样的,使用起来更方便

//main.js
Vue.prototype.$EventBus = new Vue()//挂载全局EventBus

组件1

 readmsg(){
      this.$EventBus.$emit('pushData','传递给其他子组件的值')
    },

组件2

 mounted() {

    this.$EventBus.$on('pushData',(msg)=>{
      alert(msg)
    })
    }

方法六、消息发布与订阅

安装 pubsub-js 插件: npm i pubsub-js -s 可实现全局参数传递
组件1

<template>
    <div class="wrap">
        <div>我是组件A</div>
        <button @click="sendMsg">发送</button>
    </div>
</template>

<script>
    import  pubsub from 'pubsub-js'
    export default {
        name: "A",
        methods:{
            sendMsg(){
                pubsub.publishSync("sendMsg","这是A组件发布的消息!");
            }
        }
    }
</script>


组件2

<template>
    <div>
        <div>我是组件B</div>
    </div>
</template>

<script>
    import pubsub from 'pubsub-js'
    export default {
        name: "B",
        mounted(){
            pubsub.subscribe("sendMsg",(e,msg)=>{
                console.log(e,msg);//sendMsg 这是A组件发布的消息!
            })
        },
    }
</script>


publishSync
同步发送消息
publish
同步发送消息
subscribe
订阅消息
unsubscribe
卸载特定订阅
clearAllSubscriptions
清除所有订阅

3、v-show 和 v-if的区别,v-show 和 keep-alive 的区别

答案:v-show是 CSS display 控制显示和隐藏

v-if 是组件真正的渲染和销毁,而不是显示和隐藏

频繁切换显示状态用 v-show,否则用 v-if

keep-alive 是在vue 框架层级进行的JS 对象渲染

一般简单的可用 v-show,

复杂一点的一般用 keep-alive,keep-alive 通常用于 tab 的切换

4、为何 v-for 要用 key

答案:必须要用 key, 而且不能用 index 和 random,

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

在 diff 算法中用 tag 和 key来判断,是否是sameNode

可以减少渲染次数,提高渲染性能

5、Vue 组件如何通讯

答案:

父子组件通讯:使用属性和触发事件,props, e m i t , t h i s . emit,this. emitthis.emit 调用父组件的事件,父组件向子组件传递一个信息,或者说子组件向父组件触发一个事件
组件之间没有关系或层级较深:使用自定义事件 ,event是vue实例,vue本身就具有自定义事件的能力。调用自定义事件: event. e m i t ( ′ x x x ′ , 变 量 名 ) ; 绑 定 自 定 义 事 件 : e v e n t . emit('xxx', 变量名);绑定自定义事件:event. emit(xxx,)event.on(‘xxx’, 函数名字)。在beforeDestroy 要做的一件事是及时解绑自定义事件,及时销毁,否则可能造成内存泄漏,写法:event.$off(‘xxx’, 函数名).
vuex 通讯

6、描述组件渲染和更新的过程

答案:

初次渲染过程:
解析模板为 render 函数(或在开发环境已完成, vue-loader)
触发响应式,监听 data 属性 getter,setter
执行 render 函数,生成 vnode, patch(elem, vnode)
更新过程:
修改 data,触发 setter (此前在 getter 中 已被 监听)
重新执行 render 函数,生成 newVnode
patch(vnode, newVnode)
异步渲染:
回顾 $nextTick,(以下这是对 $nextTick的回顾: n e x t T i c k : v u e 是 异 步 渲 染 ; d a t a 改 变 之 后 , D O M 不 会 立 刻 渲 染 ; nextTick: vue 是异步渲染;data改变之后,DOM 不会立刻渲染; nextTick:vuedataDOMnextTick 会在 DOM 渲染之后被触发,以获取最新 DOM 节点。vue 为何是异步渲染, n e x t T i c k 何 用 ? 异 步 渲 染 ( 以 及 合 并 d a t a 修 改 ) , 以 提 高 渲 染 性 能 , nextTick何用?异步渲染(以及合并data修改),以提高渲染性能, nextTickdatanextTick 在DOM 更新完之后,触发回调。另外,在 vue 中可以通过 ref 获取元素:给元素添加ref属性并设置名称,然后通过 this. r e f s . r e f 属 性 名 称 获 取 该 D O M 元 素 ) 总 结 : 1 、 异 步 渲 染 , refs.ref 属性名称获取该DOM 元素)总结:1、异步渲染, refs.refDOM1nextTick待 DOM 渲染完再回调;2、页面渲染时会将 data 的修改做整合,多次data修改只做一次渲染。
汇总 data 的修改,一次性更新视图
减少 DOM 操作次数,提高性能

7、双向事件绑定 v-model 的实现原理

答案:通过 input 元素的 value = this.name

绑定 input 事件 this.name = $event.target.value

data 更新触发 re-render
数据双向绑定的原理可参考:https://www.cnblogs.com/queenya/p/13426695.html 的第3 点

双向绑定的原理是什么?可以写出来吗?
在这里插入图片描述

双向是指ViewModel中的data部分和View之间的双向关系。
  正向:数据驱动页面
  反向:页面更新数据
绑定是指自动化处理,data改变了view随之改变,反之也是。
  不用像传统方式那样,通过onChange事件获取用户输入,然后再通过改变innerHtml修改显示。

双向绑定都是依赖ES5中一个重要的API,Object.defineProperty。

正向:

Object.defineProperty的作用:

监听到 data的变化,监听到变化后会有个回调函数,在定义的时候直接写回调函数,在Object.defineProperty回调函数写明 view 和 data 的关联关系,后续中data有变化就会自动根据你写的关联处理修改View的显示内容。直接操作修改data是因为 Object.defineProperty 有 set 属性

反向:

在view中输入内容时,通过 input 事件(比如 onchange),修改 data。只不过这件事不需要我们程序员自己去写了,有些框架背后在做这件事,比如,在Vue框架中,可以使用V-Model方便的关联view和data。

Object.defineProperty()

定义:

Object.defineProperty() 方法是在一个对象新定义一个属性,或者修改一个对象的现有属性,返回修改后的这个对象(换句话就是 修改一个对象返回一个修改后的对象)

语法:

Object.defineProperty(obj, prop, descriptor)

参数:
  obj:要在其上定义属性的对象。
  prop:要定义或修改的属性的名称。
  descriptor:将被定义或修改的属性描述符。

8、对 MVVM 的理解

答案:参考:https://www.cnblogs.com/queenya/p/13426695.html 第1、2点
在这里插入图片描述

9、computed 有何特点,computed 和 watch,methods 的区别

答案:

computed: 有缓存,data 不变不会重新计算;提高性能。
computed 为什么需要缓存?简单说就是可以提高性能。假设我们有一个性能开销比较大的计算属性A,它需要遍历一个巨大的数组做大量的计算,然后我们可能有其他的属性依赖于A,如果没有缓存,将不可避免的多次执行A 的getter,如果不希望有缓存请用方法代替
computed 和 methods的区别: computed 计算属性是基于它的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值,这意味着只要原属性还没发生改变,多次访问相关属性,计算属性会立即返回之前的计算结果,而不必再次执行函数;而 methods 每当触发重新渲染时,调用方法总会再次执行函数
computed 和 watch的区别:computed 默认只要 getter,不过需要时也可以提供 setter;watch 侦听器,当需要在数据变化时执行异步或开销较大的操作时,watch是最有用的,使用 watch选项允许执行异步操作(访问一个API),限制我们执行该操作的频率,并在得到最终结果前,设置中间状态,这些都是计算属性无法做到的

computed 是属性
当需要根据已有数据产生一些派生数据的时候,可使用计算属性
注意:计算属性不支持异步操作,因为计算属性一般要绑定到模板中
更重要的一点是:计算属性会缓存调用的结果,提高性能
计算属性必须有返回值,没有返回值就没有意义
watch 是一个功能:
watch不需要返回值,根据某个数据变化执行xx逻辑
watch可以执行异步操作

computed 和 watch的使用场景:如果一个数据需要经过复杂计算就用 computed;如果一个数据需要被监听并且对数据做一些操作就用watch;watch擅长处理的场景:一个数据影响多个数据;computed擅长处理的场景:一个数据受多个数据影响

10、为何组件 data 必须是一个函数?

答案:防止组件重用的时候导致数据相互影响。根本上 .vue 文件编译出来是一个类,这个组件是一个class,我们在使用这个组件的时候相当于对class 实现实例化,在实例化的时候执行data,如果 data不是函数的话拿每个组件的实例结果都一样了,共享了,如果 data不是函数的话在一个地方改了,另一个地方也改了。如果data是函数在左边实例化一个右边实例化一个都会执行这个函数,这两个data都在闭包中,两个不会相互影响

11、Ajax 请求应该放在哪个生命周期

答案:应该放在 mounted 生命周期,JS 是单线程的,Ajax 异步获取数据,放在 mounted 之前没有用,只会让逻辑更加混乱

12、如何将组件所有 props 传递给子组件?

答案: p r o p s     < u s e r v − b i n d = " props  <user v-bind=" props  <uservbind="props">

13、如何自己实现 v-model?

答案:
在这里插入图片描述

14、多个组件有相同逻辑,如何抽离?

答案: 用 mixin, mixin 的一些缺点

mixin 的用法:;定义一个 js文件将export default 中的共有内容写到里面,然后在组件中import,放到 mixin数组中
在这里插入图片描述
在这里插入图片描述
mixin 的一些缺点:

变量来源不明,不利于阅读。我们希望编程红的变量和方法是可查找的,但是 mixin 引入的内容编辑是不可寻找
多mixin 可能造成命名冲突
迷信和组件可能出现多对多的关系(一个组件引用多个 mixin, 一个mixin被多个组件引用),复杂度较高。多对多是最复杂的关系,很容易剪不断理还乱
在vue3 提出的 Composition API 旨在解决这些问题

15、何时使用异步组件?

答案:

加载大组件
路由异步加载

16、何时需要使用 keep-alive?

答案:

缓存组件,不需要重复渲染
如多个静态 tab 页的切换
优化性能
16、何时需要使用 beforeDestroy?

17、何时需要使用 beforeDestroy?

答案:

解除自定义事件 event.$off
清除定时器
解绑自定义的 DOM 事件,如 window scroll等

18、什么是作用域插槽?

答案:父组件通过 slot 获取子组件中的的值:子组件中通过自定义属性绑定数据,父组件通过 template的 v-slot 属性来接收数据
在这里插入图片描述

19、vuex 中 action 和 mutation有何区别?

答案:

action 中处理异步,mutation 不可以
mutation 做原子操作
action 可以整合多个 mutation

20、vue-router 常用的路由模式

答案:

hash 默认:有 #,也就是路由的hash,后面是路由
H5 history(需要服务端支持):没有 #,需要服务端再次,无特殊需求可选择 hash模式

21、如何配置 vue-router 异步加载?

答案:异步加载性能会优化很多,配置:component: () => import(…)
在这里插入图片描述

22、请用 vnode 描述一个 DOM 结构

答案:
在这里插入图片描述

23、监听 data 变化的核心 API 是什么?

答案: Object.defineProperty,以及要想一下如何深度监听、监听数组,有何缺点

如何深度监听:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如何监听数组:
在这里插入图片描述
在这里插入图片描述
不可像以下这样做会污染全局的Array 原型:
在这里插入图片描述

缺点:

深度监听,需要递归到底,一次性计算量大
无法监听新增/删除属性(所以需要 vue.set vue.delete 实现新增/删除属性)
无法监听原生数组,需要特殊处理

24、vue 如何监听数组变化

答案:

Object.defineProperty 不能监听数组变化
重新定义原型,重写push pop 等方法,实现监听
在这里插入图片描述
在这里插入图片描述
Proxy 可以原生支持监听数组变化

25、请描述响应式原理

答案:

监听data变化,监听data属性getter,setter(包括数组)参考:https://www.cnblogs.com/queenya/p/13426695.html 第3点
组件渲染和更新的流程:
在这里插入图片描述

26、diff 算法的时间复杂度

答案:

O(n)

在O(n^3)基础上做了一些调整

27、简述diff算法过程:

答案:

patch(elem, vnode) 和 patch(vnode, newVnode)
patchVnode 和 addVnode 和 removeVnode
updateChildren(key 的重要性)

28、Vue 常见性能优化方式

答案:

合理使用v-show 和 v-if
合理使用computed
v-for 时要加key,以及避免和 v-if 同时使用
自定义事件、DOM 事件及时销毁
合理使用异步组件
合理使用keep-alive
data层级不要太深(因为深度监听一次性监听到底)
使用 vue-loader 在开发环境做模板编译(预编译)
webpack层面的优化
前端通用的性能优化,如果图片懒加载
使用 SSR

29、vuex

vuex 是一个专门为 vue.js 应用程序开发的状态管理模式,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.

构建中大型单页应用是这个状态管理应该包含以下几个部分:

state,驱动应用的数据源
view,以声明方式将state映射到视图
actions,响应在view上的用户输入导致的状态变化
几个基本概念(属性):

state:单一状态树,储存的单一状态,是储存的基本数据.vuex 的状态储存是响应式的
getters:可以认为是 store的计算属性,对 state加工,是派生出来的数据,返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会重新计算
mutation:更改 vuex 的store中的状态的唯一方法是提交 mutation(mutation提交修改状态).每个mutation 都有一个字符串的事件类型(type)和一个回调函数(handler),这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数,使用 store.commit, (mutation是同步的)
action: 像一个装饰器,action提交的是mutation,而不是直接更改状态,action可以包含任意异步操作,通过store.dispatch 方法触发,也可以使用 mapAction
module: 是 store 分割的模块,每个模块拥有直接的 state,getter,mutation,action, 甚至是嵌套子模块 – 从上至下进行同样方式的分割模块内部的 action,局部状态通过 context.state暴露出来,根节点则为 context.rootState
用于Vue 组件(API):

dispatch
commit
mapState
mapGetters
mapMutations
mapActions
vuex设计思想,借鉴了 Flux,Redux,将数据存放到全局的store,再将 store挂载到每个 vue实例组件中,利用 vue.js 的细粒对数据响应机制来进行高效的状态更新

vuex的store是如何挂载注入到组件中的呢?

在vue 项目中先安装 vuex
利用vue 的插件机制,使用 vue.use(vuex)时,会调用 vuex 的install方法,安装 vuex
applyMixin 方法使用 vue 混入机制,vue的生命周期 beforeCreate 钩子函数混入 vuexInit 方法
vuex是利用 vue 的 mixin 混入机制,在beforeCreate 钩子函数混入 vuexInit 方法,vuexInit 方法实现了 store 注入 vue 组件实例,并注册了 vuex store 的引用属性 $store

vuex 的state 和 getter 是如何映射到各个组件实例中响应式更新状态的?

vuex 的state 状态是响应式,是借助 vue的data是响应式,将 state存入vue实例组件的data中;vuex 的getters则是借助 vue的计算属性 computed 实现数据实时监听

四、Webpack 面试题

1、前端代码为何要进行构建和打包

答案:

代码方面:

体积更小(Tree-Shaking、压缩、合并),加载更快
编译高级语言或语法(TS,ES6+,模块化,scss)
兼容性和错误检查(Polyfilll, postcss, eslint)
  研发流程方面:

统一、高效的开发环境
统一的构建流程和产出标准
集成公司构建规范(体测、上线等)

2、module、chunk、bundle分别是什么意思,有何区别

答案:

module – 各个源码文件,webpack 中一切皆模块
chunk – 多模块合并成的,如 entry import() splitChunk
bundle – 最终的输出文件

3、loader 和 plugin 的区别

答案:

loader:模块转换器,如 less --> css, 如识别 js 结尾的,css 结尾的,图片格式结尾的,通过 loader 转换成相应的文件格式

plugin:扩展插件,如 HtmlWebpackPlugin

常见 loader 和 plugin:

https://www.webpackjs.com/loaders/,

本人在项目中常用到的loader有

babel-loader – This package allows transpiling JavaScript files using Babel and webpack.
css-loader – css-loader 解释(interpret) @import 和 url() ,会 import/require() 后再解析(resolve)它们。引用资源的合适 loader 是 file-loader和 url-loade
expose-loader – The expose loader adds modules to the global object. This is useful for debugging, or supporting libraries that depend on libraries in globals.,模块必须在你的 bundle 中被 require() 过
file-loader – Instructs webpack to emit the required object as file and to return its public URL
json-loader – 注意:由于 webpack >= v2.0.0 默认支持导入 JSON 文件。如果你使用自定义文件扩展名,你可能仍然需要使用此 loader。See the v1.0.0 -> v2.0.0 Migration Guide for more information
less-loader – Compiles Less to CSS.
postcss-loader – Loader for webpack to process CSS with PostCSS
style-loader – Adds CSS to the DOM by injecting a

本人在项目中常用的 plugin 有:

HtmlWebpackPlugin(html-webpack-plugin) – HtmlWebpackPlugin简化了HTML文件的创建,以便为你的webpack包提供服务。这对于在文件名中包含每次会随着编译而发生变化哈希的 webpack bundle 尤其有用。 你可以让插件为你生成一个HTML文件,使用lodash模板提供你自己的模板,或使用你自己的loader
ExtractTextWebpackPlugin(extract-text-webpack-plugin)-- Extract text from a bundle, or bundles, into a separate file.
CopyWebpackPlugin(copy-webpack-plugin)-- Copies individual files or entire directories to the build directory
UglifyjsWebpackPlugin(uglifyjs-webpack-plugin)-- This plugin uses UglifyJS v3 (uglify-es) to minify your JavaScript
DllPlugin – DLLPlugin 和 DLLReferencePlugin 用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。
IgnorePlugin – 防止在 import 或 require 调用时,生成以下正则表达式匹配的模块:
requestRegExp 匹配(test)资源请求路径的正则表达式。
contextRegExp (可选)匹配(test)资源上下文(目录)的正则表达式。

4、babel 和 webpack 的区别

答案:

Babel --> JS 新语法编译工具,不关心模块化
webpack --> 打包构建工具,是多个 loader plugin的集合

5、webpack 如何实现懒加载

答案:

import()

结合Vue React 异步组件

结合vue-router React-router 异步加载路由

6、为何 Proxy 不能被 Polyfill

答案:

如class 可以用 function 模拟

如 Promise 可以用 callback 模拟

但 Proxy 的功能用 Object.defineProperty 无法模拟(没有任何一个语法可以模拟 Proxy)

7、如何产出一个lib

答案:
在这里插入图片描述

8、webpack 常见性能优化

答案:

webpack 优化构建速度(可用于生产)
优化 babel-loader
IgnorePlugin
noParse
happyPack
ParallelUgligyPlugin
webpack 优化构建速度(不可用于生产)
自动刷新
热更新
DllPlugin
webpack 优化产出代码
小图片 base64 编码
bundle 加 hash
懒加载
提取公共代码
使用 CDN 加速
IgnorePlugin
使用 Production
Scope Hosting

9、babel-runtime 和 babel-polyfill 的区别

答案:

babel-polyfill 会污染全局
babel-runtime 不会污染全局
产出第三方 lib 要用 babel-runtime

五、 HTTP 协议类

1、 HTTP 方法

GET ------> 获取资源
POST ------> 传输资源
PUT ------> 更新资源
DELETE ------> 删除资源
HEAD ------> 获取报文首部

2、POST 和 GET 的区别

GET在浏览器回退时是无害的,而POST 会再次提交
GET产生的URL地址是可以被收藏的,而POST不可以
GET请求会被浏览器主动缓存,而POST不会,除非手动设置
GET请求只能进行URL编码,而POST支持多种编码方式
GET请求参数会被完整保留在浏览器历史记录里,而POST 中的参数不会被保留
GET请求在URL中传送的参数是有长度限制的,而POST没有限制
对参数的数据类型,GET只接受ASCI字符,而POST 没有限制的
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息
GET参数通过URL传递,而POST放在Request body中

GET和POST是什么?HTTP协议中的两种发送请求的方法。

HTTP是什么?HTTP是基于TCP/IP的关于数据如何在万维网中如何通信的协议。

HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。GET和POST能做的事情是一样一样的。你要给GET加上request body,给POST带上url参数,技术上是完全行的通的。

GET和POST还有一个重大区别,简单的说:

GET产生一个TCP数据包;POST产生两个TCP数据包。

长的说:

对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);

而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。

也就是说,GET只需要汽车跑一趟就把货送到了,而POST得跑两趟,第一趟,先去和服务器打个招呼“嗨,我等下要送一批货来,你们打开门迎接我”,然后再回头把货送过去。

因为POST需要两步,时间上消耗的要多一点,看起来GET比POST更有效。因此Yahoo团队有推荐用GET替换POST来优化网站性能。但这是一个坑!跳入需谨慎。为什么?

GET与POST都有自己的语义,不能随便混用。

据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基本可以无视。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。

并不是所有浏览器都会在POST中发送两次包,Firefox就只发送一次。

3、HTTP状态码

1xx:指示信息 – 表示请求已接收,继续处理
2xx:成功 – 表示请求已被成功接收
200 OK:客户端请求成功
206 Partial Content:客户发送了一个带有 Range 头的GET请求,服务器完成了它
3xx:重定向 – 要完成请求必须进行更进一步的操作
301 Moved Permanently:所请求的页面已经转移至新的URL
302 Found:所请求的页面已经临时转移至新的URL
304 Not Modified:客户端与缓冲的文档并发出了一个条件性的请求,服务器告诉客户,原来缓冲的文档还可以继续使用
4xx:客户端错误 – 请求有语法错误或请求无法实现
400 Bad Request:客户端请求有语法错误,不能被服务器所理解
401 Unauthoried:请求未经授权,这个状态码必须和WWW-Authenticate 报头域一起使用
403 Forbidden:对被请求页面的访问被禁止
404 Not Found: 请求资源不存在
5xx:服务器错误 – 服务器未能实现合法的请求
500 Internal Server Error:服务器发生不可预期的错误原来缓冲的文档还可以继续使用
503 Server Unavailable:请求未完成,服务器临时过载或当机,一段时间后可能恢复正常
什么是持久连接

HTTP协议采用 “请求-应答”模式,当使用普通模式,即非 Keep-Alive模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(http协议为无连接的协议)

当使用 Keep-Alive模式(又称持久连接,连接重用)时,Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求之后,Keep-Alive功能避免了建立或者重新建立连接

版本是 1.1 才支持持久连接

六、页面性能

提升页面性能的方法有哪些?

  1. 资源压缩合并,减少 HTTP 请求

  2. 非核心代码异步加载

异步加载的方法

1)、 动态脚本加载: 利用 document.createElement() 方法,就是说用 js创建一个标签,把这个标签添加到 body 或者 header 上去,总之加到文档中,实现加载,就是创建动态节点

2)、defer:异步加载方式,在后面加上 defer, 多个按需加载,执行顺序按加载顺序

3)、async:异步加载方式, 在后面加上 async, 执行顺序与加载顺序无关

异步加载的区别

1)、defer是在 HTML 解析完成之后才会执行,如果是多个,按照加载顺序依次执行

2)、async是在加载完之后立刻执行,如果是多个,执行顺序和加载顺序无关

  1. 利用浏览器缓存:缓存指资源文件在浏览器中存在着备份也可以理解为叫副本,把资源文件放到本地文件上,存到电脑磁盘上

缓存的分类:

1)、 强缓存: 在一定时间内,不与服务器端通信,直接从浏览器副本拿出来用

利用 http 响应头中的 Expires 和 Cache-Control实现

浏览器第一次请求一个资源时,服务器在返回该资源的同时,会把上面这两个属性放在response header中。
      在这里插入图片描述
 注意:这两个response header属性可以只启用一个,也可以同时启用。当response header中,Expires和Cache-Control同时存在时,Cache-Control的优先级高于Expires。

Expires:表示绝对时间,是较老的强缓存管理 response header。浏览器再次请求这个资源时,先从缓存中寻找,找到这个资源后,拿出它的Expires跟当前的请求时间比较,如果请求时间在Expires的时间之前,就能命中缓存,否则就不行。

如果缓存没有命中,浏览器直接从服务器请求资源时,Expires Header在重新请求的时候会被更新。

存在的问题是:服务器的事件和客户端的事件可能不一致。在服务器时间与客户端时间相差较大时,缓存管理容易出现问题,比如随意修改客户端时间,就能影响缓存命中的结果。所以,在http1.1中,提出了一个新的response header,就是Cache-Control。

Cache-Control: 相对时间, 单位是秒

http1.1中新增的 response header。浏览器第一次请求资源之后,在接下来的相对时间之内,都可以利用本地缓存。超出这个时间之后,则不能命中缓存。重新请求时,Cache-Control会被更新

2)、 协商缓存:浏览器发现有副本但是不确定用不用它,向服务器问一下要不要用,和服务器协商一下

当浏览器对某个资源的请求没有命中强缓存(也就是说超出时间了),就会发一个请求到服务器,验证协商缓存是否命中。

协商缓存是利用的是两对Header:

一、Last-Modified (上次修改时间), If-Modified-Since

二、 Etag, If-None-Match
    在这里插入图片描述

  1. 使用 CDN

怎么最快地让用户请求资源。一方面是让资源在传输的过程中变小,另外就是CDN。

要注意,浏览器第一次打开页面的时候,浏览器缓存是起不了作任何用的,使用CDN,效果就很明显。

  1. 预解析 DNS
在一些高级浏览器中,页面中所有的超链接( 标签),默认打开了DNS预解析。但是页面是 https 协议开头的很多浏览器是默认关闭 超链接 dns 预解析的,通过http-equiv="x-dns-prefetch-control" 这句话强制打开 dns 预解析 当我们从某个 URL 请求一个资源时,用rel="dns-prefetch" 做到 dns 预解析,就不再需要等待 DNS 解析的过程。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值