前言

本文主要用于记录前端面试常考题

前端基础

HTTP/HTML/浏览器

说一下http和https

http 超文本传输协议 是互联网上应用最广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准(tcp),用于从www服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少
https 是以安全为目标的http通道,简单讲是http的安全版,即http下加入ssl层,https的安全基础是ssl,因此加密的详细内容就需要ssl
https协议的主要作用是:建立一个信息安全通道,来确保数组的传输,确保网站的真实性

https协议需要ca证书 费用比较高
http的信息是明文传输,https是经过ssl协议加密传输的
端口不同 一般来讲http是80端口 https是443端口

客户使用https url访问服务器,则要求web服务器建立ssl连接
web服务器收到客户端的请求后,将网站的证书(里面包含公钥)返回or传输给客户端
客户端和web服务器端开始协商ssl链接的安全等级
客户端浏览器通过双方协商一致的安全等级,建立会话密钥,然后通过网站的公钥加密会话密钥,并传送给网站
web服务器通过自己的私钥解密出会话密钥
web服务器通过会话密钥加密与客户端之间的通信

优点:使用https协议可认证用户和服务器,确保数据发送到正确的客户机和服务器。
缺点:https握手阶段比较费时,会使得页面加载时间延长50%,增加10%-20%的耗电
https缓存不如http高效,会增加数据开销
ssl证书要钱 功能越强的证书费用越高
ssl证书要绑定ip 不能在同个ip上绑定多个域名,ipv4资源支持不了这种消耗

tcp三次握手

一句话概括:
客户端和服务端都需要直到各自可收发,因此需要三次握手

tcp和udp的区别

  1. tcp是面向链接的 udp是无连接的即发送数据前不需要建立连接
  2. tcp提供可靠的服务,也就是说通过tcp连接发送的数据,无差错,不丢失,不重复,且按序到达,udp尽最大的努力交付,即不保证可靠交付。并且因为tcp可靠,面向连接,不会丢失数据因此适合大数据量的交换
  3. tcp是面向字节流,udp面向报文,并且网络出现拥塞不会使得发送速率降低(因此会产生丢包,对实时的应用比如ip电话和视频会议等)
  4. tcp只能是一对一的,udp支持1对1 1对多
  5. tcp的首部较大为20字节 udp只有8字节
  6. tcp是面向连接的可靠性传输,udp是不可靠的

websocket的实现和应用

应用:多人聊天室,客服咨询

websocket是h5中的一种协议,支持持久性连接,而http协议不支持。

首先,Websocket是一个持久化的协议,相对于HTTP这种非持久的协议来说

HTTP的生命周期通过 Request 来界定,也就是一个 Request 一个 Response ,那么在 HTTP1.0 中,这次HTTP请求就结束了。

在HTTP1.1中进行了改进,使得有一个keep-alive,也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response。但是请记住 Request = Response , 在HTTP中永远是这样,也就是说一个request只能有一个response。而且这个response也是被动的,不能主动发起。

(1)ajax轮询

ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息。

(2)long poll(长轮询)

long poll 其实原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起连接后,如果没消息,就一直不返回Response给客户端(对于PHP有最大执行时间,建议没消息,执行到一定时间也返回)。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始。

从上面可以看出其实这两种方式,都是在不断地建立HTTP连接,关闭HTTP协议,由于HTTP是非状态性的,每次都要重新传输 identity info (鉴别信息),来告诉服务端你是谁。然后等待服务端处理,可以体现HTTP协议的另外一个特点,被动性。

何为被动性呢,其实就是,服务端不能主动联系客户端,只能有客户端发起。从上面很容易看出来,不管怎么样,上面这两种都是非常消耗资源的。

ajax轮询 需要服务器有很快的处理速度和资源。(速度)long poll 需要有很高的并发,也就是说同时接待客户的能力。(场地大小)

(3)WebSocket

Websocket解决了HTTP的这几个难题。首先,被动性,当服务器完成协议升级后(HTTP->Websocket),服务端就可以主动推送信息给客户端啦。解决了上面同步有延迟的问题。

解决服务器上消耗资源的问题:其实我们所用的程序是要经过两层代理的,即HTTP协议在Nginx等服务器的解析下,然后再传送给相应的Handler(php等)来处理。简单地说,我们有一个非常快速的 接线员(Nginx) ,他负责把问题转交给相应的 客服(Handler) 。Websocket就解决了这样一个难题,建立后,可以直接跟接线员建立持久连接,有信息的时候客服想办法通知接线员,然后接线员在统一转交给客户。

