前端高频面试题

这是一份详细的前端面试题集,涵盖了HTML5新特性、CSS选择器、JavaScript基础与深入、Web移动端布局、Promise、事件循环机制、Vue项目优化、Webpack配置、前端安全等多个方面。内容包括HTML语义化、CSS盒模型、BFC渲染规则、移动端适配策略、异步请求处理、事件处理、Promise实现、CSS3新特性、ES6特性、Vue生命周期、Webpack配置优化、跨域解决方案等知识点。
摘要由CSDN通过智能技术生成

前端高频面试题

1. HTML

http://www.atguigu.com/mst/html/gp/17655.html

1.1. HTML5语义化与新特性

1. 什么是HTML语义化?

表示选择合适的标签(语义化标签)便于开发者阅读和写出更优雅的代码

2. 为什么要使用语义化标签?

  • 在没有CSS样式的情况下,页面整体也会呈现很好的结构效果
  • 更有利于用户体验
  • 更有利于搜索引擎优化
  • 代码结构清晰,方便团队开发与维护

3. HTML5新特性有哪些?

  • 语义化标签
  • 音视频处理
  • Canvas / WebGL
  • history API
  • requestAnimationFrame
  • 地理位置
  • WebSocket
  • Webworks

1.2. 什么是DOCTYPE及作用

DOCTYPE是用来声明文档类型和DTD规范的,一个主要的用途便是文件的合法性验证。如果文件代码不合法,那么浏览器解析时会出一些错误。(DOCTYPE告诉浏览器当前是哪个文档类型)

1.3. 行内元素与块级元素

1. 行内元素的特点?

  • 元素排在一行
  • 只能包含文本或者其他内联元素
  • 宽高就是内容宽高、设置宽高无效

2. 块级元素的特点?

  • 元素单独占一行
  • 元素的宽高都可以设置
  • 可以包含内联元素和其他块元素
  • 为设置宽度时,默认宽度是它容器的100%

3. 常见行内元素标签

a、br、code、em、img、input…

4. 常见块级元素标签:

div、p、dl、dt、form、h1~h6…

1.4. 简述一下src与href的区别

src是指向外部资源的位置,指向的内容会嵌入到文档中当前标签所在的位置,在请求src资源时会将其指向的资源下载并应用到文档内,如js脚本,img图片和frame等元素。当浏览器解析到该元素时,会暂停其他资源的下载和处理,知道将该资源加载、编译、执行完毕,所以一般js脚本会放在底部而不是头部。

href是指向网络资源所在位置(的超链接),用来建立和当前元素或文档之间的连接,当浏览器识别到它他指向的文件时,就会并行下载资源,不会停止对当前文档的处理。

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

**正常场景一般都适用div+CSS布局 **,

**优点 **:

  • 结构与样式分离
  • 代码语义性好
  • 更符合HTML标准规范
  • SEO友好

**Table布局的适用场景 **

某种原因不方便加载外部CSS的场景,例如邮件正文,此时用table布局可以在无css情况下保持页面布局正常。

1.6. 一个页面上有大量的图片(大型电商网站),加载很慢,你有哪些方法优化这些图片的加载,给用户更好的体验

  • 图片懒加载,在页面上的未可视区域可以添加一个滚动条事件,判断图片位置与浏览器顶端的距离与页面的距离,如果前者小于后者,优先加载。
  • 如果为幻灯片、相册等,可以使用图片预加载技术,将当前展示图片的前一张和后一张优先下载。
  • 如果图片为css图片,可以使用CSSsprite,SVGsprite,Iconfont、Base64等技术。
  • 如果图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩的特别厉害的缩略图,以提高用户体验。
  • 如果图片展示区域小于图片的真实大小,则因在服务器端根据业务需要先行进行图片压缩,图片压缩后大小与展示一致

1.7. meta有哪些属性,作用是什么

meta标签用于描述网页的元信息,如网站作者、描述、关键词,meta通过name=xxxcontent=xxx的形式来定义信息,常用设置如下:

  1. charset:定义HTML文档的字符集
 <meta charset="UTF-8" >
  1. http-equiv:可用于模拟http请求头,可设置过期时间、缓存、刷新
<meta http-equiv="expires" content="Wed, 20 Jun 2019 22:33:00 GMT">
  1. viewport:视口,用于控制页面宽高及缩放比例

    <meta 
        name="viewport" 
        content="width=device-width, initial-scale=1, maximum-scale=1"
    >
    

1.8. viewport有哪些参数,作用是什么

  1. width/height,宽高,默认宽度980px

  2. initial-scale,初始缩放比例,1~10

  3. maximum-scale/minimum-scale,允许用户缩放的最大/小比例

  4. user-scalable,用户是否可以缩放 (yes/no)

2. CSS

2.1. 盒子模型

  1. 盒模型分为标准盒模型和怪异盒模型(IE模型)

标准盒模型:

​ box-sizing:content-box

​ 元素的宽度 = width+margin+border+padding

怪异盒模型

​ box-sizing:border-box

​ 元素宽度 = width宽

2.2. rem与em的区别

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

  2. rem:相对于根元素html的font-size,假如html为font-size:12px,那么,在其当中的div设置为font-size:2rem,就是当中的div为24px

  3. em:相对于父元素计算,假如某个p元素为font-size:12px,在它内部有个span标签,设置font-size:2em,那么,这时候的span字体大小为:12*2=3D24px

2.3. CSS常用选择器

  1. 通配符:*

  2. ID选择器:#ID

  3. 类选择器:.class

  4. 元素选择器:p、a 等

  5. 后代选择器:p span、div a 等

  6. 伪类选择器:a:hover 等

  7. 属性选择器:input[type=3D”text”] 等

2.4. css选择器权重

!important -> 行内样式 -> #id -> .class -> 元素和伪元素 -> * -> 继承 -> 默认

2.5. CSS新特性

  1. transition:过渡

  2. transform:旋转、缩放、移动或者倾斜

  3. animation:动画

  4. gradient:渐变

  5. shadow:阴影

  6. border-radius:圆角

2.6. 绝对定位和相对定位的区别

  1. position: absolute
    绝对定位:是相对于元素最近的已定位的祖先元素

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

2.7. 水平垂直居中

  1. Flex布局

1). display: flex //设置Flex模式

2). flex-direction: column //决定元素是横排还是竖着排

3). flex-wrap: wrap //决定元素换行格式

4). justify-content: space-between //同一排下对齐方式,空格如何隔开各个元素

5). align-items: center //同一排下元素如何对齐

6). align-content: space-between //多行对齐方式

  1. 水平居中

1). 行内元素:display: inline-block;

2). 块级元素:margin: 0 auto;

3). Flex: display: flex; justify-content: center

  1. 垂直居中

1). 行高 =3D 元素高:line-height: height

2). flex: display: flex; align-item: center

2.8. Less,Sass,Styus三者的区别

  1. 变量

1). Sass声明变量必须是『$』开头,后面紧跟变量名和变量值,而且变量名和变量值需要使用冒号:分隔开。

2). Less 声明变量用『@』开头,其余等同 Sass。

3). Stylus 中声明变量没有任何限定,结尾的分号可有可无,但变量名和变量值之间必须要有『等号』。

  1. 作用域

1). Sass:三者最差,不存在全局变量的概念

2). Less:最近的一次更新的变量有效,并且会作用于全部的引用!

3). Stylus:Sass 的处理方式和 Stylus 相同,变量值输出时根据之前最近的一次定义计算,每次引用最近的定义有效;

  1. 嵌套

三种 css 预编译器的「选择器嵌套」在使用上来说没有任何区别,甚至连引用父级选择器的标记 & 也相同

  1. 继承

Sass和Stylus的继承非常像,能把一个选择器的所有样式继承到另一个选择器上。使用『@extend』开始,后面接被继承的选择器。Stylus 的继承方式来自 Sass,两者如出一辙。 Less 则又「独树一帜」地用伪类来描述继承关系;

  1. 导入@Import

Sass 中只能在使用 url() 表达式引入时进行变量插值

$device: mobile;

@import url(styles.#{$device}.css);

Less 中可以在字符串中进行插值

@device: mobile;

@import "styles.@{device}.css";

Stylus 中在这里插值不管用,但是可以利用其字符串拼接的功能实现

device = "mobile"

@import "styles." + device + ".css"

9. 总结

1). Sass和Less语法严谨、Stylus相对自由。因为Less长得更像 css,所以它可能学习起来更容易。

2). Sass 和 Compass、Stylus 和 Nib 都是好基友。

3). Sass 和 Stylus 都具有类语言的逻辑方式处理:条件、循环等,而 Less 需要通过When等关键词模拟这些功能,这方面 Less 比不上 Sass 和 Stylus

4). Less 在丰富性以及特色上都不及 Sass 和 Stylus,若不是因为 Bootstrap 引入了 Less,可能它不会像现在这样被广泛应用

2.9. link与@import区别与选择

<style type="text/css">
  @import url(CSS文件路径地址);
</style>
<link href="CSSurl路径" rel="stylesheet" type="text/css" />

1). link功能较多,可以定义 RSS,定义 Rel 等作用,而@import只能用于加载 css;

2). 当解析到link时,页面会同步加载所引的 css,而@import所引用的 css 会等到页面加载完才被加载;

