0%

前言

最近找到了一份实习工作,感觉也懈怠了很久,没有找工作的压力了(并不),最近老大希望每个人都能来做一下前端方面的分享,思前想向后就想到了这个主题。
选择这个主题的缘由,大概是因为历史特别有趣吧,哈哈,比如Unix是如何诞生的,网景的崩溃和火狐的新生,很感觉很神奇,好像窥视到了一点秘密,而且这也对理解现在的技术很所帮助。

这个主题会从http诞生开始一直到目前的http3,着重讲述整个协议的与前端发展的整个过程,不会涉及技术细节,不知道这个主题我能讲多少(其实取决于我能查到多少),反正尽量把有趣的都写上去233。

诞生之前

HTTP 0.9

RFC 7230 (2014)明确放弃对HTTP 0.9的支持

HTTP 1.0

在标准增加http前是否已经有人这样做了,后来标准才加进去?
强制增加了协议版本 额外的扩展头
什么时候有的cookie

HTTP/1.1

HTTP/2

HTTP/3

http诞生

参考文献

参考文章

HTTP 的进化

使用telnet实例讲解

1. 起因

最近想制作一个在散开的纸面画画的效果,纸面会按一定角度倾斜,就像下面这样↓
canvas
也就是给canvas元素加了transform: roate(-2deg)

2. 电脑端实现

在电脑端很容易实现,鼠标事件中有offsetX Y,能够获得鼠标位置相对于目标节点的位置,可以简单理解为相对于元素左上角的坐标。获得坐标后,再按坐标绘制到Canvas上,一切就OK了,不过到了移动端就有点麻烦了。

3. 移动端实现

移动端touch事件有以下几个属性

  • ClientX Y 相对于视口的坐标
  • pageX Y 相对于页面左上角原点的坐标
  • screenX Y 相对于屏幕的坐标标
  • movementX Y 相对于上一次坐标的坐标

然而就是没有offsetX Y,怎么办,自己模拟试试?

3.1 第一次模拟

  1. 获得pageX Y
  2. 通过offsetTop left计算元素左上角的顶点位置vertex
  3. 计算相对坐标

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getVertexPosition(el) {
let currentTarget = el
let top = 0
let left = 0
while (currentTarget !== null) {
top += currentTarget.offsetTop
left += currentTarget.offsetLeft
currentTarget = currentTarget.offsetParent
}
return { top, left }
}

let vertex = getVertexPosition(canvas)
canvas.addEventListener('touchmove',(e)=>{
let offsetX = e.pageX-vertex.left
let offsetY = e.pageX-vertex.top
})

在页面没有设置任何transform属性的情况下,这个代码是生效的,能获得正确的坐标点。
然而如果父元素或目标元素有任何transform属性,坐标就会错误,比如本文中的canvas元素,设置了transform: roate(-2deg),坐标就发生了偏移。
随后搜索了一下是否有类似的解决方案,结果都是这样的代码,都不能对transform元素生效。

3.2 偏移原因

偏移原因是因为变换后,元素的坐标轴已经改变了,而我们得到的坐标还是变换前的坐标,自然而然就发生错误了。
坐标图
如图所示,实际触摸点是绿色,本应也绘制在绿色坐标点,但是我们现在的坐标系仍然是之前的坐标系,就导致了绘制点相对于原来的坐标点绘制,也即紫色,导致绘制偏移。
解决办法也很简单,将触摸点按照新坐标系计算得到值,就是真实的相对坐标了。
步骤:

  1. 获得点击坐标(pageX pageY)
  2. 获得旋转角度 旋转中心
  3. 将旋转中心于元素顶点坐标相加,得到整个页面的旋转中心
  4. 旋转相同负角度(逆运算),将点击坐标还原到真实的坐标系
  5. 减去顶点坐标,获得真实相对坐标。

第二次模拟

  1. 首先获得计算后的样式,获得transform相关属性
    1
    2
    let style = window.getComputedStyle(el)
    console.log(style)

获得的transform属性如下

1
2
3
4
transform: "matrix(0.999391, -0.0348995, 0.0348995, 0.999391, 0, 0)"
transformBox: "view-box"
transformOrigin: "160px 240px"
transformStyle: "flat"

关于transform:matrix以及如何理解查看张鑫旭的文章
简而言之,就是transform属性都是通过transform:matrix进行矩阵计算得到坐标的。

  1. 解析transform属性
    由于这里涉及到矩阵运算,需要引入math.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    let transform = style.transform
    let transformOrigin = style.transformOrigin
    let origin = { x: 0, y: 0 }
    let matrix = math.ones([3, 3])
    if (transform !== 'none') {
    let originArray = transformOrigin.split(' ')
    origin.x = parseInt(originArray[0])
    origin.y = parseInt(originArray[1]) //矩阵的坐标变化是基于变换中心得。
    let matrixString = transform.match(/\(([^)]*)\)/)[1]
    let stringArray = matrixString.split(',')
    let temp = []
    stringArray.forEach((value) => {
    temp.push(parseFloat(value.trim()))
    })
    temp = [
    [temp[0], temp[2], temp[4]],
    [temp[1], temp[3], temp[5]],
    [0, 0, 1],
    ]
    matrix = math.inv(temp) //进行逆矩阵
    } else {
    matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
    }

这样我们就得到了原变换矩阵的逆矩阵和变换中心,就能将触摸点正确的还原到原坐标系了

  1. 计算

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function computPosition(obj){
    let { matrix, origin, vertex: { top, left },ponit:{x,y} } = obj
    x = x - left - origin.x
    y = y - top - origin.y
    let result = math.multiply(matrix, [x, y, 1])
    x = result[0] + origin.x
    y = result[1] + origin.y
    return (x,y)
    }
  2. 实际实现

    1. 由于子元素会受到父元素变换影响,因此需要遍历所有父元素
    2. 由于getComputStyle()和获得顶点坐标有较大性能消耗,最好将相关参数缓存起来
      实际代码实现见此https://github.com/Geylnu/touch-offset

其它

W3C给出了没有为touch事件添加offsetX Y的原因https://github.com/w3c/touch-events/issues/62

自己来实现观察者模式

今天写自己一个小demo时使用mvc的时候,想自己来实现观察者模式
也就是view层会观察model层数据的变化,相应渲染出改变后的数据

