前端 http request
Q1:http协议:
什么是http协议:
协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则,超文本传输协议(http)是一种通信协议,它允许将超文本标记语言(html)文档从web服务器传送到客户端的浏览器
通过因特网传送万维网文档的数据传送协议。目前我们使用的http版本是http/1.1其前身是http/1.0。关于两个版本不同,下面会不时提及。在整个iso传输层次模型中http处于最高层次,而tcp是处于下面一层传输层,
也就是说http是基于tcp协议建立的连接,所以http传输建立之前需要先建立tcp连接,也就是三次握手,在建立tcp连接之后方可真正请求响应请求。
http传输过程详解:
当我们发送一个ajax请求,或者地址栏输入url后,我们的浏览器就给web服务器发送一个request,之后服务器处理完成后返回响应的response给浏览器。之后浏览器拿到数据进行解析里面数据从而生成我们页面或者组装数据。
期间传输还有可能经历了代理服务器(目前很多很多网站都用代理服务器,主要原因是其隐蔽。。)从而实现服务器端文件缓存。
http传输是面向连接的,也就是说如果连接没有中断,可以继续发送请求,这个设置可以在请求头Connection来设置,例如:我通过一个url请求了一个html页面之后,经解析,html页面中包含对图片的请求,则会直接再向服务器发起请求而不必重新建立tcp连接。等到所有请求都就绪,方可完成一次页面加载或者请求完毕。
Web服务器,浏览器,代理服务器
当我们打开浏览器,在地址栏中输入URL,然后我们就看到了网页。 原理是怎样的呢?
实际上我们输入URL后,我们的浏览器给Web服务器发送了一个Request, Web服务器接到Request后进行处理,生成相应的Response,然后发送给浏览器, 浏览器解析Response中的HTML,这样我们就看到了网页,过程如下图所示
我们的Request 有可能是经过了代理服务器,最后才到达Web服务器的。
过程如下图所示
代理服务器就是网络信息的中转站,有什么功能呢?
1. 提高访问速度, 大多数的代理服务器都有缓存功能。
2. 突破限制, 也就是FQ了
3. 隐藏身份。
url详解:
无论是ajax请求还是地址栏输入url,都要用到请求地址,请求地址用来描述需要请求的资源位置以及筛选方式:大体结构如下
http://www.temas.com/myBlog/file/date.PHP?name=yuchao&age=26#modfiled
这个url分为几个部分:
http:表示底层使用的协议(如http、https、ftp)
www.temas.com:表示服务器域名(或者是ip地址)
/myBlog/file/date.php:表示资源路径
?name=yuchao&age=26:发送给服务器数据
#modfiled:锚点
http消息结构:
整个http消息结构分为request以及response两部分:为了便于讲解,我从w3c截取一个图如下:
这是从chrome的Network截图,且不管每个浏览器将其如何区分,按照我们看到的来分组:
第一部分是请求资源地址以及请求方式属于request部分
第二部分是返回状态码,属于response部分
第三部分是request部分,第四部分是response部分。
总体来说整个请求分为两个部分我们来分析里面主要的结构如下:
request部分分析:
请求的url即我们地址栏输入的url或者ajax请求的那个参数url。
请求方式:常用的有get、post以及head请求:
head请求:head请求是一种返回不呈现数据的请求,也就是只请求一个报文头,通常用于请求一个文件去判断文件是否更新或者在我的项目中去请求服务器时间。
get与post比较:
get请求一般都会用来查询资源信息,post请求一般会用来更新资源信息。
get提交数据方式是将参数放置url之后用&来分开例如http://www.temas.com/myBlog/file/date.php?name=yuchao&age=26,
post请求可以以对象字面量形式进行参数传输:{name:"yuchao";age:"26"},所以通过post方式发送的请求中包含内容这一项,而get请求直接将内容附在url之后
正是由于数据传输方式不同导致get传输数据量需要在url字节限制范围之内,而post几乎无限制。同时get参数放置于url中也不利于安全。
Accept:表示浏览器可以接受的类型,一般浏览器都会发给服务器*表示通配所有类型。text/html类型就表示我们常说的html文档。
当我们规定了一种类型时候而服务器没有这种类型可以返回,则会抛出一个406状态码的错误(no acceptable)
Accept-Encoding:浏览器自身声明接受的编码方式,通常是压缩方法;
Accept-Language:浏览器自身声明可以接受的语言例如中文:zh-CN;
cookie:将cookie数据发送给服务器
Connection:可选值有:
keep-alive:当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接,
重新向该服务器发送请求时候不会重新经过三次握手建立链接,而是直接可以进行请求,这个请求时间段可以由服务器端Keep-Alive字段进行设置,
当过了这个时间段之后没有任何请求则关闭该连接。http1.0中默认是close,而目前应用的1.1版本中默认是keep-alive长连接。具体应用看下面response介绍该key;
Connection: close 代表一个Request完成后,客户端和服务器之间用于传输HTTP数据的TCP连接会关闭, 当客户端再次发送Request,需要重新建立TCP连接。
User-Agent:客户端使用的浏览器以及操作系统
Cache-Control:浏览器缓存机制,一般会有max-age=值,或者no-cache或者public、private;
If-Modified-Since:浏览器缓存内容最后修改时间;
if-None-Match:和ETag一起工作,比较两者值 获取内容是否改变。
response部分分析:
status:返回状态码,
HTTP/1.1中定义了5类状态码, 状态码由三位数字组成,第一个数字定义了响应的类别
100-199 提示信息 - 表示请求已被成功接收,继续处理
200-299 成功 - 表示请求已被成功接收,理解,接受
300-399 重定向 - 要完成请求必须进行更进一步的处理
400-499 客户端错误 - 请求有语法错误或请求无法实现
500-599 服务器端错误 - 服务器未能实现合法的请求
我们来看一下一些常见的状态码:
200:OK,表明请求成功完成,所有资源成功发送给客户端;
302:重定向,例如google在中国被黑掉之后,只能转战利用香港服务器去请求,我们输入www.google.com,
服务器就会返回302 Found,并且客户端接收到的response中location字段包含一个新的url地址,然后浏览器会根据这个地址重新发送一个新url的request;
304:使用的缓存文件
400:客户端请求与语法错误,不能被服务器解读;
403:服务器拒绝服务;
404:请求资源不存在;
500 Internal Server Error 服务器发生了不可预期的错误503 Server Unavailable 服务器当前不能处理客户端的请求,一段时间后可能恢复正常
Keep-Alive:长连接设置的值,如下图截图:
有两个值:timeout以及max,例如:Keep-Alive:timeout=5,max=100,只有当connection为keep-alive并且服务端支持时候才会生效。
其中timeout表示超时时间,max表示最大连接数。即:在5秒之内服务器始终保持空闲连接,在这五秒之内可以发送请求不必重新建立连接,超过这个时间则会重新建立连接进行请求。
在保持持久连接之间每发送一个请求max值就会减少一个,直到为0为止则会自动断开连接。
一般实际开发中,这个值的设置要根据具体网页中嵌入的请求个数去设置:例如网页中有20个图片,五个外部脚本,三个css样式表。则可以根据传输速度设置超时时间5-20秒之内,max值设置为30-100;
这样设计初衷就是为了既能减少不必要的tcp连接,又能避免频繁的请求造成服务器连接池冗余。
这个值从根本上来说跟前端没有太多关系,但是在网站性能优化很是关键。选择多次建立tcp连接还是选择空余一段时间请求被浪费,就要看实际需求以及能否设置出一个合理的Keep-Alive值
Conent-Length:表示返回实体内容长度大小,一般应用在返回静态页面或者一张图片并且数据量不大时候被设置;大小为bite字节;例如一张图片的请求:Connent-Length:630;请求一个图片截图如下:
Transfer-Encoding:即服务器端不是一个已知的固定的返回实体时候,服务器会一边产生数据,一边发送给客户端,
这时候服务器就需要用Transfer-Encoding:chunked分块编码来代替Conent-Length,设置该key后Content-Length就失效了。
对于前段来说只需关心返回的状态是否是成功即可,但是对于后台需要用到这个设置来判断客户端是否接受完全部数据。详细请参考
Date:服务器返回数据时间,我经常就用这个值来取得服务器时间
Etag:与if-modified-since配合使用;
Last-Modified:作用: 用于指示资源的最后修改日期和时间。一般都用来处理缓存,
Content-Type:作用:WEB服务器告诉浏览器自己响应的对象的类型和字符集,例如:Content-Type: text/html; charset=utf-8,Content-Type: image/jpeg
server:指明服务器软件版本;
Referer:告诉服务器该请求是在哪个链接发过来的,据此可以统计从某个页面跳转过来次数;
X-powered0by:表示该网站开发技术
ajax修改获取header:
利用xmlHttp.setRequestHeader来设置request请求头:例如:xmlHttp.setRequestHeader('cache-control','no-cache');
利用xmlHttp.getResponseHeader来获取response头信息;例如:xmlHttp.getResponseHeader("Date");
另外 request.setCharacterEncoding("UTF-8")也可以设置发送到服务端数据编码格式(一般来说发送的编码格式跟服务端解析格式必须是一致的)
Q2:从输入 URL 到页面加载完的过程中都发生了什么事情?
1)把URL分割成几个部分:协议、网络地址、资源路径。其中网络地址指示该连接网络上哪一台计算机,可以是域名或者IP地址,可以包括端口号;协议是从该计算机获取资源的方式,常见的是HTTP、FTP,不同协议有不同的通讯内容格式;资源路径指示从服务器上获取哪一项资源。
例如:http://www.guokr.com/question/554991/
协议部分:http
网络地址:www.guokr.com
资源路径:/question/554991/
2)如果地址不是一个IP地址,通过DNS(域名系统)将该地址解析成IP地址。IP地址对应着网络上一台计算机,DNS服务器本身也有IP,你的网络设置包含DNS服务器的IP。
例如:www.guokr.com 不是一个IP,向DNS询问请求www.guokr.com 对应的IP,获得IP: 111.13.57.142。这个过程里,你的电脑直接询问的DNS服务器可能没有www.guokr.com 对应的IP,就会向它的上级服务器询问,上级服务器同样可能没有,就依此一层层向上找,最高可达根节点,找到或者全部找不到为止。
3)如果地址不包含端口号,根据协议的默认端口号确定一个。端口号之于计算机就像窗口号之于银行,一家银行有多个窗口,每个窗口都有个号码,不同窗口可以负责不同的服务。端口只是一个逻辑概念,和计算机硬件没有关系。
例如:www.guokr.com 不包含端口号,http协议默认端口号是80。如果你输入的url是http://www.guokr.com:8080/,那表示不使用默认的端口号,而使用指定的端口号8080。
4)向2和3确定的IP和端口号发起网络连接。
例如:向111.13.57.142的80端口发起连接
5)根据http协议要求,组织一个请求的数据包,里面包含大量请求信息,包括请求的资源路径、你的身份
例如:用自然语言来表达这个数据包,大概就是:请求 /question/554991/ ,我的身份是xxxxxxx。
6)服务器响应请求,将数据返回给浏览器。数据可能是根据HTML协议组织的网页,里面包含页面的布局、文字。
数据也可能是图片、脚本程序等。现在你可以用浏览器的“查看源代码”功能,感受一下服务器返回的是什么东东。如果资源路径指示的资源不存在,服务器就会返回著名的404错误。
7)如果(6)返回的是一个页面,根据页面里一些外链的URL,例如图片的地址,按照(1)-(6)再次获取。
8)开始根据资源的类型,将资源组织成屏幕上显示的图像,这个过程叫渲染,网页渲染是浏览器最复杂、最核心的功能。
9)将渲染好的页面图像显示出来,并开始响应用户的操作。
以上只是最基本的步骤,实际不可能就这么简单,一些可选的步骤例如网页缓存、连接池、加载策略、加密解密、代理中转等等都没有提及。即使基本步骤本身也有很复杂的子步骤,TCP/IP、DNS、HTTP、HTML:每一个都可以展开成庞大的课题,而浏览器的基础——操作系统、编译器、硬件等更是一个比一个复杂。不是计算机专业的同学看了上面的解释完全不明白是很正常的,可能会问为什么要搞得那么复杂,但我保证这每一个步骤都经过深思熟虑和时间的考验。你输入URL即可浏览互联网,而计算机系统在背后做了无数你看不到的工作,计算机各个子领域无数工程师为此付出你难以想象的努力。
Q3:http 缓存机制:
文章分为三部分,我们先来统一梳理一下一个缓存请求的过程,然后从请求头以及响应头缓存相关字段进行解析,最后总结一下前端需要了解的对于缓存的操作
一 . 缓存过程:
当一个用户发起一个静态资源请求的时候,浏览器会通过以下几步来获取资源
当第一次发送请求,http返回200的状态码,
如果没有关闭缓存请求的话(没标明不使用缓存,下述)则会在返回头中返回包含last-Modified以及Etag和Expires的字段(这些字段下面慢慢说),然后将文件保存在Cache目录下;
当后续请求该文件时候,先在本地查找该资源,如果在本地缓存找到对应的资源,但是不知道该资源是否过期或者已经过期, 则发一个http请求到服务器,然后服务器判断这个请求,
如果请求的资源在服务器上没有改动过,则返回304, 让浏览器使用本地找到的那个资源
而如果当服务器发现请求的资源已经修改过,或者这是一个新的请求(本地无对应资源),服务器则返回该资源的数据,并且返回200,
当然这个是指找到资源的情况下,如果服务器上没有这个资源,则返回404
经过上面的流程梳理,我们基本了解整个缓存处理过程,不过对于前端来说,我们需求无非就是使用缓存或者不使用缓存,在了解下面内容之后我们再根据原理去慢慢实现我们的需求。
二 . http头部缓存相关key:
首先我们要先根据http请求头以及响应头 先看一些跟缓存相关的报文头cache-control, if-none-match, if-modified-since, Etag,expires, last-modified,
这些在上一篇文章HTTP前端详解中也提及过留在这篇文章进行分析;
request header缓存相关:
cache-control:
其缓存指令对于前段常用的有如下no-cache、no-store、max-age这几个值;分别来说一下
no-cache:
表面意为“数据内容不被缓存”,而实际数据是被缓存到本地的,只是每次请求时候直接绕过缓存这一环节直接向服务器请求最新资源,由于浏览器解释不一样,
例如ie中我们设置了no-cache之后,请求虽然不会直接使用缓存,但是还会用缓存数据与服务器数据进行一致性检测(也就是说还是有几率会用到缓存的),
firefox中则完全无视no-cache存在,详细解释见no-store;
no-store:
指示缓存不存储此次请求的响应部分。与no-cache比较来说,一个是不用缓存,一个是不存储缓存;按理来说这个设置更加粗暴直接禁用缓存,
但是具体实现起来 浏览器之间差异却特别大,一般不会直接用该字段进行设置,不过no-store是为了防止缓存被恶意修改存储路径导致信息被泄露而设置的,
毕竟有它的用处,在firefox中实现缓存是通过文件另存为将缓存副本保存到本地,直接利用no-cache对其是无效的,如果加上no-store设置的话 则可以起到与no-cache一样的效果;
即:cache-control:no-cache,no-store;可以确保在支持http1.1版本中各大浏览器回车后退刷新无缓存;
再加上Pragma: no-cache设置兼容版本1.0即可(不过为了防止一致性检测时候的万一我们还是最好加上一致性检测的内容,如下所示几种方式);
max-age:
例如Cache-control: max-age=3;表示此次请求成功后3秒之内发送同样请求不会去服务器重新请求,而是使用本地缓存;同样我们如果设置max-age=0表示立即抛弃缓存直接发送请求到服务器
一致性检测分为两种方式:1.检测日期是否过期,检测资源是否更新;
if-none-match:
该字段与响应中的eTag一起使用,表示检查实体是否有更新改变;客户端第一次发送请求时候响应报文会包含字段Etag,表示资源状态,当资源改变后该值也会改变(客户端不必关心该值怎么生成)
然后缓存保存下该字段,第二次已经有该缓存时候在浏览本地缓存时候会将该值赋给if-none-match字段发送给服务器,服务器将发送的值与当前的状态进行对比,
如果值一样的话则答复304去使用缓存数据,如果值改变了则发送最新数据给客户端替代现有缓存数据,并且返回状态200;
if-modified-since:
该字段与last-modified配合使用,跟上述原理差不多,都是响应端先返回一个last-modified时间字段,再次请求时候 request头部会将缓存中的last-modified字段拿出来赋给if-modified-since,
发送给服务器,服务器去判断时间是否过期,如未过期则返回304,告诉客户使用缓存数据,如果过期则重新返回一个last-modified并且返回200;
repsonse header缓存相关:
Etag:
刚才也说过 是跟if-none-match配合去使用,它根据实体内容生成的一段hash字符串(类似于MD5或者SHA1之后的结果),可以标识资源的状态。 当资源发送改变时,ETag也随之发生变化。
使用Etag主要是为了解决根据时间无法解决的问题:比如文件修改频繁(秒之内修改),导致根据时间无法判断是否更新;以及修改时间变了,但是内容没变(我们应该认为该文件是没变的)
expires:
表示缓存过期时间例如:expires:Mon Dec 30 2011 11:01:19 GMT,跟cache-control中的max-age作用一样,不过在碰见max-age之后,该值会被覆盖从而被max-age替代;
last-modified:
表示文件最后修改时间;
另外响应端的报文头也有对cache-content的规定,与request大体相同,另有未提及的 欢迎大家补充
三 . 实现有关前端对于缓存的操作:
使用缓存:
默认情况下,浏览器都会使用缓存数据,
在f5刷新情况下 浏览器会发送一致性验证去服务器验证是否使用缓存,而浏览器直接回车则表示直接应用缓存不需要去服务器验证;所以我们就按照f5刷新去解释实现使用缓存:
一般来说前端默认是使用缓存的,默认情况下服务器端以及前端都会使用缓存数据,或者是根据etag或者是根据max-age或者是根据expires根据服务器不同去不同实现;
实际中大部分不需要我们手动去实现,而有些我们不确定是否使用缓存的情况下我们可以手动加以干涉强制使用缓存数据:
例如某个静态文件包括html或者图片我们需要使用缓存来提高处理速度,
方法一:
在服务器进行配置其max-age或者expires使其设置一个过期值为当前一年之后。这样每次进行检验时候都会使用缓存中文件.例如在.htaccess中
<IfModule mod_headers.c>
<FilesMatch ".(gif|jpg|jpeg|png|ico)$">
Header set Cache-Control "max-age=604800"
</FilesMatch>
方法二:
前端设置if-modified-since去设置一个上次修改时间大于当前日期,
方法三:
服务器端根据etag去判断是否匹配来根据实际业务来使用缓存;
后面两个方法属于弱缓存数据头,需要浪费http连接,所以建议使用第一种方式;
禁用缓存:
方法一:
可以在meta标签标明<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
方法二:
也可以动态去setRequestHeader,强制不用缓存设置组合如下:
cache-control='no-cache,no-store'
pragma='no-cache'
if-modified-since=0;
方法三:
请求端设置if-modified-since为已经过期的某个时间,可以是几年前或者几十年前。
方法四:
服务端设置Expires为过期某个时间,例如PHP中header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
实际开发中如果需要一致性检测则尽量去配合Etag以及last-Modified去进行比较然后返回使用缓存还是新数据;这个有点偏服务器端了,不再赘述
方法五:
url后面加随机数或者时间戳url += “&random=” + Math.random()这个方法js以及php经常用,原理就是每个请求的url都不一样这样一来缓存中找不到对应数据,就自动去服务器寻找最新资源;
最后回到上篇开始提到一个问题;请求服务器时间,由于缓存导致时间返回问题:
问题回顾:由于没有设置缓存,默认浏览器是读取缓存数据的,导致我没清缓存情况下一直使用缓存数据,这样一来我就取不到真正的当前服务器时间了。
解决方式:利用上述方法二+方法四,动态在请求时候修改header以及根据随机数去请求最新数据,问题解决!!!
Q5:http无状态 connection keep-alive
HTTP协议是无状态的和Connection: keep-alive的区别
无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。从另一方面讲,打开一个服务器上的网页和你之前打开这个服务器上的网页之间没有任何联系。
HTTP是一个无状态的面向连接的协议,无状态不代表HTTP不能保持TCP连接,更不能代表HTTP使用的是UDP协议(无连接)。
从HTTP/1.1起,默认都开启了Keep-Alive,保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。
Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。
前端 http request相关推荐
- 前端:request.js?b775:105 Uncaught (in promise) Error: nested exception is org.apache.ibatis.exceptions
request.js?b775:105 Uncaught (in promise) Error: nested exception is org.apache.ibatis.exceptions.To ...
- tp3 跨域_vue 跨域访问tp3接口,tp3 I()方法无法获取前端请求数据
跨域设置: header("Access-Control-Allow-Origin:*"); header("Access-Control-Allow-Methods:G ...
- 前端基础知识点整理(html+css+js基础)、不包含框架
css盒子模型 盒模型都是由四个部分组成的,分别是margin.border.padding和content. 标准盒模型和IE盒模型的区别在于设置width和height时,所对应的范围不同.标 ...
- 后端说:只是你不懂怎么用 headers!
事情是这样的,上一个项目我们的后端提供的接口,一次性返回了所有数据给我,分页功能是前端自己完成的. 那么这次来的新项目,换了个后端,写了另外的接口,我做项目的时候,还是用的之前的前端分页组件,但是测试 ...
- vuecli3 引入全局scss变量_vuecli3 从搭建到优化
编者按:本文转载自掘金专栏,由作者 lMadman 授权奇舞周刊转载. 前言 github地址:https://github.com/LeeStaySmall/vue-project-demo (完整 ...
- (22)Ajax的基本使用(实现登录功能和局部刷新以及防止跨站请求伪造攻击)
Ajax的作用 前后端分离的项目,需要交互,就要通过Ajax来完成交互 AJAX(Asynchronous Javascript And XML)翻译成中文就是"异步Javascript和X ...
- 手把手教会你小程序登录鉴权
导语 为了方便小程序应用使用微信登录态进行授权登录,微信小程序提供了登录授权的开放接口.乍一看文档,感觉文档上讲的非常有道理,但是实现起来又真的是摸不着头脑,不知道如何管理和维护登录态.本文就来手把手 ...
- django(7)modelform操作及验证、ajax操作普通表单数据提交、文件上传、富文本框基本使用...
一.modelForm操作及验证 1.获取数据库数据,界面展示数据并且获取前端提交的数据,并动态显示select框中的数据 views.py from django.shortcuts importr ...
- 《SpringBoot从菜鸟到老鸟》之SpringBoot 如何配置全局的异常捕获
SpringBoot 如何配置全局的异常捕获 SpringBoot中自带的异常捕获机制返回的默认页面比较丑,对用户来说不够人性化. 所以这篇文章来讲解SpringBoot钟自定义全局异常捕获. 主要讲 ...
最新文章
- mysql中tonumber函数_Oracle数据库之oracle的TO_NUMBER函数
- MySQL数学函数简明总结
- 怎么用python画简单的图-使用Python中的Turtle库绘制简单的图形
- Linux编辑器vi使用方法详细介绍
- 没有找到borlandmm.dll 报错的解决方法
- yum安装Imagick及扩展
- [ICPC 北京 2017 J题]HihoCoder 1636 Pangu and Stones
- sbe 详解_内部简单二进制编码(SBE)
- 前端学习(3342):ant design中grid排版使用
- LeetCode —— 257. 二叉树的所有路径(Python)
- 鲲鹏性能优化十板斧(四)——磁盘IO子系统性能调优
- 浪潮信息边缘服务器m5多少钱,浪潮边缘计算服务器NE5260M5发布!最高性能可达每秒70万亿次...
- 正则划分省市区(直辖市和附详细地址包括市,区)
- php语句执行顺序,sql语句执行顺序是什么
- CANoe操作介绍系列 ———— 两种测试节点的添加与配置
- oracle只导入dmp中指定表,oracle 如何导入dmp文件到指定表空间
- 群晖官方功能使用教程
- creo动画如何拖动主体_Creo如何制作关键帧动画?
- 洗地机好用吗、洗拖地一体机哪个牌子好、最好的洗拖地一体机推荐
- 离散型变量的编码方式——one-hot与哑变量(dummy variable)