3). @import需要 IE5 以上才能使用;

4). link可以使用 js 动态引入,@import不行

2.10. 多行元素的文本省略号

overflow : hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical

2.11. 什么是BFC?

BFC全称 Block Formatting Context 即块级格式上下文,简单的说,BFC是页面上的一个隔离的独立容器,不受外界干扰或干扰外界

2.12. 如何触发BFC

  • float不为 none
  • overflow的值不为 visible
  • position 为 absolute 或 fixed
  • display的值为 inline-block 或 table-cell 或 table-caption 或 grid

2.13. BFC的渲染规则是什么

  • BFC是页面上的一个隔离的独立容器,不受外界干扰或干扰外界
  • 计算BFC的高度时,浮动子元素也参与计算(即内部有浮动元素时也不会发生高度塌陷)
  • BFC的区域不会与float的元素区域重叠
  • BFC内部的元素会在垂直方向上放置
  • BFC内部两个相邻元素的margin会发生重叠

2.14. BFC的应用场景

  • 清除浮动:BFC内部的浮动元素会参与高度计算,因此可用于清除浮动,防止高度塌陷
  • 避免某元素被浮动元素覆盖:BFC的区域不会与浮动元素的区域重叠
  • 阻止外边距重叠:属于同一个BFC的两个相邻Box的margin会发生折叠,不同BFC不会发生折叠

2.15. CSS3有哪些新特性?

  1. 圆角 (border-radius:8px)

  2. 新增各种CSS选择器、伪类 (经常用到 :nth-child)

  3. 文字渲染 (Text-decoration)

  4. 转化为简写属性,可设置text-decoration-color, text-decoration-style, text-decoration-line三个属性,默认值为currentcolor solid none。

  5. 透明色 & 透明度(opacity)

  6. 旋转 (transform)

  7. 旋转 rotate,缩放 scale,倾斜 skew,平移 translate

  8. 动画(animation) & 过渡效果(transition)

  9. 阴影(box-shadow, text-shadow)

  10. 新的布局方式,如 多列布局 multi-columns 、 弹性布局 flexible box 与 网格布局 grid layouts

  11. 线性渐变(gradient)

  12. 多背景(background-image可以设置多个url或linear-gradient)

  13. 媒体查询(@media MDN) (可以看看这个)

  14. 边框可以设置图片(border-image)

2.16. 说一下CSS3的flex box(弹性盒布局模型)

1. 什么是flex box?

1). CSS3新增布局。

2). Flexbox可以把列表放在同一个方向(从上到下排列,从左到右),并让列表能延伸到占用可用的空间。

3). 较为复杂的布局还可以通过嵌套一个伸缩容器(flex container)来实现。

4). 采用Flex布局的元素,称为Flex容器(flex container),简称”容器”。

5). 它的所有子元素自动成为容器成员,称为Flex项目(flex item),简称”项目”。

6). 常规布局是基于块和内联流方向,而Flex布局是基于flex-flow流可以很方便的用来做局中,能对不同屏幕大小自适应。

7). 在布局上有了比以前更加灵活的空间。

2. 应用场景?

1). 水平垂直居中

2). 一边定宽,一边自适应

3). 多列等分布局

4). sticky footer

2.17. 用纯CSS创建一个三角形的原理是什么?

div {
    width: 0;
    height: 0; /* div里没内容,可不写 */
    border-width: 20px;
    border-style: solid;
    border-color: transparent transparent red transparent;
}
/* 或者这样写 */
div {
    width: 0;
    border: 100px solid transparent;
    border-bottom-color: #343434;
}

3. Web移动端

3.1. **移动端你们一般采用什么布局?移动端设计稿是多大的尺寸?

  1. 定宽布局

  2. 一般移动端设计稿是640或者750的尺寸

3.2. em和rem的区别

  1. em相对父级元素设置的font-size来设置大小 如果父元素没有设置font-size ,则继续向上查找,直至有设置font-size元素

  2. rem直接参照html标签字体大小,并且所有使用rem单位的都是参照html标签

3.3. 移动端用过那些meta标签?

  • 设置视口宽度 缩放比例

    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
    
  • 忽略将数字变为电话号码

    <meta content="telephone=no" name="format-detection">
    
  • 忽略识别邮箱

    <meta name="format-detection" content="email=no" />
    
  • IOS中Safari允许全屏浏览

    <meta content="yes" name="apple-mobile-web-app-capable">
    

3.4. 移动端click 300毫秒延迟原因?

移动端浏览器会有一些默认的行为,比如双击缩放、双击滚动。这些行为,尤其是双击缩放,主要是为桌面网站在移动端的浏览体验设计的。而在用户对页面进行操作的时候,移动端浏览器会优先判断用户是否要触发默认的行为。

3.5. 固定定位布局 键盘挡住输入框内容?

  1. 通过绑定窗口改变事件,监听键盘的弹出。然后去改变固定定位元素的位置。默认键盘的宽度应该是页面的2分之一。所以我们位移的距离改成键盘的二分之一就可以
window.onresize = function(){
    //$(".mian")就是固定定位的元素
    if($(".mian").css('top').replace('px','') != 0){
        $(".mian").css('top',0);
    }else{
        var winHeight = $(window).height();
        $(".mian").css('top',-(winHeight/4));
    }
}
  1. 通过定时器实时监听是否触发input。如果触发input框 就把固定定位,改变成静态定位。这样就会浏览器会总动把内容顶上去。
function fixedWatch(el) {
    //activeElement 获取焦点元素
    if(document.activeElement.nodeName == 'INPUT') {
        el.css('position', 'static');
    } else {
        el.css('position', 'fixed');
    }
}
setInterval(function() {
    fixedWatch($('.mian'));
}, 500);

3.6. 移动端横屏怎么处理?

写好一套样式 获取窗口的宽度大于高度的时候 说明横屏了 。那么就显示一个遮罩层提示用户竖屏观看更佳。

4. JS基础

4.1. JavaScript的数据类型都有什么?

  1. 基本数据类型:String,Boolean,Number,Undefined, Null

  2. 引用数据类型:Object, Array, Function

4.2. JS 的提升是什么

提升是指 JS 解释器将所有变量和函数声明移动到当前作用域顶部的操作,提升有两种类型

  • 变量提升
  • 函数提升

只要一个var或函数声明出现在一个作用域内,这个声明就被认为属于整个作用域,并且可以在任何地方访问。

var a = 2
foo() // 正常运行, foo 已被提升
function foo() {
  console.log(a)   // undefined, a已经提升
  var a = 3
}

4.3. 什么是 IIFE (立即调用的函数表达式)

1). IIFE是一个立即调用的函数表达式,它在创建后立即执行

(function IIFE(){
    console.log( "Hello!" );
})();
// "Hello!"

2). 常常使用此模式来避免污染全局命名空间,因为在IIFE中使用的所有变量(与任何其他普通函数一样)在其作用域之外都是不可见的。

4.4. 数组操作

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

4.5. 用JavaScript实现数组冒泡排序。

4.6. 在Javascript中什么是伪数组?如何将伪数组转化为标准数组?

  1. 伪数组(类数组):无法直接调用数组方法或期望length属性有什么特殊的行为,但仍可以对真正数组遍历方法来遍历它们。

  2. 典型的是函数的argument参数,还有像调用getElementsByTagName,document.childNodes之类的,它们都返回NodeList对象

  3. 可以使用Array.prototype.slice.call(fakeArray)将数组转化为真正的Array对象

4.7. 判断一个字符串中出现次数最多的字符,统计这个次数

var str = 'asdfssaaasasasasaa';
var json = {};
for (var i = 0; i < str.length; i++) {
    if(!json[str.charAt(i)]){
        json[str.charAt(i)] = 1;
    }else{
        json[str.charAt(i)]++;
    }
};
var iMax = 0;
var iIndex = '';
for(var i in json){
    if(json[i]>iMax){
        iMax = json[i];
        iIndex = i;
    }
}
alert('出现次数最多的是:'+iIndex+'出现'+iMax+'次');

4.8. 字符串反转,如将 ‘12345678’ 变成 ‘87654321’

思路:先将字符串转换为数组 split(),利用数组的反序函数 reverse()颠倒数组,再利用 jion() 转换为字符串

var str ='12345678';
str = str.split('').reverse().join('');

4.9. 如何实现一个对页面某个节点的拖曳?

回答出关键点,下面是几个要点

1). 给需要拖拽的节点绑定mousedown, mousemove, mouseup事件

2). mousedown事件触发后,开始拖拽

3). mousemove时,需要通过event.clientX和clientY获取拖拽位置,并实时更新位置

4). mouseup时,拖拽结束

5). 需要注意浏览器边界的情况

4.10. DOM操作——怎样添加、移除、移动、复制、创建和查找节点

  1. 创建新节点

1). createDocumentFragment() //创建一个DOM片段

2). createElement() //创建一个具体的元素

  1. 添加、移除、替换、插入

1). appendChild() // 添加为最后子点

2). removeChild() // 删除子节点

3). replaceChild() // 替换子节点

4). insertBefore() //在已有的子节点前插入一个新的子节点

  1. 查找

1). getElementsByTagName() //通过标签名称查找所有对应的标签的伪数组

2). getElementsByName() //通过Name属性查找所有对应的标签的伪数组