使用发布订阅模式

最开始想的办法是自己model拥有多个修改model.data的方法,如fetch(),patch(),delete(),post(),只需要每个方法里加一句

1
event.emit('dataChanged',this.data)

就能通知在监听的view层进行渲染了

不过有点不优雅,我需要在每个改变数据的方法后都加一句,很麻烦。

使用Object.defineProperty来自动化

之后想到了ES5提供的Object.defineProperty提供的getset存取描述符,对这个属性的访问和读取会分别触发getset函数, 也就是如果对data进行设置,就会触发set函数。

get()set()又是什么?

我们平常取值或者赋值都是这样的

1
2
data.val = 1 //现在val = 1
data.val //得到 val的值

在这个过程中,val就是一个值,获得val和修改val总是相等的,修改成多少,之后就会得到多少。

不过js是门很奇怪的语言,有时候会发现获得的值和修改的值不一样,比如cookie

1
2
3
document.cookie //xxx=111; yyy=222;
document.cookie = "zzz=333"
document.cookie //xxx=111; yyy=222; zzz=333

看这里,赋值和取值并不相等,就像是自己调用的是方法一样

1
2
3
document.cookie.get() //xxx=111; yyy=222
document.cookie.set("zzz=333")
document.cookie.get() //xxx=111; yyy=222; zzz=333

似乎是这样的感觉。
原理也就是这样,在js中我们可以把赋值这个过程当成函数调用
赋值调用set函数 ,取值使用get函数。

ES5就提供了Object.defineProperty来自己定义赋值和取值得行为
Object.defineProperty接受三个参数,要定义的对象,要定义的键名,定义行为的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var cookieList = []
Object.defineProperty(document,'myCookie',{
get(){
let cookieString = ''
cooieList.forEach((value)=>{
cookieString+value+'; '
})
return cookieString
},
set(value){
cookieList.push(value)
return value
}
})

document.myCookie // "" 空字符串
document.myCookie = "aaa=111"
document.myCookie //"aaa=111; "
document.myCookie = "bbb=222"
document.myCookie //"aaa=111; bbb=222"

这就是我们仿写的一个cookie,取值变成了调用get函数,赋值变成了调用set()函数
ES6有了定义对象的语法糖,也更简洁明了些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let firstName = 'Barack'
let lastName = 'Obama'

let person = {
get name() { return firstName + ' ' + lastName },
set name(value) {
let nameArray = value.split(' ')
firstName = nameArray[0]
lastName = nameArray[1]
return value
}
}

person.name //Barack Obama
person.name = 'Karl Marx'
firstName //Karl
lastName //Marx

其实在这里设置getset, 有点像java, java中一切皆对象,类中常常有私有变量不能被外部访问,就通过getName()setName()暴露api
通过这种方式,就能对赋值和取值进行控制,比如赋值的时候检测值是否合法这些,js就直接省略了写函数这部分,直接就能把赋值、取值的行为当作函数调用。
不过这种也很误导,赋值和取值竟然会不相等,那为什么不直接告诉我这是一个方法?实际情况下不应该胡乱使用getset


另外查了下,语法似乎更像c#

1
2
3
4
5
6
Class example
{
public string a {get;set;}
public string b {get;set;}
public string c {get;set;}
}

好了,回到正题,现在我们可以在赋值时,通知View层数据改变了

1
2
3
4
5
6
7
8
9
let model = {}
Object.defineProperty(model,'data',{
set(){
//do xxxxx
event.emit('dataChanged',this.data)
}
})

model.data = 'xxx' //触发set()

不过马上就发现Object.defineProperty 并不怎么好用

  1. get set存取描述符会导致递归问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let model = {}
    Object.defineProperty(model,'data',{
    set(value){
    this.data = value
    event.emit('dataChanged',this.data)
    }
    })

    model.data //递归调用 页面会卡死

    解决办法很简单,不使用data就可以了,比如使用this._data,或者使用闭包,利用外部环境的变量。

  2. 如果model.data的值不是原始类型,而是对象,那么对model.data对象的的更改不会触发set()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    let model = {}
    Object.defineProperty(model,'data',{
    get(){
    console.log('get')
    return this._data
    },
    set(value){
    this._data = value
    console.log('set')
    return this._data
    }
    })

    model.data //get
    model.data.push({a:1}) //get
    let data = model.data //get
    data.push({b:2}) //不会调用get set
    data //不会调用get

    在实际情况中,往往data都是一堆对象,存储的是引用,解决办法大概可以判断是否是对象,如果是,就递归的定义getset,或者判断这是否是一个会更改data的函数,总而言之是件挺麻烦的事

  3. Object.defineProperty只能设置已知的属性,不能对未知的属性进行设置
    这就导致上面递归解决方案行不通,因为在运行时定义的属性的key是未知的,以就无法设置get,set

使用Proxy来更优雅的实现

关于Proxy的说明可以点击这里,Proxy可以看作是Object.defineProperty()的完全升级版,可以拦截各种对对象的操作,其中就包括getset

1
2
3
4
5
6
7
8
9
10
11
let array = []
let data = new Proxy(array,{
set(target,key,value,receiver){
console.log(`set ${key}`)
return Reflect.set(target,key,value,receiver)
}
})

let model={data}
model.data.push({a:1}) //set 0 set length 这里push操作会修改'0'和'length'
array.push({b:2}) //原对象不会触发set

Proxy翻译为代理,其实挺贴切的,从上面的例子中可以看出,Proxy并不会修改原始对象的行为,而是会生成一个代理对象,用于拦截各种操作。

另外关于Reflect还是不太明白,阮一峰在ES6文中是这样说的

Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

但我在测试Proxy中,发现使用Reflect.set(target,key,value,receiver)和直接修改target[key]=value是完全一样的,这里不太明白。

最后贴下大概的最终代码

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
//能够检测所有变动的核心,递归检测所有赋值为对象的情况,并代理这个对象
function proxyAllobj(target, key, value, receiver,handler) {
console.log(`SET key:${key} value:${value}`)
/*
排除value 为null的情况,因为typeof null === 'object'
如果这个对象已经是被代理的对象了,不重新代理
*/
if ( value && typeof value === 'object' && Reflect.get(target, '_isProxy', receiver) !== true) {
value = new Proxy(value, handler) //使用新的代理对象替换原对象
console.log(`正在代理化${key}`)
let obj = value
for (const newKey in obj) { //递归检测这个对象中的对象
if (obj.hasOwnProperty(newKey)) {
const nextValue = obj[newKey];
proxyAllobj(obj, newKey, nextValue, obj)
}
}
}
return Reflect.set(target, key, value, receiver)
}