由于Websocket只需要一次HTTP握手,所以说整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直知道你的信息,直到你关闭请求,这样就解决了接线员要反复解析HTTP协议,还要查看identity info的信息。

目前唯一的问题是:不兼容低版本的IE

http请求的HEAD方式

head:类似于get请求,只不过返回的响应中没有具体的内容,用户获取报头
options: 允许客户端查看服务器的性能 比如服务器支持的请求方式等等

一个图片url访问后下载怎样实现

请求的返回头里面,用于浏览器解析的重要参数就是oss的api文档里面的返回http头,决定用户下载行为的参数
下载的情况下:

1
2
3
4
5
1. x-oss-object-type
Normal
2. x-oss-request-id:
3. x-oss-storage-class
Standard

web quality(无障碍)

能够被残障人士使用的网站才能称得上是一个易用的网站
使用alt属性

1
<img src="person,jpg" alt="this is a person">

几个实用的BOM属性对象方法

1.location对象
2.history对象
3.Navigator对象

H5的drag api

http2.0

访问速度更快
允许多路复用
二进制分帧
首部压缩
服务器端推送

400 401 403状态码

  1. 400:请求无效
    产生原因:
    前端提交数据的字段名称和字段类型与后台的实体没有保持一致
    前端提交到后台的数据应该是json字符串类型 但是前端没有将对象JSON.stringfy转化成字符串
    解决方法:
    对照字段的名称 保持一致性
    将obj对象通过JSON.stringfy实现序列化
  2. 401:当前请求需要用户验证
  3. 403:服务器已经得到请求,但是拒绝执行

fetch发送两次请求的原因

fetch发送post请求的时候 总是发送两次,第一次的状态码是204 第二次才成功?
原因:
因为用fetch的post请求的时候,导致fetch第一次发送了一个options请求 询问服务器是否支持修改的请求头 如果服务器支持 则在第二次中发送真正的请求

共同点:都是保存在浏览器 而且是同源的
cookie:cookie数据始终在同源的http请求中携带(即使不需要)即cookie在浏览器和服务器之间来回传递,而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存 cookie数据还有路径path的概念,可以限制cookie只属于某个路径下,存储的大小很小只有4k左右
(key:可以在浏览器和服务器之间来回传递 存储容量小 只有4k左右)
sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持
localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
(key:本身就是一个会话过程,关闭浏览器后消失 ,session为一个会话,当页面不同即使是同一页面打开两次,也被视为同一次会话)

web worker

对html语义化标签的理解

h5语义化标签是指正确的标签包含了正确的内容 结构良好 便于阅读 比如nav表示导航条 类似的还有artical header footer等等

iframe是什么?有什么缺点?

定义:iframe元素会创建包含另一个文档的内联框架
提示:可以将提示文字放在iframe标签之间,来提示某些不支持iframe的浏览器
缺点:

  1. 会阻塞主页面的onload事件
  2. 搜索引擎无法解读这种页面 不利于SEO
  3. iframe和主页面共享连接池 而浏览器对相同区域有限制所以会影响性能

Doctype的作用?严格模式与混杂模式如何区分?它们有何意义?

Doctype声明于文档最前面 告诉浏览器以何种方式来渲染页面 有两种模式 混杂模式和严格模式
严格模式的排版和js运作模式是以该浏览器支持的最高标准运行
混杂模式则向后兼容 模拟老实浏览器 防止浏览器无法兼容页面

cookie如何防范xss攻击

xss 跨站脚本攻击 是指攻击者在返回的html中嵌入js脚本,为了减轻这些攻击,需要在http的头部配上,set-cookie:httponly这个属性可以防止xss,他会禁止js脚本来访问cookie
secure这个属性告诉浏览器仅在请求为https的时候发送cookie

一句话概括RESTFUL

就是用url定位资源 用http描述操作

讲讲viewport和移动端布局

常用解决方案
媒体查询
百分比
rem
vw和vh

click在ios上有300ms延迟 原因以及如何解决

addEventListener的参数

代码

1
2
3
4
addEventListener(event,function.useCapture)
//event事件名
//function指定事件触发时执行的函数
//useCapture是否在捕获或者冒泡阶段执行

讲讲304

如果客户端发送了一个带条件的get请求且该请求已被允许,而文档的内容并没有改变,则服务器应该返回这个304状态码

强缓存 协商缓存什么时候用哪个?

