1、call、apply、bind、new使用及自定义实现
call、apply、bind都是js内置的api,作用是修改this指向,new则是执行一个构造函数,返回一个实例对象
call
call传入多个参数,第一个参数为需要绑定的this,后面参数为实际方法的入参
let callObj = {
name: 'call-change'
}
function changeThis(param1, param2){
console.log(`${param1}${param2}call==: ${this.name}`);
}
changeThis('参数1', '参数2'); // 参数1参数2bind==:
changeThis.call(callObj, '参数1', '参数2'); 参数1参数2bind==: call-change
apply
apply入参为两个参数,第一个参数同样为需要绑定的this,后面参数为数组
let applyObj = {
name: 'apply-change'
}
function changeThis(param1, param2){
console.log(`${param1}${param2}apply==: ${this.name}`);
}
changeThis('参数1', '参数2'); // 参数1参数2apply==:
changeThis.apply(applyObj, ['参数1', '参数2']) // 参数1参数2apply==: apply-change
bind
bind入参为多个参数,第一个参数也是需要绑定的this,后面参数则同样是实际方法的入参,但是调用bind后不会立即执行且参数可以分开传入,并且bind返回的是一个函数
let bindObj = {
name: 'bind-change'
}
function changeThis(param1, param2){
console.log(`${param1}${param2}bind==: ${this.name}`);
}
changeThis('参数1', '参数2'); // 参数1参数2bind==:
changeThis.bind(bindObj, '参数1', '参数2')
/*
ƒ changeThis(param1, param2){
console.log(`${param1}${param2}bind==: ${this.name}`);
}
*/
可以看出并没有打印出对应的参数,而是打印出了方法,由此可见bind并不会立即执行
let bindObj = {
name: 'bind-change'
}
function changeThis(param1, param2){
console.log(`${param1}${param2}bind==: ${this.name}`);
}
changeThis('参数1', '参数2'); // 参数1参数2bind==:
// 需要将bind的方法执行下才会打印处修改后的值
let bindfn = changeThis.bind(bindObj, '参数1', '参数2')
bindfn() // 参数1参数2bind==: bind-change
// 分开传参
let bindfn = changeThis.bind(bindObj, '参数1')
bindfn('参数2') // 参数1参数2bind==: bind-change
手动实现call
Function.prototype.myCall = function(context, ...args) {
context = context || window
args = args ? args : []
let s = Symbol()
context[s] = this
let res = context[s](...args)
delete context[s]
return res
}
let obj = { name: '中间的名字' }
function getName(firstName, lastName) {
console.log(`我的名字是:${firstName}${this.name}${lastName}`)
}
getName('高','风') //
getName.myCall(obj, '高','风')
手动实现apply
// call与apply区别在于入参的问题,修改args即可...args改为args
Function.prototype.myApply = function(context, args) {
// 判断是否传入context,如果未传入则赋值为window
context = context || window
args = args ? args : []
let s = Symbol()
context[s] = this
let res = context[s](...args)
delete context[s]
return res
}
let obj = { name: '中间的名字' }
function getName(firstName, lastName) {
console.log(`我的名字是:${firstName}${this.name}${lastName}`)
}
getName('高','风') //
getName.myApply(obj, ['高','风'])
手动实现bind
// 简易版,不考虑new的情况
Function.prototype.myBind = function (object) {
const that = this
let args = [].slice.apply(arguments, [1])
return function() {
that.apply(object, [...arguments, ...args])
}
}
// 调用
let bindObj = {
name: 'bind-change'
}
function changeThis(param1, param2){
console.log(`${param1}${param2}bind==: ${this.name}`);
}
let bindfn = changeThis.myBind(bindObj, '参数1')
bindfn('===22') // 参数1===22bind==: bind-change
// 因为bind返回的是一个函数,还需要考虑当传入的是new出来的对象的场景,new出来的对象this优先级
// 高于bind修改的优先级,得遵循这个原则
Function.prototype.myBind = function (object) {
if (typeof this !== 'function') {
throw new Error(this + 'must be a function')
}
let that = this
// 去除object的第一项为函数的参数,转为数组
let args = [].slice.apply(arguments, [1])
let bound = function() {
// 因为bind存在调用时传参的情况,所以要获取bound的参数
let boundArgs = [].slice.apply(arguments)
// 合并最终参数
let finArgs = [...args, ...boundArgs]
// 当new 调用的时候执行以下代码
if (this instanceof bound) {
// 判断当前bound原型链上的属性,因为存在箭头函数的情况故要考虑
if (that.prototype) {
// let sObj = new Object()
// bound.prototype = sObj.create(that.prototype)
function Empty(){}
Empty.prototype = that.prototype;
bound.prototype = new Empty();
}
let sRes = that.apply(this, finArgs)
var isObject = typeof sRes === 'object' && sRes !== null;
var isFunction = typeof sRes === 'function';
if(isObject || isFunction){
return sRes;
}
return this;
} else {
return that.apply(object, finArgs)
}
}
return bound
}
let bindObj = {
name: 'bind-change'
}
function changeThis(param1, param2){
console.log(`${param1}${param2}bind==: ${this.name}`);
}
let bindfn = changeThis.myBind(bindObj, '参数1')
bindfn('===22') // 参数1===22bind==: bind-change
手动实现new
// 正常关键字new的使用
function Person(param){
this.name = param;
}
let p = new Person('哥哥')
console.log(p) // Person {name: '哥哥'}
// 自定义new的实现
function myNew(cons, ...args) {
// new的对象必须是function
if (typeof cons !== 'function') {
throw new Error('构造函数必须得是function')
}
// 创建一个空的obj对象
let obj = new Object()
// 把构造函数cons原型链上的属性赋值给obj对象
obj._proto_ = Object.create(cons.prototype)
// 修改构造函数cons的this指向为obj
let res = cons.call(obj, ...args)
// 判断新生成的res是否是函数,不是的话返回obj
let isObj = typeof res === 'object' && typeof res !== null
let isFun = typeof res === 'function'
return isObj || isFun ? res : obj
}
function Person(param){
this.name = param;
}
let p = myNew(Person, '哥哥') // {_proto_: Persons, name: '哥哥'}
2、前端算法
3、CSS
布局水平或垂直居中方法
<div class="father">
<div class="child">
child
</div>
</div>
水平居中:
// 方案一
.father{
text-align: center;
}
.child{
display: inline;
// 或
display: inline-block;
}
// 方案二
.child{
margin: 0 auto;
}
// 方案三(定宽)
.child{
width: 200px;
position: absolute;
margin-left: -100px; // 或 transform: translateX(-50%)
left: 50%;
}
// 方案四 flex
垂直居中:
方案一
// 单行
.father{
height: 100px;
line-height: 100px;
}
// 多行
.father{
display: table-cell;
vertical-align: middle;
}
方案二(定高)
.child{
height: 200px;
position: absolute;
margin-top: -100px; // 或使用transform: translateY(-50%)
top: 50%;
}
方案三 flex
3、vue
vue修饰符
| stop | prevent | native | lazy | sync |
|---|---|---|---|---|
| 阻止冒泡 | 阻止默认事件 | 自定义click事件 | input输入完成后数据变化 | 子组件修改父组件 |
生命周期
-
beforeCreated:已实例化Vue,未初始化数据
-
created:已初始化数据,可以获取到data数据
-
beforeMounted: render函数开始执行,生成虚拟Dom,未挂载
-
mounted:真实Dom挂载
-
beforeUpdated:数据已更新,生成新的虚拟Dom
-
updated:新旧虚拟Dom已更新
-
beforeDestory:实例销毁前调用
-
destoryed:实例销毁后被调用,所有生命周期函数被停用,子组件被移除销毁
-
actived: 与keep-alive结合使用,组件被激活时使用
-
deactived: 与keep-alive结合使用,组件被停用时使用
4、promise
- promise是微任务,执行顺序在宏任务(setTimetout、setInterval)之前
- promise构造函数是同步的,promise.then()是异步的
- promise有三种状态,pending(等待)、fulfilled(完成)、reject(失败)
- promise.all接收一个promise的数组,返回的也是数组,都成功则成功,有一个失败就会失败
- promise.race也是接收一个promise的数组,但是只要有一个promise执行成功则结束
5、http相关
http1.1是目前使用最广泛的http协议
http1.0和http1.1对比
| http协议版本 | 方法 | 缓存处理 | 带宽优化 | 响应码 | HOST头域 | 长链接 |
|---|---|---|---|---|---|---|
| 1.0 | GET、POST、HEAD | 使用header里的If-Modified-Since,Expires来做为缓存判断的标准 | 存在带宽浪费 | 不支持 | 不支持 | |
| 1.1 | 新增OPTIONS、PUT、PATCH、DELETE、TRACE 和 CONNECT | Entity tag,If-Unmodified-Since, If-Match, If-None-Match | 优化带宽 | 新增24个响应码(409资源冲突/410资源被永久删除) | 支持HOST头域,如果没有Host会返回400 | 支持长链接,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。通过设置http的请求头部和应答头部,保证本次数据请求结束之后,下一次请求仍可以重用这一通道,避免重新握手 |
http1.1和http2.0
1.新的二进制格式(Binary Format):HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
2.多路复用:即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端
https和http
| 协议 | 证书 | 连接方式 | 默认端口 | 安全性 |
|---|---|---|---|---|
| http | 不需要证书 | TCP协议 | 80 | HTTP协议运行在TCP之上,传输的内容都是明文 |
| https | 需要申请CA证书 | SSL/TLS协议 | 443 | HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的(非对称加密,对称加密以及HASH算法) |
输入url回车发生了什么
1.DNS解析域名解析成ip地址:浏览器将 URL 解析出相对应的服务器的 IP 地址
(1. 本地浏览器的 DNS 缓存中查找 2. 再向系统DNS缓存发送查询请求 3. 再向路由器DNS缓存 4. 网络运营商DNS缓存 5. 递归搜索),并从 url 中解析出端口号
2.浏览器与服务器建立tcp链接
3.浏览器向服务器发起http请求
4.服务器返回给浏览器报文
5.浏览器渲染
6.关闭tcp链接
浏览器渲染过程
获取服务器报文后
1.解析dom树
2.解析css为style rule
3.dom树和style rule关联生成渲染树
4.Layout根据render tree关联节点信息
5.渲染
跨域
跨域是由于浏览器规定的同源策略导致的,同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源
跨域解决方案
<script>、<img>、<link>三个标签支持跨域,cookie、localStorage等缓存不支持跨域
| 方案 | 描述 | 缺点 |
|---|---|---|
| JSONP | 创建script标签模拟script资源请求的方式 | 只能是get请求 |
| 设置请求头 | Access-Control-Allow-Origin | 需要后端处理 |
| Nginx代理 | 使用代理服务器进行代理 | 搭建代理服务器 |
常用的传输协议
传输层有两类协议,TCP和UDP协议
- TCP协议:传输控制协议通过让数据发送方和数据接收方两方建立可靠连接,然后通过设定的格式以字节流的方式进行有序地无差错地数据传输。有流量控制、[拥塞控制]和错误控制
- UDP协议:用户数据报协议的数据发送方和接收方不建立连接进行数据传输,数据发送方只管尽快地发数据给数据接收方,并不确保数据是否被正确接收到,阻塞时会选择丢弃[数据包]来避免延迟(不可靠),但是传输速度快
http三次握手
第一次,客户端向服务端发起握手请求,设置SYN=1,随机产生起始序列号Seq码的数据包到服务端,等待服务端确认
第二次,服务端收到客户端请求后确认链接(可以接收数据),收到客户端发过来的SYN=1,知道这是一个连接请求,然后存起来客户端发起的序列号,同时自己也生成一个序列号,发起第二次握手请求,返回一个包含SYN和ACK标志(SYN=1,ACK=1)以及自己生成的序列号Seq、确认码ack(客户端序列号+1)的数据包到客户端
第三次,客户端收到服务端的回复后,发现标志ACK=1并且确认码ack=101且SYN=1,说明服务端接收到了确认连接的请求且同意连接,同时存储服务端发送的Seq码,然后客户端再回复服务端以下报文: 标志位(ACK=1)、Seq随机码(第一次生成的Seq码+1)、确认码ack(服务端的随机码+1)。等服务端接收到客户端端的报文后,解析标志位ACK=1且确认码为服务端之前发送的随机码+1后,双方建立连接
为什么要三次握手,两次可以吗 ?
需要考虑连接时丢包的问题,如果只握手2次,第二次握手时如果服务端发给客户端的确认报文段丢失,此时服务端已经准备好了收发数(可以理解服务端已经连接成功)据,而客户端一直没收到服务端的确认报文,所以客户端就不知道服务端是否已经准备好了(可以理解为客户端未连接成功),这种情况下客户端不会给服务端发数据,也会忽略服务端发过来的数据
四次挥手
第一次:当客户端发送完数据后,客户端向服务端发送释放连接的报文,报文包含FIN标志位(FIN=1),Seq(Seql=1101),这时客户端就无法再次发送数据了,只能接收数据
第二次:当服务端接收到客户端发出的FIN报文后会给客户端回复确认的报文,报文包含ACK标志(ACK=1)、确认码ack(客户端的Seq+1)、服务端的Seq码,此时服务端不会立即向客户端发FIN报文,因为有可能还有数据没发送完毕,所以此时服务端状态为等待关闭的状态
第三次:服务端所有数据发送完成后,此时会向客户端发送释放连接的报文,报文包含FIN(1)、ACK(1)、确认码ack(客户端的Seq+1,和第二次一样)以及Seq码
第四次:客户端收到服务端发的FIN后,就会向服务端发出确认报文,报文包含标志位ACK=1,确认号(服务端的Seq+1)及客户端的序号,客户端发出确认码后不会立即释放,而是会等待2MSL(最长报文段寿命的2倍时长)后断开连接,而服务端接收到确认报文后会立即断开,所以服务端的释放要早于客户端
强制缓存和协商缓存
强制缓存是在第一次请求资源的时候在http响应头设置一个过期时间,在有效时间内都直接从浏览去获取,常见的响应字段catch-ctrol和Expires
协商缓存是通过http响应头(etag、last-modified)来判断服务器上的资源是否过期,如果过期则重新从服务器获取,没过期则304指向浏览器缓存获取
304的过程
1. 浏览器请求资源时先命中资源的Expires和Cache-Ctrol,Expires受限于本地时间,如果修改了,会导致缓存失效,可以通过设置Cache-Ctrol:max-age指定最大生命周期,状态仍然返回200,但是不会有网络请求,在network里可以明显看到from cache的字样
2.强制缓存失效后会进入协商缓存阶段,首先验证Etag,Etag可以保证每个资源是唯一的,资源变化都会导致Etag发生变化,根据If-None-Match来判断是否命中缓存
3.协商缓存Last-Modifieds/If-Modify-Since,客户端第一次请求资源时会服务返回的的header里会携带Last-Modified字段,Last-Modified是该资源最后的修改时间,再次请求该资源的时候request里会携带If-Modify-Since字段,该值为上次的Last-Modified,根据资源的最后修改时间判断是否命中缓存