let handler = {
get(target, key, receiver) {
console.log(`GET key:${key}`)
if (key === '_isProxy') { //由于无法判断一个对象是否为代理对象,所以使用一个预留字段来检测是否为代理对象
return true
}
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
return proxyAllobj(target, key, value, receiver,handler)
},
}

let data = new Proxy([], handler)

data.push('a')

let ddd = 2
data.push({ ddd })

data[1] = 4
data[1] = { aaa: { hi: 'hi' } }

data[1].aaa.hi = 5 //能够检测到
let test = data[1].aaa
test.q = 'haha' //能够检测到

这只是一个小demo,能够实现data内任何数据的改变都能检测到,可以复制到浏览器控制台验证,可能还存在bug,实际情况下应该还会涉及更复杂的错误处理还有性能方面的问题

参考文章

前言

自己初中的时候有过一段时间很好奇密码学相关的问题,当时就了解到了非对称加密,觉得很神奇,只要被公钥加密,就只有私钥才能解开,当时想,加密过程不过就是按照规则进行一些数学计算而已,怎么会不可逆呢?非对称加密是怎么做到这些的?怎么让网络变成可信的?
当时百度还挺可靠的,搜索到了一篇讲的很好文章,大概有了认知,可惜那篇文章找不到了;今天正好看到一个前端问题:https原理是什么?就再学习了一次,写这篇博客记录下来。

https为什么出现?

因为网络链路默认是不可信任的,HTTP在整个传输过程中无法保证数据不被窥探、篡改,常见的中间人攻击就是利用了这个原理,日常经常发现的网页出现莫名奇妙的新增广告,通常叫做运营商劫持,就是一种中间人攻击。

https如何防止中间人攻击的?

https在TCP传输层和HTTP应用层之间再加了一层SSL/TSL(传输层安全协议),用于对HTTP报文进行加密。

SSL/TSL采用基于公钥的加密算法,比如最常用的RSA算法

RSA是一种非对称加密算法,在了解非对称加密前,先了解一下对称加密,对称加密指的是在加密或者解密时使用相同的密钥。就像是为数据上一把锁,同时也要把钥匙拿给解密的人。
使用对称加密的过程往往是这样的:

  • A生成随机密钥K
  • A与B通过某种方式
  • A密钥K加密明文数据
  • A传输密文数据
  • B使用密钥K解密密文
  • 得到明文数据
    对称加密的一大问题就是存在密钥交换环节,想要解密必须传输密钥,而传输密钥这个环节,往往容易泄露密钥,整个加密环节也就不安全了。

非对称加密算法就解决了这个问题,密钥加密解密使用不同的密钥,避免了密钥交换环节,最著名的非对称加密算法就是RSA算法

RSA算法是怎么工作的?

RSA算法利用了数论方面的知识,详细的数学解释可以看阮一峰的博客,简单易懂(虽然自己只看了半懂233)

整个过程简单概述,为

生成

  1. 随机选择两个不相等的质数pq
  2. 计算pq的乘积n
  3. 计算n的欧拉函数φ(n),φ(n) = (p-1)(q-1)
  4. 随机选择一个整数e,条件是1< e < φ(n),且e与φ(n) 互质
  5. 计算e对于φ(n)的模反元素d
  6. ne封装成公钥,nd封装成私钥。

加解密

  1. 假设需要加密的密文为m,m必须是整数,且m必须小于n
  2. 利用公钥(n,e)计算加密的密文c=m^e mod n
  3. 利用私钥(n,d)解密密文 m=c^d mod n

理论上公钥也可以当作公钥,也可以当作私钥,但实际上为了方便客户端进行加密和防止破解,公钥使用的e往往较小,私钥的更大。

对抗中间人攻击

只引入非对称加密算法实际上并不能阻止中间人攻击,非对称密钥加密依赖于公钥的正确性。
想想这样的场景:

  1. 小昂给小蕾想秘密的通过网路传输些暧昧信息
  2. 小昂生成一对公钥和私钥,将公钥传输给小蕾
  3. 小蕾收到公钥,并且将信息利用公钥进行加密
  4. 小昂收到信息

整个过程看样子很完美
但是这其中有一个很重大的缺陷,网络中一切数据都是可以被截获和伪造的,小蕾其实并不能确认公钥就是小昂发送的,公钥在传输中存在被掉包的可能

  1. 小昂生成一对公钥和私钥,将公钥传输给小蕾
  2. 小艾在中途截获了传输的公钥,并替换成了自己公钥
  3. 小蕾利用小艾的公钥加密了信息
  4. 小艾利用了自己的私钥解密了小蕾的信息
  5. 小艾篡改了信息,并使用小昂的公钥重新加密了修改后的信息
  6. 小昂收到了被篡改的信息。

这是个很大的问题!非对称加密并没有解决中间人攻击。

证书机制

证书机制利用了另一个特点,即被私钥加密的信息,可以被公钥解密,可以利用私钥对消息进行一次签名,证明自己就是私钥的拥有者。

证书机制是这样工作的

  1. 首先由受信任的机构(CA)生成一组公钥和私钥
  2. 软件商(浏览器、操作系统)会将这个公钥内置在自己软件中,并信任这个公钥。
  3. 小昂为了自己的通信安全,证明自己就是公钥的拥有者,决定使用数字证书。
  4. CA收到小昂的申请,确认了小昂的身份。
  5. CA先生成了这个证书的基本信息
    • 使用者: 小昂
    • 公钥:xxxxx
    • 有效期限:xxxxx
    • 公钥算法:RSA
    • 证书版本:xxxx
  6. CA利用hash算法计算上列信息的hash值,并用自己的私钥进行加密,并和上面的信息组合,成为最终的数字证书。
    这时小昂就得到了自己的数字证书,可以证明自己时公钥的拥有者了。

小昂与小蕾接下来的通信就是这样的

  1. 小蕾向小昂发送请求
  2. 小昂出示自己的数字证书
  3. 小蕾对得到的证书进行验证,使用CA的公钥解密被加密的数字证书hash,计算数字证书hash值,以保证证书未被篡改
  4. 小类使用数字证书的公钥和算法进行加密密文