3). getElementById() //通过元素Id对应的唯一标签

5. JS深入

5.1. 如何在 JS 中“深冻结”对象

如果咱们想要确保对象被深冻结,就必须创建一个递归函数来冻结对象类型的每个属性:

  • 没有深冻结
let person = {
    name: "Leonardo",
    profession: {
        name: "developer"
    }
};
Object.freeze(person);
person.profession.name = "doctor";
console.log(person); //output { name: 'Leonardo', profession: { name: 'doctor' } }
  • 深冻结
function deepFreeze(object) {
    let propNames = Object.getOwnPropertyNames(object);
    for (let name of propNames) {
        let value = object[name];
        object[name] = value && typeof value === "object" ? deepFreeze(value) : value;
    }
    return Object.freeze(object);
}
let person = {
    name: "Leonardo",
    profession: {
        name: "developer"
    }
};
deepFreeze(person);
person.profession.name = "doctor"; // TypeError: Cannot assign to read only property

5.2. 手写call()

/* 
自定义函数对象的call方法
*/
export function call (fn, obj, ...args) {
  // 如果传入的是null/undefined, this指定为window
  if (obj===null || obj===undefined) {
    obj = obj || window
  }
  // 给obj添加一个方法: 属性名任意, 属性值必须当前调用call的函数对象
  obj.tempFn = fn
  // 通过obj调用这个方法
  const result = obj.tempFn(...args)
  // 删除新添加的方法
  delete obj.tempFn
  // 返回函数调用的结果
  return result
}

5.3. 手写apply()

/* 
自定义函数对象的apply方法
*/
export function apply (fn, obj, args) {
  // 如果传入的是null/undefined, this指定为window
  if (obj===null || obj===undefined) {
    obj = obj || window
  }
  // 给obj添加一个方法: 属性名任意, 属性值必须当前调用call的函数对象
  obj.tempFn = fn
  // 通过obj调用这个方法
  const result = obj.tempFn(...args)
  // 删除新添加的方法
  delete obj.tempFn
  // 返回函数调用的结果
  return result
}

5.4. 手写bind()

import {call} from './call'
/* 
  自定义函数对象的bind方法
  重要技术:
    高阶函数
    闭包
    call()
    三点运算符
*/
export function bind (fn, obj, ...args) {
  if (obj===null || obj===undefined) {
    obj = obj || window
  }
  
  return function (...args2) {
    call(fn, obj, ...args, ...args2)
  }
}

5.5. 手写一个防抖函数

/* 
实现函数防抖的函数
*/
export function debounce(callback, delay) {
  return function () {
    // console.log('debounce 事件...')
    // 保存this和arguments
    const that = this
    const args = arguments
    
    // 清除待执行的定时器任务
    if (callback.timeoutId) {
      clearTimeout(callback.timeoutId)
    }
    // 每隔delay的时间, 启动一个新的延迟定时器, 去准备调用callback
    callback.timeoutId = setTimeout(function () {
      callback.apply(that, args)
      // 如果定时器回调执行了, 删除标记
      delete callback.timeoutId
    }, delay)
  }
}

5.6. 手写一个节流函数

/* 
实现函数节流的函数
*/
export function throttle(callback, delay) {
  let start = 0 // 必须保存第一次点击立即调用
  return function () {
    // 它的this是谁就得让callback()中的this是谁, 它接收的所有实参都直接交给callback()
    console.log('throttle 事件')
    const current = Date.now()
    if (current - start > delay) { // 从第2次点击开始, 需要间隔时间超过delay
      callback.apply(this, arguments)
      start = current
    }
  }
}

5.7. 手写一个深拷贝函数

/* 
1). 大众乞丐版
  问题1: 函数属性会丢失
  问题2: 循环引用会出错
*/
export function deepClone1(target) {
  return JSON.parse(JSON.stringify(target))
}
/* 
获取数据的类型字符串名
*/
function getType(data) {
  return Object.prototype.toString.call(data).slice(8, -1)
}
/*
2). 面试基础版本
  解决问题1: 函数属性还没丢失
*/
export function deepClone2(target) {
  const type = getType(target)
  if (type==='Object' || type==='Array') {
    const cloneTarget = type === 'Array' ? [] : {}
    for (const key in target) {
      if (target.hasOwnProperty(key)) {
        cloneTarget[key] = deepClone2(target[key])
      }
    }
    return cloneTarget
  } else {
    return target
  }
}

/* 
3). 面试加强版本
  解决问题2: 循环引用正常
*/
export function deepClone3(target, map = new Map()) {
  const type = getType(target)
  if (type==='Object' || type==='Array') {
    let cloneTarget = map.get(target)
    if (cloneTarget) {
      return cloneTarget
    }
    cloneTarget = type==='Array' ? [] : {}
    map.set(target, cloneTarget)
    for (const key in target) {
      if (target.hasOwnProperty(key)) {
        cloneTarget[key] = deepClone3(target[key], map)
      }
    }
    return cloneTarget
  } else {
    return target
  }
}
/* 
4). 面试加强版本2(优化遍历性能)
    数组: while | for | forEach() 优于 for-in | keys()&forEach() 
    对象: for-in 与 keys()&forEach() 差不多
*/
export function deepClone4(target, map = new Map()) {
  const type = getType(target)
  if (type==='Object' || type==='Array') {
    let cloneTarget = map.get(target)
    if (cloneTarget) {
      return cloneTarget
    }
    if (type==='Array') {
      cloneTarget = []
      map.set(target, cloneTarget)
      target.forEach((item, index) => {
        cloneTarget[index] = deepClone4(item, map)
      })
    } else {
      cloneTarget = {}
      map.set(target, cloneTarget)
      Object.keys(target).forEach(key => {
        cloneTarget[key] = deepClone4(target[key], map)
      })
    }
    return cloneTarget
  } else {
    return target
  }
}

5.8. 自定义instanceof工具函数

/* 
自定义instanceof工具函数: 
  语法: myInstanceOf(obj, Type)
  功能: 判断obj是否是Type类型的实例
  实现: Type的原型对象是否是obj的原型链上的某个对象, 如果是返回true, 否则返回false
*/
export function myInstanceOf(obj, Type) {
  // 得到原型对象
  let protoObj = obj.__proto__
  // 只要原型对象存在
  while(protoObj) {
    // 如果原型对象是Type的原型对象, 返回true
    if (protoObj === Type.prototype) {
      return true
    }
    // 指定原型对象的原型对象
    protoObj = protoObj.__proto__
  }
  return false
}

5.9. 自定义new工具函数

/* 
自定义new工具函数
  语法: newInstance(Fn, ...args)
  功能: 创建Fn构造函数的实例对象
  实现: 创建空对象obj, 调用Fn指定this为obj, 返回obj
*/
export function newInstance(Fn, ...args) {
  // 创建一个新的对象
  const obj = {}
  // 执行构造函数
  const result = Fn.apply(obj, args) // 相当于: obj.Fn()
  // 如果构造函数执行的结果是对象, 返回这个对象
  if (result instanceof Object) {
    return result
  }
  // 如果不是, 返回新创建的对象
  obj.__proto__.constructor = Fn // 让原型对象的构造器属性指向Fn
  
  return obj
}

5.10. 手写axios函数

/* 
  1. 函数的返回值为promise, 成功的结果为response, 失败的结果为error
  2. 能处理多种类型的请求: GET/POST/PUT/DELETE
  3. 函数的参数为一个配置对象
      {
        url: '',   // 请求地址
        method: '',   // 请求方式GET/POST/PUT/DELETE
        params: {},  // GET/DELETE请求的query参数
        data: {}, // POST或DELETE请求的请求体参数 
      }
  4. 响应json数据自动解析为js的对象/数组
*/
/* 发送任意类型请求的函数 */
function axios({
  url,
  method='GET',
  params={},
  data={}
}) {
  // 返回一个promise对象
  return new Promise((resolve, reject) => {
    // 处理method(转大写)
    method = method.toUpperCase()
    // 处理query参数(拼接到url上)   id=1&xxx=abc
    /* 
    {
      id: 1,
      xxx: 'abc'
    }
    */
    let queryString = ''
    Object.keys(params).forEach(key => {
      queryString += `${key}=${params[key]}&`
    })
    if (queryString) { // id=1&xxx=abc&
      // 去除最后的&
      queryString = queryString.substring(0, queryString.length-1)
      // 接到url
      url += '?' + queryString
    }
    // 1. 执行异步ajax请求
    // 创建xhr对象
    const request = new XMLHttpRequest()
    // 打开连接(初始化请求, 没有请求)
    request.open(method, url, true)
    // 发送请求
    if (method==='GET') {
      request.send()
    } else if (method==='POST' || method==='PUT' || method==='DELETE'){
      request.setRequestHeader('Content-Type', 'application/json;charset=utf-8') // 告诉服务器请求体的格式是json
      request.send(JSON.stringify(data)) // 发送json格式请求体参数
    }
    // 绑定状态改变的监听
    request.onreadystatechange = function () {
      // 如果请求没有完成, 直接结束
      if (request.readyState!==4) {
        return
      }
      // 如果响应状态码在[200, 300)之间代表成功, 否则失败
      const {status, statusText} = request
      // 2.1. 如果请求成功了, 调用resolve()
      if (status>=200 && status<=299) {
        // 准备结果数据对象response
        const response = {
          data: JSON.parse(request.response),
          status,
          statusText
        }
        resolve(response)
      } else { // 2.2. 如果请求失败了, 调用reject()
        reject(new Error('request error status is ' + status))
      }
    }
  })
}
/* 发送特定请求的静态方法 */
axios.get = function (url, options) {
  return axios(Object.assign(options, {url, method: 'GET'}))
}
axios.delete = function (url, options) {
  return axios(Object.assign(options, {url, method: 'DELETE'}))
}
axios.post = function (url, data, options) {
  return axios(Object.assign(options, {url, data, method: 'POST'}))
}
axios.put = function (url, data, options) {
  return axios(Object.assign(options, {url, data, method: 'PUT'}))
}
export default axios