前端优化

  1. 降低请求量:合并资源,减少http请求数,minify/gzip压缩,webP,lazyLoad
  2. 加快请求速度:预解析DNS,减少域名数,并行加载,CDN分发。
  3. 缓存:HTTP协议缓存请求,离线缓存,manifest,离线数据缓存localStorage
  4. 渲染:JS/CSS优化,加载顺序,服务端渲染,pipeline

post和get的区别

  1. get参数通过url传递,post放在request body中
  2. get请求在url中传递的参数是有长度限制的 post没有
  3. get比post更不安全,因为参数直接暴露在url中,所以不能用来传输敏感信息
  4. get请求只能进行url编码 post支持多种编码方式
  5. get请求会浏览器主动cache
  6. get请求的参数会被完整的保留在浏览器的历史记录里,而post不会
  7. get和post本质上都是TCP连接,没有差别。但由于http的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同
  8. get产生一个TCP数据包 post产生两个

301 和 302的区别

HTTP支持的方法

get post head options put delete trace connect

如何画一个三角形

三角形原理,边框的均分原理

1
2
3
4
5
6
7
8
div{
width:0px;
height:0px;
border-top:10px solid red;
border-right:10px solid transparent;
border-bottom:10px solid transparent;
border-left:10px solid transparent;
}

状态码304和200

200:请求已经成功 请求所希望的响应头或数据体岁此响应返回
304:如果客户端发送了一个带条件的get请求且该请求已被允许,而文档的内容并没有改变,则返回这个状态码

说一下浏览器缓存

HTML5新增的元素

首先html5为了更好的实践web语义化,增加了headerfooternavasidesection等语义化标签,在表单方面,为了增强表单,为input增加了color,emial, data,range 等类型。在存储方面,提供了sessionStoragelocalStorage和离线存储,通过这些存储方式方便数据在客户端的存储和获取,在多媒体方面规定了音频和视频元素audiovideo,另外还有地理定位,canvas画布,拖放,多线程编程的web worker和websocket协议

在地址栏里输入一个url 到这个页面呈现出来,中间会发生什么?

DNS解析
TCP连接
发送HTTP请求
服务器处理请求并返回http报文
浏览器解析渲染页面
连接结束

浏览器在生成页面的时候会生成哪两颗树

构造两棵树,DOM树和CSSOM规则树
当浏览器接收到服务器相应来的HTML文档后,会遍历文档节点,生成DOM树
CSSOM规则树则由浏览器解析css文件生成

csrf和xss网络攻击及防范

csrf 跨站请求伪造,理解为攻击者盗用了用户的身份,以用户的名义发送了恶意请求,比如用户登陆了一个网站后,立刻在另一个tab页面访问攻击者用来制造攻击的网站,这个网站要求访问刚刚登陆的网站,并发送了一个恶意请求,这时候csrf就产生了。
防范方式:使用验证码,检查http头部的refer,使用token
xss 跨站脚本攻击 可以理解为攻击者通过注入恶意的脚本,在用户浏览网页的时候进行攻击,比如获取cookie,或者其他用户身份信息,可以分为存储型和反射型。
防范方式:cookie设置httponly属性,对用户的输入进行检查,进行特殊字符过滤

怎么看网站的性能如何

检测页面加载事件一般有两种方式:

  1. 被动去测:就是在被检测的页面置入脚本或探针,当用户访问网页时,探针自动菜鸡数据并传回到数据库进行分析。
  2. 主动检测,即主动搭建分布式受控环境,模拟用户发起页面访问请求,主动采集性能数据并分析,在检测的精准度上,专业的第三方工具效果更佳,比如性能极客。

状态码

1xx 信息状态码
100 continue 继续 一般在发送post请求的时候 已发送了http header 之后服务端返回此信息 表示确认,之后发送具体的参数消息
2xx 成功状态码
200 ok 正常返回信息
201 created 请求服务器已经成功并且创建了新的资源
202 accepted 服务器已接受请求 但未响应
3xx 重定向
301 moved permanently 请求的网页已永久移到新的位置
302 found 临时性重定向
303 see other 临时性重定向 且总是使用get请求新的url
304 not modified 自从上次的请求过后 请求的网页未修改过
4xx 客户端错误
400 bad request 服务端不理解客户端请求的格式 客户端不应该继续使用相同的内容发起请求
401 unauthorized 请求未授权
403 forbidden 禁止访问
404 not found 找不到与url匹配的资源
5xx 服务器错误
500 Internal server error 最常见的服务器错误
503 service unavailable 服务端暂时无法处理请求