使用对称密钥

在使用非对称密钥中,由于公钥是公开的,被私钥加密的内容实际可以被认为等同于明文,小蕾向小昂发送信息,只有小昂能够解密,但是任何持有公钥的人都能解密小昂使用私钥加密的内容。
并且非对称加密相比对称加密,运算复杂度要高一个数量级,如果要传输大量信息,性能消耗会很大,在这种情况下,引入对称密钥就是很有必要的了。

  • 在上文的前提下,小蕾随机生成一个密钥,并将这个密钥使用公钥加密,发送给小昂
  • 小昂使用私钥解密这个私钥,并在接下来的过程中,转用这个对称加密密钥进行通信

在这个过程中往往为了前向安全性,使用的是DH密钥交换算法协商密钥,不依赖于RSA算法

增强安全性

  • 为了防止重放攻击,SSL/TLS还会在密文中附带时间戳
  • 为了保证内容完整性,SSL/TLS会计算内容的hash值并一同加密发送
  • 为了防止从 https降级成http,https拥有HSTS(HTTP Strict Transport Security),会强制客户端使用https

Cookie由来

谈到Cookie不得不谈到http协议,wiki上的定义⬇

设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法

由于这个设计目的,早期http协议被设计为无状态的协议,一个HTTP协议通信过程往往是建立连接>传输内容>关闭连接,整个过程十分简单。

然而万维网发展的比想象中快太多,HTML不再只是单纯的文档,还被用于交互,有了登陆注册等保存状态的需要,然而http协议是无状态的,这个场景需要下,Cookie就诞生了。

Cookie具体是怎么样的?

Cookie其实就是一段文本信息,由浏览器储存在本地硬盘上,服务器通过Set-Cookie设置Cookie,浏览器通过Cookie字段附上设置的Cookie信息。

emm,还是看一个完整的请求吧


响应

1
2
3
4
5
6
7
HTTP/1.1 200 ok
Date: Sat, 02 Feb 2019 14:24:28 GMT
Content-Type: text/html
Content-Length: 182
Connection: keep-alive
Set-Cookie: tgw_l7_route=80f350dcd7c650b07bd7b485fcab5bf7; Expires=Sat, 02-Feb-2019 14:39:28 GMT; Path=/
...

下次请求

1
2
3
4
GET / HTTP/1.1
Host: example.com
Cookie: tgw_l7_route=80f350dcd7c650b07bd7b485fcab5bf7
...

服务端通过在响应头中添加Set-Cookie: xxx=xxx的方式以键值对的形式设置Cookie,除此之外还有其它字段可以控制Cookie的字段

  • Domain
    指定Cookie从属于那个域名,在请求对应域名时会带上这个Cookie,默认为当前文档域名,不包含子域名,若主动设置Domain,则一般会包含子域名,例如设置了Domain=geylnu.com,则blog.geylnu.com也是可以访问这个geylnu.com的Cookie。

  • Path
    在域名符合的前提下,如果请求的路径与Path的路径相匹配就在请求中附上这个Cookie,路径匹配从前到后匹配,以/作为分隔符

  • Expires
    在指定日期Cookie到期,Cookie到期后服务器会自动删除这个Cookie,设置时间格的式为Thu, 01 Jan 1970 00:00:00 GMT这样的格林尼治标准时间,没有设置该属性或Max-age,Cookie会自动在浏览器关闭后清除。值得注意的是响应头中的时间是基于服务器时间生成的,而客户端的时间可能与服务端不一致,这种情况下使用max-age属性设置过期时间是一个更好的选择。

  • Max-age
    在指定秒后Cookie到期,作用等同于Expires,当同时设置了ExpiresMax-age时,Max-age优先级更大;由于不直接设置时间,规避了服务端时间和客户端时间可能不一致的问题。

  • Http-only
    Cookie只允许浏览器发出请求时在Cookie中附带上,通过Set-Cookie修改,不允许通过js脚本得到拿到,这可以有效的防止XSS攻击

  • Secure
    指定该Cookie只在加密协议(https)才能把这个Cookie发送到服务器,如果设置该属性是通过http协议设置的,浏览器会自动忽略该属性(自动纠错,因为http下设置这个属性没有意义)。如果协议为https,该属性默认打开

一个完整的Cookie设置示范:

1
Set-Cookie: sessionId=hihihi; Expirers=Wed, 01 Jan 2020 00:00:00 GMT; Max-age=10000; Http-only; Secure;

js操作方式


1
document.cookie //返回"xx=xxx; yy=yyy"


1
document.cookie = 'xxx=hihihi'

这里由于设定了getset属性,读是一次读写全部Cookie,写为一个个Cookie单独设置。

Session

好了,现在http协议支持将状态保存在客户端了,之后每个客户端都是有名有姓的人了。
但是客户端保存状态信息是不可靠的,客户端可以修改自己本地的Coookie值,Cookie值在非https加密协议的情况下,也容易被截获获取到敏感信息(这里吐槽下我校教务系统,密码明文直接写在Cookie中)。

对应的解决方法就是在服务端也维护对应的状态,大部分状态信息都由服务端记录,客户端只需要记录一个由服务端随机生成的sessionId用于服务端识别,这就是Session了,一般Session储存在内存,也有其它实现,甚至无Session的方式。

LocalStorage

由于Cookie在每个请求中都会被添加,网络开销很大,很多非敏感状态信息也不需要服务器来存储,Cookie的大小有也很小,只有4kb;为了解决这个问题,HTML5规范就提出了LocalStorage,LocalStorage和cookie一样都是存在本地电脑磁盘上,数据形式也是hash表,存储形式也都是字符串。

不过和cookie不同的是LocalStorage不会跟随请求发送,仅限通过浏览器api操作,储存空间一般为5M大小,远大于cookie可以使用的空间,并且没有过期期限,除非用户主动清理会一直存在,通常在能用LocalStorage的地方尽量都用LocalStorage,前端不应该使用Cookie。

sessionStorage

sessionStorage与LocalStorage基本相同,只是sessionStorage过期不同,另外还有个显著的特点:sessionStorage会在每个窗口创建时初始化一个新的会话,也就是即使是同一个域名下不同窗口,sessionStorage也是不一样的。