5.11. 自定义事件总线

*
* 自定义事件总线
*/
const eventBus = {}
/* 
{
  add:  [callback1, callback2]
  delete: [callback3]
}
*/
let callbacksObj = {}
/* 
绑定事件监听
*/
eventBus.on = function (eventName, callback) {
  const callbacks = callbacksObj[eventName]
  if (callbacks) {
    callbacks.push(callback)
  } else {
    callbacksObj[eventName] = [callback]
  }
}
/* 
分发事件
*/
eventBus.emit = function (eventName, data) {
  const callbacks = callbacksObj[eventName]
  if (callbacks && callbacks.length > 0) {
    callbacks.forEach(callback => {
      callback(data)
    })
  }
}
/* 
移除事件监听
*/
eventBus.off = function (eventName) {
  if (eventName) {
    delete callbacksObj[eventName]
  } else {
    callbacksObj = {}
  }
}
export default eventBus

5.12. 自定义消息订阅与发布

/* 
自定义消息订阅与发布
*/
const PubSub = {}
/* 
  {
    add: {
      token1: callback1, 
      token2: callback2
    },
    update: {
      token3: callback3
    }
  }
*/
let callbacksObj = {} // 保存所有回调的容器
let id = 0 // 用于生成token的标记
// 1. 订阅消息
PubSub.subscribe = function (msgName, callback) {
  // 确定token
  const token = 'token_' + ++id
  // 取出当前消息对应的callbacks
  const callbacks = callbacksObj[msgName]
  if (!callbacks) {
    callbacksObj[msgName] = {
      [token]: callback
    }
  } else {
    callbacks[token] = callback
  }
  // 返回token
  return token
}
// 2. 发布异步的消息
PubSub.publish = function (msgName, data) {
  // 取出当前消息对应的callbacks
  let callbacks = callbacksObj[msgName]
  // 如果有值
  if (callbacks) {
    // callbacks = Object.assign({}, callbacks)
    // 启动定时器, 异步执行所有的回调函数
    setTimeout(() => {
      Object.values(callbacks).forEach(callback => {
        callback(data)
      })
    }, 0)
  }
}
// 3. 发布同步的消息
PubSub.publishSync = function (msgName, data) {
  // 取出当前消息对应的callbacks
  const callbacks = callbacksObj[msgName]
  // 如果有值
  if (callbacks) {
    // 立即同步执行所有的回调函数
    Object.values(callbacks).forEach(callback => {
      callback(data)
    })
  }
}
/*
4. 取消消息订阅
  1). 没有传值, flag为undefined
  2). 传入token字符串
  3). msgName字符串
*/
PubSub.unsubscribe = function (flag) {
  // 如果flag没有指定或者为null, 取消所有
  if (flag === undefined) {
    callbacksObj = {}
  } else if (typeof flag === 'string') {
    if (flag.indexOf('token_') === 0) { // flag是token
      // 找到flag对应的callbacks
      const callbacks = Object.values(callbacksObj).find(callbacks => callbacks.hasOwnProperty(flag))
      // 如果存在, 删除对应的属性
      if (callbacks) {
        delete callbacks[flag]
      }
    } else { // flag是msgName
      delete callbacksObj[flag]
    }
  } else {
    throw new Error('如果传入参数, 必须是字符串类型')
  }
}
export default PubSub

5.13. 自定义数组声明式系列方法

/* 
实现数组声明式处理系列工具函数
*/
/* 
实现map()
*/
export function map (array, callback) {
  const arr = []
  for (let index = 0; index < array.length; index++) {
    arr.push(callback(array[index], index))
  }
  return arr
}
/*
实现reduce() 
*/
export function reduce (array, callback, initValue) {
  let result = initValue
  for (let index = 0; index < array.length; index++) {
    // 调用回调函数将返回的结果赋值给result
    result = callback(result, array[index], index)
  }
  return result
}
/* 
实现filter()
*/
export function filter(array, callback) {
  
  const arr = []
  for (let index = 0; index < array.length; index++) {
    if (callback(array[index], index)) {
      arr.push(array[index])
    }
  }
  return arr
}
/* 
实现find()
*/
export function find (array, callback) {
  for (let index = 0; index < array.length; index++) {
    if (callback(array[index], index)) {
      return array[index]
    }
  }
  return undefined
}
/* 
实现findIndex()
*/
export function findIndex (array, callback) {
  for (let index = 0; index < array.length; index++) {
    if (callback(array[index], index)) {
      return index
    }
  }
  return -1
}
 /* 
 实现every()
 */
 export function every (array, callback) {
  for (let index = 0; index < array.length; index++) {
    if (!callback(array[index], index)) { // 只有一个结果为false, 直接返回false
      return false
    }
  }
  return true
}
/* 
实现some()
*/
export function some (array, callback) {
  for (let index = 0; index < array.length; index++) {
    if (callback(array[index], index)) { // 只有一个结果为true, 直接返回true
      return true
    }
  }
  return false
}
export function test() {
  console.log('test()222')
}

5.14. 手写Promise

const PENDING = 'pending' // 初始未确定的状态
const RESOLVED = 'resolved' // 成功的状态
const REJECTED = 'rejected' // 失败的状态
/* 
Promise构造函数
*/
function Promise(excutor) {
  const self = this // Promise的实例对象
  self.status = PENDING // 状态属性, 初始值为pending, 代表初始未确定的状态
  self.data = undefined // 用来存储结果数据的属性, 初始值为undefined
  self.callbacks = [] // {onResolved(){}, onRejected(){}}
  /* 
  将promise的状态改为成功, 指定成功的value
  */
  function resolve(value) {
    // 如果当前不是pending, 直接结束
    if (self.status !== PENDING) return
    self.status = RESOLVED // 将状态改为成功
    self.data = value // 保存成功的value
    // 异步调用所有缓存的待执行成功的回调函数
    if (self.callbacks.length > 0) {
      // 启动一个延迟时间为0的定时器, 在定时器的回调中执行所有成功的回调
      setTimeout(() => {
        self.callbacks.forEach(cbsObj => {
          cbsObj.onResolved(value)
        })
      })
    }
  }
  /* 
  将promise的状态改为失败, 指定失败的reason
  */
  function reject(reason) {
    // 如果当前不是pending, 直接结束
    if (self.status !== PENDING) return
    self.status = REJECTED // 将状态改为失败
    self.data = reason // 保存reason数据
    // 异步调用所有缓存的待执行失败的回调函数
    if (self.callbacks.length > 0) {
      // 启动一个延迟时间为0的定时器, 在定时器的回调中执行所有失败的回调
      setTimeout(() => {
        self.callbacks.forEach(cbsObj => {
          cbsObj.onRejected(reason)
        })
      })
    }
  }
  // 调用excutor来启动异步任务
  try {
    excutor(resolve, reject)
  } catch (error) { // 执行器执行出错, 当前promise变为失败
    console.log('-----')
    reject(error)
  }
}
/* 
用来指定成功/失败回调函数的方法
    1). 如果当前promise是resolved, 异步执行成功的回调函数onResolved
    2). 如果当前promise是rejected, 异步执行成功的回调函数onRejected
    3). 如果当前promise是pending, 保存回调函数
返回一个新的promise对象
    它的结果状态由onResolved或者onRejected执行的结果决定
    2.1). 抛出error ==> 变为rejected, 结果值为error
    2.2). 返回值不是promise   ==> 变为resolved, 结果值为返回值
    2.3). 返回值是promise    ===> 由这个promise的决定新的promise的结果(成功/失败)
*/
Promise.prototype.then = function (onResolved, onRejected) {
  const self = this
  onResolved = typeof onResolved === 'function' ? onResolved : value => value // 将value向下传递
  onRejected = typeof onRejected === 'function' ? onRejected : reason => {
    throw reason
  } // 将reason向下传递
  return new Promise((resolve, reject) => { // 什么时候改变它的状态
    /* 
    1. 调用指定的回调函数
    2. 根据回调执行结果来更新返回promise的状态
    */
    function handle(callback) {
      try {
        const result = callback(self.data)
        if (!(result instanceof Promise)) { //  2.2). 返回值不是promise   ==> 变为resolved, 结果值为返回值
          resolve(result)
        } else { // 2.3). 返回值是promise    ===> 由这个promise的决定新的promise的结果(成功/失败)
          result.then(
            value => resolve(value),
            reason => reject(reason)
          )
          // result.then(resolve, reject)
        }
      } catch (error) { // 2.1). 抛出error ==> 变为rejected, 结果值为error
        reject(error)
      }
    }
    if (self.status === RESOLVED) {
      setTimeout(() => {
        handle(onResolved)
      })
    } else if (self.status === REJECTED) {
      setTimeout(() => {
        handle(onRejected)
      })
    } else { // PENDING
      self.callbacks.push({
        onResolved(value) {
          handle(onResolved)
        },
        onRejected(reason) {
          handle(onRejected)
        }
      })
    }
  })
}
/* 
用来指定失败回调函数的方法
catch是then的语法糖
*/
Promise.prototype.catch = function (onRejected) {
  return this.then(undefined, onRejected)
}
/* 
用来返回一个指定vlaue的成功的promise
value可能是一个一般的值, 也可能是promise对象
*/
Promise.resolve = function (value) {
  return new Promise((resolve, reject) => {
    // 如果value是一个promise, 最终返回的promise的结果由value决定
    if (value instanceof Promise) {
      value.then(resolve, reject)
    } else { // value不是promise, 返回的是成功的promise, 成功的值就是value
      resolve(value)
    }
  })
}
/* 
用来返回一个指定reason的失败的promise
*/
Promise.reject = function (reason) {
  return new Promise((resolve, reject) => {
    reject(reason)
  })
}
/* 
返回一个promise, 只有当数组中所有promise都成功才成功, 否则失败
*/
Promise.all = function (promises) {
  return new Promise((resolve, reject) => {
    let resolvedCount = 0 // 已经成功的数量 
    const values = new Array(promises.length) // 用来保存成功promise的value值
    // 遍历所有promise, 取其对应的结果
    promises.forEach((p, index) => {
      p.then(
        value => {
          resolvedCount++
          values[index] = value
          if (resolvedCount === promises.length) { // 都成功了
            resolve(values)
          }
        },
        reason => reject(reason)
      )
    })
  })
}
/* 
返回一个promise, 由第一个完成promise决定
*/
Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    // 遍历所有promise, 取其对应的结果
    promises.forEach(p => {
      // 返回的promise由第一个完成p来决定其结果
      p.then(resolve, reject)
    })
  })
}
/* 
返回一个延迟指定时间才成功(也可能失败)的promise
*/
Promise.resolveDelay = function (value, time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 如果value是一个promise, 最终返回的promise的结果由value决定
      if (value instanceof Promise) {
        value.then(resolve, reject)
      } else { // value不是promise, 返回的是成功的promise, 成功的值就是value
        resolve(value)
      }
    }, time)
  })
}
/* 
返回一个延迟指定时间才失败的promise
*/
Promise.rejectDelay = function (reason, time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(reason)
    }, time)
  })
}
export default Promise