严格模式和混杂模式之doctype

严格模式 以浏览器支持的最高标准执行 混杂模式也称怪异模式 向后兼容
没有!Doctype位于文档首部或者格式不正确 那么就是以混杂模式执行

web标准以及w3c标准是什么

标签闭合 标签小写 不乱嵌套 使用外链的js/css 结构行为表现的分离

行内元素 行内块 块元素 空元素

行内元素:a b span br i em strong label q code cite var
行内块:img input
块元素:div p h1-6 ol ul dl table form
行内元素 在一行上显示 不能设置宽高 元素的大小就是内容撑开的大小
行内块 在一行上显示 可设置宽高
块元素 独占一行 可以设置宽高 嵌套的情况下子默认和父宽度一致

空元素:不用写闭合标签的元素
常见空元素:img input hr br link meta

html全局属性

CSS篇

css盒模型

就是用来装页面上的元素的矩形区域,css中的盒模型包括ie盒子模型和标准的w3c盒子模型
标准盒子模型:
只有content
ie盒子模型:
包含了content padding 和 border

画一条0.5px的线

  1. 采用meta viewport的方法
  2. 采用border-image的方式
  3. 采用transform:scale()

link标签和import标签的区别

link属于html标签 而@import是css提供的
页面被加载的时候,link会同时被加载,而@import引用的css会等页面加载结束后加载
link是html标签,因此没有兼容性的问题,而@import只有ie5以上才能识别
link方式的权重高于import方式

transition和animation的区别

animationtransition大部分属性都是相同的,它们都是随着事件改变元素的属性值,主要区别是transition需要触发一个事件才能改变属性,而animation不需要触发事件
transition为2帧 从from...to...animation是一帧一帧的

flex布局

弹性布局
flex-direction主轴方向
flex-wrap换行原则
flex-flow
justify-content水平主轴对齐方式
align-items垂直对齐方式
属性
order:定义项目的排列顺序 越小越前 默认0
flex-grow定义项目的放大比例 即使存在空间也不会放大
flex-shrink定义了项目的缩小比例,当空间不足的情况下会等比例缩小,如果为0则不缩小
flex-basis 定义了在分配多余的空间,项目占据的空间
flex前三者的简称 默认值为0 1 auto
align-self允许单个项目与其他项目不一样的对齐方式 可以覆盖align-items默认属性为auto 表示继承父元素的align-items
圣杯布局

BFC块级格式化上下文

用于清除浮动防止margin重叠等

JS篇章

类型及检测方式及栈和堆

首先js的类型有基本数据类型和引用类型
前者有七种 后者是一种
根据以前的USONB理论 大致是
Undefined,String,Symbol,Object,Null,Number,BigInt,Boolean

  • 基本数据类型的作用?
    基础类型存储在栈内存,被引用或拷贝时,会创建一个完全相等的变量;占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
  • object为什么是引用类型?
    而引用类型 在创建对象的时候会在堆内存中开辟一个空间 用来存放对象的属性 在为对象添加属性的时候,是将属性放在堆内存中开辟的空间里。
    在栈内存中保存显示 对象名+一个地址 类似于指针 执行堆内存中对象开辟的空间
    引用类型存储在堆内存,存储的是地址,多个引用指向同一个地址,这里会涉及一个“共享”的概念;占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
    引申出栈和堆的概念!
  • BigInt?