js操作方式

1
2
3
4
5
localStorage.setItem('key','value')
localStorage.remove('key')
localStorage.getItem('key')

localStorage.clear()//移除全部

虚拟DOM是什么?

虚拟DOM其实就是在原有DOM的基础中,在js中再做一层DOM的抽象。

虚拟DOM有什么用?

在一些需要大量更改DOM的情况下,比如更新表格内的内容,重新排序等,会引起重绘、重排、引起大量的性能消耗,虚拟DOM就是针对这个问题的一个解决方案,在更改前都是针对虚拟DOM进行操作,不对真实DOM进行更改,更改完成后使用diff算法对比DOM树,只操作需要更改的DOM节点,减小 性能开销。

虚拟DOM怎么用?

首先需要创建一个类似的虚拟DOM抽象数据结构

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
class VNode {
constructor(tag, children, text) {
this.tag = tag
this.text = text
this.children = children
}

render() {
if(this.tag === '#text') {
return document.createTextNode(this.text)
}
let el = document.createElement(this.tag)
this.children.forEach(vChild => {
el.appendChild(vChild.render())
})
return el
}
}

function v(tag, children, text) {
if(typeof children === 'string') {
text = children
children = []
}
return new VNode(tag, children, text)
}

这里定义一个简单虚拟node类,拥有本元素和子节点,拥有render()函数。

改变使用diff算法对比

1
2
3
4
5
6
7
8
9
10
11
12
13
function patchElement(parent, newVNode, oldVNode, index = 0) {
if(!oldVNode) {
parent.appendChild(newVNode.render())
} else if(!newVNode) {
parent.removeChild(parent.childNodes[index])
} else if(newVNode.tag !== oldVNode.tag || newVNode.text !== oldVNode.text) {
parent.replaceChild(newVNode.render(), parent.childNodes[index])
} else {
for(let i = 0; i < newVNode.children.length || i < oldVNode.children.length; i++) {
patchElement(parent.childNodes[index], newVNode.children[i], oldVNode.children[i], i)
}
}
}

实际远远比这个复杂,拥有更多细节要处理,不过理解的话够用了。

简介

本文主要简单介绍一下风景园林/环境设计专业软件需要啥子具体要求,顺带科普一下参数具体有什么用

软件篇