5.15. 自定义数组扁平化

/* 
数组扁平化: 取出嵌套数组(多维)中的所有元素放到一个新数组(一维)中
  如: [1, [3, [2, 4]]]  ==>  [1, 3, 2, 4]
*/
/*
方法一: 递归 + reduce() + concat()
*/
export function flatten1 (array) {
  return array.reduce((pre, item) => {
    if (Array.isArray(item)) {
      return pre.concat(flatten1(item))
    } else {
      return pre.concat(item)
    }
  }, [])
}
/*
方法二: ... + some() + concat()
*/
export function flatten2 (array) {
  let arr = [].concat(...array)
  while (arr.some(item => Array.isArray(item))) {
    arr = [].concat(...arr)
  }
  return arr
}

6. ES6

6.1. 可以解释一下 ES5 和 ES6 的区别吗?

ECMAScript 5 (ES5):ECMAScript 的第五版,于 2009 年标准化,该标准已在所有现代浏览器中完全支持。

ECMAScript 6 (ES6)/ ECMAScript 2015 (ES2015): ECMAscript 第 6 版,2015 年标准化。这个标准已经在大多数现代浏览器中部分实现。

以下是 ES5 和 ES6 之间的一些主要区别:

1. 箭头函数

const greetings = (name) => {
  return `hello ${name}`;
}
也可以这样写:
const greetings = name => `hello ${name}`; 

2. const

const 表示无法修改变量的原始值。需要注意的是,const 表示对值的常量引用,咱们可以改变被引用的对象的属性值,但不能改变引用本身。

const NAMES = [];
NAMES.push("Jim");
console.log(NAMES.length === 1); // true
NAMES = ["Steve", "John"]; // error 

3. 块作用域

ES6 中 let, const会创建块级作用域,不会像 var 声明变量一样会被提升。

4. 默认参数

默认参数使咱们可以使用默认值初始化函数。当参数省略或 undefined 时使用默认参数值。

function multiply (a, b = 2) {
   return a * b;
}
multiply(5); // 10 

5. 类定义与继承

ES6 引入了对类( class 关键字)、构造函数( constructor 关键字)和 extends 关键字(用于继承)的语言支持。

6. for-of 运算符

for…of 语句创建一个遍历可迭代对象的循环。

7. 展开操作符

const obj1 = { a: 1, b: 2 }
const obj2 = { a: 2, c: 3, d: 4}
const obj3 = {...obj1, ...obj2} 

8. Promise

Promise 提供了一种机制来处理异步操作的结果和错误。可以使用回调来完成相同的事情,但是 Promise 通过方法链接和简洁的错误处理来提高可读性。

const isGreater = (a, b) => {
  return new Promise ((resolve, reject) => {
    if(a > b) {
      resolve(true)
    } else {
      reject(false)
    }
    })
}
isGreater(1, 2)
  .then(result => {
    console.log('greater')
  })
 .catch(result => {
    console.log('smaller')
 }) 

9. 模块导出和导入

const myModule = **{** x: 1, y: **()** =**>** **{** console.log**(**'This is ES5'**)** **}}**

export default myModule;   

import myModule from './myModule';

6.2. 为什么要使用 ES6 类?

选择使用类的一些原因:

  1. 语法更简单,更不容易出错。

  2. 使用新语法比使用旧语法更容易(而且更不易出错)地设置继承层次结构。

  3. class 可以避免构造函数中使用new的常见错误(如果构造函数不是有效的对象,则使构造函数抛出异常)。

  4. 用新语法调用父原型方法的版本比旧语法要简单得多,用 super.method() 代替 ParentConstructor.prototype.method.call(this) 或 Object.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this)

6.3. 什么时候不使用箭头函数? 说出三个或更多的例子

  1. 使用函数作为构造函数时(箭头函数没有构造函数)

  2. 要在函数中使用 this/arguments 时,由于箭头函数本身不具有 this/arguments,因此它们取决于外部上下文

  3. 当想要函数被提升时(箭头函数是匿名的)

  4. 当想在对象字面是以将函数作为属性添加并在其中使用对象时,因为咱们无法访问 this 即对象本身。

6.4. ES6 Map 和 WeakMap 有什么区别?

当它们的键/值引用的对象被删除时,它们的行为都不同,以下面的代码为例:

var map = new Map()
var weakmap = new WeakMap()
(function() {
    var a = {
        x: 12
    };
    var b = {
        y: 12
    };
    map.set(a, 1);
    weakmap.set(b, 2);
})()

执行上面的 IIFE,就无法再引用 {x:12} 和 {y:12}。垃圾收集器继续运行,并从 WeakMap 中删除键 b 指针,还从内存中删除了 {y:12}。

  1. 在使用 Map 的情况下,垃圾收集器不会从 Map 中删除指针,也不会从内存中删除{x:12}

  2. WeakMap 允许垃圾收集器执行其回收任务,但 Map 不允许。对于手动编写的 Map,数组将保留对键对象的引用,以防止被垃圾回收。但在 WeakMap 中,对键对象的引用被“弱”保留,这意味着在没有其他对象引用的情况下,它们不会阻止垃圾回收。

6.5. 举一个柯里化函数的例子,并说明柯里化的好处?

柯里化是一种模式,其中一个具有多个参数的函数被分解成多个函数,当被串联调用时,这些函数将一次累加一个所需的所有参数。这种技术有助于使用函数式编写的代码更容易阅读和编写。需要注意的是,要实现一个函数,它需要从一个函数开始,然后分解成一系列函数,每个函数接受一个参数。

function curry(fn) {
  if (fn.length === 0) {
    return fn;
  }
  function _curried(depth, args) {
    return function(newArgument) {
      if (depth - 1 === 0) {
        return fn(...args, newArgument);
      }
      return _curried(depth - 1, [...args, newArgument]);
    };
  }
  return _curried(fn.length, []);
}
function add(a, b) {
  return a + b;
}
var curriedAdd = curry(add);
var addFive = curriedAdd(5);
var result = [0, 1, 2, 3, 4, 5].map(addFive); // [5, 6, 7, 8, 9, 10]

7. HTTP协议

7.1. TCP 的四次挥手简单说一下

  1. 客户端发送 FIN 给服务端

  2. 服务端收到后发送 ACK 给客户端

  3. 服务端发送 FIN 给客户端

  4. 客户端收到后,发送 ACK 的 ACK 给服务端,服务端关闭,客户端等待 2MSL 后关闭

7.2. 什么是HTTP协议?

HTTP 就是超文本传输协议呀,它的英文是 HyperText Transfer Protocol。

HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范。