在js的执行过程中,主要有三种类型的内存空间,一是代码空间,二是栈空间,三是堆空间。代码空间顾名思义就是存储代码用的,栈空间是用来保存变量和变量值的,堆空间是保存地址的。
对于栈空间来说,原始类型存储的是变量的值,而引用类型存储的是在堆空间中的地址,所以当js需要访问数据的时候,是通过栈中的引用地址来访问的,相当于多了一道转手程序

  • 闭包是怎么存储
    js引擎对于闭包的处理,是当遇到一个闭包的时候,在堆空间中创建一个closure(fn)对象,用来保存闭包中的变量,所以闭包中的变量是存储在堆空间中的。这就是为什么闭包可以常驻在内存的原因。
  • js为什么需要栈和堆
    首先知道栈是让变量循环利用,通常也是设置一些小数据来放入栈中,而我们知道引用类型数据obj一般占用的空间都比较大。所以js引擎需要栈和堆来维持内存的平衡。
  • 题目1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let a = {
    name: 'lee',
    age: 18
    }
    let b = a;
    console.log(a.name); //第一个console
    b.name = 'son';
    console.log(a.name); //第二个console
    console.log(b.name); //第三个console
    第一个是lee显而易见,第二个console是son 第三个也是。这是因为a是对象 是引用类型 在赋值给b的时候 实际上是给了a在堆中的地址 所以b访问的是堆空间中a的变量 那么修改了b 自然a也会发生变动,这里就引出了堆空间共享的概念
  • 题目2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    let a = {
    name: 'Julia',
    age: 20
    }
    function change(o) {
    o.age = 24;
    o = {
    name: 'Kath',
    age: 30
    }
    return o;
    }
    let b = change(a); // 注意这里没有new,后面new相关会有专门文章讲解
    console.log(b.age); // 第一个console
    console.log(a.age); // 第二个console
    第一个输出30 第二个输出24
    原因是function里面传入的是a在堆中的地址,那么自然a的age就会变成24 但是到了return这一步 它会把传入的内存地址修改 导致o变成另外一个内存地址 将o的数据存放在该内存中, 所以b就是kath和30

数据类型检测有很多种,常用的是typeof instanceof constructor Object.prototype.toString.call([])

  1. typeof方法是基于计算机底层的数据类型的二进制进行判断。 用于判断除了array null之外的类型,即可以判断除了null之外的基础数据类型和除了array之外的应用数据类型
    下面看一下它对于所有类型的处理 注意它可以处理function
    1
    2
    3
    4
    5
    6
    7
    8
    console.log(typeof 2);               // number
    console.log(typeof true); // boolean
    console.log(typeof 'str'); // string
    console.log(typeof []); // object []数组的数据类型在 typeof 中被解释为 object
    console.log(typeof function(){}); // function
    console.log(typeof {}); // object
    console.log(typeof undefined); // undefined
    console.log(typeof null); // object null 的数据类型被 typeof 解释为 object
  • 为什么null会被typeof识别成object
    这个是一个历史遗留问题 js底层是二进制存储的 前三位代表的是数据的存储类型 对于object来说则是000 而刚好null也是全0 正好代表object类型的数据格式 所以null才会输出object
  1. instanceof
    由于上面的方法不能精确判断数组和null的原因 所以产生了新的方法instanceof
    康康它对于所有类型的处理
    1
    2
    3
    4
    5
    6
    7
    8
    console.log(2 instanceof Number);                    // false
    console.log(true instanceof Boolean); // false
    console.log('str' instanceof String); // false
    console.log([] instanceof Array); // true
    console.log(function(){} instanceof Function); // true
    console.log({} instanceof Object); // true
    // console.log(undefined instanceof Undefined);
    // console.log(null instanceof Null);
    和由此可见 instanceof方法能准确的判断引用数据类型 但是不能判断基础数据类型
    因为它的原理是和原型链相关的 ,相当于判断是不是这个类的实例,所以对于undefined和null来说,这两者是没有原型的 所以无法判断。
    引申一下 null是所有原型的终点 undefined是表示没有这个值 缺少这个值
  2. constructor
    构造器判断方法 注意带括号 否则会报错
    1
    2
    3
    4
    5
    6
    console.log((2).constructor === Number); // true
    console.log((true).constructor === Boolean); // true
    console.log(('str').constructor === String); // true
    console.log(([]).constructor === Array); // true
    console.log((function() {}).constructor === Function); // true
    console.log(({}).constructor === Object); // true
    弊端是 如果改变了对象原型,该方法会失效
    1
    2
    3
    4
    5
    6
    7
    8
    function Fn(){};

    Fn.prototype=new Array();

    var f=new Fn();

    console.log(f.constructor===Fn); // false
    console.log(f.constructor===Array); // true
  3. Object.prototype.toString.call()
    使用Object上面的toString方法 会返回一个格式为[object Xxx]的字符串,通过call重新调用就可以精确判断对象类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Object.prototype.toString({})       // "[object Object]"
    Object.prototype.toString.call({}) // 同上结果,加上call也ok
    Object.prototype.toString.call(1) // "[object Number]"
    Object.prototype.toString.call('1') // "[object String]"
    Object.prototype.toString.call(true) // "[object Boolean]"
    Object.prototype.toString.call(function(){}) // "[object Function]"
    Object.prototype.toString.call(null) //"[object Null]"
    Object.prototype.toString.call(undefined) //"[object Undefined]"
    Object.prototype.toString.call(/123/g) //"[object RegExp]"
    Object.prototype.toString.call(new Date()) //"[object Date]"
    Object.prototype.toString.call([]) //"[object Array]"
    Object.prototype.toString.call(document) //"[object HTMLDocument]"
    Object.prototype.toString.call(window) //"[object Window]"

    // 从上面这段代码可以看出,Object.prototype.toString.call() 可以很好地判断引用类型,甚至可以把 document 和 window 都区分开来。
    由于代码过长 推荐封装一下Object.prototype.toString
    实现一个全局通用的判断方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function getType(obj){
    let type = typeof obj;
    //如果是基本类型 直接返回
    if(type!=='object'){
    return type;
    }
    return Object.prototype.toStirng.call(obj).replace(/^\[object (\S+)\]$/, '$1'); // 注意正则中间有个空格

    }
    /* 代码验证,需要注意大小写,哪些是typeof判断,哪些是toString判断?思考下 */
    getType([]) // "Array" typeof []是object,因此toString返回
    getType('123') // "string" typeof 直接返回
    getType(window) // "Window" toString返回
    getType(null) // "Null"首字母大写,typeof null是object,需toString来判断
    getType(undefined) // "undefined" typeof 直接返回
    getType() // "undefined" typeof 直接返回
    getType(function(){}) // "function" typeof能判断,因此首字母小写
    getType(/123/g) //"RegExp" toString返回