我们专业主要使用的软件是以下几个

  • CAD
    2019版本官方配置表

    • cpu 2.5-2.9Ghz处理器 建议:3+Ghz处理器
    • 内存 8GB 建议:16GB
    • 显卡 显存1Gb,29Gb/s带宽,与DirectX 11 兼容 建议:显存4GB,106GB/s带宽,与 DirectX 11 兼容
      来源:AutoCAD系统要求

      总结:cpu这里的意思就是CPU主频尽可能的高,多核心多线程对于速度不会有太大提升,对于显卡的要求很粗略,只是需要一个快速且大的显存,目前一般的独立显卡都能满足这个要求,实际上显卡对于CAD来说并不重要。内存自然是越大越好,8G温饱,16G小康

  • PS
    2018版本官方配置表

    • cpu Intel® Core 2 或 AMD Athlon® 64 处理器;2 GHz 或更快处理器
    • 内存 2GB或更大RAM,推荐8GB
    • 显卡 支持OpenGL 2.0
      在其帮助页面还给出了更多的解释

      总结:PS的cpu这里给出的配置相当模糊,我去查询了第三方测评。第三方测评的意见认为,PS多线程优化仍然不够好,大量的核心并不能明显提升PS性能,但是第8代CPU由于直接多了两个物理核心,尽管优化不好,但仍然有看见的提升,Core i7 8700K比Core i7 7700K快9-14%

      由于PS需要处理大量高质量素材文件,非常占用内存和暂存盘空间,对于PS来说,更大的内存空间,更高的内存频率能够明显提高其性能表现。由于内存条较为昂贵,在不升级的情况下,把固态硬盘作为PS的暂存盘也是提高其性能的一个手段.

      PS对于显卡的要求并不高,参考官方的帮助页面,部分对于图像变形,模糊,选择和蒙版会使用GPU进行加速,一些滤镜效果必须使用显卡,但是要求并不高,集成显卡就已经能较好完成大部分的工作,一般的独立显卡,如1050,部分功能的确会相比集成显卡有更大的提升(30%),但更高的显卡,对于提升整体性能表现的帮助相当小。

  • SU
    SketchUp Pro 2018官方配置表

    • cpu 1Ghz处理器 推荐2G+hz处理器
    • 内存 4GB 推荐8+GB
    • 显卡 OpenGL 3.0或更高,显存512MB及以上
      来源:SketchUp Hardware and Software Requirements

      总结:su做为一个3D建模软件,把一个任务拆开由多个线程负责的优化是十分困难的,多核CPU不会为它带来提升,单核性能决定了它的性能表现。SU对于显卡的要求很模糊,我去其官方社区找到了答案,SU在大多数情况下,中档的显卡和顶级的显卡,差别是很轻微而不明显的,在SU中视窗内显示的模型由显卡和CPU共同管理,显卡负责模型的光线显示效果(例如阴影,纹理等),cpu负责几何体的生成(如面,边缘),当模型越来越多时,cpu性能就会构成瓶颈。
      除此之外,绘图卡(专业卡)与游戏卡相比,不会带来更好的性能变现,反而贵很多。

  • v-Ray

    • cpu 奔腾4及以上,所有支持SSE3指令集的CPU
    • 内存 4GB和最低4GB的虚拟内存 ,推荐8GB及以上和8GB以上虚拟内存
    • 显卡 无
      来源:System Requirements - V-Ray for SketchUp

      总结:这个配置实际上是一个能装vray的配置,实际上要看具体硬件。v-ray有两种渲染模式,CPU渲染和GPU渲染,CPU渲染模式很成熟了,目前大多都是CPU渲染模式,vray利用CPU进行渲染时,会充分利用所有线程,榨干cpu所有性能,对于vray cpu 渲染来说,在频率差不多的情况下,选择具有多核多线程的cpu能更好的提高渲染速度。
      v-ray还支持使用GPU渲染,使用GPU渲染速度更快(如果显卡比较好的话),但兼容性没有传统的CPU渲染好,可能出现不正确的渲染结果,不过可以使用这个模式来快速预览渲染效果。

      无论那种模式,在渲染时都会使用大量贴图文件和产生渲染后的数据,使用cpu渲染会去占用大量的内存和虚拟内存,使用GPU渲染会占用大量的显存。建议至少8G的内存,高参数、大场景的情况下会占用更多的内存。

  • Lumion
    Lumion8 官方配置表

    • 显卡 最低2,000 PassMark得分,2GB显存,支持DirectX 11或更高,推荐8,000 PassMark得分,6GB或更多显存
    • cpu
      最低:理想主频为3.0+ Ghz
      推荐:尽可能高的主频,理想主频为4.0+ GHz, 更低的主频会对GTX 1080 或 Titan X构成瓶颈,超过四个cpu核心不会带来性能提高,像 i7-4790K i7-6700K i7-7700K这种主频为4.0+Ghz的CPU是个好的选择,低于3.4G+的至强系列处理器则不被推荐。
    • 内存
      最低 8Gb(简单场景),并且尽可能高的内存频率
      推荐 16GB,尽可能高的内存频率
    • more
      Lumion 官方非常棒,给出了相当详尽的配置建议,他们自己也推荐了笔记本,pc配置,品牌台式机,甚至配件价格,推荐去看看他们的原文。
      来源:system-requirements

      总结:官方非常棒,不需要总结了,这里我单独说下PassMark得分,可以去

  • 3Ds MAX

    • cpu 支持 SSE4.2 指令集的 64 位 Intel® 或 AMD® 多核处理器
    • 显卡 官方给了一个建议显卡列表
    • 内存 至少 4 GB RAM(建议使用 8 GB 或更大空间)

      总结:3dmax官方这里给出的数据也很少,主要因为3ds Max自带渲染引擎,渲染引擎对cpu的要求自然没有上限了,3dmax在建模的时候仍然主要依靠单个CPU核心,不过显卡能够提供更多的帮助。渲染的话同vray,可以GPU和CPU,值得注意的是同价格的游戏卡与专业卡的表现几乎差不多,你不应该选择专业卡。

  • more 还有许多其它软件,这里不再列举,如果你想可以去自己查询哟~ 网络最大的百科全书!

    硬件篇

    总体来说,计算机的核心功能只有两个,计算和储存,计算机的大量部件都是为这两者服务的。计算机的计算功能一般是由cpu(中央处理器)和显卡提供的,而储存功能则有多个部件实现。

  • cpu(中央处理器)
    cpu是计算机的核心,就像人的大脑一样,处理各个部件传来的数据,对数据进行计算后返回结果;影响cpu快慢有很多因素,架构,指令集,主频,物理核心数等,但是一般来说同代处理器相同系列,主要看的是主频和核心数。

    • 主频
      主频以Hz为单位,我们常常见到的是2.6Ghz,4.0Ghz这种,对于主频的理解,可以理解为cpu就是一个大工厂,主频就是这个大工厂的的工人生产一个东西有多快。

      cpu的频率不是固定的,可以在一定范围内自动或手动调节,频率越高,会带来更高的功耗和更多的热量。笔记本由于各个厂商的设计不同,散热能力强弱不一,能够提供的功耗也不一样,同一cpu的性能表现也会不一样。

    • 核心数
      核心数就是通常说的几核cpu,代表的是cpu工厂有几个工人一起生产,值得注意的是有多少个核心并不意味着能力的翻倍提高,因为一个软件运行时的工作,很难分配给不同核心,往往还是只由少数几个核心负责,多核心对于优化差的软件来说没有明显提高。

      • 睿频
        睿频就是多核cpu提高那些优化差的软件的方法,通俗的来说,就是cpu这个大工厂接了个只能一个人(单核心)单独干的活,而其它工人就闲着了,cpu觉得这不行,干脆让其它几个工人不干了,把工资(能提供的功耗)交给正在干活的工人(核心),让工人干的更起劲(单核的主频就提高了),也就干的更快了。

      • 超线程
        超线程技术和睿频技术相反,是把一个核心分成两个线程(之前是一个核心对应一个线程),通俗来说,就是给工人(核心)点工具,让它一个人做两个人的活,虽然干的没一个人做一件事那么快,但总体来说做的更多了(无情老板压榨员工),超线程技术会略微降低cpu主频。

  • 内存
    内存也是电脑至关重要的部件,也是程序运行时所在的空间。计算机计算首先得得到数据,就像工人工作必须得到零件一样,快速的得到数据对于提高计算机速度有很大的帮助,对于我们平时一些小操作卡顿,往往并不是cpu算的太慢了,而是数据传给cpu的时间太久了。

    但是储存的价格实际上极其昂贵,比如位于cpu上速度极快的L1缓存大概只有几十kb,稍微慢一点的L2缓存有几百kb,更大更慢的的L3则有几MB的样子,那我几百G的小姐姐怎么办?这里就要引入计算机存储的局部性原理了,局部性原理表示计算机中使用的数据,如果第一次被使用,第二次被使用,那块数据第三次也会被使用,简单的说就是计算机使用的数据大量都是重复的,我们可以只把最经常用到的数据存在最快的空间中,不经常的存在慢一些的空间中,这样就可以省储存空间了。

    运用这个原理,现代计算机就设计出了分级的储存架构。cpu自己拥有一部分缓存的能力,分为L1,L2,L3,速度分别递减,容量递增,随后是内存,容量很大多了,比如4G 8G 16G,但是比cpu的缓存慢多了(差了几个量级),不过价格也降下来了,几百元不等,以上都是缓存,由于物理上的设计,其实并不能长久储存数据,一断电(关机)数据就会消失,真正储存数据的是硬盘,详细看硬盘章节。
    整个储存结构呈金字塔型,越上面速度越快,空间越少,存储的越常用的数据。

    通俗的说就是数据就是零件,拿给cpu大厂加工,常用的零件会被放到cpu大产的内部的仓库,cpu工厂的工人工作直接去取就是了,由于空间有限,这些仓库都非常非常小。内存就是一个远离cpu的另外仓库,空间大多了,不过距离很远,拿到cpu工厂要很久,更远的就是硬盘了,与cpu工厂的距离几乎不可想象了,所以一般要做什么(软件运行 )都会先拿到内存仓库作为中转。

    • 内存频率 越快内存交换数据的能力越强,能够更快的把硬盘数据存入内存。
    • 虚拟内存 由于内存实际上对于我们的计算机来说还是太少,现在一个浏览器都能占上G的内存,那么多程序,都要用内存,可能会超出实际内存,这时怎么办?系统会在硬盘上划出一块空间,这块空间就叫虚拟内存,如果内存快要满了,就把一部分它认为暂时不会工作的程序的内存移入虚拟内存,腾给其它程序用,程序要工作了,就又把它移回进内存,虚拟内存是内存不足的无奈之举,通常还是建议使用更大的内存。
  • 显卡
    cpu是个全能手,什么都能算,但是对于浮点数(就是带小数点的数)计算不是很擅长,而对图形的运算常常涉及到大量浮点数运算(比如求三角形的斜边长),特别涉及到3D的图形,cpu来计算会慢死,显卡(GPU)就是为了解决这个问题,显卡拥有超多简化的核心,这些核心设计出来就只是为了进行浮点数运算,可以做的特别小,特别多,一个显卡里面可以有上千个这种微型处理器(官方称之为流处理器)。CPU与GPU的关系就像人脑和计算器一样,一个什么都能做,但是就计算而言没计算器厉害。

    评价显卡的性能基本和cpu一致,不过显卡单个核心的主频远远低于CPU,但是核心数远远高于CPU,由于GPU做的事很单一,通过跑分来评测其性能是一个很好的办法。

    显卡主要分为两种,为集成显卡和独立显卡

    • 集成显卡
      一般指的就是cpu内置的显卡,一般比较弱,不过日常看看视频,玩玩小游戏,最低画质的网游还是可行的,GPU和CPU一样,计算也需要先把数据传给它,和内存称呼类似,我们把显卡所使用的快速存储空间叫做显存,集成显卡没有独立的显存,而是把内存一部分当做自己的显存,这部分通常非常小,一般只有几百MB
    • 独立显卡
      更加强大的显卡,目前游戏显卡主流就两家,AMD和NVIDIA(英伟达),NVIDIA比较强势。独立显卡拥有自己独立的散热器,独立的供电,独立的显存(主流1G或更多),CPU享受的待遇它都有。

      独立显卡根据用途还可以分为游戏卡,绘图卡(专业卡),和计算卡,游戏卡就是我们平常使用的显卡,很擅长一帧帧画面粗略的快速计算,绘图卡则经过优化,更擅长绘图软件中的大量顶点的空间计算,值得注意的是,事实上专业卡与游戏卡并没太多不一样,绘图卡在绘图领域表现得更好,更多是因为在驱动层面对专业软件做了更好的适配。计算卡则是专门用来计算大量数据,主要用在科学计算和人工智能领域,一般人不需要。

  • 硬盘
    硬盘是我们计算机中最后一级存储结构,是最慢,也是容量最大的一级,计算机实际运行中,会先将硬盘数据读入内存,方便更快的与cpu进行数据交换,而程序运行中最常使用的指令这些数据,会被缓存在cpu自带的缓存中。

    硬盘分为两种硬盘

    • 机械硬盘 通过磁头划过磁盘得到数据,速度主要看单碟容量和磁盘碟片的转速度,消费级主要分为5400转和7200转,一般来说容量越大,转速越高,速度也就越快。
    • 固态硬盘 固态硬盘使用的是闪存颗粒来储存数据,相比机械硬盘,速度有极大的提升,特别是在大量小文件写入/读取上。

      由于固态硬盘的价格比较昂贵,目前通用的方法是一个128或256的固态硬盘作为C盘,软件主要安装在C盘里,另外配一个大容量的机械盘,用来存电影,游戏这些庞大且不常用的数据。

  • 屏幕
    由于屏幕对于设计行业来说相当重要,我这里单独讲下屏幕
    我们说屏幕,主要说的是屏幕的面板,屏幕面板的参数觉得我们视觉体验。

    • 面板类型
      主流的两大屏幕面板类型是TN屏和IPS屏
      • TN屏:窄视角,色域低,屏幕响应时间短 主要作为游戏屏幕(主要还是便宜)
      • IPS屏:宽视角 色域低,屏幕响应时间略长 比较综合的屏幕
      • 这里主要说的是低端屏幕面板,高端则很不一样
    • 色域
      色域是对于我们设计领域最重要的一个属性,色域是一种计算机对颜色编码的方法,不同色域拥有不同的对纯色的定义和不同的色彩范围。
      目前主流有以下几个色域标准:

      • sRGB 是微软与惠普与1996年开发定下的色域标准,是目前世界上广泛使用的色域标准,手机,电脑默认都是这个色域标准,不是该色域标准的显示屏,需要载入对应的色彩管理配置文件,否则会使用近似的色彩代替,带来色彩的偏差。也因为这个原因,一般我们普通使用只需要使用一个尽可能覆盖100%sRGB标准色域的的显示屏就好了(其实达到95%以上的sRGB覆盖就算是专业级了)
      • NTSC 一个很老的彩色电视标准实际上目前已经很少使用了,但是一般测评和厂商展示数据都是使用这个标准,NTSC标准色域的色彩范围比sRGB大,并且大部分和sRGB重合,sRGB大约占NTSC标准的72%,所以我们经常说希望笔记本色域达到72%NTSC,但是实际上72%NTSC并不等于100%sRGB,可能是72%NTSC色域中的一部分颜色在100%sRGB色域显示之外,评测根据缺失和多出来的颜色,会说偏绿之类的结果,实际意思是相对于sRGB来说,色域覆盖偏向绿色。
      • adobeRGB adobe推出的色域标准,色彩范围略微超过NTSC,对于CMYK打印色域标准有更好的兼容性,而且由于adobe全家桶存在,对自己的adobeRGB兼容也很好,专业的平面设计领域可以使用adobeRGB。
    • 色准
      色准就是颜色显示是否准确,目前一般使用 ΔE(色彩偏离度) 来表示,越大表示与标准色彩差距越大,一般来说低于1就是很优秀的专业屏幕了。色准可以通过自己进行校色,部分降低色彩偏离度,提高色准。

      显示器的指标其实是很复杂的,还有亮度,游戏关心的刷新率,色深等等,我这里仅仅只介绍对颜色影响至关重要的两个参数。