7.3. 你知道哪些 HTTP 的请求方法?

  1. GET 获取资源 (幂等)

  2. POST 新增资源

  3. HEAD 获取 HEAD 元数据 (幂等)

  4. PUT 更新资源 (带条件时幂等)

  5. DELETE 删除资源 (幂等)

  6. CONNECT 建立 Tunnel 隧道

  7. OPTIONS 获取服务器支持访问资源的方法 (幂等)

  8. TRACE 回显服务器收到的请求,可以定位问题。(有安全风险)

7.4. 说一说你对 DNS 的理解?

DNS (Domain Name System) 是互联网中的重要基础设施,负责对域名的解析工作,为了保证高可用、高并发和分布式,它设计成了树状的层次结构。

  1. 由根DNS服务器、顶级域 DNS 服务器和权威 DNS 服务器组成。

  2. 解析顺序是首先从浏览器缓存操作系统缓存以及本地 DNS 缓存 (/etc/hosts) 逐级查找,然后从本地 DNS 服务器根 DNS顶级 DNS 以及权威 DNS层层递归查询。

  3. 可以基于域名在内网、外网进行负载均衡。

  4. 传统的 DNS 有很多问题(解析慢、更新不及时),HTTPDNS 通过客户端 SDK 和服务端配合,直接通过 HTTP 调用解析 DNS 的方式,可以绕过传统 DNS 这些缺点,实现智能调度。

(面试官:小伙子理解的挺细啊)

7.5. 说一说你对 CDN 的理解?

**CDN(Content Delivery Network)**就是内容分发网络。

  1. 为了突破现实生活中的光速、传输距离等物理限制,CDN 投入了大量资金,在全球范围内各大枢纽城市建立机房,部署大量高存储高带宽的节点,构建跨运营商、跨地域的专用高速传输网络。

  2. DNS 分为中心节点、区域节点、边缘节点等,在用户接入网络后,首先通过全局负载均衡 (Global Sever Load Balance),简称 GSLB 算法负责调度,找到离用户最合适的节点。然后通过 HTTP 缓存代理技术进行缓存,缓存命中就返回给用户,否则就回源站去取。CDN 擅长缓存静态资源(图片、音频等),当然也支持动态内容的缓存。

7.6. 说一说 HTTP 的重定向

重定向是服务器发起的跳转,要求客户端使用新的 URI 重新发送请求。在响应头字段 Location 中指示了要跳转的 URI。使用 Refresh 字段,还可以实现延时重定向。

301 / 302 是常用的重定向状态码。分别代表永久性重定向临时性重定向

● 303:类似于 302,重定向后的请求方法改为 GET 方法

● 307:类似于 302,含义比 302 更明确,重定向后请求的方法和实体不允许变动

● 308:类似于 301,代表永久重定向,重定向后请求的方法和实体不允许变动

● 300:是一个特殊的重定向状态码,会返回一个有多个链接选项的页面,由用户自行选择

● 304:是一个特殊的重定向状态码,服务端验证过期缓存有效后,要求客户端使用该缓存

7.7. 你知道哪些 HTTP 状态码?

1xx 请求已经接收到,需要进一步处理才能完成,HTTP/1.0 不支持

100 Continue:上传大文件前使用

101 Switch Protocols:协议升级使用

102 Processing:服务器已经收到并正在处理请求,但无响应可用

2xx 成功处理请求

200 OK:成功返回响应

201 Created:有新资源在服务器端被成功创建

202 Accepted:服务器接受并开始处理请求,但请求未处理完成

206 Partial Content:使用range协议时返回部分响应内容时的响应码

3xx 请查阅上文重定向部分,这里不再赘述。

4xx 客户端出现错误

400 Bad Request:服务器认为客户端出现了错误,但不明确,一般是 HTTP 请求格式错误

401 Unauthorized:用户认证信息确实或者不正确

403 Forbidden:服务器理解请求的含义,但没有权限执行

407 Proxy Authentication Required:对需要经由代理的请求,认证信息未通过代理服务器的验证

404 Not Found:服务器没有找到对应的资源

408 Request Timeout:服务器接收请求超时

5xx 服务器端出现错误

500 Internal Server Error:服务器内部错误,且不属于以下错误类型

502 Bad Gateway:代理服务器无法获取到合法响应

503 Service Unavailable:服务器资源尚未准备好处理当前请求

505 HTTP Version Not Supported:请求使用的 HTTP 协议版本不支持

8. AJAX

8.1. 什么是 AJAX,为什么要使用 AJAX ?

  1. AJAX 是 『Asynchronous JavaScript and XML』的缩写。它是指一种创建交互式网页应用的网页开发技术。

  2. 客户端与服务器,可以在不刷新整个浏览器的情况下,与服务器进行异步通讯的技术

8.2. 原生 AJAX 请求处理

  1. 创建 XMLHttpRequest 对象,也就是创建一个异步调用对象

  2. 创建一个新的 HTTP 请求,并指定该 HTTP 请求的方法、URL及验证信息

  3. 设置响应 HTTP 请求状态变化的函数

  4. 发送 HTTP 请求

  5. 获取异步调用返回的数据

  6. 使用 JavaScript 和 DOM 实现局部刷新

8.3. 同步请求和异步请求的区别

同步:
浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作

异步:
浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容

8.4. AJAX 的优点和缺点

AJAX 的优点

  1. 无刷新更新数据(在不刷新整个页面的情况下维持与服务器通信)
  2. 异步与服务器通信(使用异步的方式与服务器通信,不打断用户的操作)
  3. 前端和后端负载均衡(将一些后端的工作交给前端,减少服务器与宽度的负担)
  4. 界面和应用相分离(ajax将界面和应用分离也就是数据与呈现相分离)

AJAX 的缺点

  1. AJAX 不支持浏览器 Back 按钮
  2. 安全问题 AJAX 暴露了与服务器交互的细节
  3. 对搜索引擎的支持比较弱
  4. 破坏了 Back 与 History 后退按钮的正常行为等浏览器机制

8.5. 请解释一下 JavaScript 的同源策略

同源策略是客户端脚本(尤其是Javascript)的重要的安全度量标准。

  1. 最早出自Netscape Navigator2.0,其目的是防止某个文档或脚本从多个不同源装载。

  2. 同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议,指一段脚本只能读取来自同一来源的窗口和文档的属性

8.6. 如何解决跨域问题

1. 跨域的概念:

协议、域名、端口都相同才同域,否则都是跨域

2. 解决跨域问题:

1). 使用JSONP(json+padding)把数据内填充起来
2). CORS 方式(跨域资源共享),在后端上配置可跨域
3). 服务器代理,通过服务器的文件能访问第三方资源

8.7. axios 库的特点

  1. 基于xhr/http包 + promise的异步ajax请求库

  2. 浏览器端/node端都可以使用

  3. 支持请求/响应拦截器

  4. 支持请求取消

  5. 请求/响应数据转换

  6. 批量发送多个请求

9. 浏览器缓存与渲染机制

9.1. 介绍一下浏览器缓存位置和优先级

\1. Service Worker

\2. Memory Cache(内存缓存)

\3. Disk Cache(硬盘缓存)

\4. Push Cache(推送缓存)

\5. 以上缓存都没命中就会进行网络请求

9.2. 说说不同缓存间的差别

1. Service Worker

和WebWorker类似,是独立的线程,我们可以在这个线程中缓存文件,在主线程需要的时候读取这里的文件,Service Worker使我们可以自由选择缓存哪些文件以及文件的匹配、读取规则,并且缓存是持续性的

2. Memory Cache

即内存缓存,内存缓存不是持续性的,缓存会随着进程释放而释放

3. Disk Cache

即硬盘缓存,相较于内存缓存,硬盘缓存的持续性和容量更优,它会根据HTTP header的字段判断哪些资源需要缓存

4. Push Cache

即推送缓存,是HTTP/2的内容,目前应用较少

9.3. 介绍一下浏览器缓存策略

1. 强缓存(不要向服务器询问的缓存)

1). 设置Expires

即过期时间,例如「Expires: Thu, 26 Dec 2019 10:30:42 GMT」表示缓存会在这个时间后失效,这个过期日期是绝对日期,如果修改了本地日期,或者本地日期与服务器日期不一致,那么将导致缓存过期时间错误。

2). 设置Cache-Control

HTTP/1.1新增字段,Cache-Control可以通过max-age字段来设置过期时间,例如「Cache-Control:max-age=3600」除此之外Cache-Control还能设置private/no-cache等多种字段

2. 协商缓存(需要向服务器询问缓存是否已经过期)

1). Last-Modified

即最后修改时间,浏览器第一次请求资源时,服务器会在响应头上加上Last-Modified ,当浏览器再次请求该资源时,浏览器会在请求头中带上If-Modified-Since 字段,字段的值就是之前服务器返回的最后修改时间,服务器对比这两个时间,若相同则返回304,否则返回新资源,并更新Last-Modified

2). ETag

HTTP/1.1新增字段,表示文件唯一标识,只要文件内容改动,ETag就会重新计算。缓存流程和 Last-Modified 一样:服务器发送 ETag 字段 -> 浏览器再次请求时发送 If-None-Match -> 如果ETag值不匹配,说明文件已经改变,返回新资源并更新ETag,若匹配则返回304

3). 两者对比