防抖和节流

防抖主要是为了不让事件同一时间内触发多次导致请求多次的问题
防抖有两种情况
第一种是只触发第一次
第二种是只触发最后一次
防抖的主要写法如下: 但会产生一个问题 就是一开始必须等待才能进行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div id="root">
<input type="text" id="msg">
<button>点我发送请求</button>
</div>
<script>
const btn = document.getElementsByTagName('button')[0];
// const msg = document.getElementById('msg');
btn.addEventListener('click', debounce(submit), false);

function submit(e) {
console.log(this);
console.log(e);
console.log(1);
};

function debounce(fn) {
var t = null;
return function () {
if (t) {
clearTimeout(t);
}
t = setTimeout(() => {
fn.apply(this, arguments);
}, 1000);
}
}

</script>

  1. debounce传入参数 并且注意最后返回的是一个函数
  2. 设置t来决定定时器的销毁和开启
  3. 定时器用箭头函数 让this指向window
  4. apply将thisarguments传给submit

解决上面问题的做法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div id="root">
<input type="text" id="msg">
<button>点我发送请求</button>
</div>
<script>
const btn = document.getElementsByTagName('button')[0];
// const msg = document.getElementById('msg');
btn.addEventListener('click', debounce(submit, 2000), false);

function submit(e) {
console.log(this);
console.log(e);
console.log(1);
};

function debounce(fn, timer) {
var t = null;
return function () {
let firstclick = !t;
if (firstclick) {
fn.apply(this, arguments);
}
t = setTimeout(() => {
t = null;
}, timer);
}
}
</script>

原理是判断是否为第一次执行 如果是的话直接请求 然后让定时器将t经过一定时间还原,这样下一次就又是第一次 否则的话 因为t还没还原 所以不会输出;

节流是指函数在一定时间间隔内只能执行一次,从而减少一段时间内的触发频率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="root">
<input type="text" id="msg">
<button>点我发送请求</button>
</div>
<script>
const btn = document.getElementsByTagName('button')[0];
// const msg = document.getElementById('msg');
btn.addEventListener('click', throttle(submit, 2000), false);

function submit(e) {
console.log(e, this);
};

function throttle(fn, delay) {
var begin = 0;
return function () {
var cur = new Date().getTime();
if (cur - begin >= delay) {
fn.apply(this, arguments);
begin = cur;
}
}
}
</script>

  1. throttle传入参数 并且注意最后返回的是一个函数
  2. begin定义一开始的时间,用cur定义当前时间
  3. 最后begin要变成cur

手写深浅拷贝

浅拷贝顾名思义就是直接拷贝对象上面的内容 但是如果新的对象的属性发生了改变,原先对象上面的属性也会随之改变

1
2
3
4
5
6
7
8
9
10
11
12
const oldObj = {
name:'jotaro',
age:20,
color:['orange','green','blue'],
friend:{
name:'jostar'
}
}
const newObj = oldObj;

console.log('oldObj',oldObj);
console.log('newObj',newObj);