变量

定义变量

  • 颜色

    1
    2
    3
    4
    5
    @bgColor: #34c5c5;

    body{
    background: @bgColor;
    }
  • url

    1
    2
    3
    4
    5
    @url: "./xxx/"

    body{
    background: url("@{url}dog.png")
    }

最后url就会被合成,方便改路径

  • 声明值
    1
    2
    3
    4
    5
    @background: {background:red;};

    body{
    @background();
    }

嵌套

可以把选择器写在括号中,表示下一级选择器

1
2
3
4
5
6
7
8
9
#header {
color: black;
.navigation {
font-size: 12px;
}
.logo {
width: 300px;
}
}

解析完毕后

1
2
3
4
5
6
7
8
9
#header {
color: black;
}
#header .navigation {
font-size: 12px;
}
#header .logo {
width: 300px;
}

&表示当前选择器的父选择器

混合

可以在css中使用其它选择器里定义的属性,比如

1
2
3
4
5
6
7
8
9
.bordered {
border-top: dotted 1px black;
border-bottom: solid 2px black;
}

#menu a {
color: #111;
.bordered;
}

M:Model
V:view
c:controller

模块化 把每个分成一个个js小文件
每个模块把操作的html作为一个view,作为v(我觉得应该是model)
再生成一个controller,参数是view,接受view进行操作

