高频题
其他相关
自我介绍
项目难点
指尖移通
课表查询:
通过xyz轴
问卷调查:
动态解析不同类型题目
疫情打卡:
动态解析打卡字段,无限级渲染
项目重构:
新的设计模式,ui框架,新的规范体系建设
js
基本数据类型
常用的数据类型有5+1种;即5种基本的数据类型(String、undefined、null、boolean、number,Symbol),1种复杂的数据类型(object);
闭包
1.闭包是指有权访问另一个作用域中的变量的函数。
2.函数嵌套函数
3.函数内部可以引用函数外部的参数和变量
4.参数和变量不会被垃圾回收机制回收
bind和apply,call的区别
1.call、apply、bind都是改变this指向的方法
2.call是一个一个传参,apply是以数组形式传递
3.bind和call传参方式一样,返回一个函数,没有立即执行。
prototype原型的理解
1.每个对象都会在其内部初始化⼀个prototype属性,
2.当我们访问⼀个对象的属性时如果这个对象内部不存在这个属性,那么他就会去 prototype ⾥找这个属性,这个prototype ⼜会有⾃⼰的 prototype,于是就这样⼀直找下去,也就是我们平时所说的原型链的概念关系
3.当我们修改原型时,与之相关的对象也会继承这⼀改变
4.当我们需要⼀个属性的时, Javascript 引擎会先看当前对象中是否有这个属性, 如果没有的就会查找他的 Prototype 对象是否有这个属性,如此递推下去,⼀直检索到 Object 直到null。
This的理解
1. 作为函数调用,非严格模式下,this指向window,严格模式下,this指向undefined;
2. 作为某对象的方法调用,this通常指向调用的对象。
3. 使用apply、call、bind 可以绑定this的指向。
4. 在构造函数中,this指向新创建的对象
5. 箭头函数没有单独的this值,this在箭头函数创建时确定,它与声明所在的上下文相同。
ES6用过哪些
解构赋值
模板字符串
箭头函数
set map数据结构
promise
new操作符具体干了什么
1.创建一个空对象
2.链接到原型
3.绑定this值
4.返回新对象
Promise 对象
三种状态 两种结果
pending: 初始状态,不是成功或失败状态。
fulfilled: 意味着操作成功完成。
rejected: 意味着操作失败。
promise的缺点
1、无法取消Promise,一旦新建它就会立即执行,无法中途取消。
2、如果不设置回调函数,promise内部抛出的错误,不会反应到外部。
3、当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
promis的优点
1.解决回调地狱问题
Promise如何实现串行执行
proxy和Object.defineProperty区别
1.Proxy使用上比Object.defineProperty方便的多。
2.Proxy代理整个对象,Object.defineProperty只代理对象上的某个属性。
3.如果对象内部要全部递归代理,则Proxy可以只在调用时递归,而Object.defineProperty需要在一开始就全部递归,Proxy性能优于Object.defineProperty。
4.对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到。
5.数组新增删除修改时,Proxy可以监听到,Object.defineProperty监听不到。
6.Proxy不兼容IE,Object.defineProperty不兼容IE8及以下。
事件模型
W3C 中定义事件的发⽣经历三个阶段:捕获阶段( capturing )、⽬标阶段( targetin )、冒泡阶段( bubbling )
冒泡型事件:当你使⽤事件冒泡时,⼦级元素先触发,⽗级元素后触发捕获型事件:
当你使⽤事件捕获时,⽗级元素先触发,⼦级元素后触发
阻⽌冒泡:在 W3c 中,使⽤ stopPropagation() ⽅法;在IE下设置 cancelBubble = true
阻⽌捕获:阻⽌事件的默认⾏为,例如 click - <a> 后的跳转。
在 W3c 中,使⽤preventDefault() ⽅法,在 IE 下设置 window.event.returnValue = false
event loop事件循环
- 开始一个宏任务的执行(首先是script整体代码作为第一个宏任务入栈,开始执行)。
- 如果遇到异步,等异步任务有了运行结果后再把他们放到事件队列,如果属于微任务的话加入微任务队列。
- 当前宏任务执行完毕,接下来把它执行过程中产生的微任务依次执行。
- 微任务全部执行完毕,浏览器开始重新渲染。
- 去事件队列读取下一个宏任务,不断循环上面过程。
浏览器与node的事件循环区别
浏览器和 Node 环境下,microtask 任务队列的执行时机不同
浏览器环境下,microtask(微任务) 的任务队列是每个 macrotask (宏任务)执行完之后执行。而在 Node.js 中,microtask 会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask 队列的任务。
- Node 端,microtask 在事件循环的各个阶段之间执行
- 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
外部输入数据–>轮询阶段(poll)–>检查阶段(check)–>关闭事件回调阶段(close callback)–>定时器检测阶段(timer)–>I/O 事件回调阶段(I/O callbacks)–>闲置阶段(idle, prepare)–>轮询阶段(按照该顺序反复运行)…
- timers 阶段:这个阶段执行 timer(setTimeout、setInterval)的回调
- I/O callbacks 阶段:处理一些上一轮循环中的少数未执行的 I/O 回调
- idle, prepare 阶段:仅 node 内部使用
- poll 阶段:获取新的 I/O 事件, 适当的条件下 node 将阻塞在这里
- check 阶段:执行 setImmediate() 的回调
- close callbacks 阶段:执行 socket 的 close 事件回调
设计模式
面向对象三大基本特征
封装、继承、多态
垃圾回收机制
引用计数
引用计数有个最大的问题: 循环引用。
引用计数的含义是跟踪记录每个值被引用的次数,当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来
标记清除
javascript中最常用的垃圾回收方式,当变量进入执行环境是,就标记这个变量为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。
css
CSS盒模型
分为标准盒模型和怪异盒模型
怪异盒模型的宽高=内容宽高+padding+border
标准盒模型的宽高=内容宽高
如何在CSS 设置这两个模型
标准盒模型:
box-sizing: content-box
怪异盒模型:
box-sizing: border-box
实现两栏布局的几种方式
/*
1.浮动
2.flex布局
3.绝对定位
4.使用calc()函数
*/
//1.采用浮动
.outer{
height:100px;
}
.left{
float:left;
height:100px;
width:200px;
background:yellow;
}
.right{
margin left:200px;
height:100px;
width:auto;//撑满
background:red;
}
// 2.flex布局
.outer{
display:flex;
height:100px;
}
.left{
height:100px;
flex shrink:0;
flex grow:0;
flex basic:200px
}
.right{
height:100px;
flex:auto
}
// 使用绝对定位实现—absolute
.wrap {
overflow: hidden;
position: relative;
}
/* 脱离文档流 */
.left {
position: absolute;
left: 0;
top: 0;
width: 200px;
height: 200px;
background: purple;
}
.right {
margin-left: 200px;
background: skyblue;
height: 200px;
}
行内元素的垂直居中方法
(1)设置块元素的 height,line-height为相同的值;
(2)vertical-align:middle
(3)display: table-cell;vertical-align: middle;
css实现水平垂直居中的几种方式
(1)display: flex; align-items: center; justify-content: center;
(2)利用相对定位和绝对定位,再加上外边距和平移的配合;
父元素:position: relative;
子元素:position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
inline-block 有什么作用
1、和其他元素都在一行上;
2、元素的高度、宽度、行高以及顶和底边距都可设置。
position参数
absolute
生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。
元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
fixed
生成绝对定位的元素,相对于浏览器窗口进行定位。
元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
relative
生成相对定位的元素,相对于其正常位置进行定位。
因此,"left:20" 会向元素的 LEFT 位置添加 20 像素。
static 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
inherit 规定应该从父元素继承 position 属性的值。
css中怎么清除浮动?
(1)使用clear:both清除浮动
(2)利用伪元素:after来清除浮动
(3)使用CSS的overflow属性
display 属性
block 此元素将显示为块级元素,此元素前后会带有换行符。
inline 默认。此元素会被显示为内联元素,元素前后没有换行符。
inherit 规定应该从父元素继承 display 属性的值。
inline-block 行内块元素。(CSS2.1 新增的值)
table 此元素会作为块级表格来显示(类似 <table>),表格前后带有换行符。
inline-table 此元素会作为内联表格来显示(类似 <table>),表格前后没有换行符。
flex
flex-direction
flex-wrap
flex属性
flex-grow 进行扩展的量。
flex-shrink 进行收缩的量。
flex-basis 初始长度
flex-wrap:nowrap(不换行) | wrap(向下换) | wrap-reverse(向上换);
justify-content 水平(主轴上)对齐方式
align-items 十字交叉轴上对齐方式
flex-direction 项目排列方向
子元素设置 justify-self
属性设置单个盒子在其布局容器适当轴中的对其方式
可以让子元素单独排列
flex:1表示什么
flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选
- flex: 1; === flex: 1 1 0;
-
flex-grow属性(num)
flex-grow 定义自身放大比例,默认为0不放大。例如:1/2/1=25%:50%:25%
-
flex-shrink属性(num)
flex-shrink定义了空间不足时自身缩小比例,默认为1自动缩小,0不缩小。
-
flex-basis属性
flex-basis定义最小空间,默认值为auto,即自身的本来大小。
css选择器
选择器 | 例子 | 例子描述 |
---|---|---|
.class | .intro | 选择 class="intro" 的所有元素。 |
element | p | 选择所有 元素。 |
#id | #firstname | 选择 id="firstname" 的元素。 |
:active | a:active | 选择活动链接。 |
::after | p::after | 在每个 的内容之后插入内容。 |
::before | p::before | 在每个 的内容之前插入内容。 |
:first-child | p:first-child | 选择属于父元素的第一个子元素的每个 元素。 |
:last-child | p:last-child | 选择属于其父元素最后一个子元素每个 元素。 |
:nth-child(n) | p:nth-child(2) | 选择属于其父元素的第二个子元素的每个 元素。 |
圣杯布局和双飞翼布局的区别与实现
BFC
块格式化上下文
它是独立的容器,与其他的元素互不影响。
触发条件
- 设置display为flex,inline-block,table-cell,inline-flex
- 设置position为absolute,fixed
- 设置overflow不为visible
- 设置float不为none
特性
-
一个BFC中的margin重叠
-
浮动元素脱离文档流,BFC可以解决元素塌陷问题,可以自适应浮动子元素的高度
-
浮动元素脱离文档流,元素重叠在一起,通过BFC可以让其分开
z-index在什么时候才能触发
在使用了position的定位属性的时候才可以触发
网络
OSI模型各层介绍
- 物理层:RJ45、CLOCK、IEEE802.3
- 数据链路:PPP、FR、HDLC、VLAN、MAC
- 网络层:IP、ICMP、ARP、RARP、OSPF、IPX、RIP、IGRP
- 传输层:TCP、UDP、SPX
- 会话层:NFS、SQL、NETBIOS、RPC
- 表示层:JPEG、MPEG、ASII、MP4
- 应用层:FTP、DNS、Telnet、SMTP、HTTP、WWW、NFS
应用层: 应用层是开放系统的最高层,是直接为应用进程提供服务的。其作用是在实现多个系统应用进程相互通信的同时,完成一系列业务处理所需的服务。主要的协议有http ftp
表示层:简单来说就是win系统想给QQ发短信给linux的QQ的规范标准,表示层会通过使用一种通格式来实现多种数据格式之间的转换。
会话层:主要在你的系统之间发起会话或者接受会话请求。
运输层:主要的协议有tcp和udp,tcp将数据封装成用户数据报或者说是报文,然后分段传输,udp将数据封装成用户数据报直接传输。运输层向它上面的应用层提供端到端通信服务,它属于面向通信部分的最高层,同时也是用户功能中的最低层。传输层对收到的报文进行差错检测。
网络层:主要的协议有ip,主要是将报文封装成ip数据报。
*数据链路层:**ip数据报封装成帧,传给物理层。***
物理层:主要是将比特或者说是0和1转化为强弱电流,然后到接受方在将强弱电流转化为01.主要定义光纤的接口,网线的接口。
数据链路层解决的三个问题
1、封装成帧
封装成帧就是在一段数据的前后分别添加首部、尾部和帧检验序列 ,然后就构成了一个帧。
注:首部和尾部的一个重要作用就是帧定界。
2、透明传输
3、差错检测
传输过程中可能会产生比特差错:1可能会变成0,0可能会变成1,为了检查传输是否正确,需要在原始数据后加上一个帧检验序列(FCS)。
https的原理
对称加密
加密和解密都是使用同一个密钥,常见的对称加密算法有 DES、3DES 和 AES 等
- 优点:算法公开、计算量小、加密速度快、加密效率高,适合加密比较大的数据。
- 缺点:
- 交易双方需要使用相同的密钥,传输过程中无法保证不被截获,所以对称加密的安全性得不到保证。
- 每对用户每次使用对称加密算法时,都需要使用其他人不知道的惟一密钥,这会使得发收信双方所拥有的钥匙数量急剧增长,密钥管理成为双方的负担。对称加密算法在分布式网络系统上使用较为困难,主要是因为密钥管理困难,使用成本较高。
非对称加密
非对称加密需要公钥和私钥,公钥发送给客户端,私钥存在本地,私钥就像是钥匙,公钥就像是锁芯,相互对应。
客户端生成随机key,通过公钥加密key,传递给服务端。
服务端通过私钥解密获得key,双方随后使用密钥key进行对称加密传输。
TCP和UDP的区别
- TCP 是面向连接的,UDP 是面向无连接的
- UDP程序结构较简单
- TCP 是面向字节流的,UDP 是基于数据报的
- TCP 保证数据正确性,UDP 可能丢包
- TCP 保证数据顺序,UDP 不保证
http1.0、http1.1、http2.0
HTTP1.0和HTTP1.1的区别
-
长连接
HTTP1.1支持长连接和请求的流水线处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启长连接keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。HTTP1.0需要使用keep-alive参数来告知服务器端要建立一个长连接。
-
节约带宽
HTTP1.0中存在一些浪费带宽的现象,并且不支持断点续传功能,HTTP1.1支持只发送header信息。
- HOST域
在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名,虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个IP地址,HTTP1.1的请求消息和响应消息都支持host域,且请求消息中如果没有host域会报告一个错误
- 缓存处理
在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
- 新增状态码
在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
HTTP1.1和HTTP2.0的区别
- 多路复用
- 头部数据压缩
- 二进制传输
- 服务器推送
简单请求与复杂请求
简单请求方式get post head,请求头有相应的请求头范围内都算简单请求,
复杂请求会提前发起一次预请求,这个预请求实际上就是在为之后的实际请求发送一个权限请求,在预回应返回的内容当中,服务端应当对这两项进行回复,以让浏览器确定请求是否能够成功完成。
TCP三次握手
为什么需要TCP三次握手
为了保证服务端能收接受到客户端的信息并能做出正确的应答而进行前两次(第一次和第二次)握手,
为了保证客户端能够接收到服务端的信息并能做出正确的应答而进行后两次(第二次和第三次)握手。
四次挥手
为什么连接的时候是三次握手,关闭的时候却是四次握手?
- 建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
- 关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以服务器可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接。因此,服务器ACK和FIN一般都会分开发送,从而导致多了一次。
HTTP报文的结构
1.是请求方法,HTTP/1.1 定义的请求方法有8种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE,最常的两种GET和POST。
2.为请求对应的URL地址,它和报文头的Host属性组成完整的请求URL
3.是协议名称及版本号。
4.是HTTP的报文头,报文头包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息。
5.是报文体,它将一个页面表单中的组件值通过param1=value1¶m2=value2的键值对形式编码成一个格式化串,它承载多个请求参数的数据。不但报文体可以传递请求参数,请求URL也可以通过类似于“/chapter15/user.html? param1=value1¶m2=value2”的方式传递请求参数。
跨域
1.jsonp
> JSONP 的原理很简单,就是利⽤ <script> 标签没有跨域限制的漏洞。只能get请求。
> 通过<script> 标签指向⼀个需要访问的地址并提供⼀个回调函数来接收数据当需要通讯时
>
> 通过回调函数callback执行相关代码
```html
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
```
2.cors
服务端设置 Access-Control-Allow-Origin 就可以开启 CORS 。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有⽹站都可以访问资源。
3.postMessage
这种⽅式通常⽤于获取嵌⼊⻚⾯中的第三⽅⻚⾯数据。⼀个⻚⾯发送消息,另⼀个⻚⾯判断来源并接收消息
http头中的keep-alive
持久连接,在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数
从浏览器地址栏输⼊url到显示⻚⾯的步骤
1. 在浏览器地址栏输⼊URL
2. 浏览器查看缓存,如果请求资源在缓存中并且新鲜,就不发送请求
1. 如果资源未缓存,发起新请求
2. 如果已缓存,检验是否⾜够新鲜,⾜够新鲜直接提供给客户端,否则与服务器进⾏验证。
3. 检验新鲜通常有两个HTTP头进⾏控制 Expires 和 Cache-Control :max-age
4. 浏览器获取主机ip地址,过程如下:
1. 浏览器缓存
2. 本机缓存
3. hosts⽂件
4. 路由器缓存
5. ISP DNS缓存
6. DNS递归查询(可能存在负载均衡导致每次IP不⼀样)
5. 建⽴TCP链接,三次握⼿如下:
1. 客户端发送⼀个TCP的SYN=1,Seq=X的包到服务器端⼝
2. 服务器发回SYN=1, ACK=X+1, Seq=Y的响应包
3. 客户端发送ACK=Y+1, Seq=Z
6. TCP链接建⽴后发送HTTP请求
8. 服务器接受请求并解析
9. 服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
11. 服务器将响应报⽂通过TCP连接发送回浏览器
12. 浏览器接收HTTP响应,然后根据情况选择关闭TCP连接或者保留重⽤,关闭TCP连接的四次握⼿如下:
1. 主动⽅发送Fin=1, Ack=Z, Seq= X报⽂
2. 被动⽅发送ACK=X+1, Seq=Z报⽂
3. 被动⽅发送Fin=1, ACK=X, Seq=Y报⽂
4. 主动⽅发送ACK=Y, Seq=X报⽂
13. 解析HTML⽂档,构件DOM树,下载资源,构造CSSOM树,执⾏js脚本,
13. 构建DOM树->构建CSS书->合成渲染树->渲染到页面当中
HTTP状态码及其含义
1XX :信息状态码
100 Continue 继续,⼀般在发送 post 请求时,已发送了 http header 之后服务端将返回此信息,表示确认,之后发送具体参数信息
2XX :成功状态码
200 OK 正常返回信息
201 Created 请求成功并且服务器创建了新的资源
202 Accepted 服务器已接受请求,但尚未处理
3XX :重定向
301 Moved Permanently 请求的⽹⻚已永久移动到新位置。
302 Found 临时性重定向。
303 See Other 临时性重定向,且总是使⽤ GET 请求新的 URI 。
304 Not Modified ⾃从上次请求后,请求的⽹⻚未修改过。
4XX :客户端错误
400 Bad Request 服务器⽆法理解请求的格式,客户端不应当尝试再次使⽤相同的内容发起请求。
401 Unauthorized 请求未授权。
403 Forbidden 禁⽌访问。
404 Not Found 找不到如何与 URI 相匹配的资源。
5XX: 服务器错误
500 Internal Server Error 最常⻅的服务器端错误。
501未实现服务器不具备完成请求的功能。
502错误网关服务器作为网关或代理,从上游服务器收到无效响应。
503 Service Unavailable 服务器端暂时⽆法处理请求(可能是过载或维护)
504网关超时服务器作为网关或代理,但是没有及时从上游服务器收到请求。
讲一下TCP的拥塞控制
发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口,另外考虑到
接受方的接收能力,发送窗口可能小于拥塞窗口。慢开始算法的思路就是,不要一开始就发送
大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小。
过程cwnd的大小呈指数增长,直到超过慢启动门限,然后进入拥塞避免阶段,cwnd的大小线性
增长,当出现网络拥塞(三个重复的ack或者超时)时候,将慢启动门限设置为出现拥塞时候大小
的一半,cwnd的大小重新从0开始进入慢启动阶段。
快重传和快恢复:快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使
发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传算法规定
,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设
置的重传计时器时间到期
http请求方法
1、GET方法
2、POST方法
3、HEAD方法
HEAD方法与GET方法相同,但没有响应体,仅传输状态行和标题部分。这对于恢复相应头部编写的元数据非常有用,而无需传输整个内容。
4、PUT方法
PUT方法用于将数据发送到服务器以创建或更新资源,它可以用上传的内容替换目标资源中的所有当前内容。
它会将包含的元素放在所提供的URI下,如果URI指示的是当前资源,则会被改变。如果URI未指示当前资源,则服务器可以使用该URI创建资源。
5、DELETE方法
DELETE方法用来删除指定的资源,它会删除URI给出的目标资源的所有当前内容。
7、OPTIONS方法
OPTIONS方法用来描述了目标资源的通信选项,会返回服务器支持预定义URL的HTTP策略。
操作系统
进程和线程
- 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
- 线程是cpu调度的最小单位(线程是建立在进程的基础上的一个程序运行单位,一个进程中可以有多个线程)
- 一个进程可以拥有多个线程
- 一个线程只能属于一个进程
Vue
vue生命周期
beforeCreate()
在实例创建之间执行,数据未加载状态
created()
在实例创建、数据加载后,能初始化数据,dom
渲染之前执行
beforeMount()
虚拟dom
已创建完成,在数据渲染前最后一次更改数据
mounted()
页面、数据渲染完成,真实dom
挂载完成
beforeUpadate()
重新渲染之前触发
updated()
数据已经更改完成,dom
也重新 render
完成,更改数据会陷入死循环
beforeDestory()
和 destoryed()
前者是销毁前执行(实例仍然完全可用),后者则是销毁后执行
各个生命周期的作用
生命周期 | 描述 |
---|---|
beforeCreate | 组件实例被创建之初,组件的属性生效之前 |
created | 组件实例已经完全创建,属性也绑定,但真实dom还没有生成,$el还不可用 |
beforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用 |
mounted | el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子 |
beforeUpdate | 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前 |
update | 组件数据更新之后 |
activited | keep-alive专属,组件被激活时调用 |
deadctivated | keep-alive专属,组件被销毁时调用 |
beforeDestory | 组件销毁前调用 |
destoryed | 组件销毁后调用 |
父子组件生命周期
加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
常用钩子简易版
父create->子created->子mounted->父mounted
v-if原理
基于数据驱动的理念,当 v-if
指令对应的 value
为 false
的时候会预先创建一个注释节value 发生变化时,命中派发更新的逻辑,对新旧组件树进行 patch
,从而完成使用 v-if
指令元素的动态显示隐藏。
Vue-router 中hash模式和history模式的关系
1.url中 hash 带了一个很丑的 # 而history是没有#的 hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,不会重新加载页面。
2.history —— 利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法
Vue 双向绑定
vue的数据双向绑定主要通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
1.实现一个监听器 Observer,用来劫持并监听所有属性,如果发生变动,则通知订阅者。
2.实现一个订阅者 Watcher,当接到属性变化的通知时,执行对应的函数,然后更新视图,使用 Dep 来收集这些 Watcher。
3.实现一个解析器 Compile,用于扫描和解析的节点的相关指令,并根据初始化模板以及初始化相应的订阅器。
简单双向绑定:
<div id="demo"></div>
<input type="text" id="inp">
<script>
var obj = {};
var demo = document.querySelector('#demo')
var inp = document.querySelector('#inp')
Object.defineProperty(obj, 'name', {
get: function() {
return val;
},
set: function (newVal) {//当该属性被赋值的时候触发
inp.value = newVal;
demo.innerHTML = newVal;
}
})
inp.addEventListener('input', function(e) {
// 给obj的name属性赋值,进而触发该属性的set方法
obj.name = e.target.value;
});
obj.name = 'fei';//在给obj设置name属性的时候,触发了set这个方法
</script>
vue.nextTick原理
功能:
将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。
原理:
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。
Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
在执行microtask(微任务)的过程中后加入microtask队列的微任务,也会在下一次事件循环之前被执行。也就是说,macrotask(宏任务)总要等到microtask都执行完后才能执行,microtask有着更高的优先级。
当我们自己调用nextTick的时候,它就在更新DOM的那个microtask后追加了我们自己的回调函数,从而确保我们的代码在DOM更新后执行,
Promise
MutationObserver
setImmediate
setTimeout
vue和react区别
相同点:
1.都支持服务器端渲染
2.都有Virtual DOM,组件化开发,通过props参数进行父子组件数据的传递,都实现webComponent规范
3.数据驱动视图
4.都有支持native的方案,React的React native,Vue的weex
5.都有管理状态,React有redux,Vue有自己的Vuex(自适应vue,量身定做)
不同点:
1.React严格上只针对MVC的view层,Vue则是MVVM模式
2.virtual DOM不一样,vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树.
而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制
3.组件写法不一样, React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即'all in js';
Vue推荐的做法是webpack+vue-loader的单文件组件格式,即html,css,jd写在同一个文件;
4.数据绑定: vue实现了数据的双向绑定,react数据流动是单向的
5.state对象在react应用中不可变的,需要使用setState方法更新状态;
在vue中,state对象不是必须的,数据由data属性在vue对象中管理;
Vue 表单支持双向绑定开发更方便
改变数据方式不同,setState 有使用坑
props Vue 可变,React 不可变
判断是否需要更新 React 可以通过钩子函数判断,Vue 使用依赖追踪,修改了什么才渲染什么
React 16以后 有些钩子函数会执行多次
React 需要使用 JSX,需要 Babel 编译。Vue 虽然可以使用模板,但是也可以通过直接编写 render 函数不需要编译就能运行。
生态 React 相对较好
proxy和Object.defineProperty区别
1.Proxy使用上比Object.defineProperty方便的多。
2.Proxy代理整个对象,Object.defineProperty只代理对象上的某个属性。
3.如果对象内部要全部递归代理,则Proxy可以只在调用时递归,而Object.defineProperty需要在一开始就全部递归,Proxy性能优于4.Object.defineProperty。
5.对象上定义新属性时,Proxy可以监听到,Object.defineProperty监听不到。
6.数组新增删除修改时,Proxy可以监听到,Object.defineProperty监听不到。
7.Proxy不兼容IE,Object.defineProperty不兼容IE8及以下。
Vue中computed和watch的区别
计算属性computed :
-
支持缓存,只有依赖数据发生改变,才会重新进行计算
-
不支持异步,当computed内有异步操作时无效,无法监听数据的变化
3.computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
- 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
侦听属性watch:
-
不支持缓存,数据变,直接会触发相应的操作;
-
watch支持异步;
-
监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
-
当一个属性发生变化时,需要执行对应的操作;一对多;
-
监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,
immediate:组件加载立即触发回调函数执行,
deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。
vue性能优化的方法
- 子组件开发
- 活用v-show,减少v-if
- 使用局部变量
vue组件通信六种方式
1.props/$emit
2.bus总线
var Event=new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {});
3.使用vuex
- Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
- dispatch:操作行为触发方法,是唯一能执行action的方法。
- actions:操作行为处理模块,由组件中的
$store.dispatch('action 名称', data1)
来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。 - commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
- mutations:状态改变操作方法,由actions中的
commit('mutation 名称')
来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。 - state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
- getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。
4.$parent / $children与 ref
webpack
常用的loader
- style-loader 将css添加到DOM的内联样式标签style里
- css-loader 允许将css文件通过require的方式引入,并返回css代码
- less-loader 处理less
- sass-loader 处理sass
- postcss-loader 用postcss来处理CSS
- autoprefixer-loader 处理CSS3属性前缀,已被弃用,建议直接使用postcss
- file-loader 分发文件到output目录并返回相对路径
- url-loader 和file-loader类似,但是当文件小于设定的limit时可以返回一个Data Url
- html-minify-loader 压缩HTML
- babel-loader 用babel来转换ES6文件到ES5
总结
- loader是webpack核心,用于对模块的源代码进行转换
- loader支持链式调用,从右至左执行,支持同步或异步函数
vue-loader的实现
vue-loader是什么
简单的说,他就是基于webpack的一个的loader,解析和转换 .vue 文件,提取出其中的逻辑代码 script、样式代码 style、以及 HTML 模版 template,再分别把它们交给对应的 Loader 去处理,核心的作用,就是提取,划重点。
既然vue-loader的核心首先是将以为.vue为结尾的组件进行分析、提取和转换,那么首先我们要找到以下几个loader
- selector–将.vue文件解析拆分成一个parts对象,其中分别包含style、script、template
- style-compiler–解析style部分
- template-compiler 解析template部分
- babel-loader-- 解析script部分,并转换为浏览器能识别的普通js
loader 和 plugin 的区别是什么?
-
Loader(加载器)
Loader用于对模块的源代码进行转换。loader 可以使你在加载模块时预处理文件 。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。
因为 webpack只能处理 JavaScript,如果要处理其他类型的文件,就需要使用 loader 进行转换,loader 本身就是一个函数,接受源文件为参数,返回转换的结果。
Plugin(插件)
Plugin 是用来扩展 Webpack 功能的。使用 plugin 丰富的自定义功能扩展以及生命周期事件,可以控制打包流程的每个环节。
通过plugin,webpack可以实 loader 所不能完成的复杂功能。作用于整个构建周期,实现对 webpack 的自定义功能扩展。Loader和Plugin的区别
- loader是一个转换器,将a文件进行编译输出b文件,这里是操作文件。单纯的文件转换。
- plugin是一个扩展器,它丰富了webpack本身,针对是loader结束后,webpack打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听webpack打包过程中的某些节点,执行任务
React
react⽣命周期函数
初始化阶段
getDefaultProps :获取实例的默认属性
getInitialState :获取每个实例的初始化状态
componentWillMount :组件即将被装载、渲染到⻚⾯上
render :组件在这⾥⽣成虚拟的 DOM 节点
omponentDidMount :组件真正在被装载之后
运⾏中状态
componentWillReceiveProps :组件将要接收到属性的时候调⽤
shouldComponentUpdate :组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻⽌ render 调⽤,后⾯的函数不会被继续执⾏了)
componentWillUpdate :组件即将更新不能修改属性和状态
render :组件重新描绘
componentDidUpdate :组件已经更新
销毁阶段
componentWillUnmount :组件即将销毁
diff算法
把树形结构按照层级分解,只⽐较同级元素。
给列表结构的每个单元添加唯⼀的 key 属性,⽅便⽐较。
React只会匹配相同 class 的 component (这⾥⾯的 class 指的是组件的名字)合并操作,调⽤ component 的 setState ⽅法的时候, React 将其标记为 - dirty .
到每⼀个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.
选择性⼦树渲染。开发⼈员可以重写 shouldComponentUpdate 提⾼ diff 的性能
浏览器
http缓存
强缓存
1.Expires
2.Cache-Control:max-age
协商缓存
1.ETag和If-None-Match
2.Last-Modified和If-Modified-Since
为什么要有etag?
-
一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新get;
-
某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),if-modified-since能检查到的粒度是秒级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
-
某些服务器不能精确的得到文件的最后修改时间。
cookie属性详解
name 字段为一个cookie的名称。
value 字段为一个cookie的值。
domain 字段为可以访问此cookie的域名。
path 字段为可以访问此cookie的页面路径。 比如domain是abc.com,path是/test,那么只有/test路径下的页面可以读取此cookie。
expires/Max-Age 字段为此cookie超时时间。若设置其值为一个时间,那么当到达此时间后,此cookie失效。不设置的话默认值是Session,意思是cookie会和session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效。
Size 字段 此cookie大小。
http 字段 cookie的httponly属性。若此属性为true,则只有在http请求头中会带有此cookie的信息,而不能通过document.cookie来访问此cookie。
secure 字段 设置是否只能通过https来传递此条cookie
cookie和localSrorage、session、indexDB 的区别
垃圾回收机制
-
标记回收
通过对所有变量进行一个标记,变量不在执行环境后标记为需要被回收。
-
引用计数
对被引用的变量进行引用计数,只要有引用,次数就增1,减少引用次数减1,到0的时候会被垃圾回收机制回收
V8垃圾回收机制
-
新生代
新生代将内存对半分成了from和to两个空间,from存储使用状态,to为闲置状态。
首先将活动对象放置到from里
寻找出标记需要清理的对象
将不需要清理的放置to里进行交换
将from里的对象清空
再次交换并循环。
当一个对象经过多次复制任然存活,会将它从新生代中晋升到老生代中。
如果一个对象是第二次经历从From空间复制到To空间,那么这个对象会被移动到老生代中。
如果To空间使用已经达到25%,那么会直接从From晋升到老生代中,如果To占比过高,会影响后续内存分配
-
老生代
V8在老生代中主要采用了Mark-Sweep和Mark-Conpact相结合的方式进行垃圾回收。
老生代分为标记清除和标记整理。
标记清除又分为标记阶段:对存活的数据进行标记、清理阶段:回收掉死亡的数据。这样虽然能够解决,但是会存在空间不连续的情况。
标记整理可以很好的解决空间不连续问题,在标记完存活对象以后,会将活着的对象向内存空间的一端移动,移动完成后,直接清理掉边界外的所有内存
输入url加载的过程
-
输入网址URL
-
缓存机制
-
DNS解析
-
TCP连接
-
发送http请求
-
接受响应,判断状态码选择处理方式
-
判断缓存
-
解码
浏览器拿到index.html文件后,就开始解析其中的html代码,遇到js/css/image等静态资源时,就向服务器端去请求下载
解析成对应的树形数据结构DOM树、CSS规则树,Javascript脚本通过DOM API和CSSOM API来操作DOM树、CSS规则树。
-
渲染
计算CSS样式(JS可动态修改dom或css,进一步改变渲染树和分布)
构建渲染树(Repaint:屏幕的一部分要重画,比如某个CSS的背景色变了,元素的几何尺寸没有变。Reflow:几何尺寸变了,我们需要重新验证并计算Render Tree。)
确认布局(定位坐标和大小,是否换行,各种position, overflow, z-index属性 ……)
绘制(调用操作系统Native GUI的API绘制,将每个节点转化为实际像素绘制到视口上)
-
连接结束
同源策略
源分为 协议,域名,端口。只要三个其中一个不相同,就不是同一个源。
浏览器为了安全,使用这个安全策略,只有本应用才可以访问请求资源。
同源策略应该是浏览器在接收加载资源之前对其来源进行了检查,然后限制加载
跨域
跨域的几种方式
-
jsonp
/* 天然支持跨域的标签,img,link,script 通过script标签,src填写url地址,并且追加上回调函数 缺点是只能访问get数据 */ function fn(e){ console.log(e) } <script src="https://www.baidu.com/getUser?callback=fn"></script>
-
cors
实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信 header("Access-Control-Allow-Origin:*"); header("Access-Control-Allow-Methods:POST,GET");
-
postMessage
允许跨域获取dom,一般用于嵌套网页传输数据,比如使用iframe窗口 postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源 接受通过监听message,发送通过postMessage
简单请求与复杂请求
简单请求只有符合特定的头信息并且不会发送预请求的才是简单请求
简单请求的方式有:get,post,head
头信息有:
text/plain // 传输文本
application/x-www-form-urlencoded // 通过将表单内的数据转换为键值对进行传输
multipart/form-data // 将表单的数据处理为一条消息,以标签为单元,用分隔符分开,可以传输文件
其他的都是复杂请求并且会发送预请求
使用OPTIONS方法发起一个预检请求到服务区
浏览器缓存
强缓存
Expires:
http1.0的头字段,设置的是绝对时间
Cache-Control:
max-age:http1.1的头字段,设置的相对时间,多少毫秒,cache-control优先级比expires高
no-cache:需要进行协商缓存,发送请求到服务器确认是否使用缓存。
协商缓存
Last-Modify/If-Modify-Since
第一次发送请求浏览器携带last-modify中的最后修改时间
再次请求浏览器会携带if-modify-since,服务器根据携带的值判断是否更新数据
ETag/If-None-Match
因为会有周期性的数据变化,只通过时间无法判断,出现了etag唯一标识,资源变化会导致Etag变化,通过if-none-match携带etag,服务端进行判断是否更新数据,返回false状态304命中缓存
301和302
301代表永久性重定向
302代表临时重定向,会保留旧地址内容
浏览器组成和各引擎工作原理
- 1.用户界面
- 2.浏览器引擎(负责窗口管理、Tab进程管理等)
- 3.渲染引擎(有叫内核,负责HTML解析、页面渲染)
- 4.JS引擎(JS解释器,如Chrome和Nodejs采用的V8)
安全
XSS和CSRF
XSS:跨站脚本攻击,黑客将恶意脚本代码植入到页面中从而实现盗取用户信息等操作
通过网站A的xss漏洞,插入恶意代码,用户B访问网站后会将插入的文本代码当做程序执行,这样不法分子就可以执行恶意代码进行相关权限操作。
预防措施:
1、对输入、输出结果进行过滤和必要的转义
2、尽量使用post,使用get方式时对路径长度进行限制
3、使用httponly禁止黑客通过脚本获取用户cookie数据,但这样无法完全阻止xss攻击,因为发送http请求并不需要主动获取cookie
CSRF:黑客伪装成用户身份来执行一些非用户自愿的恶意以及非法操作
用户A登录过正常网站B,黑客发现B网站的CSRF漏洞,创建自己的网站C,访问危险网站C后,危险网站向正常网站B发送请求非法操作。
预防措施:
1、验证码
2、tokenId令牌
3、判断请求的Referer是否正确
CSRF和XSS的区别:
1、CSRF需要登陆后操作,XSS不需要
2、CSRF是请求页面api来实现非法操作,XSS是向当前页面植入js脚本来修改页面内容。
对称加密与非对称加密
对称加密:
1.加密解密过程:明文->密钥加密->密文,密文->密钥解密->明文。
2.算法公开,计算量小,加密速度快,加密效率高
3.双方使用相同的钥匙,安全性得不到保证
非对称加密:
1. 对称加密算法又称现代加密算法。
2. 非对称加密是计算机通信安全的基石,保证了加密数据不会被破解。
3. 非对称加密算法需要两个密钥:公开密钥(publickey) 和私有密(privatekey)
4. 公开密钥和私有密钥是一对
特点:
算法强度复杂,安全性依赖于算法与密钥。
加密解密速度慢。
与对称加密算法的对比:
对称加密只有一种密钥,并且是非公开的,如果要解密就得让对方知道密钥。
非对称加密有两种密钥,其中一个是公开的。
如何解决cookie安全性问题
第一步:设置cookie有效期不要过长,合适即可
第二步:设置HttpOnly属性为true
可以防止js脚本读取cookie信息,有效的防止XSS攻击。
第三步:设置复杂的cookie,加密cookie
(1)cookie的key使用uuid,随机生成;
(2)cookie的value可以使用复杂组合,比如:用户名+当前时间+cookie有效时间+随机数。
这样可以尽可能使得加密后的cookie更难解密,也是保护了cookie中的信息。
第四步:用户第一次登录时,保存ip+cookie加密后的token
每次请求,都去将当前cookie和ip组合起来加密后的token与保存的token作对比,只有完全对应才能验证成功。
第五步:session和cookie同时使用
sessionId虽然放在cookie中,但是相对的session更安全,可以将相对重要的信息存入session。
第六步:如果网站支持https,尽可能使用https
如果网站支持https,那么可以为cookie设置Secure属性为true,它的意思是,cookie只能使用https协议发送给服务器,而https比http更加安全。
webpack
loader和plugin的区别
两者都是为了扩展webpack的功能。
loader它只专注于转化文件(transform)这一个领域,完成压缩,打包,语言翻译;
而plugin不仅只局限在打包,资源的加载上,还可以打包优化和压缩,重新定义环境变量等
loader运行在打包文件之前(loader为在模块加载时的预处理文件);
plugins在整个编译周期都起作用
一个loader的职责是单一的,只需要完成一种转换。一个loader其实就是一个Node.js模块。当需要调用多个loader去转换一个文件时,每个loader会链式的顺序执行
在webpack运行的生命周期中会广播出许多事件,plugin会监听这些事件,在合适的时机通过webpack提供的API改变输出结果
stylus/sass/less区别
算法
常⻅排序算法的时间复杂度,空间复杂度
面试复习
javascript
理论
变量提升
在js中有两种执行环境,全局执行环境和函数执行环境,全局的作用域在windows,函数执行环境在函数里
js在执行时,有两个阶段,一个是创建阶段,会先找出需要变量提示的函数和变量,将他们加入到内存中,变量赋值undefined,一个是执行阶段,在执行阶段可以直接使用。
函数声明会比变量优先,同时声明相同名称的函数和变量会优先声明函数。
bind和apply和call的区别
bind是改变该函数this指向并返回一个函数
apply和call传递参数方式不同,apply是以数组形式传递,call是一个一个的传递,他们都是改变this指向
this指向
默认情况 在函数中this指向的是windows,在严格模式下,函数中this指向的是undefinde
在object里,this指向的是这个object对象
在new的对象里,this指向的是这个new实例化的对象
箭头函数的this指向的是父级作用域所在的this
通过bind,apply,call可以改变this的指向
原型链
每个函数和对象都有自己的原型proto,这个原型proto指向的创建该对象的构造函数的prototype,在寻找参数和方法时,会顺着这个proto一直向上寻找,直到null。
怎么判断类型
- 通过
typeof
,注意的是typeof null = object
- 通过
instanceof
,判断原型链当中是否存在 - 通过
Object.prototype.toString.call(xx)
,这样我们就可以获得类似[object Type]
的字符串。
箭头函数的特点
箭头函数的this指向会向上作用域寻找,并且this指向一但绑定上下文就不能改变
[]==![] 为什么是true
[]被解析成了true,![]这时候[]被转换成了0,再次!之后就是true了
为什么typeof(null)返回的是object
这是一个历史BUG,由于影响过大,没有修复。
主要影响原因是因为存储32位中,三位用于类型判断,000是Object,100是字符串,110是布尔,1是31位整数。
而null也是000。
为什么用Object.prototype.toString.call(obj)检测对象类型
因为其他类型重写了toString方法,比如函数就是返回的是函数的字符串形式
展开运算符和object.assign()的区别
Object.assign()
会触发 setter
,而展开操作符则不会。
js的错误
1、SyntaxError:语法错误
2. Uncaught ReferenceError:引用错误
3. RangeError:范围错误
4. TypeError类型错误
5. URIError,URL错误
6. EvalError eval()函数执行错误
捕获错误的两种方式
try catch
window.onerror = function(message, source, lineno, colno, error) { ... }
JS判断是否是苹果系统(ios)和安卓系统(Android)客户端
<script type="text/javascript">
var u = navigator.userAgent;
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //android终端
var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
alert('是否是Android:'+isAndroid);
alert('是否是iOS:'+isiOS);
</script>
栈和堆的理解
js的基本数据类型存储在栈中,复杂类型存在堆中。
说来也是形象,栈,线性结构,后进先出,便于管理。堆,一个混沌,杂乱无章,方便存储和开辟内存空间
手写代码
手写bind和apply以及call
apply和call
存储传递过来的第一个参数,将当前this绑定到传递过来参数上,执行绑定好的this函数并返回结果
Function.prototype.myApply = function(content,arg){
content = content || window;
content.fn = this;
const res = content.fn(arg); // call的话需要改一下传参方式 ...arg,上面入参也需要改
delect content.fn;
return res;
}
bind
Function.prototype.myBind = function(content,...arg){
content = content || window;
const _this = this;
return function fn(){
if(this instanceof fn){
return new _this(content,arg.concat(...arguments))
}
return _this.apply(content,arg.concat(...arguments))
}
}
手写ajax
//步骤一:创建异步对象
var xmlHTTP = new XMLHttpRequest();
//步骤二:设置请求的url参数,参数一是请求的类型,参数二是请求的url,可以带参数,动态的传递参数starName到服务端
xmlHTTP.open(method, url, isAsync)
//步骤三:发送请求
xmlHTTP.send();
//步骤四:注册事件 onreadystatechange 状态改变就会调用
xmlHTTP.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHTTP.status == 200) {
//步骤五 如果能够进到这个判断 说明 数据 完美的回来了,并且请求的页面是存在的
console.log(xmlHTTP.responseText);
}
}
#### xmlHttp.readyState的五种状态
0 :请求未初始化,XMLHttpRequest对象已经创建,但还没有调用open()方法。
1 :请求已建立,已经调用open() 方法,但尚未发送请求。
2 : 请求已发送,正在处理中(通常现在可以从响应中获取内容头)
3 : 请求在处理中;通常响应中已有部分数据可用了,但是服务器还没有完成响应的生成。
4 :响应完成,已经接收到了全部数据,并且连接已经关闭。
#### 常见的HTTP状态码
200 : OK 客户端请求成功
400 : Bad Request 客户端请求有语法错误,不能被服务器所理解
401 : Unauthorized 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
403 : Forbidden 服务器收到请求,但是拒绝提供服务
404 : Not Found 请求资源不存在,eg:输入了错误的URL
500 : Internal Server Error 服务器发生不可预期的错误
503 : Server Unavailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常
手写new
创建一个新的对象
添加父类的属性到新的对象上并初始化.
继承父类原型上的方法.
返回新对象
function _new(obj, ...rest){
// 基于obj的原型创建一个新的对象
const newObj = Object.create(obj.prototype);
// 添加属性到新创建的newObj上, 并获取obj函数执行的结果.
const result = obj.apply(newObj, rest);
// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
return typeof result === 'object' ? result : newObj;
}
手写Instanceof
function ins(a,b) {
let p = a;
while (p){
if(p.__proto__===b.prototype){
return true;
}else{
p=p.__proto__;
}
}
return false;
}
let a = new Array(1,2,3);
console.log(a);
let res = ins(a,Function);
console.log(res);
手写函数柯里化
function sum() {
return [...arguments].reduce((a,b)=> a+b);
}
function add(){
const _args=[...arguments];
const _adder = function () {
_args.push(...arguments);
return _adder;
}
_adder.toString=function () {
return sum(_args);
}
console.log(sum(_args))
return _adder;
}
console.log(add(1,2,3)(5))
手写Promise.all
/* 实现promise.all */
const promiseAll = function(arr){
return new Promise((resolve,reject)=>{
// 判断是不是数组
if(!Array.isArray(arr)){
throw Error('is not Array')
}
// 存储结果
let res = [];
// 请求成功数量
let sum = 0;
// 遍历数组
arr.forEach((item,index)=>{
// 创建promise对象
Promise.resolve(item).then((i)=>{
// 插入对应位置
res[index]=i;
// 如果请求成功数量等于数组长度返回结果
if(++sum===arr.length){
resolve(res);
}
}).catch((res)=>{
reject(res)
})
})
})
}
const pro1 = new Promise((res)=>{
setTimeout(()=>{
res(1)
},1000)
})
const pro2 = new Promise((res)=>{
setTimeout(()=>{
res(2)
},3000)
})
const pro3 = 5;
promiseAll([pro1,pro2,pro3]).then((item)=>{
console.log(item);
}).catch((item)=>{
console.log(item);
})
// [ 1, 2, 5 ]
树结构转数组
/*
[
{
id: 1,
text: 'text1',
children: [
{
id: 2,
text: 'text2',
parentId: 1,
children: [
{
id: 4,
text: 'text4',
parentId: 2
}
]
},
{
id: 3,
text: 'text3',
parentId: 1
}
]
}
]
*/
let getTree = function(data,parent){
let res = [];
for(let i=0;i<data.length;i++){
let item = data[i];
if(item.parentId==parent){
let temp = getTree(data,item.id);
if(temp.length>0){
item.children = temp;
}
res.push(item);
}
}
return res;
}
数组去重的几种方式
const arrRepeat = () => {
const res = []
for (let i = 0; i < a.length; i++) {
if (!res.includes(a[i])) res.push(a[i])
}
return res
}
const arrRepeat2 = () => {
const res = []
for (let i = 0; i < a.length; i++) {
if (res.indexOf(a[i]) == -1) res.push(a[i])
}
return res
}
const arrRepeat3 = () => {
return a.filter((item, index) => a.indexOf(item, 0) === index)
}
手写简单观察者模式
class PubSub {
constructor() {
this.handles = {}
}
// 订阅消息
on(eventType, handle) {
if (!this.handles.hasOwnProperty(eventType)) {
this.handles[eventType] = []
}
if (typeof handle === 'function') {
this.handles[eventType] = this.handles[eventType].concat([handle])
}
return this
}
// 发布消息
emit(eventType, ...args) {
if (this.handles.hasOwnProperty(eventType)) {
this.handles[eventType].forEach((item) => item(...args))
} else {
throw new Error(`${eventType}注册失败`)
}
}
off(eventType, handle) {
if (this.handles.hasOwnProperty(eventType)) {
this.handles[eventType].forEach((item, index, arr) => {
if (item == handle) {
arr.splice(index, 1)
}
})
}
}
}
const pub = new PubSub()
pub.on('send', (e) => {
console.log(e)
})
let fn5 = (e) => {
console.log(e + 1)
}
pub.on('send', fn5)
pub.emit('send', 1)
pub.emit('send', 2)
pub.off('send', fn5)
pub.emit('send', 1)
监听数组的改变
const arrPrototy = Array.prototype
const arrObject = Object.create(arrPrototy)
console.log(arrObject)
const newArray = []
;['push', 'pop'].forEach((item) => {
const newMethods = new Array()[item]
newArray[item] = function () {
console.log('监听了', item)
return newMethods.apply(this, arguments)
}
})
let list = [1, 2]
list.__proto__ = newArray
list.push(3)
console.log(list)
防抖和节流
防抖 一段时间内只能执行一次,重复触发重新计时
节流一段时间只能执行一次,多次不再触发
let debounce = function (fn,wait=500) {
let timeout = null;
return function () {
if(timeout)clearTimeout(timeout);
timeout=setTimeout(()=>{
fn.apply(this,arguments);
timeout=null
},wait)
}
}
let throttle = function(fn,wait=500){
let timeout = null;
return function(){
if(timeout)return;
timeout=setTimeout(()=>{
fn.apply(this,arguments);
timeout = null;
},wait)
}
}
继承实现
原型链继承
/*
标题:原型链继承
优点:共享了父类的方法
缺点:
1.无法传递参数
2.父类有引用类型参数 修改子类会对其他子类进行影响
*/
function parent(name, age) {
this.eat = "吃饭";
this.name = name;
this.age = age;
this.arr = [1, 2, 3, 5]
this.show = function () {
console.log(`${this.name}今年${this.age}岁了`);
}
}
function child(name, age, work) {
this.work = work;
}
child.prototype = new parent();
child.prototype.constructor = child;
let c1 = new child('付伟琪', 15, '学生');
let c2 = new child('付伟琪2', 15, '学生');
c1.arr.push(6);
console.log(c1.arr, c2.arr)
寄生组合继承
function parent(name, age) {
this.eat = "吃饭";
this.name = name;
this.age = age;
this.arr = [1, 2, 3, 5]
this.show = function () {
console.log(`${this.name}今年${this.age}岁了`);
}
}
parent.prototype.pushArr = function (val) {
this.arr.push(val);
}
function child(name, age, work) {
parent.apply(this,arguments);
this.work = work;
}
// child.prototype = Object.create(parent.prototype);
let fn = function(){};
fn.prototype = parent.prototype;
child.prototype=new fn();
child.prototype.constructor=child;
let p1 = new child('付伟琪',15,'学生');
let p2 = new child('张林康',14,'村民');
child.prototype.run = function(){
console.log(this.name+'跑了起来')
}
p1.pushArr(2);
console.log(p1.run(),p2.run())
构造函数继承
标题: 构造函数继承
优点:
1.不会对引用类型造成影响
2.可以向父类传参
缺点:
1.会造成多余内存消耗,方法不能复用
2.不能继承原型链上的方法
*/
function parent(name, age) {
this.eat = "吃饭";
this.name = name;
this.age = age;
this.arr = [1, 2, 3, 5]
this.show = function () {
console.log(`${this.name}今年${this.age}岁了`);
}
}
parent.prototype.pushArr = function (val) {
this.arr.push(val);
}
function child(name, age, work) {
parent.apply(this,arguments);
this.work = work;
}
let p1 = new child('付伟琪',15,'学生');
let p2 = new child('张林康',14,'村民');
console.log(p1)
组合继承
标题: 组合继承=原型继承+构造函数继承
优点:
1.能够传递参数
2.能够继承原型链上的方法
3.不共享引用类型
缺点:
1.会创建两次父类构造函数
*/
function parent(name, age) {
this.eat = "吃饭";
this.name = name;
this.age = age;
this.arr = [1, 2, 3, 5]
this.show = function () {
console.log(`${this.name}今年${this.age}岁了`);
}
}
parent.prototype.pushArr = function (val) {
this.arr.push(val);
}
function child(name, age, work) {
parent.apply(this,arguments);
this.work = work;
}
child.prototype = new parent();
child.prototype.constructor=child;
let p1 = new child('付伟琪',15,'学生');
let p2 = new child('张林康',14,'村民');
p1.pushArr(2);
console.log(p1.arr,p2.arr)
树的遍历
function preorder(tree) {
if(!tree)return ;
console.log(tree.val);
preorder(tree.left);
preorder(tree.right);
}
// 先序遍历非递归版本
function preorder2(tree){
let stack = [tree];
while(stack.length){
let item = stack.pop();
console.log(item.val)
item.right?stack.push(item.right):'';
item.left?stack.push(item.left):'';
}
}
// 中序遍历非递归版本
function inorder2(tree){
let stack = [];
let p = tree;
while(stack.length>0||p){
while (p){
stack.push(p)
p=p.left;
}
let item = stack.pop();
console.log(item.val)
p=item.right;
}
}
// inorder2(data)
// 后序非递归遍历
function postorder2(tree){
let stack = [tree];
let outStack = [];
while(stack.length>0){
let n = stack.pop();
outStack.push(n);
if(n.left)stack.push(n.left);
if(n.right)stack.push(n.right);
}
while(outStack.length>0){
let n = outStack.pop();
console.log(n.val)
}
}
css相关
框架
vue和react分别如何进行双向绑定以及原理
vue双向绑定是通过Observer将data数据进行监听,发生改变消息推送给Dep消息订阅器,Dep接收后统一发送广播subs里的订阅者。
react则需要通过手动setstate进行设置
浏览器相关
算法练习
链表
两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
/*
思路:循环两个节点,依次相加,超过10的进位到下一轮进行添加。
*/
var addTwoNumbers = function(l1, l2) {
// 新建一个节点存储结果
const head = new ListNode(0);
// p指针指向头节点 方便向下遍历
let p = head;
// 用于存放进位
let addNum = 0;
while( addNum || l1 || l2 ){
// 判断左右是否有值,没有则为0
const val1 = l1?l1.val:0,
val2 = l2?l2.val:0;
// 左右值相加并添加进位
const sum = val1+val2+addNum;
// 大于10进位
sum>=10?addNum=1:addNum=0;
p.next=new ListNode(sum%10);
p=p.next;
if(l1)l1=l1.next;
if(l2)l2=l2.next;
}
return head.next;
}
删除排序链表中的重复元素 II
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。
返回同样按升序排列的结果链表。
示例 1:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
var deleteDuplicates = function(head) {
// 创建链表节点指向头节点,创建指针指向首节点
let dummy = new ListNode(0, head), cur = dummy
// 后面两位有值的情况循环
while(cur.next && cur.next.next) {
// 如果后两位值相等 存储当前相等的值,循环把相同下一个节点指向下下个节点
if (cur.next.val === cur.next.next.val) {
const x = cur.next.val
while(cur.next && cur.next.val === x) cur.next = cur.next.next
}else {
// 否则当前节点指向下一个节点
cur = cur.next
}
}
return dummy.next
};
顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
/**
* @param {number[][]} matrix
* @return {number[]}
*/
var spiralOrder = function(matrix) {
let res = [];
while(matrix.length){
// 循环第一行横向打印
res = res.concat(matrix.shift());
// 循环右侧一列纵向打印
for(let i = 0;i < matrix.length;i++){
// 插入数组的最后一个数字
matrix[i].length && res.push(matrix[i].pop());
}
// 循环打印底层横向倒序打印
matrix.length && (res = res.concat(matrix.pop().reverse()));
// 循环左侧纵向向上打印
for(let i = matrix.length-1;i>=0;i--){
matrix[i].length && res.push(matrix[i].shift());
}
}
return res;
};
删除排序链表中的重复元素
存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。
返回同样按升序排列的结果链表。
示例 1:
输入:head = [1,1,2]
输出:[1,2]
/**
* @param {ListNode} head
* @return {ListNode}
*/
var deleteDuplicates = function(head) {
let p = head;
// 循环遍历链表
while(p && p.next){
// 与后一个数相等就跳过
if(p.val===p.next.val){
p.next=p.next.next;
}else{
p=p.next;
}
}
return head;
};
合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
var mergeTwoLists = function(l1, l2) {
let res = new ListNode(0);
let p = res;
while(l1 && l2){
if(l1.val<=l2.val){
p.next = l1;
l1 = l1.next;
}else{
p.next = l2;
l2 = l2.next;
}
p = p.next;
}
p.next = l1 ? l1 : l2;
return res.next;
};
链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
var getKthFromEnd = function(head, k) {
let fast = slow = head;
while(fast){
fast = fast.next;
if(k--<=0){
slow = slow.next;
}
}
return slow;
};
二叉树
DLR--前序遍历:根左右
LDR--中序遍历:左中右
LRD--后序遍历:左右中
二叉搜索树的第k大节点
给定一棵二叉搜索树,请找出其中第k大的节点。
示例 1:
输入: root = [3,1,4,null,2], k = 1
3
/
1 4
2
输出: 4
1.使用数组存储后排序取出结果
var kthLargest = function(root, k) {
let res = new Set();
const dfs = (root)=>{
if(!root)return;
res.add(root.val);
dfs(root.left);
dfs(root.right);
}
dfs(root);
res = [...res].sort((a,b)=>b-a);
return res[k-1]
};
2.使用反中序遍历用数组存储取结果
var kthLargest = function(root, k) {
let res = new Set();
const dfs = (root) => {
if(!root)return;
dfs(root.right);
res.add(root.val);
dfs(root.left);
}
dfs(root);
return [...res][k-1];
};
3.反中序直接判断并返回
var kthLargest = function(root, k) {
let num = 0,res = null;
const dfs = (root) => {
if(!root)return;
dfs(root.right);
num++;
if(num===k){
res=root.val;
return;
}
dfs(root.left);
}
dfs(root);
return res;
};
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例:
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5
腾讯什么信息部
翻转一棵二叉树
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2
输出: 1->2
示例 2:
输入: 1->1->2->3->3
输出: 1->2->3
数组
三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
/**
* @param {number[]} nums
* @return {number[][]}
*/
const threeSum = (nums) => {
nums.sort((a, b) => a - b); // 排序
const res = [];
for (let i = 0; i < nums.length - 2; i++) { // 外层遍历
let n1 = nums[i];
if (n1 > 0) break; // 如果已经爆0,不用做了,break
if (i - 1 >= 0 && n1 == nums[i - 1]) continue; // 遍历到重复的数,跳过
let left = i + 1; // 左指针
let right = nums.length - 1; // 右指针
while (left < right) {
let n2 = nums[left], n3 = nums[right];
if (n1 + n2 + n3 === 0) { // 三数和=0,加入解集res
res.push([n1, n2, n3]);
while (left < right && nums[left] == n2) left++; // 直到指向不一样的数
while (left < right && nums[right] == n3) right--; // 直到指向不一样的数
} else if (n1 + n2 + n3 < 0) { // 三数和小于0,则左指针右移
left++;
} else { // 三数和大于0,则右指针左移
right--;
}
}
}
return res;
};
最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
var maxSubArray = function (nums) {
let res = nums[0];
let sum = 0;
for (let i = 0; i < nums.length; i++){
if (sum > 0) {
sum += nums[i];
} else {
sum = nums[i];
}
res = Math.max(sum, res)
}
return res;
};
字符串
字符串相加
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。
提示:
num1 和num2 的长度都小于 5100
num1 和num2 都只包含数字 0-9
num1 和num2 都不包含任何前导零
你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式
var addStrings = function(num1, num2) {
let res = ""; // 存储结果
const max = Math.max(num1.length, num2.length); // 最大长度
let j = 0; // 进位
num1 = num1.padStart(max, 0); // 补全0
num2 = num2.padStart(max, 0); // 补全0
for (let i = max - 1; i >= 0; i--){
const sum = parseInt(num1[i]) + parseInt(num2[i]) + j;
sum >= 10 ? j = 1 : j = 0; // 进位
res = (sum % 10) + res; // 存储结果
}
if (j) res = j + res;
return res;
};
判断括号是否合法
function kuohao(s){
let stack = [];
let map ={
'{':'}',
'[':']',
'(':')'
}
for(let i=0;i<s.length;i++){
const item = s[i];
if(item in map){
stack.push(item);
}else{
const top = stack[stack.length-1];
if( item === map[top] ){
stack.pop();
}else{
return false;
}
}
}
return stack.length===0;
}
正则
url参数拆分
/**
* --- 题目描述 ---
*
* 实现一个函数,可以对 url 中的 query 部分做拆解,返回一个 key: value 形式的 object
*
* --- 实例 ---
*
* 输入:'http://sample.com/?a=1&e&b=2&c=xx&d#hash'
* 输出:{ a: '1', e: '', b: '2', c: 'xx', d: '' }
*/
// 1.正则
function toUrlParamsReg(url) {
const reg = /(?<=[?|&])(\w+)=?(\w+)?/g;
let res = {};
while (reg.exec(url)) {
res[RegExp.$1] = RegExp.$2?RegExp.$2:'';
}
return res;
}
// 2.split切割
function toUrlParamsSplit(url) {
let params = url.split('?')[1]; // 将问号后面的参数切割开
let splitRes = params.split('#')[0].split('&'); // 把#去掉,只需要#前面的参数数据
let res = {};
for (let i = 0; i < splitRes.length;i++){
const [k, v=''] = splitRes[i].split('='); // 切割key和vlaue,并且让v默认值为空
res[k] = v;
}
return res;
}
url 转化为指定结果
/**
* --- 题目描述 ---
*
* 实现一个 parseParem 函数,将 url 转化为指定结果
*
* --- 测试用例 ---
*
* 输入:url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled'
* 输出:
{
user:'anonymous',
id:[123,456],// 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
city:'北京',// 中文需解码
enabled: true // 未指定值的 key 与约定为 true
}
*/
// 正则匹配
function parseParemReg(url) {
// url = decodeURI(url);
const reg = /(?<=[?|&])(\w+)=?([\w%]+)?/g;
let res = {};
while (reg.exec(url)) {
let [k, v] = [RegExp.$1, RegExp.$2];
if (!v) v = true;
v = decodeURI(v);
// 判断里面是否存储过该Key,并且里面有value数据的情况下直接插入数据
if (res.hasOwnProperty(k) && res[k]) {
// 判断一下是不是数组 是数组就插入数据 不是就变成数组
if (Array.isArray(res[k])) {
res[k].push(v);
} else {
res[k]=[res[k],v]
}
} else {
res[k] = v;
}
}
return res;
}
去除左边的空格
const leftTrim = (str) => {
return str.replace(/^\s*/g, '');
};
排序
快速排序
function fastOrder(arr) {
if (arr.length<=1) return arr;
let item = arr[0];
let left = [],
right = [];
for (let i = 1; i < arr.length; i++){
if (arr[i] < item) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...fastOrder(left), item, ...fastOrder(right)];
}
二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例 1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例 2:输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
var search = function(nums, target) {
let left = 0, right = nums.length - 1;
while (left<=right) {
let mid = parseInt((right + left) / 2);
if (nums[mid] > target) {
right = mid-1;
} else if(nums[mid]<target){
left = mid + 1;
} else {
return mid;
}
}
return -1;
};
排序数组
给你一个整数数组
nums
,请你将该数组升序排列。示例 1:
输入:nums = [5,2,3,1]
输出:[1,2,3,5]
var sortArray = function (nums) {
console.log(nums);
if (nums.length < 1) return nums;
let left = [],right = [];
let mid = nums[0];
for(let i = 1;i < nums.length; i++ ){
if(mid <= nums[i]) right.push(nums[i]);
if(mid > nums[i]) left.push(nums[i]);
}
return [...sortArray(left),mid,...sortArray(right)];
};
动态规划
爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
var climbStairs = function(n) {
let a = 1,b=1;
while(n--){
[a,b]=[b,a+b];
}
return a;
};
面试抱佛脚
2、http状态码 知道多少说多少。。
3、vue的双向绑定原理
4、强缓存 协商缓存
6、协商缓存中etag lastmodify区别
7、jsonp实现原理
8、输入一个url到页面渲染的详细过程
9、说说计算机网络所有层级
10、代码 找出数组中重复的元素
TCP跟UDP的区别
TCP怎么保证有序
一句话总结他怎么保证有序?
面试官的话:他必须收到上一个数据帧的ACK后才发下一个数据
TCP三次握手
为什么是三次握手而不是两次握手
网络层次结构
数据链路层解决什么问题
ARP病毒
ARP是什么
这里我把ARP说成了NAT......
NAT的特点
数据库第一范式,第二范式,第三范式是怎样的
1NF 一言以蔽之:“第一范式的数据表必须是二维数据表”,第一范式是指数据库的每一列都是不可分割的基本数据项,强调列的原子性,试题中某一属性不能拥有几个值。比如数据库的电话号码属性里面不可以有固定电话和移动电话值
2NF 第二范式建立在第一范式的基础上,即满足第二范式一定满足第一范式,第二范式要求数据表每一个实例或者行必须被唯一标识。除满足第一范式外还有两个条件,一是表必须有一个主键;二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。
3NF 若某一范式是第二范式,且每一个非主属性都不传递依赖于该范式的候选键,则称为第三范式,即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。
sql注入原理
编译原理讲一下你的认识
你觉得webpack打包的过程哪些有用到编译原理的知识
你对编译原理状态机的理解
讲下你对C语言指针的理解
你觉得你的哪门课学的最好
node服务有写过吗
SSR的理解
***能优化多少
项目的难点
讲下对v-slot的认识
v-slot的原理是什么
有了解过vuex吗
react用过吗
react和vue的区别
单向数据流了解过吗
\1. 手写ES5的继承方法(尽可能多)
这块掌握的不咋地……只写出了原型链继承和构造函数继承方法;
\2. 了解过Vue双向绑定原理嘛?Vue3.0是怎么实现的?
数据劫持+观察者模式;
proxy;
\3. 数据劫持对数组有效吗?
无效;
\4. 手写代码模拟实现一下监听数组数据的变化?
磨磨蹭蹭了个半小时(面试官真的有够耐心的),总算实现出来了;
\5. 介绍一下自己做的项目?
balabala……
\6. 做项目时遇到过最大的困难是什么?
balabala……
\7. 怎么解决跨域问题的?
状态码302的意思?重定向
\4. 重定向之后会做什么?读取浏览器缓存,强缓存和协商缓存
\5. 302和强缓存什么关系?这个不是很清楚
\6. 做项目用的什么通信协议?HTTP
\7. HTTP协议底层是用什么协议?这个没答上来,然后出来了下面的问题(引导我回答出HTTP底层协议)
\6. 网络五层协议、七层协议?
\7. HTTP协议在哪一层?下面那一层是什么协议?
\8. 网络协议为什么要分层?
说一下web worker
click和onmousedown和onmouseup的执行顺序
说一下你知道的鼠标键盘响应事件方法
小程序源码
2.关于网络的认识
3.tcp和udp的区别
4.对应用层的了解
5.http和https的区别
6.https的连接过程
7.http有哪些方法,区别是什么,作用是什么
8.get和post
9.get可以带json吗
在项目的亮点是什么
用过哪些性能优化(懒加载,cdn)
响应式设计,em和rem
10.对web安全的了解
11.xss是什么
12.怎么处理xss
13.对数据结构有什么认识
14.数组和链表的区别,优缺点
15.做题:删除链表倒数第n个节点,LeetCode原题
16.看js代码输出什么,是关于this指向的
17.js数据类型有哪些
18.判断array的方法,写代码
19.关于vue的
20.生命周期
21.dom在什么时候加载
22.watch和computed用过吗,区别是什么
标签:function,return,请求,res,面试,let,准备,属性 From: https://www.cnblogs.com/mrkr/p/17444040.html