深拷贝的话,新的对象发生改变不会影响旧的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const oldObj = {
name:'jotaro',
age:20,
color:['orange','green','blue'],
friend:{
name:'jostar'
}
}
// 深拷贝的话 在复制对象属性的基础上 不能对原先对象的属性进行改变
function deepClone(obj){
// 如果传进来的参数不是对象 或者是 空 直接返回原先参数
if(typeof obj !== 'object'||obj===null){
return obj;
}
// 定义一个result 用于复制参数
let result;
// 那么只剩数组和对象两种可能 继续判断 并改变result的类型
if(obj instanceof Array){
result = [];
}else{
result = {};
}
// 之后就是把obj里面的key拿出来放到result里面 这样就完成了初步的深拷贝
// for(let key in obj){
// result[key] = obj[key];
// }
// 会发现有一点缺陷就是 如果对象里面包含了对象 那么最后做出的修改 还是浅拷贝类型的修改
// 解决方法是 递归obj[key] 让他继续判断再传递
// 还有一个可以优化的地方 就是对象原型上面的属性不应该去拷贝 所以使用到一个方法
// 只会拷贝对象自身的属性
for(let key in obj){
if(obj.hasOwnProperty(key)){
result[key] = deepClone(obj[key]);
}
}

return result;
}

const newObj = deepClone(oldObj);
newObj.age = 99;
newObj.color[0] = 'yellow';
console.log('oldObj',oldObj);
console.log('newObj',newObj);

分几步走

  1. 判断obj不为对象 和 为空的情况 直接返回obj
  2. 定义一个result用来放结果 再根据传进来的obj 判断它是否为对象或者数组 相应的改变result的类型
  3. for循环将obj的key传给result的key 注意要用递归的形式
  4. 优化 只拷贝对象的属性 不拷贝对象原型的属性

数组扁平化

需求 已知数组arr[1,2,[3,[4,[5,6]]]] 将其扁平化处理成[1,2,3,4,5,6]
方法一 flat函数

1
2
let arr = [1,2,[3,[4,[5,6]]]];
console.log(arr.flat(Infinity));

方法2 reduce加递归
1
2
3
4
5
6
7
8
function flatfn(arr){
return arr.reduce((res,item)=>{
// return res.concat((item instanceof Array)?flatfn(item):item)
// 也可以写成
return res.concat(Array.isArray(item)?flatfn(item):item);
},[])
}
console.log(flatfn(arr))

方法3 数组转字符串
1
2
3
4
5
6
function flatfn(arr){
return arr.join(',').split(',').map(item=>{
return parseInt(item);
})
}
console.log(flatfn(arr))

单例模式(设计模式)

单例模式算是编程思想中的一种设计模式,简单来讲就是一个类只能有一个实例对象,这个实例对象最终也只会被新建一次,并且要提供一个能访问到这个实例的入口

假设有个person类 里面构造器传入的是姓名 设计单例模式 让两个人的名字都用的同一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person {
constructor(name){
this.name = name;
}
}
let p1 = new Person('zzz');
let p2 = new Person('yyy');
console.log(p1===p2);
返回false

Person.getInstance = function(name){
if(this.instance) return this.instance;
return this.instance = new Person(name);
}
let p1 = Person.getInstance('zzz');
let p2 = Person.getInstance('yyy');
console.log(p1===p2);
返回true

提高版本 假设有个女朋友类 里面传入的是姓名和年龄 设计单例模式 让女朋友类变成单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class GF {
constructor(name,age) {
this.name = name;
this.age = age;
}
}
let danli = function(customClass){
let instance = null;
return class{
constructor(){
if(instance)return instance;
return instance = new customClass(...arguments);
}
}
}
// let g1 = new GF('2b',20);
// let g2 = new GF('3b',18);
// console.log(g1===g2)
// 返回false

let DanliGF = danli(GF);
let g1 = new DanliGF('2b',20);
let g2 = new DanliGF('3b',18);
console.log(g1===g2)
// 返回ture

数组去重

手写promise.all和promise.race

模拟实现new

实现call/apply/bind

模拟Object.create()的实现

千分位分隔符

实现三角形

实现三栏布局/双栏布局

编程题

判断一个数组是否是数组

关键:结合Object.prototype.toString.call(arr)判断。

1
2
3
4
5
6
function judgeArr(arr){
if(typeof arr === 'object'){
return Object.prototype.toString.call(arr) === '[object Array]';
}
return false;
}

排序

冒泡