这里最好将controller封装成一个对象,像下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
var controller = {
view: null,
init: function (view){
this.view = view
this.bindEvents()
}

bindEvents: function (){
xxx.addListener('xxx',function (){
xxx
})
}
}

值得注意的是监听函数中调用的是触发事件本身的元素,也就是这里取this取到的不是controller对象本身,这种情况可以使用箭头函数,箭头函数自身不具有this,使用的this向上寻找到的对象就是正确的了。

同时由于多个模块相互分离,并不知道其它模块的情况,可能出现全局变量污染的情况,因此,需要使用立即执行函数。

js中在ES6前不能使用{}包裹创建局部变量,避免全局变量污染可以使用function的创建局部变量环境。

1
2
3
function xxx(){
...
}.call()

但是这个方案实际还是存在问题,xxx其实也是一个全局变量

升级版就出现了

1
2
3
function (){

}.call()

然而遗憾的是这个方法会报语法错误

再升级版

1
2
!function (){
}.call()

使用其它单目运算符也能起到一样的效果,整个包裹大括号也是可行的,但可能出现bug,比如被视为函数实参。
还可以用随机数,不过有点丑陋

同时还可以使用闭包

1
2
3
4
5
6
7
!function (){
var person ={}
person.name = 'xxx'
window.getname = function (){
return person.name
}
}

如何向后端请求数据?

  • form表单提交,使用method指定提交方式,action为提交的地址。
  • a标签点击能发请求
    这两者都要刷新页面或者新开页面

  • img可以发请求,但请求的必须是图片 不需要增加到页面

  • script和link也可以发起请求,但link和script只能加载指定格式,但是需要加到页面

之后,ie5在js中引入ActiveX对象(api),使js可以直接发起http请求
随后其它浏览器也跟进,取名XMLHttpRequest
不到一年,谷歌推出gmail.com,这里可以说前端真正诞生了

ie6后来也成为了安装最多的浏览器60%,ie因此就膨胀了,随后微软拆开ie6的开发人员,只更新安全功能,让chrome也跟了上来

ajax

Jesse James Garrett 将一下技术取名AJAX(异步的javaScript和 XML

  1. 使用XMLHttpRequest发请求
  2. 服务器返回xml格式的字符串
  3. js解析xml,并更新局部页面(现在用JSON)

使用XMLHttpRequest的方法

1
2
3
4
5
6
7
8
9
10
myButton.addEventListener('click',()=>{
let request = new XMLHttpRequest()
request.open('GET','/XXX') //初始化配置 请求方式忽略大小写 默认异步
request.onreadystatechange = ()=>{
if (request.readyState === 4 && request.status === 200){
console.log(request.responseText) //响应内容
}
}
request.send() //发送
})

网络响应的时间会很长,这段时间已经够javaScript执行很多任务了,所以需要使用异步

readystate

描述
0 open()方法还没有被调用
1 send()方法还没有被调用
2 send()方法已被调用,响应头和响应状态码已经返回
3 正在下载响应体
4 请求全部完成

onreadystate 要注意放在前面

一般不用管300状态码. 浏览器会自动处理

解析数据

可以使用DOM api解析xml,但是很复杂,现在已经过时了。
1所以用什么更好的方法更好的表示数据呢?

后来道格拉斯·克罗克福发明了一种轻量的资料语言JSON

JSON语法

JSON支持object array两种数据组织方式,值支持string number object array true false null
没有undefined

json没有变量,不支持引用,没有原型链

  • string 必须具有""
    支持转义符\\

注意,永远返回的是字符串,只是将其解析为对象
解析:JSON.parse(string)

同源策略

同源策略要求协议+端口+域名一模一样才允许发起AJAX请求,保证安全

本质是禁止一个域名的js在未经允许的情况下,不得读取另一个域名的内容。但浏览器并不阻止向另一个域名发送请求。

不是同源的网页不能使用ajax发起请求(实际上发起了,只是不能读取内容),其他都可以

简单规避方法就是使用CORS,服务端设置Access-Control-Allow-Origin: xxx响应头 表示允许xxx跨域访问

设置header

必须在open()和send()之间调用。
send()可以为post 设置请求内容

getAllResponseHeader获得所有响应头

tcp进行分片传输,因此会先拿到先拿到最开始的状态包,就可以先判断是否需要继续接受等之后的逻辑

传多个变量很容易让别人不知道自己具体想传什么,这里可以采用传个对象的方式,这样就有名字了

设置header 接受多参数

析构赋值,把名字一样的赋到同名变量里

1
2
param = {a:'aga',b:'gag',c:'aga'}
let {a,b,c} = param

对于函数可以采用这个方式

1
2
3
function ({a,b,c}){//取得参数中的abc

}

还有很多方式比如对象键名为字符串,不可以调用变量,但是[x]: sss可以

自己写回调可能不同库不一样,回调函数名不统一,所以需要规范出来

Promise写法:

1
.then() //第一个是成功第二个是失败

具体写法

1
2
3
return new Promise(function (resolve,reject){
xxx
})