ETag 比 Last-Modified 更准确:如果我们打开文件但并没有修改,Last-Modified 也会改变,并且 Last-Modified 的单位时间为一秒,如果一秒内修改完了文件,那么还是会命中缓存

如果什么缓存策略都没有设置,那么浏览器会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间

9.4. 简述浏览器的渲染过程是怎样的

\1. HTML 和 CSS 经过各自解析,生成 DOM 树和 CSSOM 树

\2. 合并成为渲染树

\3. 根据渲染树进行布局

\4. 最后调用 GPU 进行绘制,显示在屏幕上

9.5. 如何根据浏览器渲染机制加快首屏速度

1. 优化文件大小

HTML 和 CSS 的加载和解析都会阻塞渲染树的生成,从而影响首屏展示速度,因此我们可以通过优化文件大小、减少 CSS 文件层级的方法来加快首屏速度

2. 避免资源下载阻塞文档解析

浏览器解析到

9.6. 什么是回流(重排),什么情况下会触发回流

\1. 当元素的尺寸或者位置发生了变化,就需要重新计算渲染树,这就是回流

\2. DOM 元素的几何属性(width/height/padding/margin/border)发生变化时会触发回流

\3. DOM 元素移动或增加会触发回流

\4. 读写 offset/scroll/client 等属性时会触发回流

\5. 调用 window.getComputedStyle 会触发回流

9.7. 什么是重绘,什么情况下会触发重绘

DOM 样式发生了变化,但没有影响 DOM 的几何属性时,会触发重绘,而不会触发回流。重绘由于 DOM 位置信息不需要更新,省去了布局过程,因而性能上优于回流

9.8. GPU加速的优点与缺点

1.优点:使用transform、opacity、filters等属性时,会直接在GPU中完成处理,这些属性的变化不会引起回流重绘

2.缺点:GPU渲染字体会导致字体模糊,过多的GPU处理会导致内存问题

9.9. 如何减少回流

\1. 使用 class 替代 style,减少 style 的使用

\2. 使用 resize、scroll 时进行防抖和节流处理,这两者会直接导致回流

\3. 使用 visibility 替换 display: none,因为前者只会引起重绘,后者会引发回流

\4. 批量修改元素时,可以先让元素脱离文档流,等修改完毕后,再放入文档流

\5. 避免触发同步布局事件,我们在获取 offsetWidth 这类属性的值时,可以使用变量将查询结果存起来,避免多次查询,每次对 offset/scroll/client 等属性进行查询时都会触发回流

\6. 对于复杂动画效果,使用绝对定位让其脱离文档流,复杂的动画效果会频繁地触发回流重绘,我们可以将动画元素设置绝对定位从而脱离文档流避免反复回流重绘。

10. jQuery最核心的3个问题

10.1. jQuery整体理解

  • 1、jQuery是一个功能强大的函数库, 封装了以下功能

    • (1)、DOM的CRUD
    • (2)、DOM事件处理
    • (3)、ajax请求
    • (4)、各种小的工具函数
  • 2、核心语法

    • (1)、jQuery核心函数

    • (2)、jQuery核心对象

  • 3、特点:

    • (1)、链式调用

    • (2)、读写合一

    • (3)、更好的浏览器兼容性

10.2. jQuery核心函数

  • 1. 是什么?

就是jquery库向外暴露的 和 j Q u e r y , 一般都用 和jQuery, 一般都用 jQuery,一般都用

  • \2. 它有2种使用方式:

    • (1) 作为函数使用

    • (2) 作为对象使用

  • \3. $作为函数使用

    • (1) 参数为函数: 指定回调函数在页面加载完后执行

    • (2) 参数是选择器字符串: 查找所有匹配的元素, 返回jQuery对象

    • (3) 参数是标签格式字符串: 创建DOM对象, 返回jQuery对象

    • (4) 参数是DOM元素: 返回包含此DOM元素的jQuery对象

  • \4. $作为函数对象使用

    • (1) 发送ajax请求的各种方法: $.ajax()/get()/post()/getJSON()

    • (2) 一些工具方法: $.each(array)/type(data)

10.3. jQuery核心对象

  • \1. 2种情况产生的对象 (内部包含n个dom元素对象)

    1. (1) 执行$函数返回的对象

    2. (2) 调用jQuery核心对象的方法返回的对象

  • \2. 主要方法

    • (1) 进行DOM进行增删改的方法

    • (2) 对内部的DOM元素进行进一步过滤查找的方法

    • (3) 进行事件处理的方法: 绑定监听/解绑监听/事件委派

11、Vue

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

1. 代码层面的优化

1). v-if 和 v-show 区分使用场景

2). computed 和 watch 区分使用场景

3). v-for 遍历必须为 item 添加 key,且避免同时使用 v-if

4). 长列表性能优化

5). 事件的销毁

6). 图片资源懒加载

7). 路由懒加载

8). 第三方插件的按需引入

9). 优化无限列表性能

10). 服务端渲染 SSR or 预渲染

2. 层面的优化

1). Webpack 对图片进行压缩

2). 减少 ES6 转为 ES5 的冗余代码

3). 提取公共代码

4). 模板预编译

5). 提取组件的 CSS

6). 优化 SourceMap

7). 构建结果输出分析

8). Vue 项目的编译优化

3. 基础的 Web 技术的优化

1). 开启 gzip 压缩

2). 浏览器缓存

3). CDN 的使用

4). 使用 Chrome Performance 查找性能瓶颈

12. Webpack

1. 谈谈你对Webpack的看法

  1. Webpack是一个模块打包工具,可以使用它管理项目中的模块依赖,并编译输出模块所需的静态文件。

  2. 它可以很好地管理、打包开发中所用到的HTML,CSS,JavaScript和静态文件(图片,字体)等,让开发更高效。

  3. 对于不同类型的依赖,Webpack有对应的模块加载器,而且会分析模块间的依赖关系,最后合并生成优化的静态资源。

2. Webpack的基本功能有哪些?

  1. 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等等

  2. 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等

  3. 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载

  4. 模块合并:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件

  5. 自动刷新:监听本地源代码的变化,自动构建,刷新浏览器

  6. 代码校验:在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过

  7. 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。

3. Webpack构建过程

  1. 从entry里配置的module开始递归解析entry依赖的所有module

  2. 每找到一个module,就会根据配置的loader去找对应的转换规则

  3. 对module进行转换后,再解析出当前module依赖的module

  4. 这些模块会以entry为单位分组,一个entry和其所有依赖的module被分到一个组Chunk

  5. 最后Webpack会把所有Chunk转换成文件输出在整个流程中Webpack会在恰当的时机执行plugin里定义的逻辑

4. 有哪些常见的Loader?

  1. file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)

  2. url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)

  3. css-loader:加载 CSS,支持模块化、压缩、文件导入等特性

  4. style-loader:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSS

  5. json-loader: 加载 JSON 文件(默认包含)

  6. babel-loader: 把 ES6 转换成 ES5

  7. ts-loader: 将 TypeScript 转换成 JavaScript

  8. **less-loader:**将less代码转换成CSS

  9. **eslint-loader:**通过 ESLint 检查 JavaScript 代码

  10. vue-loader: 加载 Vue单文件组件

5. 有哪些常见的Plugin?

  1. html-webpack-plugin:根据模板页面生成打包的 html 页面

  2. uglifyjs-webpack-plugin:不支持 ES6 压缩 ( Webpack4 以前)

  3. mini-css-extract-plugin: 分离样式文件,CSS 提取为独立文件,支持按需加载

  4. clean-webpack-plugin: 目录清理

  5. copy-webpack-plugin: 拷贝文件\6. webpack-bundle-analyzer: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块)

6. 那你再说一说Loader和Plugin的区别?

  1. Loader 本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。 因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。

  2. Plugin 就是插件,基于事件流框架 Tapable,插件可以扩展 Webpack 的功能,在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。

  3. Loader 在 module.rules 中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性。

  4. Plugin 在 plugins 中单独配置,类型为数组,每一项是一个 Plugin 的实例,参数都通过构造函数传入。

7. 说一下 Webpack 的热更新原理吧

Webpack 的热更新又称热替换(Hot Module Replacement),缩写为 HMR。 这个机制可以做到不用刷新浏览器而将新变更的模块替换掉旧的模块。

HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 Websocket,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax 请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp 请求获取该chunk的增量更新。

后续的部分(拿到增量更新之后如何处理?哪些状态该保留?哪些又需要更新?)由 HotModulePlugin 来完成,提供了相关 API 以供开发者针对自身场景进行处理,像react-hot-loadervue-loader 都是借助这些 API 实现 HMR。

8. 如何优化 Webpack 的构建速度?

  1. 使用高版本的 Webpack 和 Node.js

  2. 压缩代码

1). 通过 uglifyjs-webpack-plugin 压缩JS代码

2). 通过 mini-css-extract-plugin 提取 chunk 中的 CSS 代码到单独文件,通过 css-loader 的 minimize 选项开启 cssnano 压缩 CSS。

  1. 多线程/多进程构建:thread-loader, HappyPack

  2. 压缩图片: image-webpack-loader

  3. 缩小打包作用域

1). exclude/include (确定 loader 规则范围)

2). resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)

3). resolve.mainFields 只采用 main 字段作为入口文件描述字段 (减少搜索步骤,需要考虑到所有运行时依赖的第三方模块的入口文件描述字段)