每次比较相邻的数 如果后一个比前一个小 换位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var arr = [3, 1, 4, 6, 5, 7, 2];
function bubbleSort(arr){
for(var i = 0;i<arr.length-1;i++){
for(var j=0;j<arr.length-1-i;j++){
if(arr[j+1]<arr[j]){
var temp;
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
return arr
}
console.log(bubbleSort(arr));

快速排序

思路 二分数组 找个基准点 遍历数组小于基准点在左反之在又 之后继续递归左数组和右数组最后concat起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function quickSort(arr){
if(arr.length === 0){
return [];
}
var cIndex = Math.floor(arr.length/2);
var c = arr.splice(cIndex,1);
var l = [];
var r = [];
for(var i =0 ;i<arr.length;i++){
if(arr[i]<c){
l.push(arr[i]);
}else{
r.push(arr[i]);
}
}
return quickSort(l).concat(c,quickSort(r));
}
console.log(quickSort(arr));

二叉树

先序遍历 根左右
中序遍历 左根右
后序遍历 左右根
关键:了解递归边界

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    const root = {
val:'a',
left:{
val:'b',
left:{
val:'d'
},
right:{
val:'e'
}
},
right:{
val:'c',
left:{
val:'f'
},
right:{
val:'g'
}
}
}
//先序遍历
// function preorder(root){
// if(!root){
// return;
// }
// console.log('当前遍历的节点值是',root.val);
// preorder(root.left);
// preorder(root.right);
// }
// 中序遍历
// function inorder(root){
// if(!root){
// return;
// }
// inorder(root.left);
// console.log('当前遍历的节点值是',root.val);
// inorder(root.right);
// }
// inorder(root);
// 后序遍历
function postorder(root){
if(!root){
return;
}
postorder(root.left);
console.log('当前遍历的节点值是',root.val);
postorder(root.right);
}
postorder(root);

时间复杂度

下列代码会执行几次?

1
2
3
4
5
6
function traverse(arr) {
var len = arr.length //1
for(var i=0;i<len;i++) { //1,n+1,n
console.log(arr[i]) //n
}
}

T(n) = 1 + n +1 +(n+1) +n = 3n+3
规则:如果n是常数 无脑化为1 如果n不是常熟 取最高项的值且常数项化为1
所以时间复杂度 O(n) = n
注意 判断语句比递增语句执行多一次 因为要多判断最后一次。
下面代码会执行多少次?
1
2
3
4
5
6
7
8
9
10
11
function traverse(arr) {
var outLen = arr.length //1

for(var i=0;i<outLen;i++) { //1,n+1,n
var inLen = arr[i].length //n

for(var j=0;j<inLen;j++) { //由上知循环n 所以这部分总的是n,即n,n(n+1),n²
console.log(arr[i][j]) //n²
}
}
}

T(n) = 1 + n +1 +(n+1) +n + n(3n+2) = 3n²+5n+3
O(n) = n²
所以综上得知只要计算最高项的就可以
再看
1
2
3
4
5
6
7
function fn(arr) {
var len = arr.length //不看

for(var i=1;i<len;i=i*2) { //看i*2 已知i++是i一直加 所以是n次 而这里相当于2的n次 而跳出循环的条件是i>=len 所以这里不妨设2的x次>=n来计算 得log2(n) = x 去掉常数项 则On为log(n)
console.log(arr[i])
}
}

空间复杂度

看代码

1
2
3
4
5
6
function traverse(arr) {
var len = arr.length
for(var i=0;i<len;i++) {
console.log(arr[i])
}
}

里面有三个变量arr len i
而这三个变量在执行的时候是恒定的并没有开辟新的空间 都是时间上的开销 所以是空间复杂度是O(1)
看代码
1
2
3
4
5
6
7
function init(n) {
var arr = []
for(var i=0;i<n;i++) {
arr[i] = i
}
return arr
}

而在这个代码中 数组的大小不是恒定的 是随n变化的 所以它的空间复杂度是O(n)

http1 2

http的请求头

请求方法

get post put head delete options trace connect
put 指定了在服务器上面的位置
head只请求页面首部
delete删除某个资源

数据存储的物理结构有哪些

顺序存储、链式存储、索引存储和哈希存储

进程和线程

都是CPU工作时间片的一个描述
进程描述了CPU在运行指令加载和保存上下文之间所需的时间,放在应用上面就代表了一个程序。

线程是进程的一个更小的单位,描述了执行一段指令所需的时间

css选择符

上下文选择符,id选择符类选择符,伪类选择符