4). resolve.extensions 尽可能减少后缀尝试的可能性

5). noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)

6). ignorePlugin (完全排除模块)

7). 合理使用alias

  1. 提取页面公共资源, 基础包分离

1). 使用 html-webpack-externals-plugin,将基础包通过 CDN 引入,不打入 bundle 中。

2). 使用 SplitChunksPlugin 进行(公共脚本、基础包、页面公共文件)分离(Webpack4内置) ,替代了 CommonsChunkPlugin 插件。

  1. 充分利用缓存提升二次构建速度

babel-loader 开启缓存

terser-webpack-plugin 开启缓存

使用 cache-loader 或者 hard-source-webpack-plugin

  1. Tree shaking

打包过程中检测工程中没有引用过的模块并进行标记,在资源压缩时将它们从最终的bundle中去掉(只能对ES6 Modlue生效) 开发中尽可能使用ES6 Module的模块,提高tree shaking效率

禁用 babel-loader 的模块依赖解析,否则 Webpack 接收到的就都是转换过的 CommonJS 形式的模块,无法进行 tree-shaking

使用 PurifyCSS(不在维护) 或者 uncss 去除无用 CSS 代码

purgecss-webpack-plugin 和 mini-css-extract-plugin配合使用(建议)

  1. Scope hoisting

构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大。Scope hoisting 将所有模块的代码按照引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量名冲突

13、项目性能优化

UI库按需加载(打包)问题

1.UI库按需加载(打包)问题

1.1. 问题剖析:

当开发的项目需要引入第三方的UI组件库(antd/mint-ui/element-ui)的时, 需要会打包所有组件的js和css, 而项目中只使用了其中少部分组件. 使用按需打包实现只打包使用的组件, 从而减小打包文件大小

1.2. 解决方案:

下载bebel插件包: babel-plugin-component
添加配置: babel.config.js

plugins: [
["component", { // 使用babel-plugin-component插件包
    "libraryName": "mint-ui", // 针对特定的库
    "style": true // 相关样式自动引入
}]
]

2. 路由组件懒加载

2.1. 问题剖析:

  • Vue开发中使用路由跳转页面时,通常会注册多个路由,对应的有多个路由组件
  • 在Vue打包后文件非常之大,如果没有路由懒加载的话,一上来加载所有页面的文件
  • 如果同时加载所有页面的文件内容的话会导致首屏加载显示过慢,甚至白屏,导致用户体验差

2.2. 解决方案

const Home = () => import('../pages/Home/Home.vue'); 
const Search = () => import('../pages/Search/Search.vue'); 
const CategoryList = () => import('../pages/CategoryList/CategoryList.vue'); 

3. React性能优化值 shouldComponentUpdate

3.1. 问题剖析

  • \1. shouldComponentUpdate在组件的props,state发生变化的时候即将调用componentWillUpdate之前调用
  • \2. 该生命周期函数必须返回一个布尔值,true代表继续更新,false停止本次更新,即不会创建新的虚拟DOM数去进行DOM虚拟算法比较, 而默认值是true

3.2. 解决方案

  • \1. 方式一: 在shouldComponentUpdate中可以获取最新的nextProps,nextState,根据实际情况判断是否需要重新更新,如果不需要则return false;
  • \2. 方式二: 不实现Component, 而去实现PureComponent

4. 图片懒加载

4.1. 问题剖析

当一个项目图片过多的时候如果一次性加载渲染代价较大,导致用户看到的效果时间延迟

4.2. 解决方案

  • npm install vue-lazyload
  • 声明使用: Vue.use(VueLazyLoad, {loading: loading图片})
  • 组件使用: 新品

5. Webpack打包优化

5.1. 优化打包文件

  • \1. 目标:
    兼容性 / 减小打包文件/ 懒加载 / 预加载 / 首屏加载优化
  • \2. 技巧:

img

15.5.2. 优化打包

  • \1. 目标:
    加快打包 / 提升开发调试体验
  • \2. 技巧:

img

6. HTML性能优化

  • \1. HTML标签有始终。 减少浏览器的判断时间
  • \2. 把script标签移到HTML文件末尾,因为JS会阻塞后面的页面的显示。
  • \3. 减少iframe的使用,因为iframe会增加一条http请求,阻止页面加载,即使内容为空,加载也需要时间
  • \4. id和class,在能看明白的基础上,简化命名,在含有关键字的连接词中连接符号用’-‘,不要用’_’
  • \5. 保持统一大小写,统一大小写有利于浏览器缓存,虽然浏览器不区分大小写,但是w3c标准为小写
  • \6. 清除空格,虽然空格有助于我们查看代码,但是每个空格相当于一个字符,空格越多,页面体积越大,像google、baidu等搜索引擎的首页去掉了所有可以去掉的空格、回车等字符,这样可以加快web页面的传输。可以借助于DW软件进行批量删除 html内标签之间空格,sublime text中ctrl+a,然后长按shift+tab全部左对齐,清除行开头的空格
  • \7. 减少不必要的嵌套,尽量扁平化,因为当浏览器编译器遇到一个标签时就开始寻找它的结束标签,直到它匹配上才能显示它的内容,所以当嵌套很多时打开页面就会特别慢。
  • \8. 减少注释,因为过多注释不光占用空间,如果里面有大量关键词会影响搜索引擎的搜索
  • \9. 使用css+div代替table布局,去掉格式化控制标签如:strong,b,i等,使用css控制
  • \10. 代码要结构化、语义化
  • \11. css和javascript尽量全部分离到单独的文件中

7. css性能优化

  • \1. 多利用继承,多个子元素公用的样式,如果该样式能继承的话就写在父元素身上

  • \2. 尽量减少重绘重排的次数

  • \3. 选择器命名规范,通过id寻找更快

  • \4. 动画区域开启独立的图层

    • 1) 定位: position: absolute/relative

    • 2) Will-change: xxx;

  • \5. 合并、压缩你的css文件,减少http请求,可以借助工具或者自动化构建。

  • \6. 使用CSS sprite来处理你的图片

8. JS性能优化

  • \1. 合并压缩js
  • \2. 减少对DOM的操作,避免重绘重排
  • \3. 减少请求的个数,节省网络资源
  • \4. 封装功能函数实现复用
  • \5. 循环语句中避免定义变量
  • \6. 慎用闭包
  • \7. 函数节流,函数防抖

14. 前端安全

1. 什么是CSRF攻击

CSRF即Cross-site request forgery(跨站请求伪造),是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。

假如黑客在自己的站点上放置了其他网站的外链,例如www.weibo.com/api,默认情况下,浏览器会带着weibo.com的cookie访问这个网址,如果用户已登录过该网站且网站没有对CSRF攻击进行防御,那么服务器就会认为是用户本人在调用此接口并执行相关操作,致使账号被劫持。

2. 如何防御CSRF攻击

  • 验证Token:浏览器请求服务器时,服务器返回一个token,每个请求都需要同时带上token和cookie才会被认为是合法请求
  • 验证Referer:通过验证请求头的Referer来验证来源站点,但请求头很容易伪造
  • 设置SameSite:设置cookie的SameSite,可以让cookie不随跨域请求发出,但浏览器兼容不一

3. 什么是XSS攻击

XSS即Cross Site Scripting(跨站脚本),指的是通过利用网页开发时留下的漏洞,注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。常见的例如在评论区植入JS代码,用户进入评论页时代码被执行,造成页面被植入广告、账号信息被窃取

4. XSS攻击有哪些类型

  • . 存储型:即攻击被存储在服务端,常见的是在评论区插入攻击脚本,如果脚本被储存到服务端,那么所有看见对应评论的用户都会受到攻击。
  • 反射型:攻击者将脚本混在URL里,服务端接收到URL将恶意代码当做参数取出并拼接在HTML里返回,浏览器解析此HTML后即执行恶意代码
  • . DOM型:将攻击脚本写在URL中,诱导用户点击该URL,如果URL被解析,那么攻击脚本就会被运行。和前两者的差别主要在于DOM型攻击不经过服务端

5. 如何防御XSS攻击

  • 输入检查:对输入内容中的<iframe>等标签进行转义或者过滤
  • 设置httpOnly:很多XSS攻击目标都是窃取用户cookie伪造身份认证,设置此属性可防止JS获取cookie
  • 开启CSP,即开启白名单,可阻止白名单以外的资源加载和运行

15. 重要的核心结构图

JS事件循环(Event Loop)机制

2. 事件循环2

Vue的数据绑定原理图

img
在这里插入图片描述
在这里插入图片描述

16. 设计模式

设计模式有许多种,这里挑出几个常用的:

设计模式描述例子
单例模式一个类只能构造出唯一实例Redux/Vuex的store
工厂模式对创建对象逻辑的封装jQuery的$(selector)
观察者模式当一个对象被修改时,会自动通知它的依赖对象Redux的subscribe、Vue的双向绑定
装饰器模式对类的包装,动态地拓展类的功能React高阶组件、ES7 装饰器
适配器模式兼容新旧接口,对类的包装封装旧API
代理模式控制对象的访问事件代理、ES6的Proxy

1. 说一下设计原则

单一职责原则:一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。

开放封闭原则:核心的思想是软件实体(类、模块、函数等)是可扩展的、但不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值