
web前端,vue,小程序
暂时未有相关通用技术能力~
阿里云技能认证
详细说明part of Hypertext Transfer Protocol -- HTTP/1.1 RFC 2616 Fielding, et al. 10 状态码规定(Status Code Definitions) 本文描述了每一个状态码的相关规定,包括了对应状态码需要遵循的方法和在响应中需要的任何元信息。 10.1 Informational 1xx 该类状态码表示临时性的响应,仅由状态行和可选的头部组成,并由一个空行结束。该类状态码没有必须的头部字段。由于HTTP/1.0没有定义任何1xx状态码,除非在实验条件下,服务器不能向HTTP/1.0客户端发送1xx响应。 在一个有规律的响应返回之前,客户端必须准备好接受可能的一个或多个1xx响应,即使客户端并不需要100(Continue)状态消息。不需要的1xx响应状态可能会被客户端所忽略。 代理必须转发1xx响应,即使代理和它的客户端的链接已经关闭了,或者,除非代理本身要求生成1xx响应。(举个栗子,代理在转发请求时添加了“Expect:100-continue”字段,那么它不需要转发相应的100(Continue)字段)。 10.1.1 100 Continue 客户端应继续发送其请求。该临时响应用于通知客户端请求的初始部分已经被接受并且尚未被服务器拒绝。在请求结束后,服务器必须返回一个最终的响应。查看 8.2.3 小节获得有关该状态码更详细的内容。 10.1.2 101 切换协议(Switching Protocols) 服务器理解并愿意遵循客户端的请求,通过升级消息头字段(14.42小节),来修改在该链接上使用的应用协议。服务器在空行终止101响应之后会立即对那些包含UpGrade头字段的响应切换协议。 只有在有利的情况下,才应该切换协议。举个栗子,切换到较新版本的HTTP比旧版本更有利,并且在传递使用这些特征的资源时切换到实时、同步协议可能是更有利的。 10.2 Successful 2xx 该类状态码表示客户端的请求已经被成功接收,理解和接受。 10.2.1 200 OK 请求已经成功。在响应中返回的信息取决于在请求中使用的方法,例如: GET 与请求的资源相一致的实体会在响应中返回; HEAD 与请求的资源相一致的实体头字段会在响应中返回,响应返回的内容没有任何的消息体(message-body); POST 一个描述或包含执行结果的实体; TRACE 包含终端服务器接收到的请求消息的实体。 10.2.2 201 Created(已创建) 请求已经完成,并导致一个新的资源被创建。新创建的资源可以被响应实体中返回的URI所引用,该资源所引用的指定URI在Location头字段中给出。响应应该包括一个实体,该实体包含一个资源特性和位置的列表,用户或用户代理可以从中选择最合适的一个。实体的格式是由Content-Type头字段中的媒体类型所指定的。元服务器必须在返回201状态码之前生成相应的资源。如果执行结果无法被立即解析,那么服务器需要用202(Accepted)响应来替代。 一个201响应可能会包含一个ETag响应头字段,该字段表示刚刚创建的请求变量的实体标签的当前值,详情请见14.19小节。 10.2.3 202 Accepted(已接收) 请求已经被接受并在处理,但是处理还没有完成。请求最终可能会、也可能不会被处理,因为在处理实际发生的问题时它可能是被禁止的。在像这样的异步操作中无法重新发送一个状态码。 202响应是有意不表态的。它的目的是允许服务器接收其它进程的请求(也许一个批处理(batch-oriented)进程一天只执行一次),无需用户代理与服务器的连接一直持续到进程完成。使用此响应返回的实体应该包括请求的当前状态的说明和指向状态监视器的指针或用户何时才能预期该请求已经完成的估计。 10.2.4 203 Non-Authoritative Information(非授权信息) 实体头中返回的元信息不是元服务器可用的最终集合,但是是从本地或者第三方拷贝的。所呈现的集合可以是原始版本的子集或父集。例如,包含有关资源的本地注释信息有可能成为元服务器已知的源信息的父集。只有在响应为200的情况下才适用此响应码。 10.2.5 204 No Content(无内容) 服务器已经完成了相关请求,但是不需要返回实体主体,并且可能需要返回更新后的元信息。响应可能包括实体头形式的最新或更新后的源信息,该信息如果存在的话,需要与请求的变量相关联。 如果客户端是一个用户代理,则不应该从它在请求发送的文档中改变它的文档视图。此响应主要用于允许输入的动作而不引起对用户代理的活动文档视图的更改,尽管任何新的或更新的元信息都应应用于当前在用户代理的活动视图中的文档。 204响应无需包括消息主体,因此总是由头字段之后的第一个空行终止。 10.2.6 205 Reset Content(重置内容) 服务器已经完成了相应的请求,用户代理需要重置那些导致请求被发送的文档视图。此响应主要是为了允许通过用户输入进行操作的输入,然后清除输入的表单,以便用户可以轻松启动另一个输入操作。该响应不能包含实体。 10.2.7 206 Partial Content(部分内容) 服务器已经完成了部分GET请求的资源的获取。该请求必须包含一个Range头字段(14.35小节)指定期望获取内容的范围,并且可能包含一个If-Range头字段(14.27小节)以使请求视条件而定。 该请求的响应必须包含下列头字段: - 该响应需要包含一个描述范围的Content-Range头字段,要么就每一个部分都需要一个包含Content-Range字段的多部分/字节范围(multipart/byteranges)的Content-Type字段。如果该响应中存在Content-Length头字段,它的值必须与信息体中传输的八位字节数值相匹配。 - Date - ETag和(或)Content-Location,如果消息头将以200的形式响应给相同的请求。 - Expires,Cache-Control,和(或)Vary,如果字段值不同于之前为相同变体所发送的任何响应的值。 如果206响应是一个使用强缓存验证的If-Range请求的结果(详情请看13.3.3), 该响应则不应该包含其它实体头。如果该相应是一个使用弱缓存验证的If-Rang请求的结果,那么响应中则一定不能包含其它实体头。这是为了防止缓存后的实体与更新后的头字段不一致的问题。否则,对于那些已经返回200响应的同一个请求,那么该响应必须包含所有的实体头。 如果ETag或Last-Modified头字段不匹配,那么缓存则一定不能将206响应与其它之前已经缓存的内容组合在一起。(详情请看13.5.4小节) 一个未提供Range和Content-Range的头字段一定不能缓存206响应。 10.3 重定向(Redirection) 3xx 这类状态代码指示为了完成请求,需要由用户代理采取进一步的动作。当且仅当第二次请求是GET或HEAD请求时,所需的动作可以仅由用户代理来执行而不与用户交互。客户端应该检测无限重定向循环,因为这样的循环会使每个重定向都生成网络流量。 Note:本规范的以前版本建议最多重定向五次。开发人员应该意识到可能有客户端存在这样一个固定的限制。 10.3.1 300 多种选择(Multiple Choices) 所请求的资源与代理资源集合中的任何一个都匹配,每一个都有其指定的地址,并且提供了代理驱动(agent-driven)的相关协商信息(第12小节)可以让用户或者用户代理可以选择一个更优的代理并重定向该请求到此地址。 即使是一个HEAD请求,响应也需要包含一个实体,该实体还有一个相关资源类目的列表和地址,这样可以让用户或者用户代理选择一个最匹配的资源作为结果。实体格式由Content-Type头字段指定的媒体类型决定。根据用户代理的格式和能力,可以自动执行最合适的选择。然而,该规范没有定义任何有关于这种自动选择的标准。 如果服务器有一个优先的选择,他应该在Location字段中包含该指定资源的URI。用户代理可能会用Location字段值来自动重定向。除非另有说明,否则此响应是可以缓存的。 10.3.2 301 永久移除(Moved Permanently) 请求的资源已经分配了一个新的永久URI,并且任何对该资源的未来引用都应该使用返回的URI之一。具有链接功能的客户端应该在可能的情况下,自动将引用到的“请求URI(Request-URI)”的引用重新链接到服务器返回的一个或多个新的引用上。 新的永久URI需要在响应中的Location字段注明。 如果响应一个请求而接收到的是301状态的请求方法不是HEAD或者GET,那么用户代理就不能自动重定向该请求,除非用户可以确认该请求,因为这样可能会改变请求所发出的条件。 Note:当收到301状态码后自动重定向POST请求时,一些现有的HTTP/1.0用户代理将错误地将其更改为GET请求。 10.3.3 302 已发现(Found) 请求的资源暂时存储在不同的URI下。由于重定向有时可能会被更改,所以客户端应该继续使用该“请求URI(Request-URI)”用于未来的请求。该响应仅在被Cache-Control或Expires头字段描述的时候会被缓存。 临时的URI应该由响应中的Location字段提供。除非请求方法是HEAD,否则响应的实体应该包含一个短超文本注释,其中带有指向新URI的超链接。 如果响应一个请求而接收到的是302状态的请求方法不是HEAD或者GET,那么用户代理就不能自动重定向该请求,除非用户可以确认该请求,因为这样可能会改变请求所发出的条件。 Note:RFC 1945和RFC 2068指定不允许客户端对重定向请求更改方法。然而,大多数现有的用户代理实现都将302视为303响应,在位置字段值上执行GET,而不管原始请求方法是什么。对于那些希望明确表明客户期望的反应类型的服务器,已经添加了状态代码303和307。 10.3.4 303 参见其他(See Other) 对请求的响应可以在不同的URI下找到,应该使用该资源上的GET方法来检索。该方法主要用于允许POST激活(POST-activated)的脚本的输出将用户代理重定向到所选择的资源。新的URI不是最初请求的资源的替代引用。303响应不能被缓存,但是该响应的第二次(或重定向)请求可能会被缓存。 不同的URI需要在响应的Location字段中给出。除非请求方法是HEAD,否则响应的实体应该包含一个短超文本注释,其中带有指向新URI的超链接。 Note:很多HTTP/1.1之前版本的协议不理解303状态。当需要与此类客户端进行交互性操作时,可以使用302状态码,因为大多数的用户代理对302状态的响应就像这里所描述的303一样。 10.3.5 304 未修改(Not Modified) 如果客户端执行了有条件的GET请求并允许访问,但是文件没有修改,此状态码应该用该状态码来响应。304响应不能包含一个消息体,因此,总是以头字段后的第一个空行结束。 该响应必须包含以下的头部字段: - Date, 除非他是按照14.18.1章节所描述的被要求遗漏的 如果无时钟的服务器遵循这些规则,并且代理和客户端将自己的日期添加到没有收到服务器日期的任何响应中(如[RFC 2068]第14.19节中已经指定的),缓存将正确运行。 - ETag 和(或) Content-Location, 如果消息头将以200响应的形式发送给相同的请求。 - Expires, Cache-Control, 和(或) Vary, 如果字段值可能与之前的响应中所发送的相同的变量是不一样的。 如果有条件的GET请求使用了一个强缓存验证(详情请看13.3.3小节),响应不能包括其他实体头字段。否则(即有条件的GET请求使用弱验证),响应一定不能包含其他实体头。这可以防止缓存的实体和已更新的头字段之间的不一致。 如果304响应表示当前未缓存的实体,则缓存必须忽略响应并重新发起一个无条件的请求。 如果一个缓存使用了接收到的304响应来更新一个缓存条目,那么该缓存必须更新该条目以反映在响应中给定的任何新的字段值。 10.3.6 305 使用代理(Use Proxy) 所请求的资源必须通过Location字段所提供的代理进行访问。Location字段提供了代理的URI地址。接收方希望通过代理重复此单个请求。305响应必须仅由源服务器生成。 Note: RFC 2068协议并不清楚305是否打算重定向单个请求,并且仅由原始服务器生成。不遵守这些限制会产生重大的安全后果。 10.3.7 306 (已废弃) 306状态码在本规范的前一个版本中使用,目前不再使用,并且代码被保留。 10.3.8 307 临时重定向(Temporary Redirect) 请求的资源临时存储在另一个URI下。由于该重定向有时可能会被修改,所以未来的客户端请求仍旧使用原请求URI。该响应仅在使用了Cache-Control或者Expires头字段描述的时候才会被缓存。 临时URI的地址需要在响应中的Location中给出。除非请求方法是HEAD,否则响应的实体应该包含一个简短的超文本注释,并带有到新URI的超链接,因为许多http /1.1之前版本的用户代理不理解307状态。因此,注释应该包含用户在新URI上重新开始原始请求所需的信息。 如果在响应中收到了307状态码,但是该响应的请求方法不是HEAD或者GET,用户代理一定不能自动重定向该请求,除非它已经被用户所确认,因为这可能会改变发出请求时的条件。 10.4 客户端错误(Client Error) 4xx 4xx类的状态码是为了描述客户端的错误。除了响应HEAD请求以外,服务器应该包含一个有着错误情况解释的实体,以及它是临时的还是永久的。该类状态码适用于任何请求方法。客户代理需要为用户显示任何在响应中包含的实体内容。 如果客户端正在发送数据,那么使用TCP的服务器实现应该在服务器关闭输入连接之前确保客户端确认收到包含响应的数据包。如果客户端在关闭后继续向服务器发送数据,那么服务器的TCP堆栈将向客户机发送一个重置包,这可能会在HTTP应用程序读取和解释之前清除客户端未确认的输入缓冲区。 10.4.1 400 非法请求(Bad Request) 由于语法错误,服务器无法理解请求。客户端不应该在没有修改的情况下重复请求。 10.4.2 401 未经授权的(Unauthorized) 在发出请求时需要验证用户的身份信息。响应必须包含一个适用于所请求资源的用于询问的WWW-Authenticate头字段(14.47小节)。客户端可以使用合适的Authorization头字段重复该请求(14.8小节)。如果请求已经包含了授权验证相关信息,然后401响应表明这些证书已经被拒绝授权。如果3-1响应包含了与之前响应相同的询问信息,并且用户代理至少已经尝试过一次认证,然后向用户显示响应中给出的实体,因为该实体可能包括相关的诊断信息。HTTP询问相关的身份验证的详细说明在“HTTP身份验证:简单扼要地访问身份验证[43]”中。 10.4.3 402 支付要求(Payment Required) 该状态码会在未来提供使用方法。 10.4.4 403 被拒绝的(Forbidden) 服务器理解相应的请求,但是拒绝该请求。授权不会有帮助,请求也不应该被重复。如果请求方法不是HEAD并且服务器希望公开请求为什么没有完成,它应该在实体中说明拒绝的原因。如果服务器不希望将此信息提供给客户端,那么可以使用404状态码来代替。 10.4.5 404 未找到(Not Found) 服务器在匹配的请求URI上没有找到任何东西。没有迹象表明这种情况是暂时的还是永久的。如果服务器通过一些内部可配置的机制知道旧资源永久不可用,并且没有转发地址,则应该使用410(Gone)状态代码。当服务器不希望确切地显示请求被拒绝的原因,或者当没有其他响应适用时,通常使用此状态代码。 10.4.6 405 方法不被允许(Method Not Allowed) 请求行中指定的方法不允许用于请求URI中标识的资源。响应必须包含一个Allow头字段,其中包含对请求资源有效的方法列表。 10.4.7 406 无法接受(Not Acceptable) 请求所标识的资源仅能够根据请求中发送的接收头字段生成具有不可接受的内容特征的响应实体。 除非是一个HEAD请求,响应应该包含一个有着可用实体特征和位置列表的实体,用户或用户代理可以从中选择最合适的实体内容。实体格式由Content-Type头字段中给出的媒体类型指定。根据用户代理的格式和能力,可以自动执行最合适的选择。然而,该规范没有定义任何用于这种自动选择的标准。 Note: 服务器允许根据请求中发送的请求头返回不可接受的响应。在某些情况下,这甚至可能比发送406响应更好。我们鼓励用户代理检查传入响应的报头,以确定是否可以接受。 如果响应是不可接受的,则用户代理应该暂时停止接收更多的数据,并询问用户以决定进一步的行动。 10.4.8 407 需要代理验证身份(Proxy Authentication Required) 该状态码和401(Unauthorized)有些类似,但指示客户端必须首先用代理进行身份验证。代理必须返回一个Proxy-Authenticate头字段(14.33小节),该字段包含适用于所请求资源的代理的相关询问。客户端需要在重复该请求时包含一个合适的Proxy-Authorization头字段。http相关的访问验证说明在“HTTP Authentication: Basic and Digest Access Authentication” [43]中有详细的介绍。 10.4.9 408 请求超时(Request Timeout) 客户端没有在服务器准备等待的时间内生成请求。客户端可以在以后的任何时候重复该请求而不做任何修改。 10.4.10 409 冲突(Conflict) 由于与资源的当前状态发生冲突,请求无法完成。只有在预期用户能够解决冲突并重新提交请求的情况下才允许使用此代码。相迎体需要包含足够的信息让用户意识到该资源的冲突。理想情况下,响应实体将包含足够的信息给用户或用户代理来解决问题;然而,这可能是不可能的,并且不是必需的。 冲突最有可能发生在响应PUT请求时。例如,如果当前的资源正在使用版本控制,即将被PUT的资源包含了一些修改,这些修改还会引起之前(或第三方)请求的冲突,服务器需要使用409响应来说明它无法完成该请求。在这种情况下,响应实体可能包含两个版本之间的差异列表,格式由响应中的Content-Type定义。 10.4.11 410 已删除(Gone) 请求的资源已经不被服务器所提供,并且没有已知的地址可指向该资源。这种情况有望被认为是永久的。具有链接编辑功能的客户端应该在用户批准后删除对该请求uri的引用。如果服务器不知道,或者没有确定的条件知道它的状态是否是永久的,那么则应该使用404状态码。除非另有说明,该响应是可以缓存的。 410响应主要是通过通知接收方资源是不可用的,并且服务器所有者希望移除该资源的远程链接来协助Web维护的任务。这样的情况对于有时间限制的资源、促销服务和属于不再在服务器站点工作的个人的资源来说是常见的。不需要将所有永久不可用的资源标记为“已用(GONE)”,也不需要将标记保留任何时间——这由服务器所有者自行决定。 10.4.12 411 需要长度(Length Required) 服务器拒绝接受没有Content-Length头字段的请求。如果客户端在请求消息中添加包含消息正文长度的有效的Content-Length头字段,则可以重复该请求。 10.4.13 412 未满足前提条件(Precondition Failed) 在服务器上测试请求头字段时,在一个或多个请求头字段中给出的先决条件被评估为false。此响应码允许客户端在当前资源的元信息(header字段数据)上放置先决条件,从而防止请求的方法被应用到预期资源之外的资源。 10.4.14 413 请求实体过大(Request Entity Too Large) 服务器拒绝处理请求,因为请求实体大于服务器想要或能够处理的范围。服务器可能关闭连接,以防止客户端继续请求。 如果条件是临时的,服务器应该包含Retry-After头字段,以表明它是临时的,并且在何时可以再次尝试该请求。 10.4.15 414 请求URI过长(Request-URI Too Long) 由于请求URI过长,超过了服务器所能解析的极限,所以服务器拒绝为该请求提供服务。这种罕见的情况只可能发生在客户端将一个POST请求不当的转换成为一个具有过长的查询(query)信息的GET请求的时候,当客户端进入URI重定向的“黑洞”(比如,一个重定向URI的前缀指向了它自身的后缀),或者当服务器遭到客户端攻击时,试图利用固定长度缓冲器来读取某些服务器中存在的安全漏洞,以读取或操纵请求URI。 10.4.16 415 不支持的媒体类型(Unsupported Media Type) 服务器拒绝为该请求提供服务,因为请求的实体是使用该请求方法来请求的资源所不支持的格式。 10.4.17 416 无法满足的请求范围(Requested Range Not Satisfiable) 如果一个请求中包含看Range请求头字段,并且此字段中的范围说明符值均不与所选资源的当前范围重叠,并且请求中并没有If-Range请求头字段,那么服务器应该在响应中返回该状态码。(对于byte-ranges字段,它说明在所有字节范围中的第一字节值都大于所选资源的当前长度。) 当该状态码为响应一个byte-range请求而返回,该响应需要包含一个Content-Range实体头字段,用来说明当前所选择资源的长度(详情请看 14.16小节)。该响应不能使用multipart/byteranges类型。 10.4.18 417 未满足Expect头字段所标识的信息(Expectation Failed) 该服务器无法满足Expect 头部字段(见第14.20节)中的期望,或者,如果服务器是代理服务器,则服务器有明确的证据表明该请求不能被下一跳(next-hop)服务器所满足。 10.5 服务器错误(Server Error) 5xx 以数字“5”开头的响应状态码表示服务器意识到它已经出错或无法执行请求的情况。除了响应头部请求之外,服务器还应该返回一个包含解释错误情况的实体,以及它是否是临时的或永久性的状态。代理应该向用户展示所有的实体内容。这些响应码适合任何请求方法。 10.5.1 500 内部服务器错误(Internal Server Error) 服务器遇到了一个意外情况,阻止了它完成相应的请求。 10.5.2 501 未实现(Not Implemented) 服务器不支持完成请求所需的功能。当服务器无法识别请求的方法或者无法提供任何资源的时候,应该返回该响应。 10.5.3 502 坏网关(Bad Gateway) 服务器作为网关或代理,在尝试执行请求时从上游服务器接收到无效响应。 10.5.4 503 服务不可用(Service Unavailable) 由于服务器的临时过载或维护,服务器目前无法处理该请求。这意味着,这是一个暂时的状态,将在一些延迟后得到缓解。如果已知延迟的时间,那么延迟的长度可能会在Retey-After头字段中表示。如果没有提供Retry-After字段,客户端应该像处理500响应那样处理该响应。 Note: 503状态代码的存在并不意味着服务器在重载时必须使用它。有些服务器可能简单地希望拒绝连接。 10.5.5 504 网关超时(Gateway Timeout) 服务器作为网关或代理,没有收到来自URI(例如HTTP、FTP、LDAP)或在试图完成请求时需要访问的其他辅助服务器(例如DNS)所指定的上游服务器的及时响应。 Note: 开发者注意:当DNS查找超时时,已知一些已部署的代理返回400或500。 10.5.6 505 不支持的HTTP版本(HTTP Version Not Supported) 服务器不支持或拒绝支持请求消息中使用的HTTP协议版本。该服务器指示它不能或不愿意使用与客户端相同的主版本完成请求,如在第3.1节中所描述的,而不是使用此错误消息。响应应该包含一个实体,说明为什么不支持该版本以及该服务器支持哪些其他协议。一只想要飞得更高的小菜鸟
part of Hypertext Transfer Protocol -- HTTP/1.1RFC 2616 Fielding, et al. 9 方法定义(Method Definitions) 下面定义了HTTP/1.1协议的一组通用方法。虽然这些方法是可以扩展的,但是不能假定附加方法可以为单独扩展的客户端或服务器共享该组方法的语义。 主机请求头字段(request-header field)(14.23章节)必须拥有HTTP/1.1协议定义的相关请求。 9.1 安全(Safe)和幂等(Idempotent)方法 9.1.1 安全方法(Safe Methods) 开发者应该知道,用户通过软件在互联网上进行交互,我们应该小心的允许用户了解他们可能采取的任何行动,因为这些行为可能会对他们自己或其它人有意想不到的意义。 相关标准已经详细说明,GET和HEAD方法不能用于除了检索以外的其他目的。那么这些方法应该被认为是“安全的”。这允许用户代理以特殊的方式来表示其他方法,例如POST、PUT和DELATE,从而使用户意识到该请求可能存在不安全的动作的事实。 当然,服务器执行GET请求,不可能确保这不会产生副作用;实际上,一些动态资源把这种副作用当作是一个功能。这里有个重要的事情需要分清,就是用户没有请求副作用,所以不应对副作用负责。 9.1.2 幂等方法 (Idempotent Methods) "方法"同样拥有“幂等性”(除了错误或过期的情况),大于一次的相同请求与单独一次请求所产生的副作用是一样的。GET,HEAD,PUT,DELETE等方法共享该属性。另外,OPTIONS和TRACE方法不应该有副作用,所以本质上就是幂等的。 然而,多个请求的队列可能是非幂等的,尽管执行请求中的所有方法本身都是幂等的(如果一个序列的单次执行总是会产生一个结果,并且该结果并不会因该序列的部分或全部重新执行而改变,那么该序列就是幂等的)。比如,一个序列中的值会在稍后被修改,并且该序列的结果会依赖于此,那么该序列就是非幂等的。 根据定义,一个不产生副作用的序列是幂等的(假如没有在同一资源上执行并发操作)。 9.2 选项(OPTIONS) OPTIONS方法表示对由“请求URI标识的请求/响应链”上可用的通信选项的信息的请求。此方法允许客户端确定与资源或服务器功能相关的选项和(或)需求,而不涉及资源操作或启动资源检索。对该方法的响应是不能缓存的。 如果一个OPTIONS请求包含了一个实体(entity-body),比如存在Content-Length或Transfer-Encoding,那么媒体类型就必须使用Content-Type字段所表示。尽管该规范没有定义这种主体的任何用处,未来的HTTP扩展可能会使用OPTIONS的实体在服务器上做更为详细的查询。如果服务器不支持该类型的扩展,则可以丢弃请求体。 如果请求的URI是一个“*”号,那么OPTIONS请求往往用于服务器而不是一个指定的资源。由于服务器的通信选项通常依赖于资源,所以“*”请求只能用于“ping”或“no-op”类型的方法;它除了允许客户端测试服务器的能力外什么也做不了。例如,它可以用来测试HTTP/1.1遵从(或缺少)的代理。 如果请求URI不是星号,则OPTIONS请求仅适用于与该资源通信时可用的选项。 一个200响应应包括指示服务器实现的可选特征的任何头字段,并适用于该资源(例如,ALLOW),也可能包括未由本规范定义的扩展。响应体(如果有的话)也应该包含关于通信选项的信息。这种实体的格式并不是由本规范定义的,但是可能会由未来有关于HTTP的扩展内容来定义。内容协商(Content negotiation)可以用来选择适当的相应格式。如果未包含响应体,则响应必须包含字段值为“0”的Content-Length字段。 Max-Forwards请求头字段可以用来在一个请求链中请求一个指定的代理。当一个代理在一个允许请求转发的绝对URI地址上接收到了一个OPTIONS请求时,代理必须检查Max-Forwards字段。如果Max-Forwards的字段值是“0”,代理则不能转发信息;相反的,代理应该用自己的通信选项进行响应。如果Max-Forwards的字段值是大于0的整数,那么代理的每一次转发必须消减该字段值。如果在请求中没有Max-Forwards字段,那么转发的请求中也不能存在Max-Forwards字段。 9.3 GET* GET方法意味着检索请求URI标识的任何信息(以实体的形式)。如果请求URI指定的是一个数据产生的过程,那么应该将生成的数据作为实体返回,而不是返回该流程的原文本,除非该文本恰好是该过程的输出。 如果请求信息中包括了If-Modified-Since, If-Unmodified-Since, If-Match, If-None-Match,或者If-Range等字段,那么GET方法的语义就变成了“有条件的GET方法(conditional GET)”。条件GET(conditional GET)方法请求仅在符合条件头字段所描述的情况下才会传输实体内容。条件GET方法旨在减少不必要的网络请求,它允许刷新缓存的实体,而不需要多个请求或传输客户端里已经存在的数据。 如果请求信息中包含了一个Range头字段,那么GET方法就会变为“局部GET(partial GET)”。一个局部GET请求只获取实体的一部分内容(就像14.35章节中描述的那样)。部分GET方法旨在完成对实体的部分检索而不传输客户端已经存在的数据来减少不必要的网络请求。 当且仅当它满足第13节中所描述的有关HTTP缓存的要求时,对GET请求的响应是可以被缓存的。 在应用表单时的相关安全策略请参阅15.1.3小节。 9.4 HEAD 除了在响应中的不会返回消息体外,HEAD方法与GET方法并没有什么区别。在HEAD请求所返回的响应中所包含的头部源信息应该跟GET请求所返回的响应中的信息相同。该方法可用于在不转移实体本身的情况下获得请求所隐含的有关于实体的源信息。该方法通常用于测试超文本链接的有效性、可访问性以及最近的修改。 一个HEAD请求所返回的响应应该是可以缓存的,在该意义上来说,响应中所包含的信息应该可以用来更新之前资源中缓存的实体信息。如果新的字段值标示之前缓存的实体和现在的实体是不一样的(就像Content-Length,Content-MD5,ETAG或Last-Modified等字段表示的那样),那么缓存机制必须将该缓存条目视为过期的。 9.5 POST POST方法用于请求源服务器接受请求中包含的实体作为请求队列中的请求URI所标识的资源的新下属。POST方法被设计为可以用一个统一的方式来覆盖以下的功能: - 对现有资源的注释; - 向公告牌,新闻类,邮件列表或者类似的文章类发送信息; - 向数据处理过程提供数据块,例如提交表单的结果; - 通过附加操作扩展数据库。 POST方法执行的实际函数由服务器决定,并且通常依赖于请求URI。POST所发布的实体内容从属于该URI,就像一个文件从属于包含它的目录,新闻文章从属于发布它的新闻组,或者记录从属于数据库一样。 POST方法执行的操作可能不会产生可以由URI标识的资源。在这种情况下,200 (OK)或204 (No Content)都是适当的响应状态,这取决于响应是否包含描述结果的实体。 如果在源服务器上创建了一个资源,那么响应应该是201(Created),并包含一个描述请求状态并引用新资源的实体和一个Location头字段。(详情见14.30章节) 对该方法的响应是不可缓存的,除非响应包含适当的Cache-Control或Expires头字段。但是,可以使用303(See Other)响应指示用户代理检索可缓存资源。 POST请求必须遵守第8.2节中规定的有关消息传输要求。 有关安全性相关的问题请查阅15.1.3节 9.6 PUT PUT方法请求一个被请求URI封闭的指定实体。如果请求URI指向了一个已经存在的资源,那么被封闭的实体被认为是存在于原服务器上的一个修改版。如果一个请求URI没有指向一个已经存在的资源,并且该URI能够被请求的客户端视为一个新的资源,那么服务器就可以用该URI创建一个新的资源。如果一个新的资源被创建了,那么源服务器必须使用201(Created)来通知客户端。如果一个已存在的资源被修改了,那么服务器需要返回一个200 (OK) 或204 (No Content)状态码来告知客户端用来表明修改的完成。如果在该请求URI下的资源无法被创建或者修改,应该返回一个用来反映该错误的适当的错误响应。实体的接收者不能忽略它不理解或实现的任何Content-*(例如Content-Range)头字段,并且必须在这种情况下返回501(Not Implemented)响应。 如果该请求通过了一个缓存,并且该请求URI标识了一个或多个当前缓存的实体。那么,那些缓存实体被视为是过期的。该方法的响应不能被缓存。 POST和PUT请求之间的根本区别在于请求URI所反映的不同含义。POST请求中的URI标识的资源将操作该封闭的实体。该资源可能是一个接受数据的进程、某个其他协议的网关或接受注释的单独实体。相比之下,在PUT请求中的URI在请求中标识了被附加的实体——用户代理知道URI的意图,服务器不应尝试将该请求应用到其他资源。如果服务器希望将请求应用到不同的URI,它必须发送301(Moved Permanently)响应;然后,用户代理可以自己决定是否重定向该请求。 一个资源可能会被很多不同的URI所标识。比如,文章可能有一个用于标识“当前版本”的URI,该URI与标识每个特定版本的URI分开。在这种情况下,常规URI上的PUT请求可能导致请求到由源服务器定义的多个其他URI。 HTTP/1.1没有定义PUT方法如何影响源服务器的状态。 PUT请求必须遵守第8.2节中规定的消息传输要求。 PUT方法除非被特定的实体头(entity-header)所指定,否则PUT请求中的实体头( entity-headers)应该应用于PUT方法创建或修改的资源。 9.7 DELETE DELETE方法请求服务器删除被请求URI标识的资源。这种方法可能会被源服务器上的人工干预(或其他方法)覆盖。即使从源服务器返回的状态代码表明操作已经成功完成,也不能保证客户端已经执行了该操作。但是,服务器不应指示成功,除非在给定响应时它打算删除资源或将其移动到不可访问的位置。 如果响应包含描述状态的实体,那么成功的响应应该是200 (OK);如果操作尚未完成,响应应该是202(Accepted);如果操作已完成,但响应不包含实体,则响应应该是204(No Content)。 如果请求了一个缓存,并且请求URI标识一个或多个当前缓存的实体,那么这些条目应该被视为过期的。对该方法的响应不能缓存。 9.8 TRACE 跟踪方法用于调用请求消息的远程、应用层回环。请求的最终接收者应该把返回给客户端的消息作为200(OK)响应的实体。最终的接收者要么是源服务器,要么是接收请求中最大转发值为0的第一个代理或网关(参见14.31节)。TRACE 请求不能包含实体。 TRACE 方法允许客户端查看在请求链的另一端接收到什么,并将该数据用于测试或诊断信息。Via头字段(第14.45节)的值特别值得关注,因为它充当请求链的跟踪。使用Max-Forwards头部字段允许客户端限制请求链的长度,这对于在无限循环中测试代理转发消息链非常有用。 如果请求是有效的,响应应该包含实体主体中的整个请求消息,其中包含了一个值为“message/http”的Content-Type。TRACE方法的响应是不能缓存的。 9.9 CONNECT 本规范保留了CONNECT方法名,以便与可以动态切换为隧道的代理(例如,SSL隧道[44])一起使用。一只想要飞得更高的小菜鸟
part of Hypertext Transfer Protocol -- HTTP/1.1RFC 2616 Fielding, et al. 10 Status Code Definitions Each Status-Code is described below, including a description of which method(s) it can follow and any metainformation(元信息) required in the response. 10.1 Informational 1xx This class of status code indicates a provisional(暂定的;暂时的,临时的) response, consisting(包括;由…组成) only of the Status-Line and optional headers, and is terminated(结束) by an empty line. There are no required headers for this class of status code. Since HTTP/1.0 did not define any 1xx status codes, servers MUST NOT send a 1xx response to an HTTP/1.0 client except under experimental(实验的;根据实验的;试验性的) conditions(状况;环境;). A client MUST be prepared(准备) to accept one or more 1xx status responses prior(优先的;占先的;在…之前) to a regular(有规律的;规则,整齐的;) response, even if the client does not expect a 100 (Continue) status message. Unexpected 1xx status responses MAY be ignored by a user agent. Proxies MUST forward 1xx responses, unless the connection between the proxy and its client has been closed, or unless the proxy itself requested the generation of the 1xx response. (For example, if a proxy adds a "Expect: 100-continue" field when it forwards a request, then it need not forward the corresponding(相当的,对应的;通信的;符合的,符合) 100 (Continue) response(s).) 10.1.1 100 Continue The client SHOULD continue with its request. This interim(暂时的,临时的;期中的) response is used to inform(通知;使活跃,使充满;预示) the client that the initial part of the request has been received and has not yet been rejected(排斥;拒收;拒绝) by the server. The client SHOULD continue by sending the remainder(剩余物;) of the request or, if the request has already been completed, ignore this response. The server MUST send a final response after the request has been completed. See section 8.2.3 for detailed discussion of the use and handling of this status code. 10.1.2 101 Switching Protocols The server understands and is willing to comply(遵从;依从,顺从;应允,同意) with the client's request, via(经过;通过,凭借;取道) the Upgrade message header field (section 14.42), for a change in the application protocol being used on this connection. The server will switch protocols to those defined by the response's Upgrade header field immediately after the empty line which terminates(结束) the 101 response. The protocol SHOULD be switched only when it is advantageous(有利的;优越) to do so. For example, switching to a newer version of HTTP is advantageous over older versions, and switching to a real-time, synchronous(同时存在[发生]的,同步的) protocol might be advantageous when delivering(发表;递送,交付) resources that use such features(特征). 10.2 Successful 2xx This class of status code indicates that the client's request was successfully received, understood, and accepted. 10.2.1 200 OK The request has succeeded. The information returned with the response is dependent(依赖的;依靠的;取决于…的;) on the method used in the request, for example: GET an entity corresponding(相当的,对应的;通信的;) to the requested resource is sent in the response; HEAD the entity-header fields corresponding to the requested resource are sent in the response without any message-body; POST an entity describing or containing the result of the action; TRACE an entity containing the request message as received by the end server. 10.2.2 201 Created The request has been fulfilled(应验;满足) and resulted in a new resource being created. The newly created resource can be referenced(参考的,引用的) by the URI(s) returned in the entity of the response, with the most specific(具体的;明确的;) URI for the resource given by a Location header field. The response SHOULD include an entity containing a list of resource characteristics and location(s) from which the user or user agent can choose the one most appropriate. The entity format is specified by the media type given in the Content-Type header field. The origin server MUST create the resource before returning the 201 status code. If the action cannot be carried out immediately, the server SHOULD respond with 202 (Accepted) response instead. A 201 response MAY contain an ETag response header field indicating the current value of the entity tag for the requested variant(变形,变量,转化;) just created, see section 14.19. 10.2.3 202 Accepted The request has been accepted for processing, but the processing has not been completed. The request might or might not eventually(终究;终于) be acted upon(采取行动), as it might be disallowed when processing actually takes place. There is no facility(设备;容易;能力;灵巧) for re-sending a status code from an asynchronous(异步的) operation such as this. The 202 response is intentionally(有意地,故意地) non-committal(拘押;收监;送入医院). Its purpose is to allow a server to accept a request for some other process (perhaps a batch-oriented(导向的;定向的;) process that is only run once per day) without requiring that the user agent's connection to the server persist(坚持;存留;固执;) until the process is completed. The entity returned with this response SHOULD include an indication of the request's current status and either a pointer to a status monitor(监督;监控,监听;) or some estimate(估计,预测;) of when the user can expect the request to be fulfilled(应验;满足). 10.2.4 203 Non-Authoritative Information(非授权信息) The returned metainformation in the entity-header is not the definitive(最后的;确定的,决定性的;) set as available(可获得的;有空的;可购得的;能找到的) from the origin server, but is gathered(集合;(使)聚集;推断;了解) from a local or a third-party copy. The set presented MAY be a subset(子集) or superset(超集,扩展集,父集) of the original version. For example, including local annotation(注释) information about the resource might result in a superset of the metainformation known by the origin server. Use of this response code is not required and is only appropriate(适当的;合适的;恰当的) when the response would otherwise(否则;另外;别的方式) be 200 (OK). 10.2.5 204 No Content The server has fulfilled(应验;满足) the request but does not need to return an entity-body, and might want to return updated metainformation. The response MAY include new or updated metainformation in the form of entity-headers, which if present SHOULD be associated(合伙,合营;联合,结合;联想) with the requested variant. If the client is a user agent, it SHOULD NOT change its document view from that which caused the request to be sent. This response is primarily(首先;首要地,主要地;) intended(预期的;有意的) to allow input for actions to take place without causing a change to the user agent's active document view, although any new or updated metainformation SHOULD be applied to the document currently in the user agent's active view. The 204 response MUST NOT include a message-body, and thus is always terminated(结束) by the first empty line after the header fields. 10.2.6 205 Reset Content The server has fulfilled the request and the user agent SHOULD reset the document view which caused the request to be sent. This response is primarily intended to allow input for actions to take place via(经过;通过,凭借;取道) user input, followed by a clearing of the form in which the input is given so that the user can easily initiate(开始,发起;) another input action. The response MUST NOT include an entity. 10.2.7 206 Partial(部分的) Content The server has fulfilled the partial GET request for the resource. The request MUST have included a Range header field (section 14.35) indicating the desired range, and MAY have included an If-Range header field (section 14.27) to make the request conditional(有条件的,由一定条件诱发的). The response MUST include the following header fields: - Either a Content-Range header field (section 14.16) indicating the range included with this response, or a multipart/byteranges Content-Type including Content-Range fields for each part. If a Content-Length header field is present in the response, its value MUST match the actual number of OCTETs(八位位组,八位字节) transmitted(传播;发射,播送,广播) in the message-body. - Date - ETag and/or Content-Location, if the header would have been sent in a 200 response to the same request - Expires, Cache-Control, and/or Vary, if the field-value might differ from that sent in any previous response for the same variant. If the 206 response is the result of an If-Range request that used a strong cache validator (see section 13.3.3), the response SHOULD NOT include other entity-headers. If the response is the result of an If-Range request that used a weak validator, the response MUST NOT include other entity-headers; this prevents(阻止;阻碍;) inconsistencies(矛盾) between cached entity-bodies and updated headers. Otherwise(否则;另外;), the response MUST include all of the entity-headers that would have been returned with a 200 (OK) response to the same request. A cache MUST NOT combine(使结合;使化合;兼有;) a 206 response with other previously cached content if the ETag or Last-Modified headers do not match exactly, see 13.5.4. A cache that does not support the Range and Content-Range headers MUST NOT cache 206 (Partial) responses. 10.3 Redirection(重定向) 3xx This class of status code indicates that further action needs to be taken by the user agent in order to fulfill the request. The action required MAY be carried out by the user agent without interaction(互动;一起活动;合作;互相影响) with the user if and only if the method used in the second request is GET or HEAD. A client SHOULD detect(查明,发现;洞察;侦察,侦查;) infinite(无限的,无穷的;) redirection(改方向,改寄) loops(环;回路;循环;圈), since such loops generate(形成,造成;) network traffic for each redirection. Note: previous versions of this specification recommended a maximum of five redirections. Content developers should be aware(意识到的;知道的;觉察到的) that there might be clients that implement(实施,执行;) such a fixed limitation(限制;局限;极限;). 10.3.1 300 Multiple Choices(多重选择) The requested resource corresponds(相当;相符合) to any one of a set of representations(陈述;陈述,投诉,抗议;), each with its own specific location, and agent-driven negotiation(协商,谈判;转让;通过) information (section 12) is being provided so that the user (or user agent) can select a preferred representation and redirect its request to that location. Unless it was a HEAD request, the response SHOULD include an entity containing a list of resource characteristics and location(s) from which the user or user agent can choose the one most appropriate. The entity format is specified by the media type given in the Content-Type header field. Depending upon the format and the capabilities of the user agent, selection of the most appropriate choice MAY be performed(表演;履行;执行) automatically(自动地;无意识地;不自觉地;机械地). However, this specification does not define any standard for such automatic selection. If the server has a preferred choice of representation, it SHOULD include the specific URI for that representation in the Location field; user agents MAY use the Location field value for automatic redirection. This response is cacheable unless indicated otherwise. 10.3.2 301 Moved Permanently(永久地,长期不变地) The requested resource has been assigned(指定;指派;分配) a new permanent(永久(性)的,永恒的,不变的,耐久的,持久的,经久的;稳定的;常务的,常设的) URI and any future references(参考) to this resource SHOULD use one of the returned URIs. Clients with link editing capabilities(容量;能力) ought to automatically re-link references to the Request-URI to one or more of the new references returned by the server, where possible. This response is cacheable unless indicated otherwise. The new permanent URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s). If the 301 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions(状况;环境;) under which the request was issued(发行者). Note: When automatically redirecting a POST request after receiving a 301 status code, some existing HTTP/1.0 user agents will erroneously(错误,不正确) change it into a GET request. 10.3.3 302 Found The requested resource resides(属于) temporarily(暂时地;临时地) under a different URI. Since the redirection might be altered(改变,更改) on occasion(场合;机会,时机;理由;需要), the client SHOULD continue to use the Request-URI for future requests. This response is only cacheable if indicated by a Cache-Control or Expires header field. The temporary URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s). If the 302 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued. Note: RFC 1945 and RFC 2068 specify that the client is not allowed to change the method on the redirected request. However, most existing user agent implementations treat 302 as if it were a 303 response, performing a GET on the Location field-value regardless(不顾后果地;) of the original request method. The status codes 303 and 307 have been added for servers that wish to make unambiguously(明白地,不含糊地) clear which kind of reaction(反应;反作用力;反动;保守) is expected of the client. 10.3.4 303 See Other The response to the request can be found under a different URI and SHOULD be retrieved(恢复;取回) using a GET method on that resource. This method exists primarily(首先;首要地,主要地;根本上;本来) to allow the output of a POST-activated(有活性的) script to redirect the user agent to a selected resource. The new URI is not a substitute(代替,替换,代用) reference(参考;参考书;提及,涉及;) for the originally(本;起初,原来;独创地,独出心裁地;自来) requested resource. The 303 response MUST NOT be cached, but the response to the second (redirected) request might be cacheable. The different URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s). Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability(互用性,协同工作的能力) with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303. 10.3.5 304 Not Modified(修改) If the client has performed(表演;履行;执行) a conditional(视…而定的;) GET request and access is allowed, but the document has not been modified, the server SHOULD respond with this status code. The 304 response MUST NOT contain a message-body, and thus is always terminated(结束) by the first empty line after the header fields. The response MUST include the following header fields: - Date, unless its omission(遗漏;疏忽;省略,删节;) is required by section 14.18.1 If a clockless origin server obeys(服从,听从) these rules, and proxies and clients add their own Date to any response received without one (as already specified by [RFC 2068], section 14.19), caches will operate(操作;经营;运转;管理) correctly. - ETag and/or Content-Location, if the header would have been sent in a 200 response to the same request - Expires, Cache-Control, and/or Vary, if the field-value might differ from that sent in any previous response for the same variant If the conditional(视…而定的;) GET used a strong cache validator (see section 13.3.3), the response SHOULD NOT include other entity-headers. Otherwise (i.e., the conditional GET used a weak validator), the response MUST NOT include other entity-headers; this prevents inconsistencies(矛盾) between cached entity-bodies and updated headers. If a 304 response indicates an entity not currently cached, then the cache MUST disregard(不顾;不理会;漠视,忽视;蔑视,轻视) the response and repeat the request without the conditional. If a cache uses a received 304 response to update a cache entry, the cache MUST update the entry to reflect(反射;映出;) any new field values given in the response. 10.3.6 305 Use Proxy The requested resource MUST be accessed(接近,进入) through the proxy given by the Location field. The Location field gives the URI of the proxy. The recipient(接受者;容器;容纳者) is expected to repeat this single request via the proxy. 305 responses MUST only be generated(生成;引起;) by origin servers. Note: RFC 2068 was not clear that 305 was intended to redirect a single request, and to be generated by origin servers only. Not observing(注意的,留心的) these limitations(局限;限制,边界;) has significant(重要的;有意义的;) security consequences(结果;重要). 10.3.7 306 (Unused) The 306 status code was used in a previous version of the specification, is no longer used, and the code is reserved(预订的;矜持的;储藏着的). 10.3.8 307 Temporary Redirect(临时重定向) The requested resource resides(属于) temporarily under a different URI. Since the redirection MAY be altered(改变) on occasion(场合;机会,时机;理由;需要), the client SHOULD continue to use the Request-URI for future requests. This response is only cacheable if indicated by a Cache-Control or Expires header field. The temporary URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s) , since many pre-HTTP/1.1 user agents do not understand the 307 status. Therefore(因此;所以), the note SHOULD contain the information necessary for a user to repeat the original request on the new URI. If the 307 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued. 10.4 Client Error 4xx The 4xx class of status code is intended(意指) for cases(案例) in which the client seems to have erred(犯错误,做错事). Except when responding(回答,回报,响应) to a HEAD request, the server SHOULD include an entity containing an explanation(解释;说明;辩解;) of the error situation(情况;局面,形势,处境;), and whether it is a temporary or permanent(永久) condition. These status codes are applicable to any request method. User agents SHOULD display any included entity to the user. If the client is sending data, a server implementation(贯彻;成就;) using TCP SHOULD be careful to ensure that the client acknowledges receipt of the packet(s) containing the response, before the server closes the input connection. If the client continues sending data to the server after the close, the server's TCP stack will send a reset packet to the client, which may erase(抹去;清除;擦掉) the client's unacknowledged(不被承认的,未答复的) input buffers(缓冲) before they can be read and interpreted(理解;解释) by the HTTP application. 10.4.1 400 Bad Request The request could not be understood by the server due to malformed(难看的,畸形的) syntax(语法;句法;). The client SHOULD NOT repeat the request without modifications(改变;更改;缓和). 10.4.2 401 Unauthorized(未经授权的) The request requires user authentication. The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource. The client MAY repeat the request with a suitable Authorization header field (section 14.8). If the request already included Authorization credentials(证书;凭证,证件), then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant(有关的,中肯的;) diagnostic(诊断的,判断的;) information. HTTP access authentication is explained in "HTTP Authentication: Basic and Digest(吸收;领悟;玩味) Access Authentication" [43]. 10.4.3 402 Payment Required This code is reserved for future use. 10.4.4 403 Forbidden The server understood the request, but is refusing to fulfill(履行) it. Authorization will not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal(拒绝;优先取舍权) in the entity. If the server does not wish to make this information available to the client, the status code 404 (Not Found) can be used instead. 10.4.5 404 Not Found The server has not found anything matching the Request-URI. No indication is given of whether the condition(状态;环境;健康状况;条款) is temporary or permanent. The 410 (Gone) status code SHOULD be used if the server knows, through some internally(内地;内心地;国内地;本质地) configurable(结构的,可配置的) mechanism, that an old resource is permanently unavailable and has no forwarding address. This status code is commonly used when the server does not wish to reveal(揭露;泄露;显露;[神]启示) exactly why the request has been refused, or when no other response is applicable. 10.4.6 405 Method Not Allowed The method specified in the Request-Line is not allowed for the resource identified(确认;辨认;认出) by the Request-URI. The response MUST include an Allow header containing a list of valid methods for the requested resource. 10.4.7 406 Not Acceptable The resource identified by the request is only capable of generating response entities which have content characteristics(性质;特性,特征) not acceptable according to the accept headers sent in the request. Unless it was a HEAD request, the response SHOULD include an entity containing a list of available entity characteristics and location(s) from which the user or user agent can choose the one most appropriate. The entity format is specified by the media type given in the Content-Type header field. Depending(依赖;依靠) upon the format and the capabilities of the user agent, selection of the most appropriate choice MAY be performed automatically. However, this specification does not define any standard for such automatic selection. Note: HTTP/1.1 servers are allowed to return responses which are not acceptable according to the accept headers sent in the request. In some cases, this may even be preferable(更好的,更可取的;略胜一筹的) to sending a 406 response. User agents are encouraged to inspect the headers of an incoming response to determine if it is acceptable. If the response could be unacceptable, a user agent SHOULD temporarily stop receipt of more data and query the user for a decision on further actions. 10.4.8 407 Proxy Authentication Required This code is similar to 401 (Unauthorized), but indicates that the client must first authenticate itself with the proxy. The proxy MUST return a Proxy-Authenticate header field (section 14.33) containing a challenge applicable to the proxy for the requested resource. The client MAY repeat the request with a suitable Proxy-Authorization header field (section 14.34). HTTP access authentication is explained in "HTTP Authentication: Basic and Digest Access Authentication" [43]. 10.4.9 408 Request Timeout The client did not produce a request within(不超过,在…的范围内;在…能达到的地方;在…内,在…里面) the time that the server was prepared(准备) to wait. The client MAY repeat the request without modifications at any later time. 10.4.10 409 Conflict(冲突;矛盾;) The request could not be completed due to a conflict with the current state of the resource. This code is only allowed in situations where it is expected that the user might be able to resolve the conflict and resubmit the request. The response body SHOULD include enough information for the user to recognize the source of the conflict. Ideally, the response entity would include enough information for the user or user agent to fix the problem; however, that might not be possible and is not required. Conflicts are most likely to occur in response to a PUT request. For example, if versioning were being used and the entity being PUT included changes to a resource which conflict with those made by an earlier (third-party) request, the server might use the 409 response to indicate that it can't complete the request. In this case, the response entity would likely contain a list of the differences between the two versions in a format defined by the response Content-Type. 10.4.11 410 Gone The requested resource is no longer available at the server and no forwarding address is known. This condition is expected to be considered permanent. Clients with link editing capabilities SHOULD delete references to the Request-URI after user approval. If the server does not know, or has no facility to determine, whether or not the condition is permanent, the status code 404 (Not Found) SHOULD be used instead. This response is cacheable unless indicated otherwise. The 410 response is primarily intended to assist the task of web maintenance by notifying the recipient that the resource is intentionally unavailable and that the server owners desire that remote links to that resource be removed. Such an event is common for limited-time, promotional(促销的;增进的) services and for resources belonging to individuals(与众不同的人;某种类型的人) no longer working at the server's site(遗址;地点,位置,场所;). It is not necessary to mark all permanently unavailable resources as "gone" or to keep the mark for any length of time -- that is left to the discretion of the server owner. 10.4.12 411 Length Required The server refuses to accept the request without a defined Content- Length. The client MAY repeat the request if it adds a valid Content-Length header field containing the length of the message-body in the request message. 10.4.13 412 Precondition(前提;先决条件) Failed The precondition given in one or more of the request-header fields evaluated(估价的,已评估的) to false when it was tested on the server. This response code allows the client to place preconditions on the current resource metainformation (header field data) and thus prevent the requested method from being applied to a resource other than the one intended. 10.4.14 413 Request Entity Too Large The server is refusing to process a request because the request entity is larger than the server is willing or able to process. The server MAY close the connection to prevent the client from continuing the request. If the condition is temporary, the server SHOULD include a Retry-After header field to indicate that it is temporary and after what time the client MAY try again. 10.4.15 414 Request-URI Too Long The server is refusing to service the request because the Request-URI is longer than the server is willing to interpret(解释;理解;). This rare(罕见的,特殊的;) condition(状态;环境;) is only likely to occur(发生;出现;闪现) when a client has improperly(不正确地,不适当地) converted(更换信仰的,修改的) a POST request to a GET request with long query information, when the client has descended(继承,遗传下来;) into a URI "black hole" of redirection (e.g., a redirected URI prefix(前缀) that points to a suffix(后缀,词尾) of itself), or when the server is under attack by a client attempting to exploit(开拓;剥削;开采;) security holes present in some servers using fixed-length buffers for reading or manipulating((暗中)控制,操纵,影响;) the Request-URI. 10.4.16 415 Unsupported Media Type The server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method. 10.4.17 416 Requested Range Not Satisfiable A server SHOULD return a response with this status code if a request included a Range request-header field (section 14.35), and none of the range-specifier values in this field overlap(互搭,重叠;) the current extent of the selected resource, and the request did not include an If-Range request-header field. (For byte-ranges, this means that the first-byte-pos of all of the byte-range-spec values were greater than the current length of the selected resource.) When this status code is returned for a byte-range request, the response SHOULD include a Content-Range entity-header field specifying the current length of the selected resource (see section 14.16). This response MUST NOT use the multipart/byteranges content-type. 10.4.18 417 Expectation Failed The expectation given in an Expect request-header field (see section 14.20) could not be met by this server, or, if the server is a proxy, the server has unambiguous(清楚的;不含糊的;明白的) evidence(证据;迹象;证词;明显) that the request could not be met by the next-hop(下一个节点;下一跳节点) server. 10.5 Server Error 5xx Response status codes beginning with the digit "5" indicate cases in which the server is aware(意识到的;) that it has erred or is incapable(无能力的,不会的;) of performing(表演的,履行的) the request. Except when responding to a HEAD request, the server SHOULD include an entity containing an explanation of the error situation, and whether it is a temporary or permanent condition. User agents SHOULD display any included entity to the user. These response codes are applicable to any request method. 10.5.1 500 Internal Server Error The server encountered(遭遇;) an unexpected(意外的;) condition(状态;环境;) which prevented it from fulfilling(使人满足的) the request. 10.5.2 501 Not Implemented(执行) The server does not support the functionality required to fulfill the request. This is the appropriate response when the server does not recognize the request method and is not capable of supporting it for any resource. 10.5.3 502 Bad Gateway The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request. 10.5.4 503 Service Unavailable The server is currently unable to handle the request due to a temporary overloading or maintenance(维护;维修;) of the server. The implication(含义;) is that this is a temporary condition which will be alleviated(减轻,缓解,缓和) after some delay. If known, the length of the delay MAY be indicated(指示的;表明的) in a Retry-After header. If no Retry-After is given, the client SHOULD handle the response as it would for a 500 response. Note: The existence(存在,实在;) of the 503 status code does not imply that a server must use it when becoming overloaded. Some servers may wish to simply refuse the connection. 10.5.5 504 Gateway Timeout The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary(辅助的;备用的,补充的;附加的;副的) server (e.g. DNS) it needed to access in attempting(尝试;试图) to complete the request. Note: Note to implementors(实现): some deployed proxies are known to return 400 or 500 when DNS lookups time out. 10.5.6 505 HTTP Version Not Supported The server does not support, or refuses to support, the HTTP protocol version that was used in the request message. The server is indicating that it is unable or unwilling to complete the request using the same major(主要的;重要的;) version as the client, as described in section 3.1, other than with this error message. The response SHOULD contain an entity describing why that version is not supported and what other protocols are supported by that server.一只想要飞得更高的小菜鸟
part of Hypertext Transfer Protocol -- HTTP/1.1RFC 2616 Fielding, et al. 9 Method Definitions The set of common methods for HTTP/1.1 is defined below. Although this set can be expanded(扩大;使…变大;伸展;伸开), additional(补充;额外的,附加的;另外的,追加的;外加) methods cannot be assumed(假定的;假装的;假冒的;被承担的) to share the same semantics(语义学;词义学) for separately(分别地,另行;分开,单独;分离地;个别地) extended clients and servers. The Host request-header field (section 14.23) MUST accompany(陪伴,陪同;附加,补充;与…共存;) all HTTP/1.1 requests. 9.1 Safe(安全的;保险的,肯定的;无损的;提供保护的) and Idempotent(幂等,等幂) Methods 9.1.1 Safe Methods Implementors(实现者) should be aware(意识到的;知道的;觉察到的) that the software(软件;软体;软设备) represents(代表;体现;表现) the user in their interactions(互动;一起活动) over the Internet, and should be careful to allow the user to be aware of any actions they might take which may have an unexpected significance(意义;重要性;意思) to themselves or others. In particular(特别的;详细的;独有的;挑剔的), the convention(国际公约;惯例,习俗,规矩) has been established(已建立的;已设立的;已制定的;确定的) that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval(检索;收回,挽回). These methods ought to be considered "safe". This allows user agents to represent(表现,象征;代表,代理;扮演;作为示范) other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested. Naturally, it is not possible to ensure that the server does not generate side-effects as a result of performing a GET request; in fact, some dynamic(动态的;动力的,动力学的;充满活力的,精力充沛的;不断变化的,充满变数的) resources consider that a feature. The important distinction(区别;荣誉;特质;卓越) here is that the user did not request the side-effects, so therefore cannot be held accountable(负有责任的) for them. 9.1.2 Idempotent Methods Methods can also have the property of "idempotence" in that (aside from error or expiration issues(问题,发表,宣布,分配)) the side-effects of N > 0 identical(同一的;完全同样的,相同的;恒等的;) requests is the same as for a single request. The methods GET, HEAD, PUT and DELETE share this property. Also, the methods OPTIONS and TRACE SHOULD NOT have side effects(边际效应,副作用), and so are inherently(天性地,固有地) idempotent. However, it is possible that a sequence(数列,序列;) of several requests is non- idempotent, even if all of the methods executed in that sequence are idempotent. (A sequence is idempotent if a single execution(依法处决;实行,执行;演奏) of the entire sequence always yields(产量( yield的名词复数 );收益量;[财政学]投资实得率) a result that is not changed by a reexecution(重新执行) of all, or part, of that sequence.) For example, a sequence is non-idempotent if its result depends on a value that is later modified in the same sequence. A sequence that never has side effects is idempotent, by definition(定义;规定,明确;) (provided that no concurrent(同时发生的;同时完成的;) operations are being executed on the same set of resources). 9.2 OPTIONS The OPTIONS method represents(代表;体现;) a request for information about the communication options available(可获得的;有空的;可购得的;能找到的) on the request/response chain identified(确认;辨认;) by the Request-URI. This method allows the client to determine the options and/or requirements associated with a resource, or the capabilities(容量;能力) of a server, without implying(暗示,暗指) a resource action or initiating(发起;开始) a resource retrieval.Responses to this method are not cacheable. If the OPTIONS request includes an entity-body (as indicated by the presence of Content-Length or Transfer-Encoding), then the media type MUST be indicated by a Content-Type field. Although this specification does not define any use for such a body, future extensions to HTTP might use the OPTIONS body to make more detailed queries on the server. A server that does not support such an extension MAY discard(丢弃,抛弃;) the request body. If the Request-URI is an asterisk ("*"), the OPTIONS request is intended(预期的;有意的) to apply to the server in general rather than to a specific resource. Since a server's communication options typically depend on the resource, the "*" request is only useful as a "ping" or "no-op" type of method; it does nothing beyond allowing the client to test the capabilities of the server. For example, this can be used to test a proxy for HTTP/1.1 compliance(服从,听从) (or lack thereof). If the Request-URI is not an asterisk, the OPTIONS request applies only to the options that are available when communicating with that resource. A 200 response SHOULD include any header fields that indicate optional features implemented(执行;实现) by the server and applicable(适当的;可应用的) to that resource (e.g., Allow), possibly including extensions not defined by this specification. The response body, if any, SHOULD also include information about the communication options. The format for such a body is not defined by this specification, but might be defined by future extensions to HTTP. Content negotiation(协商,谈判;转让;通过) MAY be used to select the appropriate response format. If no response body is included, the response MUST include a Content-Length field with a field-value of "0". The Max-Forwards request-header field MAY be used to target a specific proxy in the request chain. When a proxy receives an OPTIONS request on an absoluteURI for which request forwarding is permitted, the proxy MUST check for a Max-Forwards field. If the Max-Forwards field-value is zero ("0"), the proxy MUST NOT forward the message; instead, the proxy SHOULD respond with its own communication options. If the Max-Forwards field-value is an integer greater than zero, the proxy MUST decrement(减量;消耗;缩减) the field-value when it forwards the request. If no Max-Forwards field is present in the request, then the forwarded request MUST NOT include a Max-Forwards field. 9.3 GET The GET method means retrieve(取回;恢复,挽回;) whatever information (in the form of an entity) is identified(确认;辨认;) by the Request-URI. If the Request-URI refers to a data-producing process, it is the produced data which shall(必须;应该;可以;将要) be returned as the entity in the response and not the source text of the process, unless that text happens to be the output of the process. The semantics of the GET method change to a "conditional(视…而定的;) GET" if the request message includes an If-Modified-Since, If-Unmodified-Since, If-Match, If-None-Match, or If-Range header field. A conditional GET method requests that the entity be transferred only under the circumstances(境况;境遇;) described by the conditional header field(s). The conditional GET method is intended to reduce unnecessary network usage by allowing cached entities to be refreshed without requiring multiple requests or transferring data already held by the client. The semantics of the GET method change to a "partial GET" if the request message includes a Range header field. A partial GET requests that only part of the entity be transferred, as described in section 14.35. The partial GET method is intended to reduce unnecessary network usage by allowing partially-retrieved(部分检索) entities to be completed without transferring data already held by the client. The response to a GET request is cacheable if and only if it meets the requirements for HTTP caching described in section 13. See section 15.1.3 for security considerations(考虑) when used for forms. 9.4 HEAD The HEAD method is identical(同一的;完全同样的,相同的;) to GET except that the server MUST NOT return a message-body in the response. The metainformation(元信息) contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method is often used for testing hypertext links for validity, accessibility, and recent modification. The response to a HEAD request MAY be cacheable in the sense(感到;理解,领会;检测出) that the information contained in the response MAY be used to update a previously cached entity from that resource. If the new field values indicate that the cached entity differs from the current entity (as would be indicated by a change in Content-Length, Content-MD5, ETag or Last-Modified), then the cache MUST treat(对待;处理;款) the cache entry as stale. 9.5 POST The POST method is used to request that the origin server accept the entity enclosed(封闭的;被附上的;) in the request as a new subordinate(次要的;附属的) of the resource identified by the Request-URI in the Request-Line. POST is designed to allow a uniform method to cover the following functions: - Annotation(注释) of existing resources; - Posting a message to a bulletin(公布,公告;) board, newsgroup, mailing list,or similar group of articles; - Providing a block of data, such as the result of submitting a form, to a data-handling process; - Extending a database through an append operation. The actual function performed by the POST method is determined by the server and is usually dependent on the Request-URI. The posted entity is subordinate to that URI in the same way that a file is subordinate to a directory containing it, a news article is subordinate to a newsgroup to which it is posted, or a record is subordinate to a database. The action performed by the POST method might not result in a resource that can be identified by a URI. In this case, either 200 (OK) or 204 (No Content) is the appropriate response status, depending on whether or not the response includes an entity that describes the result. If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain an entity which describes the status of the request and refers to the new resource, and a Location header (see section 14.30). Responses to this method are not cacheable, unless the response includes appropriate Cache-Control or Expires header fields. However, the 303 (See Other) response can be used to direct the user agent to retrieve a cacheable resource. POST requests MUST obey the message transmission(传送;播送;) requirements set out in section 8.2. See section 15.1.3 for security considerations. 9.6 PUT The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers(提到) to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing(定居;居住) on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable(能干的;有才能的;有才华的;能胜任的) of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. If a new resource is created, the origin server MUST inform(通知;使活跃,使充满;预示) the user agent via the 201 (Created) response. If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request. If the resource could not be created or modified with the Request-URI, an appropriate(适当的;合适的;) error response SHOULD be given that reflects the nature of the problem. The recipient of the entity MUST NOT ignore any Content-* (e.g. Content-Range) headers that it does not understand or implement(实施,执行;) and MUST return a 501 (Not Implemented) response in such cases. If the request passes through a cache and the Request-URI identifies one or more currently cached entities, those entries SHOULD be treated as stale. Responses to this method are not cacheable. The fundamental(基础的,基本的,根本的,重要的,原始的,主要的,十分重大的;) difference between the POST and PUT requests is reflected(考虑;反照;表达) in the different meaning of the Request-URI. The URI in a POST request identifies the resource that will handle the enclosed entity. That resource might be a data-accepting process, a gateway to some other protocol, or a separate entity that accepts annotations. In contrast(对比,对照;), the URI in a PUT request identifies the entity enclosed with the request -- the user agent knows what URI is intended and the server MUST NOT attempt(尝试;试图) to apply the request to some other resource. If the server desires(渴望;希望) that the request be applied to a different URI,it MUST send a 301 (Moved Permanently) response; the user agent MAY then make its own decision regarding whether or not to redirect the request. A single resource MAY be identified(确认;辨认;认出) by many different URIs. For example, an article might have a URI for identifying "the current version" which is separate from the URI identifying each particular version. In this case, a PUT request on a general URI might result in several other URIs being defined by the origin server. HTTP/1.1 does not define how a PUT method affects the state of an origin server. PUT requests MUST obey(服从,听从) the message transmission requirements set out in section 8.2. Unless otherwise specified for a particular entity-header, the entity-headers in the PUT request SHOULD be applied to the resource created or modified by the PUT. 9.7 DELETE The DELETE method requests that the origin server delete the resource identified by the Request-URI. This method MAY be overridden by human intervention(介入,干涉,干预;调解,排解) (or other means) on the origin server. The client cannot be guaranteed(有保证的;有人担保的) that the operation(手术;操作,经营;) has been carried out, even if the status code returned from the origin server indicates that the action has been completed successfully. However, the server SHOULD NOT indicate success unless, at the time the response is given, it intends to delete the resource or move it to an inaccessible(达不到的;难见到的;不能接近的;无法理解的) location. A successful response SHOULD be 200 (OK) if the response includes an entity describing the status, 202 (Accepted) if the action has not yet been enacted(制定), or 204 (No Content) if the action has been enacted but the response does not include an entity. If the request passes through a cache and the Request-URI identifies one or more currently cached entities, those entries SHOULD be treated as stale. Responses to this method are not cacheable. 9.8 TRACE(跟踪,追踪;追溯,探索;探索;查找) The TRACE method is used to invoke(借助) a remote, application-layer loop-back of the request message. The final recipient of the request SHOULD reflect the message received back to the client as the entity-body of a 200 (OK) response. The final recipient is either the origin server or the first proxy or gateway to receive a Max-Forwards value of zero (0) in the request (see section 14.31). A TRACE request MUST NOT include an entity. TRACE allows the client to see what is being received at the other end of the request chain and use that data for testing or diagnostic(诊断的,判断的;特征的) information. The value of the Via header field (section 14.45) is of particular interest, since it acts as a trace of the request chain. Use of the Max-Forwards header field allows the client to limit the length of the request chain, which is useful for testing a chain of proxies forwarding messages in an infinite(无限的,无穷的;无数的,许许多多的;极大的) loop. If the request is valid, the response SHOULD contain the entire request message in the entity-body, with a Content-Type of "message/http". Responses to this method MUST NOT be cached. 9.9 CONNECT This specification reserves the method name CONNECT for use with a proxy that can dynamically switch to being a tunnel (e.g. SSL tunneling [44]).一只想要飞得更高的小菜鸟
part of Hypertext Transfer Protocol -- HTTP/1.1RFC 2616 Fielding, et al. 14 Header Field Definitions(规定) This section(部分,章节) defines(规定定义) the syntax(语法) and semantics(语意) of all standard(标准) HTTP/1.1 header fields. For entity-header fields, both sender(发送人) and recipient(接收者) refer(提及,归因,起源) to either the client or the server, depending on who sends and who receives the entity. 14.1 Accept(接受) The Accept request-header field can be used to specify(指定) certain(某一,必然,确定的) media(媒体) types(类型) which are acceptable(可接受的,合理的) for the response. Accept headers can be used to indicate(标识) that the request is specifically(特有的,指定的) limited to a small set of desired(需要的) types, as in the case of(就...来说,至于...) a request for an in-line(内联) image. Accept = "Accept" ":" #( media-range(媒体范围) [ accept-params (可接受的参数)] ) media-range = ( "*/*" | ( type "/" "*" ) | ( type "/" subtype ) ) *( ";" parameter ) accept-params = ";" "q" "=" qvalue *( accept-extension(可接受的扩展) ) accept-extension = ";" token(记号) [ "=" ( token | quoted-string(引用字符串) ) ] The asterisk(星号) "*" character(特点,使具有特征) is used to group(分类,归类) media types into ranges(范围), with "*/*" indicating(指示,标识) all media types and "type/*" indicating all subtypes(子类型) of that type. The media-range MAY include media type parameters(参数) that are applicable(适当的,适用的) to that range. Each media-range MAY be followed by one or more accept-params(接受参数), beginning with the "q" parameter for indicating a relative(相对) quality(质量) factor(因子). The first "q" parameter (if any) separates(隔开,区分,分开) the media-range parameter(s) from the accept-params. Quality factors allow the user or user agent to indicate the relative degree (相对次数)of preference(优先级) for that media-range, using the qvalue scale from 0 to 1 (section 3.9). The default value is q=1. Note: Use of the "q" parameter name to separate media type parameters from Accept extension parameters is due to historical(历史姓的,有根据的) practice. Although this prevents(阻碍) any media type parameter named "q" from being used with a media range, such an event is believed to be unlikely given the lack(缺乏) of any "q" parameters in the IANA(因特网编号管理局) media type registry(记录,登记) and the rare(罕见的,特殊的) usage(使用;用法;) of any media type parameters in Accept. Future media types are discouraged(使沮丧;阻碍) from registering any parameter named "q". The example Accept: audio/*; q=0.2, audio/basic SHOULD be interpreted(理解;解释) as "I prefer audio/basic, but send me any audio type if it is the best available after an 80% mark-down(降低标准) in quality." If no Accept header field is present, then it is assumed(假定;假设;取得(权力);呈现) that the client accepts all media types. If an Accept header field is present, and if the server cannot send a response which is acceptable(可接受的) according(依照) to the combined(结合) Accept field value, then the server SHOULD send a 406 (not acceptable) response. A more elaborate(复杂) example is Accept: text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c Verbally(言词上;口头地;照字面地;作为动词), this would be interpreted(解释) as "text/html and text/x-c are the preferred(首选的) media types, but if they do not exist, then send the text/x-dvi entity, and if that does not exist, send the text/plain entity." Media ranges can be overridden(优先于) by more specific media ranges or specific media types. If more than one media range applies to a given type, the most specific reference has precedence(优先权). For example, Accept: text/*, text/html, text/html;level=1, */* have the following precedence: 1) text/html;level=1 2) text/html 3) text/* 4) */* The media type quality factor associated(联合的;有关联的;联合的) with a given type is determined(决定;(使)下决心,(使)做出决定) by finding the media range with the highest precedence which matches that type. For example, Accept: text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5 would cause the following values to be associated: text/html;level=1 = 1 text/html = 0.7 text/plain = 0.3 image/jpeg = 0.5 text/html;level=2 = 0.4 text/html;level=3 = 0.7 Note: A user agent might be provided with a default set of quality values for certain media ranges. However, unless(除非) the user agent is a closed system which cannot interact(互动;相互作用;互相影响) with other rendering(渲染,表演) agents, this default set ought to(理当,应该) be configurable(可配置的) by the user. 14.2 Accept-Charset(可接受的字符集) The Accept-Charset request-header field can be used to indicate(标示,指示;) what character(字母) sets(集合) are acceptable for the response. This field allows clients capable(有能力的) of understanding more comprehensive(综合的;广泛的;有理解力的,悟性好的;) or special- purpose(用途) character(字母,性格,特点) sets to signal(标识,信号) that capability(性能,能力) to a server which is capable of representing(体现,表现) documents in those character sets. Accept-Charset = "Accept-Charset" ":" 1#( ( charset | "*" )[ ";" "q" "=" qvalue ] ) Character set(字符集) values are described in section 3.4. Each charset MAY be given an associated(相关的) quality(质量,品种,这里理解位权重) value which represents(表示,代表) the user's preference(偏爱,优先权) for that charset. The default value is q=1. An example is Accept-Charset: iso-8859-5, unicode-1-1;q=0.8 The special value "*", if present in the Accept-Charset field, matches every character set (including ISO-8859-1) which is not mentioned(提到) elsewhere(在别处) in the Accept-Charset field. If no "*" is present in an Accept-Charset field, then all character sets not explicitly mentioned get a quality value of 0, except for ISO-8859-1, which gets a quality value of 1 if not explicitly(明确的) mentioned. If no Accept-Charset header is present, the default is that any character set is acceptable. If an Accept-Charset header is present, and if the server cannot send a response which is acceptable(可接受的) according to the Accept-Charset header, then the server SHOULD send an error response with the 406 (not acceptable) status code, though the sending of an unacceptable response is also allowed. 14.3 Accept-Encoding(编码) The Accept-Encoding request-header field is similar (相似的,类似的)to Accept, but restricts(约束,限制) the content-codings(内容编码) (section 3.5) that are acceptable in the response. Accept-Encoding = "Accept-Encoding" ":" 1#( codings [ ";" "q" "=" qvalue ] ) codings = ( content-coding | "*" ) Examples of its use are: Accept-Encoding: compress, gzip Accept-Encoding: Accept-Encoding: * Accept-Encoding: compress;q=0.5, gzip;q=1.0 Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0 A server tests whether a content-coding is acceptable, according to an Accept-Encoding field, using these rules: 1. If the content-coding is one of the content-codings listed in the Accept-Encoding field, then it is acceptable, unless it is accompanied(附有,伴随) by a qvalue of 0. (As defined in section 3.9, a qvalue of 0 means "not acceptable.") 2. The special "*" symbol in an Accept-Encoding field matches any available content-coding not explicitly(明确的) listed in the header field. 3. If multiple content-codings are acceptable, then the acceptable content-coding with the highest non-zero qvalue is preferred. 4. The "identity"(身份) content-coding is always acceptable, unless specifically refused because the Accept-Encoding field includes "identity;q=0", or because the field includes "*;q=0" and does not explicitly include the "identity" content-coding. If the Accept-Encoding field-value is empty, then only the "identity" encoding is acceptable. If an Accept-Encoding field is present in a request, and if the server cannot send a response which is acceptable according to the Accept-Encoding header, then the server SHOULD send an error response with the 406 (Not Acceptable) status code. If no Accept-Encoding field is present in a request, the server MAY assume(承担;呈现;假定,认为;装出) that the client will accept any content coding. In this case, if "identity" is one of the available content-codings, then the server SHOULD use the "identity" content-coding, unless(除非) it has additional(额外的,补充) information that a different content-coding is meaningful to the client. Note: If the request does not include an Accept-Encoding field, and if the "identity" content-coding is unavailable, then content-codings commonly understood by HTTP/1.0 clients (i.e., "gzip" and "compress") are preferred; some older clients improperly(不正确地,不适当地) display messages sent with other content-codings. The server might also make this decision based on information about the particular(特别的) user-agent(用户代理) or client. Note: Most HTTP/1.0 applications(申请,申请书) do not recognize(承认,识别,认出) or obey(服从,听从) qvalues associated with content-codings. This means that qvalues will not work and are not permitted(许可,允许) with x-gzip or x-compress. 14.4 Accept-Language The Accept-Language request-header field is similar to Accept, but restricts(限制) the set of natural languages that are preferred(首选,优先) as a response to the request. Language tags are defined in section 3.10. Accept-Language = "Accept-Language" ":" 1#( language-range [ ";" "q" "=" qvalue ] ) language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" ) Each language-range MAY be given an associated quality value which represents(体现,表现) an estimate(预估,估计) of the user's preference for the languages specified by that range. The quality value defaults to "q=1". For example, Accept-Language: da, en-gb;q=0.8, en;q=0.7 would mean: "I prefer Danish, but will accept British English and other types of English." A language-range matches a language-tag if it exactly equals(相等的) the tag, or if it exactly(明确的,精确的) equals a prefix(前缀,在...前加上) of the tag such that the first tag character following the prefix is "-". The special range "*", if present in the Accept-Language field, matches every tag not matched by any other range present in the Accept-Language field. Note: This use of a prefix matching rule does not imply(暗示,说明) that language tags are assigned to languages in such a way that it is always true that if a user understands a language with a certain tag, then this user will also understand all languages with tags for which this tag is a prefix. The prefix rule simply(简单的,简易的) allows the use of prefix tags if this is the case. The language quality factor assigned to a language-tag by the Accept-Language field is the quality value of the longest language-range in the field that matches the language-tag. If no language-range in the field matches the tag, the language quality factor(权重因子) assigned is 0. If no Accept-Language header is present in the request, the server SHOULD assume(假设) that all languages are equally(相等的,公平的) acceptable. If an Accept-Language header is present, then all languages which are assigned a quality factor greater than 0 are acceptable. It might be contrary(相反的,出乎意料的) to the privacy(隐私) expectations(希望,预料) of the user to send an Accept-Language header with the complete linguistic(语言) preferences of the user in every request. For a discussion of this issue, see section 15.1.4. As intelligibility(可理解性,可理解的事物) is highly dependent(依赖的;依靠的;取决于…的;有瘾的) on the individual(个人的,个体的) user, it is recommended(被推荐的) that client applications make the choice of linguistic preference available to the user. If the choice is not made available, then the Accept-Language header field MUST NOT be given in the request. Note: When making the choice of linguistic preference available to the user, we remind(提醒;使想起,使记起) implementors(执行者) of the fact that users are not familiar with the details of language matching as described above, and should provide appropriate guidance(引导,指导). As an example, users might assume(认为) that on selecting "en-gb", they will be served any kind of English document if British English is not available. A user agent might suggest in such a case to add "en" to get the best matching behavior(行为,态度). 14.5 Accept-Ranges(接受范围) The Accept-Ranges response-header field allows the server to indicate(表明,标示,指示) its acceptance of range requests for a resource: Accept-Ranges = "Accept-Ranges" ":" acceptable-ranges acceptable-ranges = 1#range-unit | "none" Origin servers that accept byte-range requests MAY send Accept-Ranges: bytes but are not required to do so. Clients MAY generate byte-range requests without having received this header for the resource involved(复杂难懂的,受到牵扯的). Range units are defined in section 3.12. Servers that do not accept any kind of range request for a resource MAY send Accept-Ranges: none to advise(通知,建议) the client not to attempt(尝试,试图) a range request. 14.6 Age The Age response-header field conveys(表达;输送;运送;运输) the sender's estimate(估计,预测;) of the amount(总共,等同,接近) of time since the response (or its revalidation(重新生效)) was generated(生成;引起;) at the origin server. A cached response is "fresh" if its age does not exceed its freshness lifetime. Age values are calculated(计算出的,有计划的) as specified(指定;详述) in section 13.2.3. Age = "Age" ":" age-value age-value = delta-seconds Age values are non-negative(消极的,否定的) decimal integers(十进制整数), representing(体现,表现) time in seconds. If a cache receives a value larger than the largest positive integer it can represent, or if any of its age calculations overflows, it MUST transmit an Age header with a value of 2147483648 (2^31). An HTTP/1.1 server that includes a cache MUST include an Age header field in every response generated from its own cache. Caches SHOULD use an arithmetic(算法,算术,计算) type of at least 31 bits of range. 14.7 Allow The Allow entity-header field lists the set of methods supported by the resource identified by(由...鉴别) the Request-URI. The purpose of this field is strictly(严格的) to inform(通知) the recipient(接收者,容器) of valid(有效的) methods associated(合伙,联合) with the resource. An Allow header field MUST be present in a 405 (Method Not Allowed) response. Allow = "Allow" ":" #Method Example of use: Allow: GET, HEAD, PUT This field cannot prevent(阻止) a client from trying other methods. However, the indications(指示,标示) given by the Allow header field value SHOULD be followed. The actual(实际的,真实的) set of allowed methods is defined(明确的,清晰的) by the origin server at the time of each request. The Allow header field MAY be provided with a PUT request to recommend(推荐) the methods to be supported by the new or modified(修改,更改) resource. The server is not required(必须) to support these methods and SHOULD include an Allow header in the response giving the actual supported methods. A proxy MUST NOT modify the Allow header field even if it does not understand all the methods specified(使具有特性), since the user agent might have other means of communicating with the origin server. 14.8 Authorization(授权,批准) A user agent that wishes to authenticate itself with a server--usually, but not necessarily, after receiving a 401 response--does so by including an Authorization request-header field with the request. The Authorization field value consists(由...组成) of credentials(凭证) containing the authentication information of the user agent for the realm(范围,领域) of the resource being requested. Authorization = "Authorization" ":" credentials HTTP access authentication is described in "HTTP Authentication:Basic and Digest Access Authentication". If a request is authenticated and a realm specified, the same credentials SHOULD be valid for all other requests within(在...范围内) this realm (assuming(如果,假定) that the authentication scheme(计划) itself does not require otherwise, such as credentials that vary(变化,不同) according(依照) to a challenge(挑战,质疑) value or using synchronized(同步的) clocks). When a shared cache (see section 13.7) receives a request containing an Authorization field, it MUST NOT return the corresponding(相当于,相对应) response as a reply to any other request, unless one of the following specific exceptions(例外) holds: 1. If the response includes the "s-maxage" cache-control directive(指令), the cache MAY use that response in replying to a subsequent(随后,后来的) request. But (if the specified maximum age has passed) a proxy cache MUST first revalidate it with the origin server, using the request-headers from the new request to allow the origin server to authenticate the new request. (This is the defined behavior for s-maxage.) If the response includes "s-maxage=0", the proxy MUST always revalidate it before re-using it. 2. If the response includes the "must-revalidate" cache-control directive, the cache MAY use that response in replying to a subsequent request. But if the response is stale(陈旧的), all caches MUST first revalidate it with the origin server, using the request-headers from the new request to allow the origin server to authenticate the new request. 3. If the response includes the "public" cache-control directive, it MAY be returned in reply to any subsequent request. 14.9 Cache-Control(缓存控制) The Cache-Control general-header field is used to specify(指定) directives(指令) that MUST be obeyed(服从;遵守) by all caching (机械,机制) along the request/response chain(链条). The directives specify behavior(行为;态度) intended(打算;意指) to prevent(阻止) caches from adversely(反对) interfering(干涉;阻碍) with the request or response. These directives typically(通常的;典型的) override(覆盖;推翻) the default caching algorithms(算法). Cache directives are unidirectional(单项的) in that the presence(出席) of a directive in a request does not imply(暗示;意味) that the same directive is to be given in the response. Note : that HTTP/1.0 caches might not implement(使生效,实现;) Cache-Control and might only implement Pragma(编译指示): no-cache (see section 14.32). Cache directives MUST be passed through by a proxy or gateway application(网关), regardless(不顾后果地;不管怎样,无论如何;) of their significance(意义;重要性;) to that application, since the directives might be applicable(适当的;可应用的) to all recipients(接受的;受领的;容纳的;) along the request/response chain. It is not possible to specify a cache- directive for a specific(具体的;明确的;特种的;) cache. Cache-Control = "Cache-Control" ":" 1#cache-directive cache-directive = cache-request-directive | cache-response-directive cache-request-directive = "no-cache" ; Section 14.9.1 | "no-store" ; Section 14.9.2 | "max-age" "=" delta-seconds ; Section 14.9.3, 14.9.4 | "max-stale" [ "=" delta-seconds ] ; Section 14.9.3 | "min-fresh" "=" delta-seconds ; Section 14.9.3 | "no-transform" ; Section 14.9.5 | "only-if-cached" ; Section 14.9.4 | cache-extension ; Section 14.9.6 cache-response-directive = "public" ; Section 14.9.1 | "private" [ "=" <"> 1#field-name <"> ] ; Section 14.9.1 | "no-cache" [ "=" <"> 1#field-name <"> ]; Section 14.9.1 | "no-store" ; Section 14.9.2 | "no-transform" ; Section 14.9.5 | "must-revalidate" ; Section 14.9.4 | "proxy-revalidate" ; Section 14.9.4 | "max-age" "=" delta-seconds ; Section 14.9.3 | "s-maxage" "=" delta-seconds ; Section 14.9.3 | cache-extension ; Section 14.9.6 cache-extension = token [ "=" ( token | quoted-string ) ] When a directive appears without any 1#field-name parameter, the directive applies to the entire(整个的;全部的;全体的;) request or response. When such a directive appears with a 1#field-name parameter, it applies only to the named field or fields, and not to the rest(休息;剩余部分;) of the request or response. This mechanism(机制,机能) supports extensibility(延伸性;可延长性,展开性;可延展性); implementations(实现;实施;实现工具;实作) of future versions of the HTTP protocol might apply these directives to header fields not defined in HTTP/1.1. The cache-control directives can be broken down into these general categories(种类,类别): - Restrictions(管制;约束) on what are cacheable(可缓存的); these may only be imposed(自己担负的) by the origin server. - Restrictions on what may be stored(存储的;存信息的) by a cache; these may be imposed by either the origin server or the user agent. - Modifications(改变;更改;缓和) of the basic(基础,基本;) expiration(过期) mechanism(); these may be imposed by either the origin server or the user agent. - Controls over cache revalidation(重新生效) and reload; these may only be imposed by a user agent. - Control over transformation(变化;转换) of entities(实体). - Extensions(拓展;延申) to the caching system. 14.9.1 What is Cacheable(可缓存的) By default, a response is cacheable if the requirements(需要;所需的(或所要的)东西;) of the request method, request header fields, and the response status indicate(表明,标示,指示;) that it is cacheable. Section 13.4 summarizes(总结,概述) these defaults for cacheability(缓存能力;). The following Cache-Control response directives allow an origin server to override(覆盖;推翻,无视;践踏;优先于) the default cacheability of a response: public Indicates(指示;标示) that the response MAY be cached by any cache, even if it would normally be non-cacheable or cacheable only within(不超过,在…的范围内;) a non- shared cache. (See also Authorization, section 14.8, for additional(补充;额外的,附加的;另外的,追加的;外加) details.) private Indicates that all or part of the response message is intended(打算) for a single user and MUST NOT be cached by a shared cache. This allows an origin server to state(规定;陈述,声明) that the specified(指定) parts of the response are intended for only one user and are not a valid response for requests by other users. A private (non-shared) cache MAY cache the response. Note: This usage of the word private only controls where the response may be cached, and cannot ensure(确保;) the privacy(隐私,秘密;) of the message content. no-cache If the no-cache directive does not specify a field-name, then a cache MUST NOT use the response to satisfy(使满意,满足) a subsequent(随后的;后来的;) request without successful revalidation(重新生效) with the origin server(原始服务器). This allows an origin server to prevent caching even by caches that have been configured(设定;配置) to return stale(陈腐的;不新鲜的;走了味的) responses to client requests. If the no-cache directive does specify one or more field-names, then a cache MAY use the response to satisfy a subsequent request, subject(提供,提出;使…隶属) to any other restrictions(管制;约束) on caching. However, the specified field-name(s) MUST NOT be sent in the response to a subsequent request without successful revalidation with the origin server. This allows an origin server to prevent the re-use(复用) of certain header fields in a response, while still allowing caching of the rest of the response(剩余的响应). Note: Most HTTP/1.0 caches will not recognize(承认;识别;认出) or obey(遵守,遵循) this directive. 14.9.2 What May be Stored by Caches(可以由缓存存储的内容) no-store The purpose(目的;意志;) of the no-store directive is to prevent the inadvertent(不经意的) release(释放;发布;) or retention(保留;) of sensitive(敏感的;感觉的;) information (for example, on backup(本分,候补) tapes). The no-store directive applies to the entire(整个的;全部的) message, and MAY be sent either in a response or in a request. If sent in a request, a cache MUST NOT store any part of either this request or any response to it. If sent in a response, a cache MUST NOT store any part of either this response or the request that elicited(引出,探出) it. This directive applies to both non-shared and shared caches. "MUST NOT store" in this context means that the cache MUST NOT intentionally(有意的;故意的) store the information in non-volatile(易变的;不稳定的) storage, and MUST make a best-effort(努力;尝试) attempt(尝试;试图) to remove the information from volatile storage as promptly(迅速的;立即的) as possible after forwarding(推进;促进) it. Even when this directive is associated(合伙;联合)with a response, users might explicitly(明白的;明确的) store such a response outside of the caching system (e.g., with a "Save As" dialog). History buffers(历史缓存) MAY store such responses as part of their normal operation(手术;操作,经营;). The purpose of this directive is to meet the stated requirements(需要;所需的) of certain(某一;必然的;已确定的) users and service authors who are concerned(涉及;参与,卷入;) about accidental(意外的,偶然(发生)的;附属的) releases of information via(经过;通过,凭借;取道) unanticipated(不曾预料到的) accesses(入口;接近) to cache data structures. While the use of this directive might improve privacy(隐私,秘密;) in some cases, we caution(警告;小心;) that it is NOT in any way a reliable(可靠的;可信赖的;真实可信的) or sufficient(足够的; 充足的; 充分的) mechanism for ensuring(确保) privacy. In particular(特别的;详细的;独有的;挑剔的), malicious(恶意的,有敌意的;) or compromised(妥协,让步的) caches might not recognize(承认) or obey(服从) this directive, and communications networks might be vulnerable(易受攻击的;) to eavesdropping(偷听). 14.9.3 Modifications(改变; 更改; ) of the Basic Expiration(截止;满期;) Mechanism The expiration time of an entity MAY be specified by the origin server using the Expires header (see section 14.21). Alternatively(或者;二者择一地;要不然), it MAY be specified(指定) using the max-age directive in a response. When the max-age cache-control directive is present in a cached response, the response is stale if its current age is greater than the age value given (in seconds) at the time of a new request for that resource. The max-age directive on a response implies(暗示,暗指) that the response is cacheable (i.e., "public") unless(除了,…除外) some other, more restrictive(限制的;约束的;限定的) cache directive is also present. If a response includes both an Expires(失效;断气;逝世) header and a max-age directive, the max-age directive overrides(优先于;比…更重要) the Expires header, even if the Expires header is more restrictive. This rule allows an origin server to provide, for a given response, a longer expiration time to an HTTP/1.1 (or later) cache than to an HTTP/1.0 cache. This might be useful if certain HTTP/1.0 caches improperly(不正确地,不适当地) calculate(预测,推测) ages or expiration times, perhaps due to desynchronized(不同步) clocks. Many HTTP/1.0 cache implementations(实现;实施;) will treat(治疗;对待;处理;款待) an Expires(失效;断气;逝世) value that is less than or equal(相等,相平) to the response Date value as being equivalent(相等的,相当的,等效的;) to the Cache-Control response directive "no-cache". If an HTTP/1.1 cache receives such a response, and the response does not include a Cache-Control header field, it SHOULD consider(考虑;认为;以为;) the response to be non-cacheable in order to retain(保持;留在心中,记住;) compatibility(适合;互换性; 通用性;和睦相处) with HTTP/1.0 servers. Note: An origin server might wish to use a relatively new HTTP cache control feature(特征,特点;), such as the "private" directive, on a network including older caches that do not understand that feature. The origin server will need to combine(使结合;使化合;兼有;) the new feature with an Expires field whose value is less than or equal to the Date value. This will prevent older caches from improperly(不正确地,不适当地) caching the response. s-maxage If a response includes an s-maxage directive, then for a shared cache (but not for a private cache), the maximum age specified by this directive overrides the maximum age specified by either the max-age directive or the Expires header. The s-maxage directive also implies(暗示,暗指) the semantics(语义学;词义学) of the proxy-revalidate directive (see section 14.9.4), i.e., that the shared cache must not use the entry after it becomes stale to respond to a subsequent request without first revalidating it with the origin server. The s-maxage directive is always ignored(对…不予理会;佯装未见) by a private cache. Note that most older caches, not compliant(遵从的;依从的;) with this specification(规格;说明书;详述), do not implement(实施,执行;使生效,) any cache-control directives. An origin server wishing to use a cache-control directive that restricts(约束;), but does not prevent, caching by an HTTP/1.1-compliant cache MAY exploit(开拓;剥削;) the requirement that the max-age directive overrides the Expires header, and the fact that pre-HTTP/1.1-compliant(遵从的;依从的;) caches do not observe(观察;研究) the max-age directive. Other directives allow a user agent(代理人;) to modify(修改;) the basic expiration mechanism. These directives MAY be specified(详述;提出…的条件;使具有特性) on a request: max-age Indicates(象征;) that the client is willing to accept a response whose age is no greater than the specified time in seconds. Unless max-stale directive is also included, the client is not willing to accept a stale response. min-fresh Indicates that the client is willing to accept a response whose freshness(鲜度;气味清新地,精神饱满地) lifetime is no less than its current age plus the specified time in seconds. That is, the client wants a response that will still be fresh for at least the specified number of seconds. max-stale Indicates that the client is willing to accept a response that has exceeded(过度的,非常的) its expiration(截止;) time. If max-stale is assigned a value, then the client is willing to accept a response that has exceeded its expiration time by no more than the specified number of seconds. If no value is assigned to max-stale, then the client is willing to accept a stale response of any age. If a cache returns a stale response, either because of a max-stale directive on a request, or because the cache is configured(设定;配置) to override the expiration time of a response, the cache MUST attach(附着;从属;) a Warning header to the stale response, using Warning 110 (Response is stale). A cache MAY be configured to return stale responses without validation(确认), but only if this does not conflict(冲突;抵触;) with any "MUST"-level requirements concerning(涉及;) cache validation (e.g., a "must-revalidate" cache-control directive). If both the new request and the cached entry include "max-age" directives, then the lesser of the two values is used for determining(确定;决定;) the freshness of the cached entry for that request. 14.9.4 Cache Revalidation and Reload Controls(缓存重校验及重加载控制) Sometimes a user agent might want or need to insist(坚持;强调;) that a cache revalidate its cache entry(进入,入场;入口处,门口;) with the origin server (and not just with the next cache along the path to the origin server), or to reload its cache entry from the origin server. End-to-end revalidation might be necessary if either the cache or the origin server has overestimated(对(数量)估计过高,对…作过高的评价) the expiration time of the cached response. End-to-end reload may be necessary if the cache entry has become corrupted(破坏;) for some reason(原因;理由;理性;理智). End-to-end revalidation may be requested either when the client does not have its own local cached copy, in which case we call it "unspecified(未指明的,未加规定的;) end-to-end revalidation", or when the client does have a local cached copy, in which case we call it "specific end-to-end revalidation." The client can specify these three kinds of action using Cache-Control request directives: End-to-end reload The request includes a "no-cache" cache-control directive or, for compatibility(适合;互换性; 通用性;和睦相处) with HTTP/1.0 clients, "Pragma: no-cache". Field names MUST NOT be included with the no-cache directive in a request. The server MUST NOT use a cached copy when responding to such a request. Specific end-to-end revalidation The request includes a "max-age=0" cache-control directive, which forces(强作;施强力于;促成早熟) each cache along the path to the origin server to revalidate its own entry, if any, with the next cache or server. The initial(最初的;开始的;首字母的) request includes a cache-validating conditional with the client's current validator(验证器). Unspecified end-to-end revalidation The request includes "max-age=0" cache-control directive, which forces each cache along the path to the origin server to revalidate its own entry, if any, with the next cache or server. The initial request does not include a cache-validating conditional(视…而定的;[语]条件的,假定的;); the first cache along the path (if any) that holds a cache entry for this resource includes a cache-validating conditional with its current validator. max-age When an intermediate cache is forced, by means of a max-age=0 directive, to revalidate its own cache entry, and the client has supplied its own validator in the request, the supplied validator might differ from the validator currently stored with the cache entry. In this case, the cache MAY use either validator in making its own request without affecting semantic transparency. However, the choice of validator might affect performance. The best approach is for the intermediate cache to use its own validator when making its request. If the server replies with 304 (Not Modified), then the cache can return its now validated copy to the client with a 200 (OK) response. If the server replies with a new entity and cache validator, however, the intermediate cache can compare the returned validator with the one provided in the client's request, using the strong comparison function. If the client's validator is equal to the origin server's, then the intermediate cache simply returns 304 (Not Modified). Otherwise, it returns the new entity with a 200 (OK) response. If a request includes the no-cache directive, it SHOULD NOT include min-fresh, max-stale, or max-age. only-if-cached In some cases, such as times of extremely(非常,很;去;绝) poor network connectivity(连通性), a client may want a cache to return only those responses that it currently has stored, and not to reload or revalidate with the origin server. To do this, the client may include the only-if-cached directive in a request. If it receives this directive, a cache SHOULD either respond using a cached entry that is consistent(一致的;连续的;不矛盾的;坚持的) with the other constraints(约束;限制;) of the request, or respond with a 504 (Gateway Timeout) status. However, if a group of caches is being operated(操作;经营;) as a unified(统一的;一元化的;) system with good internal connectivity, such a request MAY be forwarded(发送;促进) within that group of caches. must-revalidate Because a cache MAY be configured(设定;配置) to ignore(忽视,不顾;) a server's specified expiration time, and because a client request MAY include a max-stale directive (which has a similar effect(效果; 影响; 印象;)), the protocol also includes a mechanism for the origin server to require revalidation of a cache entry on any subsequent(随后的;后来的;) use. When the must-revalidate directive is present in a response received by a cache, that cache MUST NOT use the entry after it becomes stale to respond to a subsequent request without first revalidating it with the origin server. (I.e., the cache MUST do an end-to-end revalidation every time, if, based solely(仅仅;纯粹;唯一地;独一无二地) on the origin server's Expires or max-age value, the cached response is stale.) The must-revalidate directive is necessary to support reliable(可靠的;可信赖的;真实可信的) operation(手术;操作,经营;) for certain(某一;必然的;已确定的) protocol features(特征). In all circumstances(境况;境遇;) an HTTP/1.1 cache MUST obey(遵守,遵循) the must-revalidate directive; in particular(特别的;), if the cache cannot reach the origin server for any reason, it MUST generate(形成,造成;) a 504 (Gateway Timeout) response. Servers SHOULD send the must-revalidate directive if and only if failure(失败,不及格;) to revalidate a request on the entity(实体;实际存在物;本质) could result in incorrect operation, such as a silently(寂静地,沉默地;闷头儿) unexecuted(未实行的,未执行的,) financial(金融的;财务的;财政的;有钱的) transaction(交易,业务,事务;). Recipients(接受的;受领的;) MUST NOT take any automated action that violates(违反;侵犯;亵渎) this directive, and MUST NOT automatically provide an unvalidated copy of the entity if revalidation fails. Although this is not recommended, user agents operating(操作的;营运的;) under severe connectivity constraints(约束;限制) MAY violate this directive but, if so, MUST explicitly(明白地,明确地) warn the user that an unvalidated response has been provided. The warning MUST be provided on each unvalidated access, and SHOULD require explicit user confirmation(证实;证明;确认,认可;). proxy-revalidate The proxy-revalidate directive has the same meaning as the must-revalidate directive, except that it does not apply to non-shared user agent caches. It can be used on a response to an authenticated(证明是真实的、可靠的或有效的) request to permit(许可,准许;默许,放任;允许,容许) the user's cache to store and later return the response without needing to revalidate it (since it has already been authenticated once by that user), while still requiring proxies that service many users to revalidate each time (in order to make sure that each user has been authenticated). Note that such authenticated responses also need the public cache control directive in order to allow them to be cached at all. 14.9.5 No-Transform Directive(无变化指令) no-transform Implementors(实现者) of intermediate(中间的,中级的) caches (proxies) have found it useful to convert(转变;) the media type of certain(某一;必然的;已确定的) entity bodies. A non-transparent(透明的;清澈的;) proxy might, for example, convert between image formats in order to save cache space or to reduce(减少;) the amount of traffic(交通,运输量;) on a slow link. Serious operational(操作的;经营的;) problems occur(发生;出现;闪现), however, when these transformations are applied to entity bodies intended(预期的;有意的) for certain kinds of applications(申请;申请书). For example, applications for medical imaging, scientific(科学的;) data analysis(分析,分解;) and those using end-to-end authentication, all depend on receiving an entity body that is bit for bit identical(同一的;完全同样的,相同的;) to the original(原始的;最初的;) entity-body. Therefore, if a message includes the no-transform directive, an intermediate(中间的,中级的) cache or proxy MUST NOT change those headers that are listed in section 13.5.2 as being subject(提供,提出;使…隶属) to the no-transform directive. This implies(暗示,暗指) that the cache or proxy MUST NOT change any aspect(方面;面貌;方位,方向;形势) of the entity-body that is specified by these headers, including the value of the entity-body itself. 14.9.6 Cache Control Extensions(缓存控制扩展) The Cache-Control header field can be extended through the use of one or more cache-extension tokens, each with an optional assigned value. Informational extensions (those which do not require a change in cache behavior(行为;态度;)) MAY be added without changing the semantics(语义学;词义学) of other directives. Behavioral extensions are designed to work by acting as modifiers to the existing base of cache directives. Both the new directive and the standard(标准,规格;) directive are supplied, such that applications which do not understand the new directive will default to the behavior specified by the standard directive, and those that understand the new directive will recognize(承认;识别;认出) it as modifying the requirements associated(合伙,合营;联合,结合;联想) with the standard directive. In this way, extensions to the cache-control directives can be made without requiring changes to the base protocol. This extension mechanism depends on an HTTP cache obeying(服从,听从) all of the cache-control directives defined for its native HTTP-version, obeying certain extensions, and ignoring(不顾) all directives that it does not understand. For example, consider a 假想;假设的hypothetical() new response directive called community which acts as a modifier to the private directive. We define(规定;使明确;) this new directive to mean that, in addition(加,增加,附加;) to any non-shared cache, any cache which is shared only by members of the community named within its value may cache the response. An origin server wishing to allow the UCI community to use an otherwise private response in their shared cache(s) could do so by including Cache-Control: private, community="UCI" A cache seeing this header field will act correctly even if the cache does not understand the community cache-extension, since it will also see and understand the private directive and thus(于是,因此;如此,这样) default to the safe behavior. Unrecognized(未被承认的) cache-directives MUST be ignored; it is assumed(假定的;假装的;) that any cache-directive likely to be unrecognized by an HTTP/1.1 cache will be combined(结合的;) with standard directives (or the response's default cacheability(缓存能力;缓冲能力;)) such that the cache behavior will remain(剩余物,残骸;) minimally(最低限度地,最低程度地) correct even if the cache does not understand the extension(s). 14.10 Connection The Connection general-header field allows the sender to specify options that are desired for that particular connection and MUST NOT be communicated by proxies over further connections. The Connection header has the following grammar: Connection = "Connection" ":" 1#(connection-token) connection-token = token HTTP/1.1 proxies MUST parse the Connection header field before a message is forwarded(发送;促进) and, for each connection-token in this field, remove any header field(s) from the message with the same name as the connection-token. Connection options are signaled by the presence(出席;仪表;风度;鬼魂,神灵) of a connection-token in the Connection header field, not by any corresponding(相对的,对应的,符合的) additional(附加) header field(s), since the additional header field may not be sent if there are no parameters(参数) associated(结合) with that connection option. Message headers listed in the Connection header MUST NOT include end-to-end headers, such as Cache-Control. HTTP/1.1 defines the "close" connection option for the sender to signal that the connection will be closed after completion(完成,结束) of the response. For example, Connection: close in either the request or the response header fields indicates(指示,象征) that the connection SHOULD NOT be considered(被当作) `persistent(持久的)' (section 8.1) after the current request/response is complete. HTTP/1.1 applications that do not support persistent connections MUST include the "close" connection option in every message. A system receiving an HTTP/1.0 (or lower-version) message that includes a Connection header MUST, for each connection-token in this field, remove and ignore(忽视) any header field(s) from the message with the same name as the connection-token. This protects against(以防) mistaken forwarding of such header fields by pre-HTTP/1.1 proxies. See section19.6.2. 14.11 Content-Encoding(内容编码) The Content-Encoding entity-header field is used as a modifier(调节器;) to the media-type. When present, its value indicates(指示;标示) what additional(补充;额外的,附加的;) content codings have been applied to the entity-body, and thus(于是,因此;) what decoding mechanisms must be applied in order to obtain the media-type referenced(参考的,引用的) by the Content-Type header field. Content-Encoding is primarily(首先;首要地,主要地;) used to allow a document to be compressed(压缩的;) without losing the identity(身份;) of its underlying(潜在的,含蓄的;基础的;表面下的,下层的;) media type. Content-Encoding = "Content-Encoding" ":" 1#content-coding Content codings are defined in section 3.5. An example of its use is Content-Encoding: gzip The content-coding is a characteristic(特有的;独特的;表示特性的;显示…的特征的) of the entity identified(确认;辨认;认出) by the Request-URI. Typically(通常;典型地;代表性地), the entity-body is stored with this encoding and is only decoded before rendering or analogous(相似的,可比拟的;) usage(使用;用法;习惯;惯例). However, a non-transparent proxy MAY modify the content-coding if the new coding is known to be acceptable(可接受的;合意的;) to the recipient(接受者;容器;容纳者), unless the "no-transform" cache-control directive is present in the message. If the content-coding of an entity is not "identity", then the response MUST include a Content-Encoding entity-header (section 14.11) that lists the non-identity content-coding(s) used. If the content-coding of an entity in a request message is not acceptable to the origin server, the server SHOULD respond with a status code of 415 (Unsupported Media Type). If multiple encodings have been applied to an entity, the content codings MUST be listed in the order in which they were applied. Additional information about the encoding parameters MAY be provided by other entity-header fields not defined by this specification. 14.12 Content-Language(内容语言) The Content-Language entity-header field describes the natural language(s) of the intended(预期的;有意的) audience(观众;听众;读者;接见) for the enclosed(封闭的;被附上的;) entity. Note that this might not be equivalent(相等的,相当的,等效的;) to all the languages used within(不超过,在…的范围内;在…能达到的地方;在…内,在…里面) the entity-body. Content-Language = "Content-Language" ":" 1#language-tag Language tags are defined in section 3.10. The primary(首要的,主要的;最早的,原始的;) purpose of Content-Language is to allow a user to identify(确定;识别) and differentiate(i. 区分,区别,辨别) entities according to the user's own preferred language. Thus, if the body content is intended(预期的;有意的) only for a Danish-literate(丹麦语言) audience, the appropriate(适当的;合适的;恰当的) field is Content-Language: da If no Content-Language is specified, the default is that the content is intended for all language audiences. This might mean that the sender does not consider(考虑;认为;以为;看重) it to be specific(具体的;明确的;特种的;) to any natural language, or that the sender does not know for which language it is intended. Multiple languages MAY be listed for content that is intended for multiple audiences. For example, a rendition(演奏;翻译;给予;引渡逃奴) of the "Treaty of Waitangi(瓦伊坦吉条约)," presented simultaneously(同时地;一壁;齐;一齐) in the original Maori(毛利语) and English versions, would call for Content-Language: mi, en However, just because multiple languages are present within an entity does not mean that it is intended for multiple linguistic(语言的;语言学的) audiences. An example would be a beginner's language primer(底漆;启蒙读本;入门书), such as "A First Lesson in Latin," which is clearly intended to be used by an English-literate audience. In this case, the Content-Language would properly only include "en". Content-Language MAY be applied to any media type -- it is not limited to textual documents. 14.13 Content-Length(内容长度) The Content-Length entity-header field indicates the size of the entity-body, in decimal(十进位的,小数的) number of OCTETs(八位位组,八位字节), sent to the recipient(接受者;容器;容纳者) or, in the case of the HEAD method, the size of the entity-body that would have been sent had the request been a GET. Content-Length = "Content-Length" ":" 1*DIGIT An example is Content-Length: 3495 Applications(申请;申请书;) SHOULD use this field to indicate(表明,标示) the transfer-length(转换长度) of the message-body, unless this is prohibited(禁止,阻止) by the rules in section 4.4. Any Content-Length greater than or equal(相等的,平等的) to zero is a valid value. Section 4.4 describes how to determine(决定,确定;判定,判决;使决定;限定) the length of a message-body if a Content-Length is not given. Note that the meaning of this field is significantly(意味深长地;值得注目地) different from the corresponding(相当的,对应的;) definition(定义;规定) in MIME(多用途的网际邮件扩充协议), where it is an optional(可选择的;随意的) field used within the "message/external-body" content-type. In HTTP, it SHOULD be sent whenever the message's length can be determined(确定的;坚定的;) prior(优先的;) to being transferred(转让;转移), unless this is prohibited(禁止,阻止) by the rules in section 4.4. 14.14 Content-Location(内容位置) The Content-Location entity-header field MAY be used to supply the resource location for the entity enclosed in the message when that entity is accessible(易接近的;) from a location separate(分开;(使)分离;区分;隔开) from the requested resource's URI. A server SHOULD provide a Content-Location for the variant(变形,变量,转化;) corresponding(相当的,对应的;通信的;符合的,符合) to the response entity; especially in the case where a resource has multiple entities associated with it, and those entities actually have separate locations by which they might be individually(分别地;各个地;各自地;独特地) accessed, the server SHOULD provide a Content-Location for the particular variant which is returned. Content-Location = "Content-Location" ":" ( absoluteURI | relativeURI ) The value of Content-Location also defines(规定) the base URI for the entity. The Content-Location value is not a replacement(代替;归还,复位;替代者;) for the original requested URI; it is only a statement(声明;) of the location of the resource corresponding(相当的,对应的;) to this particular entity at the time of the request. Future requests MAY specify the Content-Location URI as the request-URI if the desire(渴望;希望;要求;请求) is to identify the source of that particular entity. A cache cannot assume(承担;呈现;假定,认为;装出) that an entity with a Content-Location different from the URI used to retrieve(取回;恢复;) it can be used to respond to later requests on that Content-Location URI. However, the Content-Location can be used to differentiate(区分,区别,辨别) between multiple entities retrieved from a single requested resource, as described in section 13.6. If the Content-Location is a relative URI, the relative(相对的;相关的;) URI is interpreted(理解;解释) relative to the Request-URI. The meaning of the Content-Location header in PUT or POST requests is undefined; servers are free to ignore it in those cases. 14.15 Content-MD5 The Content-MD5 entity-header field, as defined(有定义的,明确的;) in RFC 1864, is an MD5 digest(文摘;摘要;) of the entity-body for the purpose of providing an end-to-end message integrity(完整;正直,诚实) check (MIC) of the entity-body. (Note: a MIC is good for detecting(探测,检定,检波) accidental(意外的,偶然) modification(修改,修正,) of the entity-body in transit(通过,经过), but is not proof(证明;校样;) against(反对;对…不利;紧靠;以防) malicious(恶意的) attacks.) Content-MD5 = "Content-MD5" ":" md5-digest md5-digest = <base64 of 128 bit MD5 digest as per RFC 1864> The Content-MD5 header field MAY be generated by an origin server or client to function(功能,作用;) as an integrity(完整;正直,诚实;) check of the entity-body. Only origin servers or clients MAY generate the Content-MD5 header field; proxies and gateways MUST NOT generate it, as this would defeat its value as an end-to-end integrity check. Any recipient of the entity- body, including gateways and proxies, MAY check that the digest value in this header field matches that of the entity-body as received. The MD5 digest(文摘;摘要;法律汇编;) is computed based on the content of the entity-body, including any content-coding that has been applied, but not including any transfer-encoding applied to the message-body. If the message is received with a transfer-encoding, that encoding MUST be removed prior(在前;居先) to checking the Content-MD5 value against the received entity. This has the result that the digest is computed on the octets(八位位组,八位字节) of the entity-body exactly as, and in the order that, they would be sent if no transfer-encoding were being applied. HTTP extends RFC 1864 to permit(许可,准许;) the digest to be computed for MIME composite(合成物,混合物,) media-types (e.g., multipart/* and message/rfc822), but this does not change how the digest is computed as defined in the preceding(在…之前发生) paragraph(段落;分段符号). There are several consequences(结果;重要(性),重要地位;因果关系) of this. The entity-body for composite(合成物,混合物) types MAY contain many body-parts, each with its own MIME and HTTP headers (including Content-MD5, Content-Transfer-Encoding, and Content-Encoding headers). If a body-part has a Content-Transfer-Encoding or Content-Encoding header, it is assumed(假定的;假装的;假冒的;被承担的) that the content of the body-part has had the encoding applied, and the body-part is included in the Content-MD5 digest as is -- i.e., after the application(适用,应用,运用;). The Transfer-Encoding header field is not allowed within body-parts. Conversion(转换) of all line breaks to CRLF(回车换行)MUST NOT be done before computing or checking the digest: the line break convention(议;全体与会者;国际公约;惯例,习俗,规矩) used in the text actually transmitted(传播;发射,播送) MUST be left unaltered(未改变的,未被改变的;) when computing the digest. Note: while the definition(定义;规定,明确;) of Content-MD5 is exactly the same for HTTP as in RFC 1864 for MIME entity-bodies, there are several ways in which the application of Content-MD5 to HTTP entity-bodies differs(不同,有异) from its application to MIME entity-bodies. One is that HTTP, unlike MIME, does not use Content-Transfer-Encoding, and does use Transfer-Encoding and Content-Encoding. Another is that HTTP more frequently(往往;动辄;频繁地,屡次地;) uses binary(二进制的;二元的;) content types than MIME, so it is worth noting that, in such cases, the byte order used to compute the digest is the transmission(传送;播送;) byte order defined for the type. Lastly, HTTP allows transmission of text types with any of several line break conventions(协议) and not just the canonical(权威的;) form using CRLF. 14.16 Content-Range(内容范围) The Content-Range entity-header is sent with a partial entity-body to specify where in the full entity-body the partial(局部的;偏爱的;不公平的) body should be applied(应用;实施). Range units are defined in section3.12. Content-Range = "Content-Range" ":" content-range-spec content-range-spec = byte-content-range-spec byte-content-range-spec = bytes-unit SP byte-range-resp-spec "/" ( instance-length | "*" ) byte-range-resp-spec = (first-byte-pos "-" last-byte-pos) | "*" instance-length = 1*DIGIT The header SHOULD indicate(表明;指出;预示;象征) the total length of the full entity-body, unless this length is unknown or difficult to determine((使)下决心,(使)做出决定). The asterisk(星号,星状物) "*" character means that the instance(情况;例子,实例;)-length is unknown at the time when the response was generated. Unlike byte-ranges-specifier values (see section 14.35.1), a byte- range-resp-spec MUST only specify(指定;详述;) one range, and MUST contain absolute byte positions for both the first and last byte of the range. A byte-content-range-spec with a byte-range-resp-spec whose last-byte-pos value is less than its first-byte-pos value, or whose instance-length value is less than or equal to its last-byte-pos value, is invalid. The recipient(接受者;容器;容纳者) of an invalid byte-content-range- spec MUST ignore(忽视,不顾;) it and any content transferred(转让;转移) along with it. A server sending a response with status code 416 (Requested range not satisfiable(可以满足的)) SHOULD include a Content-Range field with a byte-range-resp-spec of "*". The instance-length specifies the current length of the selected resource. A response with status code 206 (Partial Content) MUST NOT include a Content-Range field with a byte-range- resp-spec of "*". Examples of byte-content-range-spec values, assuming(假定;取得) that the entity contains a total of 1234 bytes: . The first 500 bytes: bytes 0-499/1234 . The second 500 bytes: bytes 500-999/1234 . All except for the first 500 bytes: bytes 500-1233/1234 . The last 500 bytes: bytes 734-1233/1234 When an HTTP message includes the content of a single range (for example, a response to a request for a single range, or to a request for a set of ranges that overlap(互搭,重叠;部分相同) without any holes(球洞;破洞;洞穴)), this content is transmitted(传播;发射,播送) with a Content-Range header, and a Content-Length header showing the number of bytes actually transferred. For example, HTTP/1.1 206 Partial content Date: Wed, 15 Nov 1995 06:25:24 GMT Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT Content-Range: bytes 21010-47021/47022 Content-Length: 26012 Content-Type: image/gif When an HTTP message includes the content of multiple ranges (for example, a response to a request for multiple non-overlapping(重叠,搭接) ranges), these are transmitted as a multipart(多部件的) message. The multipart media type used for this purpose is "multipart/byteranges" as defined in appendix(附录;) 19.2. See appendix 19.6.3 for a compatibility(适合;互换性; 通用性;和睦相处) issue. A response to a request for a single range MUST NOT be sent using the multipart/byteranges media type. A response to a request for multiple ranges, whose result is a single range, MAY be sent as a multipart/byteranges media type with one part. A client that cannot decode(译(码),解(码)) a multipart/byteranges message MUST NOT ask for multiple byte-ranges in a single request. When a client requests multiple byte-ranges in one request, the server SHOULD return them in the order that they appeared in the request. If the server ignores a byte-range-spec because it is syntactically(依照句法地,在语句构成上) invalid(无效的;), the server SHOULD treat(招待;款待;) the request as if the invalid Range header field did not exist. (Normally, this means return a 200 response containing the full entity). If the server receives a request (other than one including an If-Range request-header field) with an unsatisfiable(未可满足的,未能偿还的) Range request-header field (that is, all of whose byte-range-spec values have a first-byte-pos value greater than the current length of the selected resource), it SHOULD return a response code of 416 (Requested range not satisfiable) (section 10.4.17). Note: clients cannot depend on servers to send a 416 (Requested range not satisfiable) response instead of a 200 (OK) response for an unsatisfiable Range request-header, since not all servers implement this request-header. 14.17 Content-Type(内容类型) The Content-Type entity-header field indicates the media type of the entity-body sent to the recipient(接受者;容器;容纳者) or, in the case of the HEAD method, the media type that would have been sent had the request been a GET. Content-Type = "Content-Type" ":" media-type Media types are defined in section 3.7. An example of the field is Content-Type: text/html; charset=ISO-8859-4 Further discussion of methods for identifying(确认;辨认;认出) the media type of an entity is provided in section 7.2.1. 14.18 Date The Date general-header field represents the date and time at which the message was originated(起源于,来自,产生), having the same semantics as orig-date in RFC 822. The field value is an HTTP-date, as described in section 3.3.1; it MUST be sent in RFC 1123 [8]-date format. Date = "Date" ":" HTTP-date An example is Date: Tue, 15 Nov 1994 08:12:31 GMT Origin servers MUST include a Date header field in all responses, except(把…除外;不计) in these cases: 1. If the response status code is 100 (Continue) or 101 (Switching Protocols), the response MAY include a Date header field, at the server's option. 2. If the response status code conveys(表达;输送;运送;) a server error, e.g. 500(Internal Server Error) or 503 (Service Unavailable), and it is inconvenient(不便;不方便的;打扰人的,麻烦的) or impossible(不可能的,做不到的;) to generate a valid Date. 3. If the server does not have a clock that can provide a reasonable(合理的,公道的;) approximation(接近;) of the current time, its responses MUST NOT include a Date header field. In this case, the rules in section 14.18.1 MUST be followed. A received message that does not have a Date header field MUST be assigned one by the recipient if the message will be cached by that recipient or gatewayed via(经过;通过) a protocol which requires a Date. An HTTP implementation(贯彻;成就;) without a clock MUST NOT cache responses without revalidating them on every use. An HTTP cache, especially a shared cache, SHOULD use a mechanism, such as NTP [28], to synchronize(使同步;使同时) its clock with a reliable(可靠的;可信赖的;真实可信的) external(外部,外面;外观;外部情况) standard. Clients SHOULD only send a Date header field in messages that include an entity-body, as in the case of the PUT and POST requests, and even then it is optional. A client without a clock MUST NOT send a Date header field in a request. The HTTP-date sent in a Date header SHOULD NOT represent a date and time subsequent(随后的;后来的) to the generation(产生;) of the message. It SHOULD represent(表现,象征;) the best available approximation(接近;) of the date and time of message generation, unless the implementation(贯彻;成就;) has no means of generating a reasonably accurate(精确的,准确的;) date and time. In theory(理论;原理;), the date ought to represent the moment just before the entity is generated. In practice, the date can be generated at any time during the message origination without affecting(影响( affect的现在分词 );) its semantic value. 14.18.1 Clockless Origin Server Operation Some origin server implementations might not have a clock available. An origin server without a clock MUST NOT assign Expires or Last-Modified values to a response, unless these values were associated with the resource by a system or user with a reliable clock. It MAY assign an Expires value that is known, at or before server configuration time, to be in the past (this allows "pre-expiration" of responses without storing separate(分开;) Expires values for each resource). 14.19 ETag The ETag response-header field provides the current value of the entity tag for the requested variant(变形,变量). The headers used with entity tags are described in sections 14.24, 14.26and 14.44. The entity tag MAY be used for comparison(比较,对照) with other entities from the same resource (see section 13.3.3). ETag = "ETag" ":" entity-tag Examples: ETag: "xyzzy" ETag: W/"xyzzy" ETag: "" 14.20 Expect The Expect request-header field is used to indicate that particular(特别的;详细的;独有的;挑剔的) server behaviors are required by the client. Expect = "Expect" ":" 1#expectation expectation = "100-continue" | expectation-extension expectation-extension = token [ "=" ( token | quoted-string ) *expect-params ] expect-params = ";" token [ "=" ( token | quoted-string ) ] A server that does not understand or is unable to comply(遵从;依从,顺从;应允,同意) with any of the expectation(预期;期待;) values in the Expect field of a request MUST respond with appropriate(适当的;合适的;恰当的) error status. The server MUST respond with a 417 (Expectation Failed) status if any of the expectations cannot be met or, if there are other problems with the request, some other 4xx status. This header field is defined with extensible(可展开的,可扩张的) syntax(语法;句法;) to allow for future extensions(延伸;扩展名;). If a server receives a request containing an Expect field that includes an expectation-extension that it does not support, it MUST respond with a 417 (Expectation Failed) status. Comparison(比较,对照;) of expectation values is case-insensitive(大小写不敏感;不区分大小写;) for unquoted(结束引语) tokens (including the 100-continue token), and is case-sensitive for quoted-string expectation-extensions. The Expect mechanism is hop-by-hop: that is, an HTTP/1.1 proxy MUST return a 417 (Expectation Failed) status if it receives a request with an expectation that it cannot meet. However, the Expect request-header itself is end-to-end; it MUST be forwarded(发送;促进) if the request is forwarded. Many older HTTP/1.0 and HTTP/1.1 applications do not understand the Expect header. See section 8.2.3 for the use of the 100 (continue) status. 14.21 Expires(期满;文件、协议等(因到期而)失效;) The Expires entity-header field gives the date/time after which the response is considered(想;注意;看重) stale. A stale cache entry may not normally be returned by a cache (either a proxy cache or a user agent cache) unless it is first validated with the origin server (or with an intermediate cache that has a fresh copy of the entity). See section 13.2 for further discussion of the expiration model. The presence(出席;仪表;风度;鬼魂,神灵) of an Expires field does not imply(暗示;意味;隐含;说明,表明) that the original resource will change or cease(停止,终止,结束) to exist at, before, or after that time. The format is an absolute date and time as defined by HTTP-date in section 3.3.1; it MUST be in RFC 1123 date format: Expires = "Expires" ":" HTTP-date An example of its use is Expires: Thu, 01 Dec 1994 16:00:00 GMT Note: if a response includes a Cache-Control field with the max-age directive (see section 14.9.3), that directive overrides(优先于;比…更重要) the Expires field. HTTP/1.1 clients and caches MUST treat(治疗;对待;处理;款待) other invalid(无效的;不能成立的;有病的;病人用的) date formats, especially including the value "0", as in the past (i.e., "already expired"). To mark a response as "already expired," an origin server sends an Expires date that is equal(相等的,平等的;) to the Date header value. (See the rules for expiration calculations in section 13.2.4.) To mark a response as "never expires," an origin server sends an Expires date approximately(近似地,大约;许) one year from the time the response is sent. HTTP/1.1 servers SHOULD NOT send Expires dates more than one year in the future. The presence(出席;仪表;风度;鬼魂,神灵) of an Expires header field with a date value of some time in the future on a response that otherwise(否则;另外;别的方式) would by default be non-cacheable indicates(指示;标示) that the response is cacheable, unless indicated otherwise by a Cache-Control header field (section 14.9). 14.22 From The From request-header field, if given, SHOULD contain an Internet e-mail address for the human user who controls the requesting user agent. The address SHOULD be machine-usable, as defined by "mailbox(信箱;邮筒)" in RFC 822 [9] as updated by RFC 1123 [8]: From = "From" ":" mailbox An example is: From: webmaster@w3.org This header field MAY be used for logging purposes(目的;意志) and as a means for identifying(确认;辨认;认出) the source of invalid or unwanted requests. It SHOULD NOT be used as an insecure(无安全的;不稳定的;) form of access protection. The interpretation(理解;解释,说明;) of this field is that the request is being performed(表演;履行;执行) on behalf(利益;维护;支持) of the person given, who accepts responsibility(责任;职责;负责任;责任感,责任心) for the method performed. In particular(特别的;), robot agents SHOULD include this header so that the person responsible for running the robot can be contacted if problems occur on the receiving end. The Internet e-mail address in this field MAY be separate(分开;(使)分离;区分;隔开) from the Internet host which issued the request. For example, when a request is passed through a proxy the original issuer's address SHOULD be used. The client SHOULD NOT send the From header field without the user's approval(批准;同意;赞成), as it might conflict(冲突;矛盾;) with the user's privacy(隐私,秘密;) interests or their site's(遗址; 地点) security(安全;保证) policy(政策;策略;). It is strongly recommended that the user be able to disable, enable, and modify the value of this field at any time prior(优先的;占先的;在…之前) to a request. 14.23 Host(主机) The Host request-header field specifies the Internet host and port number of the resource being requested, as obtained(获得) from the original URI given by the user or referring resource (generally an HTTP URL,as described in section 3.2.2). The Host field value MUST represent(表现,象征;) the naming authority of the origin server or gateway given by the original URL. This allows the origin server or gateway to differentiate between internally-ambiguous(内部歧义) URLs, such as the root "/" URL of a server for multiple host names on a single IP address. Host = "Host" ":" host [ ":" port ] ; Section 3.2.2 A "host" without any trailing(曳尾的;被拖动的;蔓延的) port information implies the default port for the service requested (e.g., "80" for an HTTP URL). For example, a request on the origin server for <http://www.w3.org/pub/WWW/> would properly include: GET /pub/WWW/ HTTP/1.1 Host: www.w3.org A client MUST include a Host header field in all HTTP/1.1 request messages . If the requested URI does not include an Internet host name for the service being requested, then the Host header field MUST be given with an empty value. An HTTP/1.1 proxy MUST ensure that any request message it forwards does contain an appropriate(适当的;合适的;恰当的) Host header field that identifies(确认;辨认;认出) the service(服役;服务,服侍;) being requested by the proxy. All Internet-based HTTP/1.1 servers MUST respond with a 400 (Bad Request) status code to any HTTP/1.1 request message which lacks(缺乏,不足,没有) a Host header field. See sections 5.2 and 19.6.1.1 for other requirements(需要;所需的) relating(联系起来) to Host. 14.24 If-Match The If-Match request-header field is used with a method to make it conditional(视…而定的;[语]条件的,假定的;). A client that has one or more entities previously obtained(获得) from the resource can verify(核实;证明;判定) that one of those entities is current by including a list of their associated entity tags in the If-Match header field. Entity tags are defined in section 3.11. The purpose of this feature is to allow efficient(有效率的) updates of cached information with a minimum amount of transaction(交易,业务,事务;办理,处理;) overhead(头顶上的;上面的,高架的;). It is also used, on updating requests, to prevent inadvertent(不经意的,出于无心的;疏忽的,漫不经心的;粗心大意) modification of the wrong version of a resource. As a special case, the value "*" matches any current entity of the resource. If-Match = "If-Match" ":" ( "*" | 1#entity-tag ) If any of the entity tags match the entity tag of the entity that would have been returned in the response to a similar GET request (without the If-Match header) on that resource, or if "*" is given and any current entity exists for that resource, then the server MAY perform(执行;履行;) the requested method as if the If-Match header field did not exist. A server MUST use the strong comparison(比较,对照;) function(功能,作用;) (see section 13.3.3) to compare the entity tags in If-Match. If none of the entity tags match, or if "*" is given and no current entity exists, the server MUST NOT perform the requested method, and MUST return a 412 (Precondition Failed) response. This behavior is most useful when the client wants to prevent an updating method, such as PUT, from modifying a resource that has changed since the client last retrieved(恢复;取回) it. If the request would, without the If-Match header field, result in anything other than a 2xx or 412 status, then the If-Match header MUST be ignored. The meaning of "If-Match: *" is that the method SHOULD be performed if the representation(表现;陈述;表现…的事物;有代理人) selected by the origin server (or by a cache, possibly using the Vary mechanism(变化机制), see section 14.44) exists, and MUST NOT be performed if the representation does not exist. A request intended(预期的;有意的) to update a resource (e.g., a PUT) MAY include an If-Match header field to signal that the request method MUST NOT be applied if the entity corresponding(相当的,对应的;通信的;符合的,符合) to the If-Match value (a single entity tag) is no longer a representation of that resource. This allows the user to indicate that they do not wish the request to be successful if the resource has been changed without their knowledge. Examples: If-Match: "xyzzy" If-Match: "xyzzy", "r2d2xxxx", "c3piozzzz" If-Match: * The result of a request having both an If-Match header field and either an If-None-Match or an If-Modified-Since header fields is undefined by this specification(规格;说明书;详述). 14.25 If-Modified-Since The If-Modified-Since request-header field is used with a method to make it conditional(…而定的;[语]条件的,假定的;): if the requested variant(变形,变量,转化;[统]变式) has not been modified since the time specified in this field, an entity will not be returned from the server; instead, a 304 (not modified) response will be returned without any message-body. If-Modified-Since = "If-Modified-Since" ":" HTTP-date An example of the field is: If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT A GET method with an If-Modified-Since header and no Range header requests that the identified entity be transferred only if it has been modified since the date given by the If-Modified-Since header. The algorithm for determining(确定;决定;) this includes the following cases: a) If the request would normally result in anything other than a 200 (OK) status, or if the passed If-Modified-Since date is invalid, the response is exactly the same as for a normal GET. A date which is later than the server's current time is invalid. b) If the variant has been modified since the If-Modified-Since date, the response is exactly the same as for a normal GET. c) If the variant has not been modified since a valid If-Modified-Since date, the server SHOULD return a 304 (Not Modified) response. The purpose of this feature is to allow efficient(有效率的;) updates of cached information with a minimum amount of transaction(交易,业务,事务;办理,处理;) overhead. Note: The Range request-header field modifies the meaning of If-Modified-Since; see section 14.35 for full details. Note: If-Modified-Since times are interpreted(理解;解释() by the server, whose clock might not be synchronized(同步的) with the client. Note: When handling an If-Modified-Since header field, some servers will use an exact date comparison function(比对函数), rather than a less-than function, for deciding whether to send a 304 (Not Modified) response. To get best results when sending an If-Modified-Since header field for cache validation, clients are advised to use the exact date string received in a previous Last-Modified header field whenever possible. Note: If a client uses an arbitrary(随意的,任性的,随心所欲的;) date in the If-Modified-Since header instead of a date taken from the Last-Modified header for the same request, the client should be aware(意识到的;知道的;觉察到的) of the fact that this date is interpreted in the server's understanding of time. The client should consider unsynchronized clocks and rounding problems due to the different encodings of time between the client and server. This includes the possibility of race conditions(竞态条件) if the document has changed between the time it was first requested and the If-Modified-Since date of a subsequen(随后的;后来的;) request, and the possibility of clock-skew-related problems if the If-Modified-Since date is derived(衍生;导出;起源;由来) from the client's clock without correction(修改;改[纠]正;惩罚;有待改正) to the server's clock. Corrections for different time bases between client and server are at best approximate(接近于;近似于) due to network latency(潜伏;潜在因素). The result of a request having both an If-Modified-Since header field and either an If-Match or an If-Unmodified-Since header fields is undefined by this specification. 14.26 If-None-Match The If-None-Match request-header field is used with a method to make it conditional. A client that has one or more entities previously obtained from the resource can verify that none of those entities is current by including a list of their associated entity tags in the If-None-Match header field. The purpose of this feature is to allow efficient updates of cached information with a minimum amount of transaction overhead. It is also used to prevent a method (e.g. PUT) from inadvertently modifying an existing resource when the client believes that the resource does not exist. As a special case, the value "*" matches any current entity of the resource. If-None-Match = "If-None-Match" ":" ( "*" | 1#entity-tag ) If any of the entity tags match the entity tag of the entity that would have been returned in the response to a similar GET request (without the If-None-Match header) on that resource, or if "*" is given and any current entity exists for that resource, then the server MUST NOT perform the requested method, unless required to do so because the resource's modification date fails to match that supplied in an If-Modified-Since header field in the request. Instead, if the request method was GET or HEAD, the server SHOULD respond with a 304 (Not Modified) response, including the cache-related header fields (particularly ETag) of one of the entities that matched. For all other request methods, the server MUST respond with a status of 412 (Precondition Failed). See section 13.3.3 for rules on how to determine if two entities tags match. The weak comparison function can only be used with GET or HEAD requests. If none of the entity tags match, then the server MAY perform the requested method as if the If-None-Match header field did not exist, but MUST also ignore any If-Modified-Since header field(s) in the request. That is, if no entity tags match, then the server MUST NOT return a 304 (Not Modified) response. If the request would, without the If-None-Match header field, result in anything other than a 2xx or 304 status, then the If-None-Match header MUST be ignored. (See section13.3.4 for a discussion of server behavior when both If-Modified-Since and If-None-Match appear in the same request.) The meaning of "If-None-Match: *" is that the method MUST NOT be performed if the representation selected by the origin server (or by a cache, possibly using the Vary mechanism, see section 14.44) exists, and SHOULD be performed if the representation does not exist. This feature is intended to be useful in preventing races between PUT operations. Examples: If-None-Match: "xyzzy" If-None-Match: W/"xyzzy" If-None-Match: "xyzzy", "r2d2xxxx", "c3piozzzz" If-None-Match: W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz" If-None-Match: * The result of a request having both an If-None-Match header field and either an If-Match or an If-Unmodified-Since header fields is undefined by this specification. 14.27 If-Range If a client has a partial copy of an entity in its cache, and wishes to have an up-to-date copy of the entire entity in its cache, it could use the Range request-header with a conditional GET (using either or both of If-Unmodified-Since and If-Match.) However, if the condition fails because the entity has been modified, the client would then have to make a second request to obtain the entire current entity-body. The If-Range header allows a client to "short-circuit" the second request. Informally, its meaning is `if the entity is unchanged, send me the part(s) that I am missing; otherwise, send me the entire new entity'. If-Range = "If-Range" ":" ( entity-tag | HTTP-date ) If the client has no entity tag for an entity, but does have a Last-Modified date, it MAY use that date in an If-Range header. (The server can distinguish(区分,辨别,分清;辨别是非) between a valid HTTP-date and any form of entity-tag by examining no more than two characters.) The If-Range header SHOULD only be used together with a Range header, and MUST be ignored if the request does not include a Range header, or if the server does not support the sub-range operation. If the entity tag given in the If-Range header matches the current entity tag for the entity, then the server SHOULD provide the specified sub-range of the entity using a 206 (Partial content) response. If the entity tag does not match, then the server SHOULD return the entire entity using a 200 (OK) response. 14.28 If-Unmodified-Since The If-Unmodified-Since request-header field is used with a method to make it conditional. If the requested resource has not been modified since the time specified in this field, the server SHOULD perform the requested operation as if the If-Unmodified-Since header were not present. If the requested variant has been modified since the specified time, the server MUST NOT perform the requested operation, and MUST return a 412 (Precondition Failed). If-Unmodified-Since = "If-Unmodified-Since" ":" HTTP-date An example of the field is: If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT If the request normally (i.e., without the If-Unmodified-Since header) would result in anything other than a 2xx or 412 status, the If-Unmodified-Since header SHOULD be ignored. If the specified date is invalid, the header is ignored. The result of a request having both an If-Unmodified-Since header field and either an If-None-Match or an If-Modified-Since header fields is undefined by this specification. 14.29 Last-Modified The Last-Modified entity-header field indicates the date and time at which the origin server believes the variant was last modified. Last-Modified = "Last-Modified" ":" HTTP-date An example of its use is Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT The exact meaning of this header field depends on the implementation(贯彻;成就;) of the origin server and the nature of the original resource. For files, it may be just the file system last-modified time. For entities with dynamically(动态的) included parts, it may be the most recent of the set of last-modify times for its component parts. For database gateways, it may be the last-update time stamp(标志,印记;) of the record(记录,记载;). For virtual objects, it may be the last time the internal state changed. An origin server MUST NOT send a Last-Modified date which is later than the server's time of message origination. In such cases, where the resource's last modification would indicate some time in the future, the server MUST replace that date with the message origination date. An origin server SHOULD obtain the Last-Modified value of the entity as close as possible to the time that it generates the Date value of its response. This allows a recipient(接受者;容器;容纳者) to make an accurate(精确的,准确的;正确无误的) assessment(评估;评价;) of the entity's modification time, especially if the entity changes near the time that the response is generated. HTTP/1.1 servers SHOULD send Last-Modified whenever feasible. 14.30 Location The Location response-header field is used to redirect the recipient(容易接受的;感受性强的) to a location other than the Request-URI for completion(完成,结束;实现;) of the request or identification(认同;鉴定,识别;) of a new resource. For 201 (Created) responses, the Location is that of the new resource which was created by the request. For 3xx responses, the location SHOULD indicate the server's preferred URI for automatic redirection to the resource. The field value consists(包括;) of a single absolute URI. Location = "Location" ":" absoluteURI An example is: Location: http://www.w3.org/pub/WWW/People.html Note: The Content-Location header field (section 14.14) differs from Location in that the Content-Location identifies(确认;辨认;) the original location of the entity enclosed in the request. It is therefore possible for a response to contain header fields for both Location and Content-Location. Also see section 13.10 for cache requirements of some methods. 14.31 Max-Forwards The Max-Forwards request-header field provides a mechanism with the TRACE (section 9.8) and OPTIONS (section 9.2) methods to limit the number of proxies or gateways that can forward the request to the next inbound(入境的;回内地的;归本国的;到达的) server. This can be useful when the client is attempting to trace a request chain which appears to be failing or looping in mid-chain. Max-Forwards = "Max-Forwards" ":" 1*DIGIT The Max-Forwards value is a decimal integer indicating the remaining number of times this request message may be forwarded. Each proxy or gateway recipient of a TRACE or OPTIONS request containing a Max-Forwards header field MUST check and update its value prior(优先的;占先的;) to forwarding(推进,促进) the request. If the received value is zero (0), the recipient MUST NOT forward the request; instead, it MUST respond as the final recipient. If the received Max-Forwards value is greater than zero, then the forwarded message MUST contain an updated Max-Forwards field with a value decremented(递减的) by one (1). The Max-Forwards header field MAY be ignored for all other methods defined by this specification and for any extension methods for which it is not explicitly referred to as part of that method definition. 14.32 Pragma(杂注;编译指示) The Pragma general-header field is used to include implementation(贯彻;成就;)-specific(特效药;特性;细节;) directives that might apply to any recipient along the request/response chain. All pragma directives specify optional(可选择的;随意的,任意的;) behavior from the viewpoint of the protocol; however, some systems MAY require that behavior be consistent(一致的;连续的;不矛盾的;坚持的) with the directives. Pragma = "Pragma" ":" 1#pragma-directive pragma-directive = "no-cache" | extension-pragma extension-pragma = token [ "=" ( token | quoted-string ) ] When the no-cache directive is present in a request message, an application SHOULD forward the request toward(向;对于;为了;接近) the origin server even if it has a cached copy of what is being requested. This pragma directive has the same semantics as the no-cache cache-directive (see section 14.9) and is defined here for backward compatibility(适合;互换性; 通用性;) with HTTP/1.0. Clients SHOULD include both header fields when a no-cache request is sent to a server not known to be HTTP/1.1 compliant(遵从的;依从的;). Pragma directives MUST be passed through by a proxy or gateway application, regardless(不顾后果地;不管怎样,无论如何;不惜费用地) of their significance(意义;重要性;意思) to that application, since the directives might be applicable to all recipients(接受的;受领的;容纳的;愿意接受的) along the request/response chain. It is not possible to specify a pragma for a specific recipient; however, any pragma directive not relevant(有关的,中肯的;) to a recipient SHOULD be ignored by that recipient. HTTP/1.1 caches SHOULD treat "Pragma: no-cache" as if the client had sent "Cache-Control: no-cache". No new Pragma directives will be defined in HTTP. Note: because the meaning of "Pragma: no-cache as a response header field is not actually specified, it does not provide a reliable(可靠的;可信赖的;) replacement(代替;归还,,复位;) for "Cache-Control: no-cache" in a response 14.33 Proxy-Authenticate(鉴定) The Proxy-Authenticate response-header field MUST be included as part of a 407 (Proxy Authentication Required) response. The field value consists(包括;) of a challenge(战;质疑;盘问;怀疑) that indicates(指示;标示) the authentication scheme()策划,图谋 and parameters(因素,特征;) applicable to the proxy for this Request-URI. Proxy-Authenticate = "Proxy-Authenticate" ":" 1#challenge The HTTP access authentication process is described in "HTTP Authentication: Basic and Digest Access Authentication" [43]. Unlike WWW-Authenticate, the Proxy-Authenticate header field applies only to the current connection and SHOULD NOT be passed on to downstream clients. However, an intermediate proxy might need to obtain its own credentials(证书;凭证,证件) by requesting them from the downstream client, which in some circumstances(境况;境遇;) will appear as if the proxy is forwarding the Proxy-Authenticate header field. 14.34 Proxy-Authorization(授权,批准;) The Proxy-Authorization request-header field allows the client to identify itself (or its user) to a proxy which requires authentication. The Proxy-Authorization field value consists of credentials containing the authentication information of the user agent for the proxy and/or realm(领域,范围;) of the resource being requested. Proxy-Authorization = "Proxy-Authorization" ":" credentials The HTTP access authentication process is described in "HTTP Authentication: Basic and Digest Access Authentication" [43] . Unlike Authorization, the Proxy-Authorization header field applies only to the next outbound proxy that demanded authentication using the Proxy- Authenticate field. When multiple proxies are used in a chain, the Proxy-Authorization header field is consumed(消耗) by the first outbound proxy that was expecting to receive credentials. A proxy MAY relay the credentials from the client request to the next proxy if that is the mechanism by which the proxies cooperatively(合作地) authenticate a given request. 14.35 Range(范围) 14.35.1 Byte Ranges(字节范围) Since all HTTP entities are represented in HTTP messages as sequences of bytes, the concept(观念,概念;观点;) of a byte range is meaningful for any HTTP entity. (However, not all clients and servers need to support byte-range operations.) Byte range specifications(规格;载明;) in HTTP apply to the sequence of bytes in the entity-body (not necessarily the same as the message-body). A byte range operation MAY specify a single range of bytes, or a set of ranges within a single entity. ranges-specifier = byte-ranges-specifier byte-ranges-specifier = bytes-unit "=" byte-range-set byte-range-set = 1#( byte-range-spec | suffix-byte-range-spec ) byte-range-spec = first-byte-pos "-" [last-byte-pos] first-byte-pos = 1*DIGIT last-byte-pos = 1*DIGIT The first-byte-pos value in a byte-range-spec gives the byte-offset of the first byte in a range. The last-byte-pos value gives the byte-offset of the last byte in the range; that is, the byte positions specified are inclusive(包括的,包罗广泛的;). Byte offsets start at zero. If the last-byte-pos value is present, it MUST be greater than or equal to the first-byte-pos in that byte-range-spec, or the byte- range-spec is syntactically(依照句法地) invalid. The recipient(接受者;容器;) of a byte-range-set that includes(包含;包括) one or more syntactically invalid byte-range-spec values MUST ignore the header field that includes that byte-range-set. If the last-byte-pos value is absent(缺席的), or if the value is greater than or equal to the current length of the entity-body, last-byte-pos is taken to be equal(相等的,平等的;) to one less than the current length of the entity- body in bytes. By its choice of last-byte-pos, a client can limit the number of bytes retrieved(恢复;取回) without knowing the size of the entity. suffix-byte-range-spec = "-" suffix-length suffix-length = 1*DIGIT A suffix-byte-range-spec is used to specify the suffix(后缀,词尾) of the entity-body, of a length given by the suffix-length value. (That is, this form specifies the last N bytes of an entity-body.) If the entity is shorter than the specified suffix-length, the entire entity-body is used. If a syntactically valid byte-range-set includes at least one byte-range-spec whose first-byte-pos is less than the current length of the entity-body, or at least one suffix-byte-range-spec with a non- zero suffix-length, then the byte-range-set is satisfiable(可以满足的). Otherwise, the byte-range-set is unsatisfiable. If the byte-range-set is unsatisfiable, the server SHOULD return a response with a status of 416 (Requested range not satisfiable). Otherwise, the server SHOULD return a response with a status of 206 (Partial Content) containing the satisfiable ranges of the entity-body. Examples of byte-ranges-specifier values (assuming(假定;) an entity-body of length 10000): - The first 500 bytes (byte offsets 0-499, inclusive): bytes=0-499 - The second 500 bytes (byte offsets 500-999, inclusive):bytes=500-999 - The final 500 bytes (byte offsets 9500-9999, inclusive):bytes=-500 - Or bytes=9500- - The first and last bytes only (bytes 0 and 9999): bytes=0-0,-1 - Several legal but not canonical specifications of the second 500 bytes (byte offsets 500-999, inclusive): bytes=500-600,601-999 bytes=500-700,601-999 14.35.2 Range Retrieval(检索;收回,挽回) Requests HTTP retrieval requests using conditional(有条件的,由一定条件诱发的) or unconditional GET methods MAY request one or more sub-ranges of the entity, instead of the entire entity, using the Range request header, which applies to the entity returned as the result of the request: Range = "Range" ":" ranges-specifier A server MAY ignore the Range header. However, HTTP/1.1 origin servers and intermediate caches ought to support byte ranges when possible, since Range supports efficient(有效率的;) recovery(恢复,复原;) from partially(部分地;) failed transfers, and supports efficient(有效率的;) partial retrieval of large entities. If the server supports the Range header and the specified range or ranges are appropriate for the entity: - The presence of a Range header in an unconditional GET modifies what is returned if the GET is otherwise successful. In other words, the response carries a status code of 206 (Partial Content) instead of 200 (OK). - The presence of a Range header in a conditional GET (a request using one or both of If-Modified-Since and If-None-Match, or one or both of If-Unmodified-Since and If-Match) modifies what is returned if the GET is otherwise successful and the condition is true. It does not affect the 304 (Not Modified) response returned if the conditional is false. In some cases, it might be more appropriate to use the If-Range header (see section 14.27) in addition to the Range header. If a proxy that supports ranges receives a Range request, forwards the request to an inbound server, and receives an entire entity in reply, it SHOULD only return the requested range to its client. It SHOULD store the entire received response in its cache if that is consistent(一致的;连续的;不矛盾的;坚持的) with its cache allocation(配给,分配;) policies(策略;政策). 14.36 Referer(来路;提交方;来源网址;引用;推荐人) The Referer[sic] request-header field allows the client to specify(指定;详述;), for the server's benefit(利益,好处;), the address (URI) of the resource from which the Request-URI was obtained(获得) (the "referrer", although the header field is misspelled(拼写错).) The Referer request-header allows a server to generate(形成,造成;) lists of back-links to resources for interest, logging, optimized caching, etc. It also allows obsolete(废弃的;老式的) or mistyped(错误录入,用打字机错打) links to be traced for maintenance. The Referer field MUST NOT be sent if the Request-URI was obtained from a source that does not have its own URI, such as input from the user keyboard. Referer = "Referer" ":" ( absoluteURI | relativeURI ) Example: Referer: http://www.w3.org/hypertext/DataSources/Overview.html If the field value is a relative URI, it SHOULD be interpreted(理解;解释) relative to the Request-URI. The URI MUST NOT include a fragment(碎片;片段,未完成的部分;). See section 15.1.3 for security considerations. 14.37 Retry-After The Retry-After response-header field can be used with a 503 (Service Unavailable) response to indicate how long the service is expected to be unavailable to the requesting client. This field MAY also be used with any 3xx (Redirection) response to indicate the minimum time the user-agent is asked wait before issuing the redirected request. The value of this field can be either an HTTP-date or an integer(整数) number of seconds (in decimal) after the time of the response. Retry-After = "Retry-After" ":" ( HTTP-date | delta-seconds ) Two examples of its use are Retry-After: Fri, 31 Dec 1999 23:59:59 GMT Retry-After: 120 In the latter(后者的;末了的;较后的) example, the delay is 2 minutes. 14.38 Server The Server response-header field contains information about the software used by the origin server to handle(操作,操控;) the request. The field can contain multiple product tokens (section3.8) and comments identifying(确认;辨认;认出) the server and any significant subproducts(子积;子乘积;[). The product tokens are listed in order of their significance(意义;重要性;意思) for identifying the application. Server = "Server" ":" 1*( product | comment ) Example: Server: CERN/3.0 libwww/2.17 If the response is being forwarded through a proxy, the proxy application MUST NOT modify the Server response-header. Instead, it SHOULD include a Via field (as described in section 14.45). Note: Revealing(泄露;显示,展示;揭示,揭露) the specific software version of the server might allow the server machine to become more vulnerable(易受攻击的;易受伤的;易受批评的;[桥牌]已成局的) to attacks against software that is known to contain security holes. Server implementors(实现者) are encouraged(鼓动;鼓励) to make this field a configurable(结构的,可配置的) option. 14.39 TE(瞄准角;仰角) The TE request-header field indicates what extension transfer-codings it is willing to accept in the response and whether or not it is willing to accept trailer(拖车;追踪者) fields in a chunked(分块) transfer-coding. Its value may consist of the keyword "trailers" and/or a comma-separated list of extension transfer-coding names with optional accept parameters (as described in section 3.6). TE = "TE" ":" #( t-codings ) t-codings = "trailers" | ( transfer-extension [ accept-params ] ) The presence of the keyword "trailers" indicates that the client is willing to accept trailer fields in a chunked transfer-coding, as defined in section 3.6.1. This keyword is reserved for use with transfer-coding values even though it does not itself represent a transfer-coding. Examples of its use are: TE: deflate TE: TE: trailers, deflate;q=0.5 The TE header field only applies to the immediate connection. Therefore, the keyword MUST be supplied within a Connection header field (section 14.10) whenever TE is present in an HTTP/1.1 message. A server tests whether a transfer-coding is acceptable(可接受的;合意的;), according to a TE field, using these rules: 1. The "chunked" transfer-coding is always acceptable. If the keyword "trailers" is listed, the client indicates that it is willing to accept trailer fields in the chunked response on behalf(利益;维护;支持) of itself and any downstream clients. The implication(含义;含蓄,含意,言外之意;) is that, if given, the client is stating(陈述;规定) that either all downstream clients are willing to accept trailer fields in the forwarded response, or that it will attempt(尝试;试图) to buffer(缓冲;) the response on behalf of downstream recipients(接受的;受领的;). Note: HTTP/1.1 does not define any means to limit the size of a chunked response such that a client can be assured(自信的;确定的;) of buffering the entire response. 2. If the transfer-coding being tested is one of the transfer-codings listed in the TE field, then it is acceptable unless it is accompanied by a qvalue of 0. (As defined in section 3.9, a qvalue of 0 means "not acceptable.") 3. If multiple transfer-codings are acceptable, then the acceptable transfer-coding with the highest non-zero qvalue is preferred. The "chunked" transfer-coding always has a qvalue of 1. If the TE field-value is empty or if no TE field is present, the only transfer-coding is "chunked". A message with no transfer-coding is always acceptable. 14.40 Trailer(追踪者;) The Trailer general field value indicates that the given set of header fields is present in the trailer of a message encoded with chunked transfer-coding. Trailer = "Trailer" ":" 1#field-name An HTTP/1.1 message SHOULD include a Trailer header field in a message using chunked transfer-coding with a non-empty trailer. Doing so allows the recipient(接受者;容器;容纳者) to know which header fields to expect in the trailer. If no Trailer header field is present, the trailer SHOULD NOT include any header fields. See section 3.6.1 for restrictions on the use of trailer fields in a "chunked" transfer-coding. Message header fields listed in the Trailer header field MUST NOT include the following header fields: . Transfer-Encoding . Content-Length . Trailer 14.41 Transfer-Encoding The Transfer-Encoding general-header field indicates what (if any) type of transformation has been applied to the message body in order to safely transfer it between the sender and the recipient. This differs from the content-coding in that the transfer-coding is a property of the message, not of the entity. Transfer-Encoding = "Transfer-Encoding" ":" 1#transfer-coding Transfer-codings are defined in section 3.6. An example is: Transfer-Encoding: chunked If multiple encodings have been applied to an entity, the transfer- codings MUST be listed in the order in which they were applied. Additional information about the encoding parameters MAY be provided by other entity-header fields not defined by this specification. Many older HTTP/1.0 applications do not understand the Transfer- Encoding header. 14.42 Upgrade The Upgrade general-header allows the client to specify what additional communication protocols it supports and would like to use if the server finds it appropriate to switch protocols. The server MUST use the Upgrade header field within a 101 (Switching Protocols) response to indicate which protocol(s) are being switched. Upgrade = "Upgrade" ":" 1#product For example, Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 The Upgrade header field is intended to provide a simple mechanism for transition from HTTP/1.1 to some other, incompatible(不相容;矛盾) protocol. It does so by allowing the client to advertise(通告,通知) its desire to use another protocol, such as a later version of HTTP with a higher major version number, even though the current request has been made using HTTP/1.1. This eases the difficult transition between incompatible protocols by allowing the client to initiate a request in the more commonly supported protocol while indicating to the server that it would like to use a "better" protocol if available (where "better" is determined by the server, possibly according to the nature of the method and/or resource being requested). The Upgrade header field only applies to switching application-layer protocols upon the existing transport-layer connection. Upgrade cannot be used to insist on a protocol change; its acceptance and use by the server is optional. The capabilities and nature of the application-layer communication after the protocol change is entirely dependent upon the new protocol chosen, although the first action after changing the protocol MUST be a response to the initial HTTP request containing the Upgrade header field. The Upgrade header field only applies to the immediate connection. Therefore, the upgrade keyword MUST be supplied within a Connection header field (section 14.10) whenever Upgrade is present in an HTTP/1.1 message. The Upgrade header field cannot be used to indicate a switch to a protocol on a different connection. For that purpose, it is more appropriate to use a 301, 302, 303, or 305 redirection response. This specification only defines the protocol name "HTTP" for use by the family of Hypertext Transfer Protocols, as defined by the HTTP version rules of section 3.1 and future updates to this specification. Any token can be used as a protocol name; however, it will only be useful if both the client and server associate the name with the same protocol. 14.43 User-Agent The User-Agent request-header field contains information about the user agent originating(起源于,来自) the request. This is for statistical purposes, the tracing of protocol violations(违反), and automated recognition of user agents for the sake(缘故;理由;) of tailoring responses to avoid particular user agent limitations. User agents SHOULD include this field with requests. The field can contain multiple product tokens (section 3.8) and comments identifying the agent and any subproducts which form a significant part of the user agent. By convention, the product tokens are listed in order of their significance for identifying the application. User-Agent = "User-Agent" ":" 1*( product | comment ) Example: User-Agent: CERN-LineMode/2.15 libwww/2.17b3 14.44 Vary(变化;不同,偏离;) The Vary field value indicates the set of request-header fields that fully determines(决定;确定;), while the response is fresh, whether a cache is permitted to use the response to reply to a subsequent request without revalidation. For uncacheable or stale responses, the Vary field value advises the user agent about the criteria(标准,准则) that were used to select the representation. A Vary field value of "*" implies(暗示,暗指) that a cache cannot determine from the request headers of a subsequent request whether this response is the appropriate(适当的;合适的;恰当的) representation(表现;陈述;表现…的事物;有代理人). See section 13.6 for use of the Vary header field by caches. Vary = "Vary" ":" ( "*" | 1#field-name ) An HTTP/1.1 server SHOULD include a Vary header field with any cacheable response that is subject to server-driven negotiation(协商,谈判;转让;通过). Doing so allows a cache to properly(适当地;正确地;恰当地;完全,非常) interpret(解释;理解;口译;诠释,体现) future requests on that resource and informs(通知) the user agent about the presence of negotiation on that resource. A server MAY include a Vary header field with a non-cacheable response that is subject(主题,话题;学科,科目;) to server-driven negotiation, since this might provide the user agent with useful information about the dimensions(模;方面;面积;特点;) over which the response varies at the time of the response. A Vary field value consisting of a list of field-names signals that the representation selected for the response is based on a selection algorithm which considers ONLY the listed request-header field values in selecting the most appropriate representation. A cache MAY assume(承担;呈现;假定,认为;装出) that the same selection will be made for future requests with the same values for the listed field names, for the duration of time for which the response is fresh. The field-names given are not limited to the set of standard request-header fields defined by this specification. Field names are case-insensitive(大小写不敏感;不区分大小写;). A Vary field value of "*" signals that unspecified parameters not limited to the request-headers (e.g., the network address of the client), play a role in the selection of the response representation. The "*" value MUST NOT be generated by a proxy server; it may only be generated by an origin server. 14.45 Via(经过;通过,凭借;取道) The Via general-header field MUST be used by gateways and proxies to indicate the intermediate(中间的) protocols and recipients(接受的;受领的;) between the user agent and the server on requests, and between the origin server and the client on responses. It is analogous(相似的,可比拟的;) to the "Received" field of RFC 822 [9] and is intended to be used for tracking message forwards, avoiding request loops, and identifying the protocol capabilities of all senders along the request/response chain. Via = "Via" ":" 1#( received-protocol received-by [ comment ] ) received-protocol = [ protocol-name "/" ] protocol-version protocol-name = token protocol-version = token received-by = ( host [ ":" port ] ) | pseudonym pseudonym = token The received-protocol indicates the protocol version of the message received by the server or client along each segment(环节;部分,段落;) of the request/response chain. The received-protocol version is appended to the Via field value when the message is forwarded so that information about the protocol capabilities of upstream applications remains visible to all recipients. The protocol-name is optional if and only if it would be "HTTP". The received-by field is normally the host and optional port number of a recipient server or client that subsequently forwarded the message. However, if the real host is considered to be sensitive information, it MAY be replaced by a pseudonym(假名,化名). If the port is not given, it MAY be assumed to be the default port of the received-protocol. Multiple Via field values represents each proxy or gateway that has forwarded the message. Each recipient MUST append its information such that the end result is ordered according to the sequence of forwarding applications. Comments MAY be used in the Via header field to identify the software of the recipient proxy or gateway, analogous to the User-Agent and Server header fields. However, all comments in the Via field are optional and MAY be removed by any recipient prior(优先的;占先的;) to forwarding the message. For example, a request message could be sent from an HTTP/1.0 user agent to an internal proxy code-named "fred", which uses HTTP/1.1 to forward the request to a public proxy at nowhere.com, which completes the request by forwarding it to the origin server at www.ics.uci.edu. The request received by www.ics.uci.edu would then have the following Via header field: Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) Proxies and gateways used as a portal through a network firewall SHOULD NOT, by default, forward the names and ports of hosts within the firewall region. This information SHOULD only be propagated(传播,宣传,普及) if explicitly enabled. If not enabled, the received-by host of any host behind the firewall SHOULD be replaced by an appropriate pseudonym for that host. For organizations that have strong privacy requirements for hiding internal structures, a proxy MAY combine an ordered subsequence of Via header field entries with identical received-protocol values into a single such entry. For example, Via: 1.0 ricky, 1.1 ethel, 1.1 fred, 1.0 lucy could be collapsed(倒塌;崩溃;) to Via: 1.0 ricky, 1.1 mertz, 1.0 lucy Applications SHOULD NOT combine multiple entries unless they are all under the same organizational control and the hosts have already been replaced by pseudonyms. Applications MUST NOT combine entries which have different received-protocol values. 14.46 Warning The Warning general-header field is used to carry additional information about the status or transformation of a message which might not be reflected in the message. This information is typically used to warn about a possible lack of semantic transparency(透明度;透明;透明性;透明的东西) from caching operations or transformations applied to the entity body of the message. Warning headers are sent with responses using: Warning = "Warning" ":" 1#warning-value warning-value = warn-code SP warn-agent SP warn-text [SP warn-date] warn-code = 3DIGIT warn-agent = ( host [ ":" port ] ) | pseudonym ; the name or pseudonym of the server adding ; the Warning header, for use in debugging warn-text = quoted-string warn-date = <"> HTTP-date <"> A response MAY carry more than one Warning header. The warn-text SHOULD be in a natural language and character set that is most likely to be intelligible(可理解的,明白易懂的,清楚的) to the human user receiving the response. This decision MAY be based on any available knowledge, such as the location of the cache or user, the Accept-Language field in a request, the Content-Language field in a response, etc. The default language is English and the default character set is ISO-8859-1. If a character set other than ISO-8859-1 is used, it MUST be encoded in the warn-text using the method described in RFC 2047 [14]. Warning headers can in general be applied to any message, however some specific warn-codes are specific to caches and can only be applied to response messages. New Warning headers SHOULD be added after any existing Warning headers. A cache MUST NOT delete any Warning header that it received with a message. However, if a cache successfully validates a cache entry, it SHOULD remove any Warning headers previously attached to that entry except as specified for specific Warning codes. It MUST then add any Warning headers received in the validating response. In other words, Warning headers are those that would be attached to the most recent relevant(有关的,中肯的;) response. When multiple Warning headers are attached to a response, the user agent ought to inform the user of as many of them as possible, in the order that they appear in the response. If it is not possible to inform the user of all of the warnings, the user agent SHOULD follow these heuristics(启发(法),探索法): - Warnings that appear early in the response take priority(优先,优先权;) over those appearing later in the response. - Warnings in the user's preferred character set take priority over warnings in other character sets but with identical warn-codes and warn-agents. Systems that generate multiple Warning headers SHOULD order them with this user agent behavior in mind. Requirements for the behavior of caches with respect to Warnings are stated in section 13.1.2. This is a list of the currently-defined warn-codes, each with a recommended warn-text in English, and a description of its meaning. 110 Response is stale MUST be included whenever the returned response is stale. 111 Revalidation failed MUST be included if a cache returns a stale response because an attempt to revalidate the response failed, due to an inability to reach the server. 112 Disconnected operation SHOULD be included if the cache is intentionally disconnected from the rest of the network for a period of time. 113 Heuristic(启发式) expiration MUST be included if the cache heuristically chose a freshness lifetime greater than 24 hours and the response's age is greater than 24 hours. 199 Miscellaneous(混杂的;各种各样的;五花八门的;多方面的) warning The warning text MAY include arbitrary information to be presented to a human user, or logged. A system receiving this warning MUST NOT take any automated action, besides presenting the warning to the user. 214 Transformation applied MUST be added by an intermediate cache or proxy if it applies any transformation changing the content-coding (as specified in the Content-Encoding header) or media-type (as specified in the Content-Type header) of the response, or the entity-body of the response, unless this Warning code already appears in the response. 299 Miscellaneous persistent warning The warning text MAY include arbitrary(乱;随意的,任性的,随心所欲的;) information to be presented to a human user, or logged. A system receiving this warning MUST NOT take any automated action. If an implementation(贯彻;成就;) sends a message with one or more Warning headers whose version is HTTP/1.0 or lower, then the sender MUST include in each warning-value a warn-date that matches the date in the response. If an implementation receives a message with a warning-value that includes a warn-date, and that warn-date is different from the Date value in the response, then that warning-value MUST be deleted from the message before storing, forwarding, or using it. (This prevents bad consequences of naive caching of Warning header fields.) If all of the warning-values are deleted for this reason, the Warning header MUST be deleted as well. 14.47 WWW-Authenticate(证明是真实的、可靠的或有效的;鉴定,使生效) The WWW-Authenticate response-header field MUST be included in 401 (Unauthorized) response messages. The field value consists of at least one challenge that indicates the authentication scheme(s)(设计,计划;) and parameters applicable to the Request-URI. WWW-Authenticate = "WWW-Authenticate" ":" 1#challenge The HTTP access authentication process is described in "HTTP Authentication: Basic and Digest Access Authentication" [43]. User agents are advised to take special care in parsing the WWW- Authenticate field value as it might contain more than one challenge, or if more than one WWW-Authenticate header field is provided, the contents of a challenge itself can contain a comma-separated(逗号分隔) list of authentication parameters. 原文地址:https://www.w3.org/Protocols/rfc2616/rfc2616.html一只想要飞得更高的小菜鸟
去年读了《图解HTTP》、《图解TCP/IP》以及《图解网络硬件》但是读了之后并没有什么深刻的印象,只是有了一层模糊的脉络,刚好最近又接触了一些有关http的相关内容。所以,就打算把它写成一个系列,一方面可以让自己对http的理解更为深入。也可以为不懂不会http的同学在学习的路上先把荆棘剔除,以便学习的路更加的快速顺畅。 http是前后端沟通的桥梁,无论是前端还是后端,都是极为重要的基础知识。大多数前端开发只关注页面布局好不好,css简不简洁,js的可读性可复用性是不是还行,框架用的熟不熟练。但是我觉得像http这种基础知识是十分重要的。也是程序员生涯中无法回避的问题。 一、http含义 http的大名叫做超文本传输协议(HyperText Transfer Protocol),那么什么是超文本传输协议呢?我们先从字面意思来理解,就是传输“超文本”的协议。比方说A和B两个人,每个人手里都有一份文件叫做“超文本”,A按照“协议”把“超文本”文件“递给(传输)”B。这就是超文本传输协议的一个比较形象的说明。那么,A按照协议传递给B的是超文本,协议我们比较容易理解,就是一种规则嘛...我们在A和B之间传递“超文本”的时候要遵守这种规则。就像是你开车不能喝酒,酒驾被抓轻则扣分重则拘留。所以大家一定要注意开酒不喝车,喝车不开酒,至理名言啊。那么协议我们理解了,那什么是超文本呢?超文本就是超级文本!说的真有道理....哎呦...住手..不对...住脚...轻点踢...哎呦...确实是这样的。文本我们知道,可以解释为有图文内容的文件。那么超级文本(Hypertext)是啥子呢?嘿...这个就比较有意思了。超文本简单来说就是文本内容中有超链接(Hyperlink)的文本,你点击超链接就可以跳转到其它内容。这就是超文本了。超文本的格式有很多,目前最常用的就是超文本标记语言。唉?超文本标记语言?听着有点耳熟啊?超文本标记语言(HyperText Markup Language)。卧槽,就是HTML嘛?是的...没错。我们走了一小圈,绕到了这里。用一句话来解释HTTP就是,用来在网页(小A和小B)间传递(传输)HTML(超文本)的一种规则(协议)。 二、http从出生到现在 我们了解了http的名字是什么含义,就像是张全蛋、王二嘎这种名字也都是父母在起名时赋有深刻寓意在里面的。所以我们了解人家名字的含义更有助于我们了解其本身,还是十分必要的。那么我们再来看看http从出生到成长的人生历程。 1991年我们的http0.9版本终于诞生(发布),当然,在发布之前还必须“怀胎十月”,但是我们不去纠结人家是怎么怀上的,我们来纠结一下生下来之后都发生了什么。刚出生的http还比较年幼,好吧,废话了,谁出生的时候不年幼...所以它的能力还并不是很强。只有一个get技能,并且还只能传递html格式的字符串,比如今天的JSON啊,XML啊,TXT啊什么的。想都别想。毕竟我年纪还小啊。。。要求这么多。。。真是的。并且在0.9版本,每个http请求都是短链接(后面会具体介绍什么是短链接)。 时间飞逝,我们来到了1996年,此时在5年的时间中,http一边工作,一边成长,终于,http1.0版本产生了,我们的小朋友也长大了,有了更多的社会阅历和见解,随之而来的,它的能力也提升了不少。此时的http拥有了更多的技能,POST和HEAD。拥有了更多的功能,包括我们现在耳熟能详的状态码(status code)、缓存(cache)、重定向(redirect)、权限(authorization)等等一大堆的内容。此时的HTTP可以说成长为了一个可以支撑起一片天地的一家之主了。 时间继续慢慢悠悠的走,此时大概过了半年左右,http的成长极为迅速。在1997年初,http1.1发布了,此时的http技能更加的多,比如像OPTIONS,PUT, DELETE, TRACE, CONNECT等方法。要知道,http1.1是我们目前最为常用的http版本。 再简单说一下http2和https吧,其实大家可以发现,现在普遍的浏览器域名地址前的http都已经变为了https,简单来说,https就是http的安全版本,其实https就是加密数据过后的http,使网络传输的数据更加安全,而加密的过程实际上就是在七层网络模型中的表示层和会话层来完成的,而http2其实是为了适应当代浏览器及网络发展速度而产生的一个各方面性能都更好的http版本。 三、网络模型 我们的目的不是搞懂整个互联网络模型,而是在学习http前极为必要的“背景故事”之一,所以,我会简单介绍一下互联网络模型。咱们先来看张图: 图中左边的是五层网络模型,中间是七层网络模型,最右边是每一层的作用。实际上,七层网络模型不过是在应用层中又细分出了表示层和会话层。如果大家想要深入了解整个互联网模型的过程及具体的内容,大家可以去看一下阮一峰老师的互联网协议入门(一)。也可以自己取查找资料。这不是本系列的重点。要知道这是一个庞大的知识体系。 我们每一次发送的网络请求在客户端都是从上至下,到了服务器端再从下至上的一个过程。当然,从服务器返回到客户端的响应也是在服务器端从上至下,到客户端再从下至上的获取到。也就是说,哪里传输出去的,就是从应用层直到物理层,哪里接收的,就是从物理层直到应用层。 四、三次握手 在客户端与服务器发送http请求及返回响应的过程中,我们需要创建一个确保http可以传递的通道,也就是传输层所建立起来的tcp connection。在连接已经建立之后,我们才可以完成http的请求与响应。那么如何才能确定tcp connection已经创建了呢?那么就需要通过三次握手,来确定连接已经建立。我们来看张图,了解一下三次握手是如何工作的: 首先客户端发送一个数据包,包的内容是一个标志位syn和一个随机数seq,然后发送给服务器。这时候服务器知道了,噢这个客户端想要请求连接,那么就会返回一个新的数据包,同样的包括syn,并且再返回一个ack标志位,并在接收到的seq基础上+1作为ack的值返回,重新生成一个新的随机数seq传递给客户端,意思是我确实收到了这个请求。客户端此时收到了第二次返回的数据包,于是再告诉服务器我收到了你得信息,把计算后的ack和新生成的seq再传递给服务器。至此,三次握手完成,可以进行http请求了。 五、URI—URL、URN 在开始http真正的内容之前,我们还需要了解一下什么是uri、url以及urn。因为我们几乎所有的http请求都是通过url来完成的。所以我们有必要了解一下url相关的知识。 URI统一资源标志符(Uniform Resource Identifier),是一个用于标识某一互联网资源名称的字符串。 URL统一资源定位符(Uniform Resource Locator),如同在网络上的门牌,是因特网上标准的资源的地址。 URN统一资源名称(Uniform Resource Name),期望为资源提供持久的、位置无关的标识方式,并允许简单地将多个命名空间映射到单个URN命名空间。 其实URL和URN是URI的子集,我们看一张图,图片来源维基百科相关条目: URN我们并不常见,做个了解就行了。 六、报文格式 我们先来看一张图,这张图是我在网上随便找的一个http请求的截图: 大家可以自己打开浏览器的f12,在netwrok随便找一个请求看一下, 那么这是所有的“请求”信息。包括通用信息(general)以及请求头(request header)和响应头(response header)。但是可能你看到的跟我截图中的不一样。这是因为浏览器为我们做了格式化,让我们可以更快速的获取到想要的信息。你点一下下图中的按钮,就可以看到source信息,而不是parsed后的信息了。 第一张图中的红框部分,就是我们的起始行,在view parsed的时候,我们的浏览器把起始行省略掉了,只留下了报文首部,也就是红框外的其它部分。除了起始行和首部外还有一个主体,就是我们服务器返回的响应的内容,我们可以在preview中查看格式化后的主体内容。 上面的内容讲解的都十分基础,没有深入的去探索,但是对于我们接下来学习http是足够且必要的前提。如果大家想要深入了解其中的每一项内容,可以自行查找资料。网络上有关的内容还是相当多的。那么,本文到这里就结束了,下面会开始http的正式内容。还望大家准时搬好小板凳,不要迟到噢。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
算法复杂度是我们来衡量一个算法执行效率的一个度量标准,算法复杂度通常主要有时间复杂度和空间复杂度两种。时间复杂度就是指算法代码在运行最终得到我们想要的结果时所消耗的时间,而空间复杂度则是指算法中用来存储的数据结构所占用的空间。往往一个时间复杂度比较低的算法拥有着较高的空间复杂度,两者是互相影响的,我们前面讲解数据结构中的一些例子和代码也足以说明这一点。本文会简单介绍一下用于描述算法的性能和复杂程度的大O表示法。 我们先来看一段简单的代码,来帮助我们理解什么是大O表示法: function increment(num) { return ++num; } console.log(increment(1)); 上面的代码声明了一个函数,然后调用它。这样的代码无论我们传入的参数是什么,它都会返回自增后的结果。也就是说该函数的执行时间跟我们传入的参数没有任何关系,执行的时间都是X。因此,我们称该函数的复杂度是O(1),常数的。 我们再来看看前面讲过的顺序搜索算法,我们直接把代码拿过来用就好了。 //顺序搜索 function sequentialSearch(array,item) { for(var i = 0; i < array.length; i++) { if(item === array[i]) { return i; }; }; return -1; }; 现在我们假设要搜索的数组是[1,2,3...9,10],然后我们搜索1这个元素,那么在第一次判断时就能找到想要搜索的元素。那么我们这里先假设每执行一次循环的开销是1。那么我们还想搜索11,数组中没有这个元素,sequentialSearch 就会执行十次遍历整个数组,发现没有后返回-1。那么在最坏的情况下,数组的长度大小决定了算法的搜索时间。这样的函数的时间复杂度就是O(n),线性的,这里的n就是数组的长度。 那么,我们来稍微修改一下sequentialSearch让它可以计算开销: //顺序搜索 function sequentialSearch(array,item) { var cost = 0; for(var i = 0; i < array.length; i++) { cost++; if(item === array[i]) { return i; }; }; console.log(array.length,cost); return -1; }; sequentialSearch([1,2,3,4,5,6,7,8,9,10],11); 很简单,就是加上了一个cost变量来计数。那么我们下面再来看看冒泡排序的时间复杂度是怎样的,这里我们不再浪费篇幅,直接在代码中加入计数器: function swap(array,index1,index2) { var aux = array[index1]; array[index1] = array[index2]; array[index2] = aux; } function bubbleSort(array) { var length = array.length; var cost = 0; for (var i = 0; i < length; i++) { cost++; for (var j = 0; j < length - 1; j++) { cost++; if(array[j] > array[j + 1]) { swap(array,j,j+1); } } } console.log(length,cost); } bubbleSort([2,3,4,1,8,7,9,10]); 代码本身没什么好说的,我们发现,如果数组的长度是8,那么我们排序所消耗的时间就是64,如果长度是10,消耗的时间就是100。换句话说,冒泡排序的时间复杂度就是数组长度的平方,也就是O(n2)。 <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <style> body { background-color: #DDDDDD; font: 30px sans-serif; } </style> <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript"> google.load('visualization', '1.0', {'packages':['corechart']}); google.setOnLoadCallback(drawChart); function drawChart() { var data = new google.visualization.DataTable(); data.addColumn('string', 'n'); data.addColumn('number', 'O(1)'); data.addColumn('number', 'O(log n)'); data.addColumn('number', 'O(n)'); data.addColumn('number', 'O(n log n)'); data.addColumn('number', 'O(n^2)'); data.addColumn('number', 'O(2^n)'); for (var i = 0; i <= 30; i++) { data.addRow([i+'', 1, Math.log(i), i, Math.log(i)*i, Math.pow(i,2), Math.pow(2,i)]); } var options = {'title':'Big O Notation Complexity Chart', 'width':700, 'height':600, 'backgroundColor':{stroke:'#CCC',strokeWidth:1}, 'curveType':'function', 'hAxis':{ title:'Elements (n)', showTextEvery:5 }, 'vAxis':{ title:'Operations', viewWindowMode:'explicit', viewWindow:{min:0,max:450} } }; var chart = new google.visualization.LineChart(document.getElementById('chart_div')); chart.draw(data, options); } </script> </head> <body> <div id='chart_div'></div> </body> </html> 上面的代码是用js画的一幅图,其中有一些常用的大O表示法所对应的时间复杂度,大家可以把代码COPY到本地自行去看一下,这样会才会对大O表示法有更好的理解,为了偷点懒,也为了大家可以确实的自己去看一下图标。我这里不会把图给大家贴上的。 下面给大家贴上几张图,来看看我们之前学习的一些数据结构和算法的时间复杂度是怎样的。 1、常用数据结构的时间复杂度 2、图的时间复杂度 表格中的V代表顶点,E代表边。 3、排序算法 4、搜索算法的时间复杂度 终于,我们了解了几乎我们前面学过的所有的数据结构和算法的时间复杂度,这样我们在选择算法的时候可以更确切地评估它的效率。 那么,我们本系列的文章也将告一段落。后面准备深入的学习一下CSS。可能会把其中一些比较有意思的地方贴出来与大家分享。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢! 最后,感谢大家的阅读。如果您觉得或许还有那么点用处,可以把我的主页加入你的收藏夹中以便需要的时候可以快速的找到。一只想要飞得更高的小菜鸟
在解释什么是函数式编程之前,我们先要说下什么是命令式编程,它们都属于编程范式的一种。命令式编程其实就是一块一块的代码,其中包括了我们要执行的逻辑或者判断或者一些运算。也就是按部就班的一步一步完成我们所需要的逻辑。而函数式编程则是类似于一个函数一个函数的调用。我们来看代码,更清晰的理解一下函数式编程与命令式编程的区别。 //这是命令式 var printArray = function (array) { for (var i = 0; i < array.length; i++) { console.log(array[i]) } } printArray([1,2,3,4,5]); //函数式 var forEach = function (array,action) { for (var i = 0; i < array.length; i++) { action(array[i]) } } var logItem = function (item) { console.log(item) } forEach([2,3,4,5,6],logItem) 我们先来看看上面的代码做了什么——“遍历数组,然后打印数组的每一项”。在命令式编程中,我们一步一步的完成了这句话。先便利数组,然后打印每一项元素。那么我们再来看函数式编程,我们先声明了两个函数,一个是遍历数组元素的forEach(这里的action参数其实就是一个回调函数),一个是打印每一项的logItem。我们把每一步骤的需要操作的逻辑都用函数来区分开,最后再调用函数来执行运算。 再有了ES6之后,我们可以更加方便的用函数式编程范式来编写我们的代码,下面我们再来看一个例子。 //找出数组中元素最小的值 //代码十分简单,我们假设数组的第一个元素是最小的并赋值给minVal变量 //遍历除第一项元素以外的所有数组内元素并与minVal比较,如果当前的minVal比array[i]还要大,那么就把minVal替换成array[i]; //最后返回结果 var findMinValInArray = function (array) { var minVal = array[0]; for (var i = 1; i < array.length; i++) { if(minVal > array[i]) { minVal = array[i]; } } return minVal; } console.log(findMinValInArray([7,8,9,5,31,2])); //那么我们其实可以更简单的实现上面的方法,比如Math.min以及结构操作符(...) const _min = function (array) { return Math.min(...array); } console.log(_min([5,6,9,3,1])); //我们还可以用ES6的箭头函数,让我们的代码更好看一些。 const min = arr => Math.min(...arr); console.log(min([2,3,9,4,8])) 上面代码中Math.min是一个方法,返回参数中的最小值,参数可以是无限个。那么还有ES6的箭头函数以及扩展运算符(...)。这里不做详细的解释,附上连接地址,大家可以更为详细的知道什么是箭头函数以及扩展运算符。 那么,接下来我们看看如何利用我们前面已经学过的数组方法来让我们的代码更加“函数式”。 //我们先看一个命令式编程的例子 var daysOfWeek = [ {name:"Monday",value:1}, {name:"Tuesday",value:2}, {name:"Wednesday",value:7}, ] var daysOfWeekValues_ = []; for (var i = 0; i < daysOfWeek.length; i++) { daysOfWeekValues_.push(daysOfWeek[i].value); } //再来看看函数式编程的样子 var daysOfWeekValues = daysOfWeek.map(function (day) { //这个day其实就是数组中的每一项,具体可以去我前面的文章查看map的参数 return day.value; }) console.log(daysOfWeekValues); //我们还可以使用filter来过滤一个数组的值。 //比如: //命令式 var positiveNumbers_ = function (array) { var positive = []; for (var i = 0; i < array.length; i++) { if(array[i] >= 0) { positive.push(array[i]); } } return positive; } console.log(positiveNumbers_([-1,2,1,-2])); //函数式 var positiveNumbers = function (array) { return array.filter(function (num) { return num >= 0; }) } console.log(positiveNumbers([1,2,-1,-2,-5])); //我们再来看看reduce函数 //命令式 var sumValues = function (array) { var total = array[0]; for (var i = 1; i < array.length; i++) { total += array[i]; } return total; } console.log(sumValues([1,2,3,4,5])); //函数式 var sum_ = function (array) { return array.reduce(function (a,b) { return a + b; }) } console.log(sum_([1,2,3,4,5])) //我们还可以用ES6的方法改进一下 var sum = arr => arr.reduce((a,b) => a + b); console.log(sum([1,2,3,4,5])) 上面我们看了一些函数式编程的例子,代码都不复杂,很容易理解。所以就没做详细的注释。那么我们下面再看最后一个有趣的例子。 //我们来用命令式编程实现一个二维数组合并为一维数组的方法 var mergeArrays_ = function (arrays) { var count = arrays.length, newArray = [], k = 0; for (var i = 0; i < count; i++) { for (var j = 0; j < arrays[i].length; j++) { newArray[k++] = arrays[i][j]; } } return newArray; } console.log(mergeArrays_([[1,2,3],[4,5],[6]])); //我们最后再看看函数式的写法 var mergeArraysConcat = function (arrays) { return arrays.reduce(function (p,n) { return p.concat(n); }) }; console.log(mergeArraysConcat([[1,2,3],[4,5],[6],[7]])) //我们再来看看牛逼的方法 const mergeArrays = (...arrays) => [].concat(...arrays); console.log(mergeArrays([1,2,3],[4,5],[6],[7],[8])); //这一行代码需要解释下。我们来看看(...arrays)会变成什么 console.log(...[[1,2,3],[4,5],[6],[7],[8]])//一个一个单独的数组 //然后我们再用一个空数组去合并参数中的每一个单独的数组就可以了 到这里我们函数式编程的简单讲解就结束了,上面的内容其实不过万分之一,希望能让大家对代码的编写打开了另一扇窗户,其实函数式编程在我们的实际工作中也是极为有用的。希望大家可以认真对待和学习,最后,附上一个可以学习函数式编程的网址:http://reactivex.io/learnrx/。这是一个外国的练习网站,只要会简单的英语看下来应该是没有问题的。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
在前面的文章中(js算法初窥02(排序算法02-归并、快速以及堆排)我们学习了如何用分治法来实现归并排序,那么动态规划跟分治法有点类似,但是分治法是把问题分解成互相独立的子问题,最后组合它们的结果,而动态规划则是把问题分解成互相依赖的子问题。 那么我还有一个疑问,前面讲了递归,那么递归呢?分治法和动态规划像是一种手段或者方法,而递归则是具体的做操作的工具或执行者。无论是分治法还是动态规划或者其他什么有趣的方法,都可以使用递归这种工具来“执行”代码。 用动态规划来解决问题主要分为三个步骤:1、定义子问题,2、实现要反复执行来解决子问题的部分(比如用递归来“反复”),3、识别并求解出边界条件。这么说有点懵逼....那么我们试试用动态规划来解决一些经典的问题。 一、最少硬币找零问题 最少硬币找零问题是硬币找零问题的一个变种。硬币找零问题是给出要找零的钱数,以及可用的硬币面额以及对应的数量,找出有多少种找零的方法。最少硬币找零问题则是要找出其中所需最少数量的硬币。比如我们有1,5,10,25面额的硬币,如果要找36面额的钱,要如何找零呢?答案是一个25,一个10,一个1。这就是答案。那么如何把上面的问题转换成算法来解决呢?毕竟有了计算机很快速简单的就可以得到结果,不用我们再费力地用人脑去解决问题了,下面我们就来看一下代码: //最少硬币找零 function MinCoinChange(coins) { // coins是有多少种面额的钱币。 // 这里我们直接把构造函数传进来的参数用私有变量存储一下。 var coins = coins; // 缓存结果集的变量对象 var cache = {}; // 定义一个构造函数的私有方法, this.makeChange = function (amount) { // 这里的this指向的就是this.makeChange私有函数本身,把它赋值给一个变量是为了不用在每次调用的时候都要计算(个人见解) var me = this; // amount就是我们要找零的钱数,如果为非正数,直接返回空数组,因为你找零的钱数不应该为负数。 if(!amount) { return []; }; // cache[amount]的判断是为了在重复计算前面已经计算过的结果时可以直接返回结果 // 避免重复计算所造成的时间浪费 if(cache[amount]) { return cache[amount]; }; // min用来存储最终结果的数组,newMin和newAmount分别是在逻辑的执行过程中,用于存储当前的符合条件的找零数组和找零钱数的。 var min = [],newMin,newAmount; // 我们循环coins的长度。通过循环,我们为每一个conis数组中的面额都进行下面的逻辑操作。(主要是为当前coin做递归) for(var i = 0; i < coins.length; i++) { // 选择coins中的当前面额。 var coin = coins[i]; // 我们用要找零的钱数减去当前要找零的面额。并存储为newAmount变量。 newAmount = amount - coin; // 在当前循环的递归中,如果newAmount是不小于0的值,也就是合法的找零的钱数,我们同样为该数调用找零方法。 // 这里就是有点类似分而治之的那种策略了,递归求解。 if(newAmount >= 0) { newMin = me.makeChange(newAmount); }; // 在前面符合条件的newAmount递归后会进入下一个值得逻辑执行,然后就会到这里的逻辑判断 // 下面的if判断主要是判断是否是当前的最优解,如果是,那么就放入我们最终的数组内。 console.log(!min.length,min.length) if(newAmount >= 0 && (newMin.length < min.length - 1 || !min.length) && (newMin.length || !newAmount)) { min = [coin].concat(newMin); //console.log('new Min' + min + 'for' + amount); } }; //cache存储了1到amount之间的所有结果 //console.log(cache) return (cache[amount] = min); }; }; var minCoinChange = new MinCoinChange([1,5,10,25]); console.log(minCoinChange.makeChange(36)) 这是用动态规划的方法来解决最少硬币找零问题,那么我们再来看看如何用贪心算法求解最少硬币找零的问题。那么什么是贪心算法呢?贪心算法在有最优子结构的问题中尤为有效。最优子结构的意思是局部最优解能决定全局最优解。简单地说,问题能够分解成子问题来解决,子问题的最优解能递推到最终问题的最优解。贪心算法与动态规划的不同在于它对每个子问题的解决方案都做出选择,不能回退。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,有回退功能。 我们还是来看下代码: function MinCoinChange(coins) { var coins = coins; this.makeChange = function (amount) { var change = [],total = 0; for(var i = coins.length; i >= 0; i--) { var coin = coins[i]; while(total + coin <= amount) { change.push(coin); total += coin; } } return change; }; } var minCoinChange = new MinCoinChange([1,5,10,25]); console.log(minCoinChange.makeChange(36)) 我们看上面的代码,主要逻辑跟动态规划十分相似,只是代码本身要简单了不少。贪心算法从我们的硬币中最大的开始拿,直到拿不了了再去拿下一个,直到返回最终结果。那么我们看看两种解决方法有什么不通过。动态规划会通过cache来缓存之前的计算结果,在当前的计算结果中与之前的对比,选择两者之间的最优解。而贪心算法则只是选择了当前的最优解,不会回退,也不会去存储记录之前的解决方案。 二、背包问题 背包问题其实是一个组合优化问题,问题是这样的,给定一个固定大小,能携带重量为W的背包,以及一组有价值和重量的物品,找出一个最佳解决方案,使得装入背包的物品总重量不超过W,且总价值是最大的。这个问题有两个版本,一个是0-1背包问题,该版本只允许背包里装入完整的物品,不能拆分。还有另外一个是可以装入分数物品。我们后面会用贪心算法来解决分数背包问题。 我们来看代码: //背包问题 function knapSack(capacity,weights,values,n) { var i,w,a,b,kS = []; for (var i = 0; i <= n; i++) { kS[i] = []; } for(i = 0; i <= n; i++) { for(w = 0; w <= capacity; w++) { if(i == 0 || w == 0) { kS[i][w] = 0; } else if(weights[i - 1] <= w) { a = values[i - 1] + kS[i - 1][w - weights[i - 1]]; b = kS[i - 1][w]; kS[i][w] = (a > b) ? a : b; } else { kS[i][w] = kS[i - 1][w]; } } } findValues(n,capacity,kS,weights,values); return kS[n][capacity]; }; function findValues(n,capacity,kS,weights,values) { var i = n,k = capacity; console.log('解决方案包括以下物品:'); while(i > 0 && k > 0) { if(kS[i][k] !== kS[i - 1][k]) { console.log('物品' + i + ',重量:' + weights[i- 1] + ',价值:' + values[i - 1]); i--; k = k - kS[i][k]; } else { i--; } } } var values = [3,4,5],weights = [2,3,4],capacity = 5,n = values.length; console.log(knapSack(capacity,weights,values,n)) 上面的代码中,我们最开始初始化一个矩阵,用来存放各种解决方案,而且要注意装入背包的物品i必须小于capacity,也就是小于背包可容纳的重量,才可以成为装入背包的一部分,不然你一个物品就超过了背包可容纳的重量,这是不允许的。并且当有两个物品重量相同的时候,我们选择价值较大的哪一个。 其实上面的算法还可以继续优化,这里不做多讲,大家有兴趣可以深入学习。 贪心算法的分数背包问题: 分数背包问题和0-1背包问题类似,只是我们可以在分数背包中加入部分的物品。代码并不难,大家自己写一下就明白了。 function knapSack(capacity,values,weights) { var n = values.length,load = 0,i = 0,val = 0; for(i = 0; i < n && load < capacity; i++) { if(weights[i] <= (capacity - load)) { val += values[i]; load += weights[i]; } else { var r = (capacity - load) / weights[i]; val += r * values[i]; load += weights[i]; } } return val; } var values = [3,4,5],weights = [2,3,4],capacity = 6; console.log(knapSack(capacity,values,weights)) 三、最长公共子序列问题 该问题是这样的,找出两个字符串序列中的最长子序列的长度。最长子序列是指,在两个字符串序列中以相同的顺序出现,但不要求一定是连续的字符串序列。 //最长公共子序列LCS function lcs(wordX,wordY) { var m = wordX.length,n = wordY.length,l = [],i,j,a,b; var solution = []; for (i = 0; i <= m; ++i) { l[i] = []; solution[i] = []; for(j = 0; j <= n; ++j) { l[i][j] = 0; solution[i][j] = '0'; } } for(i = 0; i <= m; i++) { for(j = 0; j <= n; j++) { if(i == 0 || j == 0) { l[i][j] = 0; } else if(wordX[i - 1] == wordY[j - 1]) { l[i][j] = l[i - 1][j - 1] + 1; solution[i][j] = 'diagonal'; } else { a = l[i - 1][j]; b = l[i][j - 1]; l[i][j] = (a > b) ? a : b; solution[i][j] = (l[i][j] == l[i - 1][j]) ? 'top' : 'left'; } } } printSolution(solution,l,wordX,wordY,m,n); return l[m][n]; } function printSolution(solution,l,wordX,wordY,m,n) { var a = m,b = n,i,j, x = solution[a][b], answer = ''; while(x !== '0') { if(solution[a][b] === 'diagonal') { answer = wordX[a - 1] + answer; a--; b--; } else if(solution[a][b] === 'left') { b--; } else if(solution[a][b] === 'top') { a--; } x = solution[a][b]; } console.log('lcs:' + answer); } lcs("acbaed","abcadf"); 四、矩阵链相乘 该问题是要找出一组矩阵相乘的最佳方式(顺序),在开始之前,有必要给大家简单讲解一下矩阵相乘,简单来说就是,加入一个n行m列的矩阵A和m行p列的矩阵B相乘,会得到一个n行p列的矩阵C。要注意,只有一个矩阵的行与另一个矩阵的列相同两个矩阵才可以想乘。 那么如果我想有A,B,C,D四个矩阵相乘,由于乘法满足结合律(小学数学知识点)。所以我们可以这样(A(B(CD))),或者这样((AB)(CD))等五种相乘的方法,但是要注意的是,每种相乘的顺序不一样,我们的计算量也是不一样的。所以,我们来构建一个函数,找出计算量最少的相乘方法。这就是矩阵链相乘问题了。 //矩阵链相乘 function matrixChainOrder(p,n) { var i,j,k,l,q,m = []; //辅助矩阵s var s = []; for(i = 0; i <= n; i++) { s[i] = []; for(j = 0; j <= n; j++) { s[i][j] = 0; } } for(i = 0; i <= n; i++) { m[i] = []; m[i][i] = 0; }; for(l = 2; l < n; l++) { for(i = 1; i <= n - l + 1; i++) { j = i + l - 1; m[i][j] = Number.MAX_SAFE_INTEGER; for(k = i; k <= j - 1; k++) { q = m[i][k] + m[k + 1][j] + p[i - 1]*p[k]*p[j]; if(q < m[i][j]) { m[i][j] = q; s[i][j] = k;//辅助矩阵 } } } } printOptimalParenthesis(s,1,n - 1); return m[1][n - 1]; } function printOptimalParenthesis(s,i,j) { if(i == j) { console.log("A[" + i + "]"); } else { console.log("("); printOptimalParenthesis(s,i,s[i][j]); printOptimalParenthesis(s,s[i][j] + 1,j); console.log(")"); } } var p = [10,100,5,50,1,100]; n = p.length; console.log(matrixChainOrder(p,n)); 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
终于来到了有点意思的地方——递归,在我最开始学习js的时候,基础课程的内容就包括递归,但是当时并不知道递归的真正意义和用处。我只是知道,哦...递归是自身调用自身,递归要记得有一个停止调用的条件。那时,我还不了解递归的内在含义,好在现在知道了一点。 有些问题的本身就是递归的,我们想一个程序问题,也是比较经典的面试问题——有一个对象a,我们不知道它有多少层级,如何复制对这个对象?你可能会说,直接声明一个变量var b = a不就可以了嘛?但是,如果我改动了a中的一个属性,b中的属性也跟着改变了。因为你只是将b得到指针指向了a,并没有开辟一块新的空间来存储“存储在a中的属性”。也就是我们所谓的浅拷贝。那么如何改变a中的属性,b的属性还是原来的样子呢?我们可以利用递归来解决这样的问题。 我记得前面的文章(用js来实现那些数据结构05(栈02-栈的应用))例举了用栈解决问题的实例。其中最后一个问题是汉诺塔问题,也需要用递归来解决。那么就汉诺塔问题来说,如果不用递归,是否还有其它的可行的算法得以解决这样的问题呢? 很多人会觉得递归是低效率的,只不过是因为人脑的有限性不得不让计算机去更忙碌一点,其实这种想法实在是片面的。因为有些问题本身就是递归的,比如我们上面所举例子。再比如,有些问题或许可以递归,可以循环,还可以用其他方法来解决,但是递归更容易让我们的代码简洁易懂,于是我们选择了递归。 好了,说了很多,我们还是回到递归本身吧,递归说到底是一种解决问题的方法,它解决问题的各个小部分,直到解决最初的大问题。那么,递归通常都会调用自身,就像下面这样: function a() { a(); } 当然,这样写也是一样的: function a() { b(); } function b() { a(); } 当然,上面代码只是举个例子,没有什么实际意义。 在我们在最开始试着去实现一个递归的时候,往往会出现stack overflow error等类似栈溢出的错误。因为我们的递归无限的执行下去以至于浏览器不得不强制停止递归,然后告诉你,出错了。我们可以写一点简单的代码来测试一下: var i = 0; function recursiveFn() { i++; recursiveFn(); } try{ recursiveFn(); } catch(err) { console.log(i,"error is:" + err); } // Google //15710 "error is:RangeError: Maximum call stack size exceeded" // FireFox //65657 error is:InternalError: too much recursion //QQ // 41756 "error is:RangeError: Maximum call stack size exceeded" //ie //8225 error is:Error: 堆栈溢出 //edge // 15466 error is:Error: Out of stack space 我们发现似乎每一个浏览器,栈溢出的上限都是不一样的。因为每一种浏览器厂商都为其自己的浏览器设置了不同的限度。甚至包括一些js原生api的内部实现方式,在不同的浏览器上都是不一样的。 我们发现递归是如此的简单,就是自身调用自身,再加一个限制条件,就可以实现递归了。上面我们所写的代码在一定程度上只是为了解释递归这个概念。没有太多的实际意义。那么,下面我们看看用递归来解决斐波那契数列问题。 那么我们先来看这样一个问题,经典的兔子繁殖问题。一般而言,兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。如果所有兔子都不死,那么一年以后可以繁殖多少对兔子? 我们不妨拿新出生的一对小兔子分析一下:第一个月小兔子没有繁殖能力,所以还是一对,两个月后,生下一对小兔,对数共有两对,三个月以后,老兔子又生下一对,因为小兔子还没有繁殖能力,所以一共是三对。依次类推: 这就是斐波那契数列了,在生活中,也有许多斐波那契数列存在的地方。 那么我们可以提取一下:1和2的斐波那契数是1,3的斐波那契数是2,4的斐波那契数是3。换句话说,在n>2的情况下,F(n) = F(n-1) + F(n - 2)——这里的n代表着在斐波那契数列中的第几个斐波那契数。那么,我们再用语言描述一下——除开最开始的两项以外,以后的每一项都是前两项的和,这就是我们的递归体和递归终止条件,我们来看下代码: function fibonacci(num) { if(num === 1 || num === 2) { return 1; } return fibonacci(num - 1) + fibonacci(num - 2); } console.log(fibonacci(6)) 要注意,不要试超过50的数噢,因为越往后相加的计算量就会越来越巨大。那么我们画个图来看看,我们递归算出第6项的斐波那契数时,递归是如何进行的: 我们看上图一步一步的解释: 每一个方块中“/”后面的是当前调用的计算结果。我们从第一次fib(6)开始,由于6既不是1也不是2所以停止条件不符合,我们直接return了两次调用但是这两次调用又对num参数做了减一和减二的操作。所以就到了下一层。直到最后每一层的调用都执行到了num=1或者num=2的情况时。递归最终终止。那么,在递归终止的时候,结果是由递归到最底层条件一点一点向上返回的。所以,递归的执行时由上至下但是递归结果的返回则是由下至上的。这样我们就完成了一次整个递归的过程。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
前面我们了解了一些常用的排序算法,那么这篇文章我们来看看搜索算法的一些简单实现,我们先来介绍一个我们在实际工作中一定用到过的搜索算法——顺序搜索。 1、顺序搜索 其实顺序搜索十分简单,我们还是以第一篇文章写好的架子作为基础,在其中加入顺序搜索的方法: //顺序搜索 this.sequentialSearch = function(item) { for(var i = 0; i < array.length; i++) { if(item === array[i]) { return i; }; }; return -1; }; 我想这个代码没什么好说的。你一定能理解的十分透彻。那么下面我们来看看二分搜索。 2、二分搜索 我们先来做一个简单的游戏。想象一个场景,我们在聚会,大约有7、8个人,这个时候有人提议我们来做个游戏吧。我来想一个1到100的数字,你们来猜数字是什么,我会依照我想的数字告诉你们猜测的数字是比我脑海中的数字大了还是小了。这就是二分搜索。 与顺序搜索不同的是,二分搜索需要在搜索之前对要搜索的数组排序。我们来看下代码: //二分搜索 this.binarySearch = function(item) { //先对数组进行快速排序 this.quickSort(); //low和high是边界指针,也就是item是高了还是低了的表示,mid是我们数组的中间索引变量,element则是对应的mid的元素 var low = 0,high = array.length - 1,mid,element; //如果low小于等于high说明边界范围是合理的。 while(low <= high) { //为mid和element变量赋值。 mid = Math.floor((low + high) / 2); element = array[mid]; // 如果中间值比我们要找的元素小,说明item在中间值的右侧,要注意我们的数组时排序过后的数组了。 // 所以我们直接让等于0的low的值设置为mid+1,因为item>element,所以item必然在mid+1开始到high的区间范围内。 // 下同。 if(element < item) { low = mid + 1; } else if(element > item) { high = mid - 1; } else { return mid; }; }; return -1; }; 其实二分搜索也并不难,看代码和注释就一定可以看懂的。感觉这篇内容实在是不太多,所以我决定再加入一些其他的内容吧。 3、去重 想必大家在面试中被问到过最多的问题就是排序和去重了吧。其实这个东西真的算是老生常谈了,但是却又有它存在的必要,其实说到底,去重更重要的是思想,而不是实现,就跟前面我们学过的那些数据结构和算法一样。 下面我们就介绍一下去重的一些实现方法吧。 1)set方法 set是ES6新增的一种数据结构——集合,我在前面的有关集合的章节中也介绍过这种数据结构,集合是一种不允许重复的数据存在的数据结构,我们刚好可以利用这种特性来为数组去重。如果你还不了解set数据结构,可以去这里或者这里查看。 this.uniqueSetWay = function () { //array.form方法从类似数组或可迭代对象中创建一个新的数组实例 var set = new Set(array); return Array.from(set); }; //测试方法 var repeatArray = new ArrayList(); repeatArray.insert(1); repeatArray.insert(1); repeatArray.insert(3); repeatArray.insert(3); repeatArray.insert(5); repeatArray.insert(7); repeatArray.insert(7); repeatArray.insert(9); repeatArray.insert(9); repeatArray.insert(8); console.log(repeatArray.uniqueSetWay()) 要注意的是,我们这里仍旧使用了第一章所构建的数组类。 2)双循环 //双循环 this.uniqueDoubleCycle = function () { var newArr = []; var len = array.length; var isRepeat; for(var i=0; i<len; i++) { //第一次循环 isRepeat = false; for(var j=i+1; j<len; j++) { //第二次循环 if(array[i] === array[j]){ isRepeat = true; break; } } if(!isRepeat){ newArr.push(array[i]); } } return newArr; }; 这种方法使用了双重循环设置一个标记位,确定我们加入新数组的元素是否是重复的,代码很好理解,但是这是效率最低的实现方式。 3)排序辅助去重 //利用排序算法来辅助判断 this.sortUnique = function () { var newArr = []; this.quickSort(); //将原数组中的第一项放入新数组 var newArr = [array[0]]; // 我们来循环比较 for(var i = 1; i < array.length; i++){ //如果新数组中的最后一项与array[i]不想等,那么我们就把它加入新数组。 if(array[i] !== newArr[newArr.length - 1]){ newArr.push(array[i]); } } return newArr; }; 我们就简单的介绍这三种去重方法,其实有关于去重的实现有很多种,如果大家想要继续学习有关去重的一些内容,我这里给大家贴上几篇不错的文章。这里就不再多说。 1、【 js 算法 】这么全的数组去重,你怕不怕? 2、也谈JavaScript数组去重 3、js数组去重 当然,有关数组去重的文章远不止这些,只是个人觉得这些内容还不错。本文中的代码也是借鉴于此。那么本文到这里也就差不多结束了,下面会和大家一起学习一下算法模式(递归、动态规划等)。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
上一篇,我们讲述了一些简单的排序算法,其实说到底,在前端的职业生涯中,不涉及node、不涉及后台的情况下,我目前还真的没想到有哪些地方可以用到这些数据结构和算法,但是我在前面的文章也说过了。或许你用不到,但是,真的,如果你想要在前端领域有一个不错的发展。数据结构和算法一定是你的必修课。它不仅仅让你在处理问题的时候可以有一个思维底蕴,更重要的是,在遇到一些奇葩产品的时候,你可以和他PK到底!嗯,到底! 哈哈,开个小玩笑。咱们还是聊点有营养的。上一篇的算法比较简单,主内容就是循环,次内容就是比较。但是,这篇文章所介绍的一些算法,在实现上有些许的复杂,甚至在其中涉及到了一部分数据结构相关的知识,如果你对数据结构还不是十分了解,请移步这里用js来实现那些数据结构—目录。 那么,我们这篇文章要一起来看看另外一些在执行上有着更高效率的算法,比如归并排序,比如快速排序,还有堆排序等等。 1、归并排序 我们先来看看什么是归并排序,以及归并排序是怎么实现的。 归并排序属于一种分治算法。归并排序的思想就是将原始数组切分成一个一个较小的数组,直到每一个数组只有一个元素为止,然后再把一个一个小数组,一点一点的结合成一个最终排序后的数组。其实简单来说,就是先分,再合。归并排序的实现有两种方法,一种是递归,一种是迭代。下面我们只用递归的方式来实现一下代码: //归并排序 // 不多说,你懂的。 this.mergeSort = function () { array = mergeSortRec(array); }; //这个私有函数,其实就是整个归并排序中“分”的部分。我们来看看它是如何“分”的。 var mergeSortRec = function (array) { //存储数组长度。 var length = array.length; //由于是递归,所以当length 为 1 的时候,说明我们分到底了。直接返回这个数组。也就是只有一个元素的数组。 //同时这个判断条件也是递归的终止条件,要记住任何递归操作都必须有终止条件。不然会陷入死循环。 if(length === 1) { return array; } //我们要把原始数组从中一分为二。下面就是一分为二的操作。无需多说。 var mid = Math.floor(length / 2), left = array.slice(0,mid), right = array.slice(mid,length); // 这里,我们先不去管merge函数是做什么的。我们先看递归到最底层。merge的两个参数会变成什么。 // 由于我们又返回了自身,至此递归就形成了。在merge的参数中我们又递归调用了一次自身。 // 那么这次调用我们把left和right两个数组又拆分了一次。直到最后array.length 为 1(归并的最小单位)。 // 那么换句话说,实际上merge函数递归的最底层传入的就是两个只有一个元素的数组。 return merge(mergeSortRec(left),mergeSortRec(right)); }; var merge = function (left,right) { // 我们声明一个最终结果的数组result, // il和ir是用来控制左右两个数组的迭代的变量 var result = [],il = 0,ir = 0; // 这里,我们的最底层是只有两个只有一个元素的数组 /* array[left]和array[right] 第一个while, 循环的条件是两个长度变量是否在合法值内。 */ while(il < left.length && ir < right.length) { // 如果左侧小于右侧,此时的il和ir是相等的都是0。注意这一点 // 我们就把左侧的left[il]放入数组并il++。否则,我们就把right[ir]存入数组result并ir++。此时,il和ir就不相等了。 // 所以,这时候,我们下一次循环判断的条件就是递增的ir或il与没有递增的il或者ir做比较。这样就使得一个数组中的一个元素与另外数组中所有元素都做过比较。 // 希望上面我说明白了想要表达的意思。 if(left[il] < right[ir]) { // 这里,不太容易理解。为什么我们要在result中加入il++而不是il? // 其实这里的意思是,先加如left[il]再il++。 // 不信,你可以把代码改成这个样子 /* result.push(left[il]); il++ */ // 效果是一样一样的。 result.push(left[il++]); } else { result.push(right[ir++]); } }; //这两个循环的目的是把剩余的数组元素(包括left数组和right数组)都存入result数组中。 // 这样我们就行了一个归并后的结果数组,然后进行下一次的归并过程的初始参数。 while(il < left.length) { result.push(left[il++]); }; while(ir < right.length) { result.push(right[ir++]); }; return result; }; //我们用上一章的方法来测试一下归并排序 var arraylist = creatArrayList(5); console.log(arraylist.toString());//5,4,3,2,1 arraylist.mergeSort(); console.log(arraylist.toString());//1,2,3,4,5 其实归并排序的核心思想就是先拆分数组直至分为只有一个元素的数组时,再对其一点一点的进行合并。 2、快速排序 快速排序是我们在组织架构或者实际应用中最为常用的排序算法之一,你也可以把快速排序用在你的项目中。 快速排序的思想和归并排序有些类似,但是快速排序不需要把拆分开的元素装入一个新的数组,而是直接在原数组上进行交换操作。 那么我们来看看快速排序的操作步骤: 首先我们要找到用于与之相比较的“主元”。理论上主元可以为数组中的任意的元素,但是在本文中我们选取数组的中间项作为主元。 然后,我们选择主元左侧和右侧的两部分中的元素分别和主元做比较,直到我们找到了左侧比主元大并且右侧比主元小的元素,那么我们就交换两个元素的位置。直到迭代的左侧指针超过了右侧指针。这样,我们就使比主元小的元素和比主元大的元素分别存在于主元的两侧。 最后,我们再对左右两侧的数组递归重复上面的步骤。直至数组排序结束。 我们来看下代码。 //快速排序 this.quickSort = function () { // 这里传入的参数是原始数组本身和首尾元素的下标。 quick(array,0,array.length - 1); }; var quick = function (array,left,right) { // 这个index是为了帮助我们分离出较小值数组和较大值数组的 var index; // 如果length>1才执行逻辑,因为只有一个元素的数组意味着无需排序。 if(array.length > 1) { // partition返回一个元素的下标。 index = partition(array,left,right); //下面两个判断条件为了区分我们要递归的是较小值数组还是较大值数组。 if(left < index - 1) { quick(array,left,index - 1); }; if(index < right) { quick(array,index,right); }; } }; //我们来看看划分过程的这个方法 var partition = function (array,left,right) { // pivot(主元),也就是我们要与之比较的元素。我们选取中间项作为主元。 // i和j代表着较小值数组和较大值数组当前元素的指针 var pivot = array[Math.floor((right + left) / 2)],i = left,j = right; //要知道最开始的i是0,j是lenght-1。所以i++是往右侧移动指针,j--是往左侧移动指针。 //循环的条件是较小值数组的下标小于较大值数组的下标。 while(i <= j) { // 如果array[i]元素小于主元,向右移动指针。 while(array[i] < pivot) { i++; }; // 如果array[j]元素大于主元,向左移动指针。 while(array[j] > pivot) { j--; }; //上面两个while迭代,当遇到不符合条件的时候就会停下来。 // 而这种不符合条件的情况是array[i]>array[j]。这是要调整的情况。但是此时i仍旧是小于等于j的。要注意,i在这里不可能比j大。 // 所以我们此时交换两个下标对应的元素,并改变i和j的指针。最后返回下标i。 if(i <= j) { swap(array,i,j); i++; j--; }; }; return i; }; var arraylist = creatArrayList(5); console.log(arraylist.toString());//5,4,3,2,1 arraylist.quickSort(); console.log(arraylist.toString());//1,2,3,4,5 3、堆排序 在说堆排序之前,我们得先了解一下什么是堆,堆这种数据结构本质是一个完全二叉树(如果你还不知道什么是树,请看我前面的文章),那么既然堆是一种树,那么有关于树的一些概念都可以用在堆上面,比如深度,比如节点等。要知道,树通常都会有左孩子又孩子节点和父节点的指针,但是在完全二叉树中,这些指针都可以去掉,因为我们可以用一定的规律来直接找到当前节点的关联节点。比如给定某一个结点,假设它的下标为i,那么它的左孩子结点的下标就是2i + 1,右孩子结点的下标就是2i + 2,它的父结点为(i−1)/2。这样,我们就把可以省略去这些指针,直接将堆中的结点存储在数组中了。 在了解了什么是堆之后,我们看看堆排序是怎么操作的。我们直接从代码中看比较具体: //堆排序 // 这里你需要对树数据结构有一定的了解和认识。如果你对树结构还不是十分了解,请看我前面有关于树结构的相关章节。 // 或者,你可以直接用下面的代码来解决问题,当然,你需要做一些细微的改动。 this.heapSort = function () { // 把数组长度存入一个变量 var heapSize = array.length; // 把该数组“堆”化。 buildHeap(array); //迭代递减heapSize的长度,交换数组中下标为0和当前的heapSize重新使变动的堆“合理化”。这里的合理化是指让交换了元素位置的数组重新生成符合堆原理的一个新数组。 while(heapSize > 1) { heapSize--; swap(array,0,heapSize); heapify(array,heapSize,0); }; }; //生成堆函数,我们的i为数组中间值并且递减i的循环为heapify函数传入。 var buildHeap = function (array) { var heapSize = array.length; for(var i = Math.floor(array.length / 2);i >= 0; i--) { heapify(array,heapSize,i); }; }; //“堆”化函数。 var heapify = function (array,heapSize,i) { //我们声明左节点,右节点,以及父节点(也就是largest)的变量 var left = i * 2 + 1, right = i * 2 + 2, largest = i; //这两个判断是为了知道在当前轮中的父节点是谁。 if(left < heapSize && array[left] > array[largest]) { largest = left; }; if(right < heapSize && array[right] > array[largest]) { largest = right; }; //如果largest变了,我们就需要交换两个值得位置。并且重新调用heapify。 if(largest !== i) { swap(array,i,largest); heapify(array,heapSize,largest); }; }; var arraylist = creatArrayList(125); console.log(arraylist.toString());//5,4,3,2,1 arraylist.heapSort(); console.log(arraylist.toString());//1,2,3,4,5 堆排序的概念其实并不难理解,唯一需要注意的就是堆数据结构的概念,希望我说清楚了,如果你觉得我对于堆的讲解并不详细,首先你可以百度,其次你可以谷歌,再次你还可以去搜维基百科。实在不行,你去这里看看https://zhuanlan.zhihu.com/p/25820535。这是一篇有关于java相关的系列教程之一。当然,就算你不懂java,相信你也一样可以看懂,真的,我没开玩笑。 我在纠结要不要画张图还是就这样结束,因为画一个完整的流程图真的很花时间......我抽根烟考虑下......要不大家去买本书看吧。。。。嗯....一个不错的建议。 下面我们以数组[3,5,1,6,4,7,2]作为图例的基本数据: 图片要结合代码一起看,不然看不懂的噢.......还有,这个图你最好放大了看,不然累眼睛。。。 最后,其实有关于排序算法还有很多,比如计数排序,桶排序,基数排序等等等等等。排序算法远不止如此。但是本系列不会介绍这么多的算法,如果你想要更深入的去了解其它算法的内容,可以自行查找相关的资料。 好了,终于要结束了,希望本文的内容能带给你一点点的收获。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
排序,我想大家一定经历过或者正在经历着。或许你不懂算法,对排序算法一无所知,但是你一定用过一些第三方库的api来一键排序,那么,在你享受便捷的同时,你是否想过它的底层是如何实现的?这样的算法实现方式是不是最好的?还有没有其它的可能性来实现更快速的排序?那么,希望这一篇文章过后。对于排序算法,你不会再觉得陌生和迷惑。 这篇文章会介绍一些简单常用的排序算法,比如我们耳熟能详的冒泡排序,以及选择排序、插入排序、归并排序等等等等。当然,你一旦学会了这些算法在js中的实现方式,其实你也就弄懂了这种算法。就算以后要用其它语言来实现这些算法,也不过就是一些语言特性上的差别罢了。 我们会专门写一个数组类,并在其中加入各种排序算法。那么,我们先开始搭一个简单的架子。 function ArrayList() { var array = []; this.insert = function (item) { array.push(item); }; this.toString = function() { return array.join(); }; } 我们构建一个数组类,并且只有一个insert和toString方法,以便我们输入数组元素和打印数组元素。 下面我们为这个数组类添加各种排序方法。我们先从最简单的开始。 1、冒泡排序 冒泡排序十分简单,就是比较数组中任何两个相邻的元素,如果第一个比第二个大,那么就交换两个元素的位置。这样,较大值得元素就会一点一点移动到正确的位置,就像气泡升至表面一样。我们先来看一下代码。 //交换数组中两个相邻元素的方法,传入的是相关的数组,以及相邻两个元素的下标。 var swap = function (array,index1,index2) { // 这里,是最“普通”的方式,通过一个中间量来存储index1元素,因为要把index1的值设置为index2,然后再把index2的值设置为刚才存储index1的aux变量。 // 希望我上面说的足够清楚。 var aux = array[index1]; array[index1] = array[index2]; array[index2] = aux; // 但是我们可以有更为简便的方式来做到替换两个数组元素位置的方式。 // 一个是下面的ES6新增的方法。数组的解构赋值。不多说,大家可以自行去查看。 //[array[index1],array[index2]] = [array[index2],array[index1]]; // 另外一个方法是利用数组的splice方法,删除从index1开始的两个元素,并且在删除的位置插入index2,和index1以达到替换元素的目的。 // 如果你对数组方法还不是很清楚,请看这里用js来实现那些数据结构02(数组篇02-数组方法) //array.splice(index1,2,array[index2],array[index1]); }; // 这是最简单,当然也是最慢的排序方法,我们需要两层循环来判断值得大小,以此来一步一步确定每一个值得位置。 // 这里我要刨根问底一下,为什么外层循环(i)循环的是数组元素的长度,而内层(j)是比数组长度少1的循环次数? // 因为这是可以保证每两个元素都进行过比较的最小的循环次数,不信你把两次循环的次数增加(length +100000);来试一下,发现结果仍旧是我们想要的。(当然,这更耗费时间。) // 但是你把次数减得更少就不行了,排序的结果就不对了(其实这里可以合理的减少内层循环的次数,后面说)。你还可以这么理解,外层循环控制我们有多少个数需要比较,内层循环去具体的操作两个数的比较。 this.bubbleSort = function () { var length = array.length; for(var i = 0; i < length; i++) { //i=0,1,2,3,4...length-1; //console.log(i,"i"); for(var j = 0; j < length-1;j++) {//j = 0,1,2,3,4...length - 1 - 1; //console.log(j,"j"); if(array[j] > array[j+1]){ swap(array,j,j+1) } } } }; //我们来写一个简单的方法生成一个未排序的arraylist。//这个方法是在构造函数外的。你不用也可以。只是为了方便生成一个数组罢了。 function creatArrayList (size) { var array = new ArrayList(); for(var i = size; i > 0; i--) { array.insert(i); } return array; } var arraylist = creatArrayList(5); console.log(arraylist.toString());//5,4,3,2,1 arraylist.bubbleSort(); console.log(arraylist.toString());//1,2,3,4,5 上面的解释我相信已经够详细了,我们下面接着看看是否可以改进一下这垃圾的耗时间的效率低的冒泡排序,让我们在简单实现的基础上提高一点点性能? //咱们看看modifiedBubbleSort和bubbleSort的区别,唯一不同的地方就在于内层循环的时候在for循环的第二个条件中多减了一个i。这么做的用意是什么呢? // 我们一点一点来捋一下这点点代码。假设我们的数组是【5,4,3,2,1】;当i = 0的时候(第一次外循环),我们拿5去依次和4,3,2,1来比较,最后数组渐变成了【4,3,2,1,5】; // 那么此时,5就是最大的,当i=1的时候(第二次外循环)。此时我们的内循环就不再需要去拿当前的j去与5(也就是数组中确定了的最大的)比较。 // 外层每循环一次,内层中的每个元素就相互交换了一下位置(如果符合条件的话),最终每一次内层循环完毕,都会确定一个当前轮数最大的值。 // 那么既然我们已经知道最大的值是什么,就无需在后面的循环轮数中再去和已经确定了位置的值去做比较了。这样就可以提高一点我们的执行效率。 // 这里再多句嘴,当i = 0时,j = 0,j < length - 1 - 0;当i = 1时,j = 0,j < length - 1 - 1;(要理解这句话) this.modifiedBubbleSort = function () { var length = array.length; for(var i = 0; i < length; i++) { for(var j = 0; j < length - 1 - i;j++) { if(array[j] > array[j+1]) { swap(array,j,j+1); } } } }; 改进后的冒泡排序我们也学会了,但是也就只能这样了。没办法再进一步的进行优化和效率的提升。冒泡排序,是最基础的,最不推荐的排序方式。因为它的时间复杂度是O(n2),大O表示法,我们会在后面的内容中详细的讲解什么是大O表示法。这里可以暂时的理解为,两层循环形成了i*j的计算结果,也就是length*length的循环总次数。也就是n2.。(事实上就是这么回事)。如果是三层循环,那很有可能就是O(n3)的复杂度了。 2、选择排序 选择排序的思想是,找到数据结构中最小值并将其放在第一位,接着找到第二小的值,放到第二位,以此类推。(当然你也可以找最大的值放在最后一位)。我们还是来看代码。 //其实选择排序也并不复杂,我们来一起看一看。 this.selectionSort = function () { //首先,我们声明一个存储数组长度的变量,以及一个存储当前最小值的变量,哦不对,存储当前最小值的对应下标的变量。 var length = array.length,indexMin; // 在外层循环,我们循环次数是整个数组的长度。 for(var i = 0; i < length - 1; i++) { // 这里,最开始的循环我们也不知道谁是最小的,所以我们就把第一个值得下标作为最小值得下标。 indexMin = i; // 内层循环,我们会依次比较当前最小值(也就是indexMin对应的值)和数组中的其它值。 // 这里,为什么是j=i呢? // 我们每一次外层循环,都会确定一个最小值并把最小值放置在相应的位置(从下标0开始每次外循环都会往后加1)。 // 那么我们就不需要再去比较开始循环过比较过的下标了,所以我们每次外层循环过后,内层循环都从i的位置开始就可以了。 // 有点类似于modifiedBubbleSort的j<length - 1 - i; for(var j = i; j < length; j++) { // 这样,我们就可以判断出最小值是什么,如果indexMin所对应的值比j所对应的值还要大,说明最小值对应的下标应该为j。 if(array[indexMin] > array[j]) { indexMin = j; } } // 在外层循环一次结束后,如果i(最开始我们确定的最小值)不等于indexMin。这说明i并不是最小值。我们就交换两个值的位置。 // 如果相等,说明当前的indexMin就是最小值,无需交换位置。 console.log(i,indexMin) if(i !== indexMin) { swap(array,i,indexMin) } } }; 不过选择排序的复杂度也是O(n2),效率并不是很好。那么我们继续往下看。 3、插入排序 插入排序,怎么说呢....就是假设数组中的第一个元素是已经排序过的了(不假设不行,或者说它就是排过序的了,因为就一个元素嘛),那么我们和第二个元素比较,第二个元素是应该在第一个元素之前,还是在原位置不动呢?也就是说,第一个元素和第二个元素比较大小来确定这两个元素的位置。那么这样,单纯就数组元素的前两项来说,他们是排好序的了。那么我们再以第三个元素跟前两个元素进行比较,来确定第三个元素应该插入在前两项元素的什么位置。 简单来说,我们可以认为在排序数组中有一个已排序的子数组,我们依次用后面的元素与子数组中的元素进行比较,以确定后面的元素应该插入到子数组的什么位置。最后,我们就会得到一个完全排序的数组了。 我们继续来看代码。 this.insertionSort = function () { //j和temp分别用来存储当前的下标和当前下标所对应的值。 var length = array.length,j,temp; // 为什么i要从1开始呢?因为index为0的元素我们视为已经排序过的了。 for(var i = 1; i < length; i++) { // 我们用j来存储当前要比较的值的下标也就是i,因为0上的元素已经排序过了(我们默认这样做的)。 j = i; // 同样,我们要比较当前索引的值得大小来确定是否需要换位,所以我们还要有一个临时存储当前下标所对应的值的变量。 temp = array[i]; // 如果j>0说明是数组中的元素, // 并且,如果当前j(i)的前一个元素(j-1)比当前的变量大,那么就把j(i)的值设置为j-1的值,也就是把j(i)的位置往后挪了一个。 // 直到array[j - 1] > temp为false为止。为什么不说j>0这个条件呢?因为这是保证数组正确对比的一个防护层,当然,它是很重要的。 // 这里有一个十分必要且需要注意的point,就是我们的变量j的值的问题。 // 我们在循环直到条件不成立跳出循环的时候,此时的j就是需要把临时存储array[i]的值(也就是temp)插入的地方。 // 因为,我们在while循环中,每次循环都会用j--来使下标的位置一点点往前移动,直到条件不成立后,我们得到了一个应该插入temp的位置的j。 while (j > 0 && array[j - 1] > temp) { array[j] = array[j - 1]; j--; } array[j] = temp; } }; 上面的代码就是插入排序了,其中要注意的点就在while循环这一块,需要花费一点心思去理解一下。我在简单的啰嗦两句,其实在代码中,我们声明了j和temp变量用来存储当前的下标和其所对应的值。那么temp的作用是我们可以在找到该插入的位置的时候,可以知道应该插入的值是什么,而j的存在的意义是确定这个位置是哪里。所以,我们在while循环中会拿递减的j所对应的值的前一个去和temp比较,如果条件成立,那么我就往后挪,直到挪不动为止(while循环的条件不匹配),我们就找到了应该插入temp位置的j。这时候在该位置上插入temp就可以了。 那么,简单的排序方法就介绍到这里了,下一章我们来看看复杂一点的,但是效率更高的排序算法。 其实我在写这篇文章的时候,一直在纠结要不要去画画图,让大家可以更容易的去理解这些代码和这些排序算法的实现方式,但是,我在网上搜了一下。一大堆!!所以,我就觉得算了吧。不过文中我已经附上了相关的链接地址,其中有对该算法的概念的更为详细的解释。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
首先,有一点要声明,下面所有文章的所有内容的代码,都不是我一个人独立完成的,它们来自于一本叫做《学习JavaScript数据结构和算法》(第二版),人民邮电出版社出版的这本书。github代码地址是https://github.com/loiane/javascript-datastructures-algorithms。 先说下我个人对这本书的看法吧。对于数据结构的介绍不够深入和详细,对于那些计算机专业的前端从业者来说,十分的鸡肋。而对于那些非计算机专业,又完全没接触过数据结构是什么东西的前端coder来说。其中的讲解又十分的不具体,很多内容都是寥寥数笔一带而过,完全没有代表性,对于思路的讲解并不明了,只是罗列了每一步骤,但是实际上单独的列举出每一步做了什么并没什么用,我自己看代码就可以了,用你说什么。当然,我也在网上找到了很多类似于本系列的文章,大多数都是基于此书的代码,但是要么就是完全复制的代码,解释十分的少或者几乎没有,要么就是整体不清不楚,让人不知所以然。 所以,我就想在学习的过程中把自己的理解,自己对于这个数据结构的思路和每一行代码在上下文中所起到的作用以及相互之间的影响等等写出来。希望大家在学习数据结构的时候可以走的更容易一点。在学习的过程中,有不理解的地方,书中讲的不易理解的地方,都是自己画图,自己去找资料,然后再回来结合代码,给出一个“自认为”还不错的解释。 终于,完成了有关用js来实现数据结构的所有内容,前后大概花了一个多月将近40小时的时间,一共16篇文章。有最基本的js数组的详细讲解。也有非线性的散列表、树和图。其实对于用js来实现数据结构,个人感觉更多要学习的是那种数据结构的思想。一旦你理解了这个数据结构的思想,那么自然,实现代码也不过就是时间的问题了。本系列所有的内容,都是我一个字一个字打出来的,包括书中的代码和自己加上的注释,当然,其中概念性的问题一部分来自于此书,书中解释不清楚的我都在文中附上了资料的链接。 那么,如果各位老爷在阅读的时候有不理解或者觉得不清楚的地方,还希望可以留言提出。互相学习。 噢,对,还是要说一下我觉得这个系列的重点吧。重点在后6篇和前3篇(Array,hashMap,Tree和Graph)。而对于前面的栈,队列等。是你学习后面的基础,因为在树,图等数据结构的实现中,都用到了前面的数据结构。 所以,大家如果想要学习数据结构。那么个人觉得这系列文章是个不错的开始。或许你会问,我看完这系列文章会学到什么?我真的就懂了数据结构么?当然没有。就单单拿一个图来说,足够写一本厚厚的书了。所以,你学完这个系列,最多只是打开了数据结构的大门,迈出了你的右脚(或者左脚)向门里面的世界探了探,连走进这个大门都不算。 最后说一下本系列的使用方法和大概的阅读时间吧。 使用方法:首先,你把代码复制到本地,去掉所有的注释,然后就着文章,看一遍代码。然后自己打一遍代码,有不明白的地方再去看注释。这是本系列的服用说明。 所花时间:每天一个小时,大概需要花上一个月。当然,不仅仅是读一遍,而是跟着文章中的代码和注释完整的过了一遍自己的脑子。 好了,不罗嗦了。就到这里吧。也算是给自己的交上了一份6分的答卷。 下面是目录: 数据结构部分(已完结): 1、用js来实现那些数据结构01(数组篇01-数组的增删) 2、 用js来实现那些数据结构02(数组篇02-数组方法) 3、用js来实现那些数据结构03(数组篇03-排序及多维数组) 4、用js来实现那些数据结构04(栈01-栈的实现) 5、用js来实现那些数据结构05(栈02-栈的应用) 6、用js来实现那些数据结构06(队列) 7、用js来实现那些数据结构07(链表01-链表的实现) 8、用js来实现那些数据结构08(链表02-双向链表) 9、用js来实现那些数据结构09(集合01-集合的实现) 10、用js来实现那些数据结构10(集合02-集合的操作) 11、用js来实现那些数据结构11(字典) 12、用js来实现那些数据结构12(散列表) 13、用js来实现那些数据结构13(树01-二叉搜索树的实现) 14、用js来实现那些数据结构14(树02-AVL树) 15、用js来实现那些数据结构15(图01) 16、用js来实现那些数据结构16(图02-图的遍历) 附:算法部分也基本上全部完成了,其中比如搜索、排序算法, 比如函数式编程这几篇文章是极力推荐大家去仔细阅读一下的,因为如果你对这方面没有深入的学习研究过,那么你阅读学习下面的文章之后,会有不小的收货。当然这里有一篇文章我个人的见解是你可以简单阅读,但是不懂也没什么关系,因为它对于算法已经算是较为深入的部分了,就是js算法初窥05(算法模式02-动态规划与贪心算法)这一篇,如果你本身就有算法基础,那么就当我没说。 算法部分: 1、js算法初窥01(排序算法01-冒泡、选择、插入) 2、js算法初窥02(排序算法02-归并、快速以及堆排序) 3、js算法初窥03(简单搜索及去重算法) 4、js算法初窥04(算法模式01-递归) 5、js算法初窥05(算法模式02-动态规划与贪心算法) 6、js算法初窥06(算法模式03-函数式编程) 最后,谢谢!一只想要飞得更高的小菜鸟
上一篇文章我们简单介绍了一下什么是图,以及用JS来实现一个可以添加顶点和边的图。按照惯例,任何数据结构都不可或缺的一个point就是遍历。也就是获取到数据结构中的所有元素。那么图当然也不例外。这篇文章我们就来看看如何遍历以及用js来实现图的遍历。 首先,有两种算法可以对图进行遍历:广度优先搜索(BFS)和深度优先搜索(DFS)。图的遍历可以用来寻找特定的顶点,可以寻找两个顶点之间有哪些路径,检查图是否是联通的,也可以检查图是否含有环等等。 在开始代码之前,我们需要了解一下图遍历的思想,也就是说,我们要知道如何去遍历一个图,知道了图遍历的方法方式,距离实现代码也就不远了。 图遍历的思想是: 1、必须追踪每个第一次访问的节点,并且追踪有哪些节点还没有被完全探索。对于BFS和DFS两种算法,都需要明确给出第一个被访问的顶点。 2、完全探索一个顶点,要求我们查看该顶点的每一条边。对于每一条边所链接的没有被访问过的顶点,将其标注为被发现的,并将其加入到待访问顶点列表中。 那么,总结一下上面的两句话,首先,我们在遍历一个图的时候,需要指定第一个被访问的顶点是什么(也就是我们要在方法中传入第一个顶点的值)。然后呢.....我们需要知道三个状态: 一个是还未被访问的,也就是我还不知道有这么个顶点,也不知道它的边都去向哪里。 另外一个是已经访问过但未被探索过,就是说,我知道有这个顶点,但是我不知道它的边都去向哪里,连接着哪些顶点。 最后一个是访问过并且完全探索过。也就是我访问过该顶点,也探索过它有哪些边,它的边连接哪些顶点。 那么,我们就会在构造函数中用三种颜色来代表上面的三种状态,分别是白色(未被访问),灰色(已经访问过但未被探索过)和黑色(访问过并且完全探索过); 还有另外一个要注意的地方,BFS和DFS在算法上其实基本上是一样的,但是有一个明显的不同——待访问顶点的数据结构。BFS用队列来存储待访问顶点的列表,DFS用栈来存储待访问顶点的列表。 好了,下面我们来上代码。(这里不会贴上所有的代码,只会贴上有关BFS和DFS的相关代码。) 如果你看到了这里,但是并不觉得自己可以耐心的把下面的代码看完,那么你看到这里就可以 结束所有有关于用js来实现数据结构的内容了。如果你还是想继续往下学习,那么希望你一定可以耐心看完整。 //引入前面章节学过的栈和队列,因为我们后面会用到。 function Stack () {}; function Queue() {}; function Graph() { var vertices = []; var adjList = new Map(); //添加顶点的方法。 this.addVertices = function (v) {}; this.addEdge = function (v,w) {}; this.toString = function () {}; //初始化图中各顶点的状态(颜色)的私有方法,并返回该状态数组。 var initializeColor = function () { var color = []; for (var i = 0; i < vertices.length; i++) { color[vertices[i]] = 'white'; } return color; }; //简单的广度优先搜索算法,传入参数v是图中的某一个顶点,从此顶点开始探索整个图。 this.bfs = function (v,callback) { //为color状态数组赋值,初始化一个队列 var color = initializeColor(),queue = new Queue(); //将我们传入的顶点v入队。 queue.enqueue(v); // 如果队列非空,也就是说队列中始终有已发现但是未探索的顶点,那么执行逻辑。 while(!queue.isEmpty()) { // 队列遵循先进先出的原则,所以我们声明一个变量来暂时保存队列中的第一个顶点元素。 var u = queue.dequeue(); // adjList是我们的邻接表,从邻接表中拿到所有u的邻接顶点。 neighbors = adjList.get(u); //并把状态数组中的u的状态设置未已发现但是未完全探索的灰色状态。 color[u] = 'grey'; //我们循环当前的u的所有的邻接顶点,并循环访问每一个邻接顶点并改变它的状态为灰色。 for(var i = 0; i < neighbors.length; i++) { var w = neighbors[i]; if (color[w] === "white") { color[w] = 'grey'; //入队每一个w,这样while循环会在队列中没有任何元素,也就是完全访问所有顶点的时候结束。 queue.enqueue(w); } } // 完全访问后设置color状态。 color[u] = 'black'; // 如果存在回调函数,那么就执行回掉函数。 if(callback) { callback(u); } } }; //改进后计算最短路径的BFS // 其实这里改进后的BFS并没有什么特别复杂,只是在原有的bfs的基础上,增加了一些需要计算和储存的状态值。 // 也就是我们在函数结束后所返回的 this.BFS = function (v) { //d是你传入的顶点v距离每一个顶点的距离(这里的距离仅为边的数量) //pred就是当前顶点沿着路径找到的前一个顶点是什么。没有就是null var color = initializeColor(),queue = new Queue(),d = [],pred = []; //我们把v入队。 queue.enqueue(v); //初始化距离和前置点数组。一个都为0,一个都为null,无需解释。 for(var i = 0; i < vertices.length; i++) { d[vertices[i]] = 0; pred[vertices[i]] = null; } while(!queue.isEmpty()) { var u = queue.dequeue(); neighbors = adjList.get(u); color[u] = 'grey'; for(var i = 0; i < neighbors.length; i++) { var w = neighbors[i]; if (color[w] === "white") { color[w] = 'grey'; // 到这里都和bfs方法是一样的,只是多了下面这两个。 // 这里容易让人迷惑的是w和u分别是啥?弄清楚了其实也就没啥了。 // u是队列中出列的一个顶点,也就是通过u来对照邻接表找到所有的w。 // 那么因为是d(距离,初始为0)。所以我们只要在d的数组中w的值设为比u大1也就是d[u] + 1就可以了 d[w] = d[u] + 1; // 而这个就不用说了,理解了上面的,这个自然就很好懂了。 pred[w] = u; // 这里可能大家会问,循环不会重复加入么?不会! // 注意看这里if (color[w] === "white")这句,如果是white状态才会执行后面的逻辑, // 而进入逻辑后,状态就随之改变了,不会再次访问到访问过的顶点。 queue.enqueue(w); } } color[u] = 'black'; } return { distances:d, predecessors:pred } }; //深度优先搜索 // 这个没啥东西大家自己看一下就可以了 this.dfs = function (callback) { var color = initializeColor(); for(var i = 0; i < vertices.length; i++) { if(color[vertices[i]] === 'white') { // 这里调用我们的私有方法 dfsVisit(vertices[i],color,callback); } } }; //深度优先搜索私有方法 // 从dfs中传入的三个参数 var dfsVisit = function (u,color,callback) { // 改变u的颜色状态 color[u] = 'grey'; if(callback) {callback(u);} // 获取所有u的邻接顶点 var neighbors = adjList.get(u); // 循环 for(var i = 0; i < neighbors.length; i++) { //w为u的每一个邻接顶点的变量 var w = neighbors[i]; // 如果是白色的我们就递归调用dfsVisit if(color[w] === 'white') { dfsVisit(w,color,callback); } } color[u] = 'black'; }; //改进后的DFS,其实也就是加入了更多的概念和要记录的值 this.DFS = function () { // d,发现一个顶点所用的时间。f,完全探索一个顶点所用的时间,p前溯点。 var color = initializeColor(),d = [],f = [], p = []; // 初始化时间为0; time = 0; //初始化所有需要记录的对象的值/ for(var i = 0; i < vertices.length; i++) { f[vertices[i]] = 0; d[vertices[i]] = 0; p[vertices[i]] = null; } for (var i = 0; i < vertices.length; i++) { if(color[vertices[i]] === 'white') { DFSVisit(vertices[i],color,d,f,p); } } return { discovery:d, finished:f, predecessors:p } }; //注意这里我们为什么要在外层定义时间变量,而不是作为参数传递进DFSVisit。 //因为作为参数传递在每次递归的时候time无法保持一个稳定变化的记录。 var time = 0; //这里个人觉得也没什么好说的了,如果你看不懂,希望你可以数据结构系列的第一篇看起。 var DFSVisit = function (u,color,d,f,p) { console.log('discovered--' + u); color[u] = 'grey'; d[u] = ++time; var neighbors = adjList.get(u); for (var i = 0; i < neighbors.length; i++) { var w = neighbors[i]; if (color[w] === 'white') { p[w] = u; DFSVisit(w,color,d,f,p); } } color[u] = 'black'; f[u] = ++time; console.log('explored--' + u); }; } 上面是有关于BFS和DFS的代码及注释。希望大家可以认真耐心的看完。下面我们来看看简单的最短路径算法和拓扑排序。 1、最短路径算法 //最短路径,也就是说我们在地图上,想要找到两个点之间的最短距离(我们经常会用地图软件来搜索此地与彼地的路径)。 //那么下面我们就以连接两个顶点之间的边的数量的多少,来计算一下各自的路径,从而得到一个最短路径。 // 我们通过改进后的BFS算法,可以得到下面这样的数据,各个顶点距离初始顶点的距离以及前溯点 var shortestPathA = graph.BFS(verticesArray[0]); console.log(shortestPathA) /* distances: [A: 0, B: 1, C: 1, D: 1, E: 2, F:2,G:2,H:2,I:3], predecessors: [A: null, B: "A", C: "A", D: "A", E: "B", F:"B",G:"C",H:"D",I:"E"] */ //我们选择数组中的第一个元素为开始的顶点。 var fromVertex = verticesArray[0]; for(var i = 1; i < verticesArray.length;i++) { // 到达的定点不定 var toVertex = verticesArray[i]; //声明路径为一个初始化的栈。 path = new Stack(); //嘿嘿,这个循环比较有趣了,通常大家都会用var i= 0; i < xxx;i++这种。 //但是这里这么用是几个意思?首先大家要知道for循环中两个“;”所分割的三个语句都是什么意思。 //语句 1 在循环(代码块)开始前执行,语句 2 定义运行循环(代码块)的条件,语句 3 在循环(代码块)已被执行之后执行 //所以我们怎么写都是可以的!!当然你要符合你想要的逻辑 //后面就不说了,没啥好说的。 for(var v = toVertex;v!== fromVertex;v = shortestPathA.predecessors[v]) { path.push(v); } path.push(fromVertex); var s = path.pop(); while(!path.isEmpty()) { s += '-' + path.pop(); } console.log(s) } /* A-B A-C A-D A-B-E A-B-F A-C-G A-D-H A-B-E-I */ 2、拓扑排序 拓扑排序,想了想,还是有必要给大家解释一下概念再开始代码,不然真的容易一脸懵逼。 大家先来看张图: 那,这是一个什么东西呢?这是一个有向图,因为边是有方向的,这个图没有环,意味着这是一个无环图。所以这个图可以称之为有向无环图。那么有向无环图可以做什么呢?我记得前面某一篇文章说过,所有的实例都有其所面对的要解决的实际问题。而有向无环图可以视作某一个序列的待执行的任务,该任务不是可跳跃的。比如一个产品上线,需要产品经理定需求,画流程图,再到UI出效果图标注图再到开发再到测试再到改bug再到上线。就是这个意思。 那么我们上面所形容的产品上线的整个流程就成为拓扑排序。拓扑排序只能应用于DAG(有向无环图)。 那么我们看下代码。 //重新声明一个图并所有的顶点加入图中。 var DFSGraph = new Graph(); var DFSarray = ["a","b","c","d","e","f"]; for (var i = 0; i < DFSarray.length; i++) { DFSGraph.addVertices(DFSarray[i]); } //我们为图加上边。 DFSGraph.addEdge("a","c"); DFSGraph.addEdge("a","d"); DFSGraph.addEdge("b","d"); DFSGraph.addEdge("b","e"); DFSGraph.addEdge("c","f"); DFSGraph.addEdge("f","e"); var result = DFSGraph.DFS(); console.log(result); //大家自己去看看打印的结果是什么。 那么到这里,有关于图的一部分内容基本上就都讲解完毕了。可能大家觉得我有些偷懒,注释写的没有以前那么详细了啊。这是因为我觉得很多的内容前面都已经很详细的说明过了。同样的思路实在是没必要翻来覆去的说来说去。所以反而到后面一些复杂的数据结构并没有前面解释的那么详细。但是我觉得如果你一路看下来,这点东西绝壁难不倒你。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
其实在上一篇介绍树结构的时候,已经有了一些算法的相关内容介入。而在图这种数据结构下,会有更多有关图的算法,比如广度优先搜索,深度优先搜索最短路径算法等等。这是我们要介绍的最后一个数据结构。同时也是本系列最为复杂的一个。那么我们先来简单介绍一下,什么是图? 一、图的概念 简单说,图就是网络结构的抽象模型,图是一组由边连接的节点(或顶点)。任何二元关系都可以用图来表示。比如我们的地图,地铁线路图等。都是图的实际应用。 接着我们看看图的一些相关概念和术语。 一个图G = (V,E)由以下元素组成: V:一组顶点。 E:一组边,链接V中的顶点。 在继续之前我们先来上张图,继续我们的看图说话。 请看上图,我们来解释一些概念。 1、由一条边连接在一起的顶点称为相邻顶点。比如上图中的A和B,A和C,A和D都是相邻的,但是A和E不是相邻的。 2、一个顶点的度取决于其相邻顶点的数量。也就是说,有多少个顶点与其相连,那么它的度就是多少。比如A的度是3,D的度就是4。 3、路径是顶点V1,V2.....Vn的一个连续序列,其中Vi和Vi+1是相邻的。比如上图中的ACDG,ABEI都是一个路径。 4、简单路径要求不包含重复的定点。比如ADG就是一条简单路径。 5、除去最后一个顶点(因为它和第一个顶点时相同的),环也是一个简单路径,比如ADCA。 6、如果图中不存在环。则该图是无环的。 7、如果图中每两个顶点间都存在路径,则该图是连通的。 为了便于对比,我又花了一张图。 跟第一幅图几乎是一样的,只不过我们在路径上加了点东西。 8、图可以是有向的(边有方向)或者是无向的(边没有方向)。比如上图我们在边上加了方向就变成了有向图。 9、如果在图中的每两个顶点间在双向上都存在路径,则该图是强连通的。比如上图中我们可以说C和D是强连通的。A和B不是强连通的。但是上图并不是一个强连通图。因为上图并不是每两个点都有双向的路径。 10、图还可以是未加权的或是加权的。上图边上加的数字就是加权值。(加权的意思可以简单理解为CSS选择器中的那种权重。) 二、图的表示方法 我们可以表示图的方法有很多。根据我们要解决问题的类型和图的类型。我们可以选择不同的方法来表示图。下面我们会简单介绍两种表示图的方法。 1、邻接矩阵。每一个节点都和一个整数相关联,该整数将作为数组的索引。我们用一个二维数组来表示各个顶点之间的连接情况。比如索引为i的节点和索引为j的节点相邻,则表示为arrya[i][j]=1。否则arrya[i][j]=0。 邻接矩阵看起来就是这样子的。要注意我们上面的邻接矩阵只是表示两个顶点是否相邻。我们还需要一个数组来存储所有的顶点。 但是邻接矩阵会有一些性能问题。比如我们会用很多的空间来表示一些根本就不存在的边。比如上图所有的0。再比如我们想要找到A顶点的相邻顶点,即使A顶点只有一个相邻顶点。我们也必须遍历整个数组才能找到。 2、邻接表,鉴于以上的问题。我们在本篇中所使用的图的表示方法就是邻接表。邻接表由图中每个顶点的相邻顶点列表所组成。我们可以用数组,链表,map或者hashMap来实现邻接表。 邻接表看起来就像是上图这样。 那么我们知道了图的一些基本概念和我们要使用的图的表示方法。下面我们先来完成我们Graph类的架子。 function Map () { //......其他各种方法,详见前面的字典部分 } //代码很简单,但是还是要解释一下。 function Graph() { //vertices数组存放我们图中所有的顶点 var vertices = []; //adjList存放我们的邻接表。adjList会使用顶点来作为键,邻接顶点列表作为值 var adjList = new Map(); //添加顶点的方法。 this.addVertices = function (v) { //存放到顶点数组中 vertices.push(v); //生成一个还没有邻接顶点列表的Map,因为这时我们已经有顶点了,所以要生成以待使用 adjList.set(v,[]); } //这里有个小细节我们需要注意,哦对,这是为图添加边的方法。要注意的是,实际上,在代码中,我们是没有一个东西(变量或者其他什么)来代表边的。 //我们为两个顶点之间添加一个边实际上只是为两个顶点的邻接表中加入彼此。这样就代表了这两个顶点是相邻的。 this.addEdge = function (v,w) { //而这里我们所实现的图是无向图,所以需要给两个顶点所对应的邻接表加入彼此。 //而如果是有向图的话,只需要根据方向添加一个就可以了。 adjList.get(v).push(w); adjList.get(w).push(v); } // 为了方便观察,我们再实现一个toString方法 // 没啥好说的,双重循环遍历两个数组。 this.toString = function () { var s = ""; for(var i = 0;i < vertices.length;i++) { s += vertices[i] + "->"; var neighbors = adjList.get(vertices[i]); for(var j = 0; j < neighbors.length; j++) { s += neighbors[j] + ' '; } s += '\n'; } return s; } } //我们来试一下 var graph = new Graph(); var verticesArray = ['A','B','C','D','E','F','G','H','I']; for(var i = 0; i < verticesArray.length; i++) { graph.addVertices(verticesArray[i]); } graph.addEdge('A','B'); graph.addEdge('A','C'); graph.addEdge('A','D'); graph.addEdge('C','D'); graph.addEdge('C','G'); graph.addEdge('D','G'); graph.addEdge('D','H'); graph.addEdge('B','E'); graph.addEdge('B','F'); graph.addEdge('E','I'); console.log(graph.toString()); /* A->B C D B->A E F C->A D G D->A C G H E->B I F->B G->C D H->D I->E */ 那么我们就实现了Graph类中最简单的部分——如何添加顶点和边。大家会不会觉得有点简单了。嘿嘿.....有趣的还在后面,别急...... 好了,那么到这里这篇文章就结束了。下一篇文章我们再继续学习图的遍历。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢! 一只想要飞得更高的小菜鸟
在使用二叉搜索树的时候会出现 一个问题,就是树的一条分支会有很多层,而其他的分支却只有几层,就像下面这样: 如果数据量够大,那么我们在某条边上进行增删改查的操作时,就会消耗大量的时间。我们花费精力去构造一个可以提高效率的结构,反而事与愿违。这不是我们想要的。所以,我们需要另外一种树来解决这样的问题,那就是自平衡二叉搜索树--Adelson-Velskii-Landi(AVL)。什么意思呢?就是说这种树的任何一个节点左右两侧子树的高度之差最多为1。也就是说这种树会在添加或删除节点时尽量试着成为一棵完全树。 自平衡二叉搜索树和二叉搜索树的实现几乎是一模一样的,唯一的区别就在于每次在插入或者删除节点的时候,我们需要检测它的平衡因子(因为只有再插入或者删除的时候才有可能会影响到树的平衡性)。如果有需要,那么就将其逻辑应用于树的自平衡。 首先我们需要知道这个平衡因子是如何计算的。平衡因子的计算是来自于每个节点的右子树高度(hr)和左子树高度(hl)的差值, 该值应为0,1,-1.如果不是这三个值,那么说明需要平衡该AVL树。这就是平衡因子的简单计算方式。什么意思呢? 我们以上图为例,根节点11的平衡因子6 - 3 = 3。左侧子节点7的平衡因子是2 - 2 = 0;右侧子节点18的平衡因子就是5 - 2 = 3;节点70的平衡因子是0,要记住所有的叶节点(外部节点)的平衡因子都是0。因为叶节点没有子节点。还有一点一定要注意。我们所计算的平衡因子,是该节点的左右子树的高度。 我们学会了如何去计算平衡因子,那么我们下面进行一项及其重要的仪式......噢,sorry。是及其重要的知识——旋转。 在开始讲解旋转之前,我们先来点开胃菜。看看我们插入子节点后,导致该树不平衡的可能的情况有哪些。我会画几个图,以便大家看得仔细透彻。 首先,我们以上面这张图(截取前面树结构的一部分)作为初始的树,这棵树绝对一定必然是平衡的。大家都没意见吧。那么RR,LL,RL,LR是什么意思呢?那么我们继续往下看。 第一种情况:RR。 我们在18的右侧子节点再加一个节点20,右侧是要加入比父节点大的值的。 在我们加入了一个节点20之后,我们发现这棵树还是平衡的!唉?不对啊,跟我想要的结果好像不太一样。我再加一个节点试试? 嗯......现在绝壁不平衡了。那么我们来看看怎么回事。在加入节点21(19)后,11节点左侧子树的深度是1,而右侧子树的深度是1,2,3。是3没错。那么1-3等于-2。嗯,十以内加减法应该不会算错。我们确定在节点18后面加入了两个右侧(R)节点后,这棵树就不平衡了。而现在有一个重要的问题。就是是哪一棵子树导致这棵树不平衡的呢?是在我们加入节点21(19)之后,也就是上图我们用小圈圈诅咒它的那一部分。那么我们可以用一句话来描述,我们在该树右侧子节点的右侧子节点加入了一个右侧子节点(如果加入的是左侧子节点也是一样的)之后,导致了该树的不平衡,所以我们这时候需要去操作也就是旋转右侧的子节点也就是18节点,来使这颗自平衡树来自平衡。换句话说,如果我们加入了一个节点(或者删除了一个节点),导致了我们整颗树的不平衡,那么我们首先要找到最近的不平衡的树来进行调整。 上面RR情况的我分别加入了19,和21两个字节点,要说明一下,这两个子节点是为了更为清晰的告诉大家在root的右节点的右节点下,无论插入的是左节点还是右节点都属于RR的情况。下同。在具体旋转的时候会给大家详细介绍。 换句话说,我们判断在增删节点的时候是否会导致不平衡的情况,由插入节点的前两个父节点来确定!大家要注意噢!很重要! 趁热打铁,上面解释了RR的情况,那么其实下面的LL,LR,RL等情况也是一样的。思路没有任何区别。但是这个时候我想打断大家一下。问大家两个问题。这两个问题的解决会为后面的学习带来极大的便利。 1、在AVL树或者其他树中,是否可以出现重复的值,比如树中已经有了一个11,我还想再加入一个11,是否允许?是否可以? 2、看上图(RR情况图),是否有可能出现除了这四种情况外的其他情况?或者说,节点的平衡因子是否可能出现大于2或者小于-2的情况?(这种情况我们要旋转树超过两次,也就超出了我们这四种情况之外。) OK。希望大家闭上眼睛,想一想你的梦中情人,哦不对。想一想你的答案。 不卖关子了,但是我真的希望大家想一想,因为这很必要也很重要。 好吧,我开始回答第一个问题。其实在前一篇实现的树中是不允许重复的值出现的,我们可以去看一下上一篇的代码,如果相等则会覆盖。那么可能有人会问,我想要这棵树存储重复的值(当然其实这种情况出现的话大多数都是你的设计有问题。。。没有唯一标识了啊......需求还怎么实现)。那么我记得在hashMap篇中有一个解决冲突的办法,是不是可以通过链表来存储key值相同的映射呢?是否还可以使用其他的存储方式?答案比较开放。所以是否可以存放重复的值,看你的实际需求咯。 第二个问题的答案,不可能出现,因为大家一定要记住一个前提,就是我们在插入了一个导致该树不平衡的节点前,该树一定是平衡的。为什么这么说呢?因为我们的AVL树,是自平衡二叉搜索树,如果在插入之前就是不平衡的,那你告诉我你这是啥?赶紧回头看代码,有BUG了亲。 这里希望大家已经解除了心中不少的疑惑,如果还有问题,大家可以继续留言探讨。 那么我们下面继续,把其它几种情况的图示画完。 第二种情况:LL。 第三种情况:LR。 第四种情况:RL。 那么看完上面这几幅图想必大家都了解了在插入节点的时候影响到树的平衡的4种可能性。那么为了面对这4种可能性。我们给出了与之相对应的4种解决不平衡的方法(其实就两种)。那么这里我们就要进入本篇最重要的内容了,旋转。在开始之前,希望大家记住一句话。那就是,什么情况导致的不平衡,那就用相反方向的旋转。什么意思呢,比如是LL导致的不平衡,那么我们就向右旋转。如果是RR导致的不平衡我们就向左,如果是RL,我们就LL再RR。如果是LR,我们就先RR再LL。好了,下面我们来看看究竟是如何旋转的吧。 那么下面就有点意思了。希望大家可以仔细看。 一、RR情况的左旋转 我们还得来看图说话,我尽量把图画的让人容易误解,哦不,容易理解。 本人那个,画图工具用得还不是太熟练,拐歪的曲线没画出来,我拿嘴说吧...... 大家看上图,左旋是以18为轴心整个树的左部分向左旋转,这样就使18变成了根节点,11变成了18的左侧子节点。这样旋转一下,就相当于减少了一层右侧字树的一层深度,从而使整颗树变成了平衡树。那么可能还有下面的这种情况,但其实是一样的。 那么这种情况是要旋转的轴心节点(18),还有左侧子节点,在旋转之后,18的左侧子节点13就会变成11的右侧子节点。其实可以简单的认为是左旋过后被节点11给“挤”过来的。 其实,18的左侧子节点在旋转过后会成为11的右侧子节点还有一个原因,就是,18左侧子节点的值一定是大于11小于18的(旋转之前的图)。为什么自己想。那么在旋转过后,它也一定是大于11的,所以它可以成为11的右侧子节点。 一、LL情况的右旋转 那么LL情况的右旋转就没什么好说的了,跟RR情况是一样的,我们直接上图吧。 这绝壁没问题吧,原理都是一样的。只不过换了一个方向而已。 这样没啥好说的了,对吧。下面我们看看其他地情况。双旋转...... 三、LR情况的左旋(RR)再右旋(LL) 我们还是直接上图,然后再解释,解释完这个RL情况的又不用再啰嗦了。挺好......挺省事,嘿嘿。 其实让人有点懵逼的是名字,我特意加了个括号,希望你别懵逼。 不知道大家看没看懂,总感觉这图不是很友好啊,还有8节点的小瑕疵就不要在意了,反正都是虚线......还有指向节点10的那条线是虚线.....不影响.....嘿嘿。 解释一下,我们需要双旋转的情况下,第一次旋转的是红框部分,也就是说,如果我们需要双旋转,两次旋转的轴心点是不一样的,第一次旋转的轴心是插入节点的父节点,而第二次旋转的轴心是插入节点的祖父节点。大家一定要注意。 那么这里可能会有一个疑问,就是8节点在第一次旋转过后,为什么会成为7节点的右侧子节点。这里十分重要,直接关系到你是否理解了AVL树的旋转。 我们先看第一次旋转,如果插入的是8节点而不是10节点,那么在第一次左旋的时候,节点7会成为节点9的左侧子节点,而这个时候8节点是无处可去的,因为7占了我的位置,这咋整,不能因为一次平衡就删除我这个节点啊,节点8肯定不干,不然你插入我干啥.....哎?感觉有点不对劲.....额咳咳....咱们继续吧....而节点8这个位置一定比9小比7大,所以我们在旋转过后,让它成为7节点的右子节点就可以了。希望我说明白了。 那么这个时候可能还存在7节点有左侧子节点的情况,上面没画,没关系啊,你是7节点的左侧子节点,左旋转过后你还在原来的位置,没人占你的位置,你就不用动了。嗯,就这样.....完毕! 四、RL情况的右旋(LL)再左旋(RR) 其实这里真没啥好说的了,我一点都不解释,大家自己看,看不懂你就从头看! 唉.......说了一大堆,终于可以到最后的代码了,上代码! //这是我们计算当前节点的高度的方法 var heightNode = function (node) { // 如果没有那就为-1 if(node === null) { return -1; } else { // 如果存在执行逻辑 //那么说一下这里我的理解吧,Math.max比较左节点和右节点的大小,返回大的那个值,然后 + 1。 //为什么要返回大的那个值呢?因为如果左节点存在,那么值为0(-1 + 1);并且右节点是不存在的,那么右节点为-1。 //但是此时我们是有高度的,所以我们要选取有高度的那个节点,也就是值大的那一个。 //那为什么要+1呢?因为高度只能为0不能为-1。-1是我们通过相减计算得到的,而不是计算高度得到的。记住这里是计算高度。 console.log(Math.max(heightNode(node.left),heightNode(node.right)) + 1) return Math.max(heightNode(node.left),heightNode(node.right)) + 1; } } //RR:向左的单旋转 var rotationRR = function (node) { var tmp = node.right; node.right = tmp.left; tmp.left = node; return tmp; } //LL:向右的单旋转 var rotationLL = function (node) { var tmp = node.left; node.left = tmp.right; tmp.right = node; return tmp; } //LR:向右得到双旋转 var rotationLR = function (node) { node.left = rotationRR(node.left); return rotationLL(node); } //RL:向左的双旋转 var rotationRL = function (node) { node.right = rorarionLL(node.right); return rorarionRR(node); } var balanceInsertNode = function (node,element) { // 如果node的位置没有值,那么直接加入就好了。 if(node === null) { node = newNode(element); // 如果假如的值是小于当前节点的话,说明我们要加在当前节点的左侧。 } else if(element < node.key) { node.left = insertNode(node.left,element); // 那么下面就要判断是否是null,如果是null,那么没问题,直接加上就好了。 if(node.left !== null) { // 如果不是,我们就要计算node的左侧高度减去右侧高度是否大于1,如果是,说明不平衡,需要来调用平衡方法来平衡。 if((heightNode(node.left) - heightNode(node.right)) > 1) { // 如果当前插入的节点的值小于node.left的值,说明是LL的情况,我们需要右旋。否则的话我们就需要先左旋,再右旋。 if(element < node.left.key) { node = rorarionLL(node); } else { node = rorarionLR(node); } } } } else if(element > node.key) { node.right = insertNode(node.right,element); if(node.right !== null) { if((heightNode(node.right) - heightNode(node.left)) > 1) { if(element > node.right.key) { node = rorarionRR(node); } else { node = rorarionRL(node); } } } } } 代码中多了一个balanceInsertNode方法,这个方法是需要替换我们前面写好的insertNode方法的,这样写是为了让大家更好的对比下。这些代码不像以前那样,写了一大堆的注释用来解释。其实要说的很多,都在前面的图和语言描述中说过了。所以大家看这个代码的时候。有不明白的地方,对照着前面的逻辑一点一点看,肯定就看明白了。比如rotationLL和rotationRR内部的替换以及为什么要这样替换,都在前面说过了。所以就不再在代码中啰嗦了。 这一篇文章有点长,也花了我一点心思才完成。很重要,如果你想要对树有一个不错的了解,这些必须要会。我尽可能的用我理解的思路给大家讲解,如果有什么不清楚的地方,大家可以留言讨论。 哦对了,本来还要跟大家说说其他树的,但是想了想也没什么必要,给大家一个链接,大家可以自行去做一些简单的了解,比如红黑树,堆积树,还有B树等等等等。种类很多。要想都讲完大概几十篇都不够,希望这两篇树结构的文章可以抛砖引玉。让大家提起对数据结构的兴趣。 大家可以看一下这个了解https://zh.wikipedia.org/wiki/AVL%E6%A0%91,滑动到页底,你就能看到其他的树结构了。 好了,终于,自平衡二叉搜索树到这里基本就结束了。下一部分会讲解最后一种也是最复杂的一种非线性数据结构——图。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
前一篇文章我们学会了第一个非顺序数据结构hashMap,那么这一篇我们来学学树,包括树的概念和一些相关的术语以及二叉搜索树的实现。唉?为什么不是树的实现,不是二叉树的实现。偏偏是二叉搜索树的实现?嗯,别急。我们一点一点循序渐进。 我们先来了解一下什么是树。树是一种非线性数据结构,直观的看,它是数据元素(在树中称为节点)按分支关系组织起来的结构,很像自然界中的树那样。在现实生活中,最常见的例子就是家谱或者公司的组织架构图。就像是这样: 那么我们还要知道树的一些相关术语,比较多,大家要仔细阅读,不然后面就完全懵逼了。我们先来看一下树这种数据结构的图示。 这是我在百度上找到的一张图,还算清晰明了。这就是树数据结构了。 首先,一个树结构,存在一系列的父子关系,除了顶部的第一个节点以外,每一个结点都有一个父节点以及零个或多个子节点。位于树顶部的节点叫做根节点。看上图,根节点就是A。树中的每一个元素(A,B,C,D,E,F这些)都叫做节点。节点又分为内部节点和外部节点。至少有一个子节点的节点称为内部节点(如上图的A,B,C,E)。没有子节点的节点称为外部节点或叶节点(如上图的D,F,G)。 另一个概念是子树,定义是这样的:子树由节点和它的后代构成。也就是说,把树中的一部分剖离出来,它仍旧可以看作是是一颗单独的树,那么就可以称之为子树。 节点还有一个属性,叫做度,也可以叫做深度,节点的深度取决于它有多少个祖先节点。如上图的H,深度就是3,因为它有E,B,A三个祖先节点。E的深度就是2。 除了节点的深度,一棵树还可以被分解层级。根节点是第0层,根节点的子节点是第1层。以此类推。 那么我们对树的概念有了简单的了解,那么什么是二叉树呢?其实不论是二叉树,还是二叉搜索树,又或者是其它什么树,只不过是在树的基础上加上一个限制条件以便更高效率的操作。 在二叉树中,一个节点的子节点最多只能有两个节点,一个左节点,一个右节点,二叉树只能是左右分叉的,所以叫做二叉树。 那二叉搜索树(BST)呢?不过是在二叉树的基础上,又加了一个插入元素的条件,就是,只允许你在左侧节点存储比父节点小的值,在右侧节点存储大于或等于父节点的值。这里要注意的一点是,二叉树的子节点最多只能有两个节点,也就说不一定非要有两个节点,只有一个左节点,或者只有一个右节点都是可以的可能的允许的。 那么似乎我们不去实现树,也不去实现二叉树,而是直接实现二叉搜索树的原因就出来了。只要我们学会了二叉搜索树,自然树和二叉树的实现也就会了。 来,我们来看图说话,在开始实现二叉搜索树之前,先给大家放张图(图片百度的),以便大家更好的理解。 既然图有了,我们就来看看如何实现一个BinarySearchTree。首先,要告诉大家的是,在链表中,我们称每一个节点本身称作节点,但是在树中,我们叫它键。唉?我好像看到了链表?树跟链表有毛关系?嗯。。。确实没关系,但是我们要实现树的方式却跟链表有关系。我们之前学习过双向链表,双向链表中有prev和next,分别指向当前节点的上一个和下一个节点。树的实现我们也要借用类似的方式,只不过是一个指向左侧子节点,另一个指向右侧子节点。 那么我们都要实现哪些方法呢? 1、insert(key):像树中插入一个新的键。 2、search(key):在树中查找一个键。 3、inOrderTraverse:中序遍历。 4、preOrderTraverse:先序遍历。 5、postOrderTraverse:后序遍历。 6、min:返回树中最小的值/键。 7、max:返回树中最大的值/键。 8、remove(key):从树中移除某个键。 我们知道了基本的实现方式和BinarySearchTree需要的方法。我们开始吧。 // 这个二叉搜索树的实现根本其实并没有多复杂,复杂的其实是概念。 // 但是,我会尽量给大家解释清楚。不至于让大家一脸懵逼。 function BinarySearchTree() { // 首先这里,声明一个node节点,也就是我们树结构中所代表的每一个节点,包括根节点在内。 var Node = function(key) { // 节点的key,也就是键,大家要记住一个事情,我们所有的数据结构,都是为了应对合理适当的场景。 // 而无论何种数据结构,都需要检索,我记得前面说过,也就是增删改查这种万年不变的操作。 // 而这里的键(也就是key),是为了依照一定的规则来设置键,以便我们更快速的检索到,提取其值。 // 当然,我们实现的这个二叉搜索树貌似并没有value,但是我们可以自己去设置一个键值对的映射关系。 // 既然能检索到key,也就可以找到其对应的值。当然,这里就不都说了。 // 比如说这里,我们就可以给Node私有构造函数加一个this.value = value。来形成一个映射关系。 this.key = key; // left和right,也就是指向当前节点的左右子节点的指针。 this.left = null; this.right = null; }; //初始化一个二叉搜索树,声明一个私有变量root代表根节点。 var root = null; // 这是插入节点的私有属性,我们会在insert方法中直接调用。那么我们先去看insert方法。 // 其实这里也不复杂,但是用到了递归,如果大家对递归不太了解,可以去百度搜一下。后面的文章我也会写一些算法的相关内容。 // 我们回到这里,insertNode有两个参数,在insert方法调用的时候我们传入了root,newNode。以便我们从根节点去查找。 var insertNode = function (node,newNode) { // 这里就分为了两种情况,其实后面的方法也是这样,新插入的key和node(第一次执行的时候是root)相对比。 // 如果新插入的key小于node的key,我们要插入到left里,如果是大于等于node的key,就插入到right。这是我们二叉搜索树的规则。 if(newNode.key < node.key) { // 那么这里,如果(或者说是‘直到’)node.left是空,也就是没有元素,那么就插入到node.left中。 // 否则,再调用一下这个函数自身(也就是递归了,这就是为什么上面也可以说是‘直到’,递归必须有递归终止的条件,不然会陷入死循环)。 // 那么下面的else情况也是同理。 if (node.left === null) { node.left = newNode; } else { insertNode(node.left,newNode); } } else { if (node.right === null) { node.right = newNode; } else { insertNode(node.right,newNode); } } } // 中序遍历,首先需要说一下什么是中序遍历。 // 中序遍历是一种以上行顺序访问BST所有节点的遍历方式(也就是从小到大的顺序访问所有节点),BST就是binary search tree。 // 那么该方法有两个参数,一个是node,一个是回调函数(这个回调函数,在本文的应用是下面的console每一个节点的值,当然,你也可以用回调函数做一些羞羞的事)。 var inOrderTraverseNode = function (node,callback) { // 我们要递归使用该方法,前面说了,必须有一个终止回调的条件。这里就是如果节点为空,我们就认为元素遍历完成,停止递归。 if(node !== null) { // 这里,递归调用相同的函数来访问左侧子节点,然后对这个节点进行一些操作,最后访问右侧子节点。 // 到这里,其实中序遍历可以说是,左(左侧子节点),中(该节点),右(右侧子节点)的访问方式。 inOrderTraverseNode(node.left,callback); callback(node.key); inOrderTraverseNode(node.right,callback); } } // 先序遍历,其实我们看代码就可以知道了,先序遍历就是中,左,右。也就是先访问节点本身,再访问左侧然后是右侧子节点。 var preOrderTraverseNode = function (node,callback) { if(node !== null) { callback(node.key); preOrderTraverseNode(node.left,callback); preOrderTraverseNode(node.right,callback); } } // 那么后序遍历呢?额......可想而知,也就是左右中的方式,先访问节点的后代节点,再访问节点本身。 var postOrderTraverseNode = function (node,callback) { if(node !== null) { postOrderTraverseNode(node.left,callback); postOrderTraverseNode(node.right,callback); callback(node.key); } } // 搜索树中的最小值,嗯......我们根据前面了解的内容,猜猜看最小的值是哪一个?如果你说不知道,请从头再来! // 树中最小的值,就是在树的最底层最左侧的节点。那么最大值就是右侧的节点了。 // 为什么会这样呢?如果你还是不知道。请从头再......再来! var minNode = function (node) { // 如果该节点是否是合法值,是->继续,不是,返回null。 if(node) { // 这里就是循环判断node.left是否存在,知道不存在的时候(说明已经得到最左侧的子节点了)就直接返回上一次赋值的node.key。 while(node && node.left !== null) { node = node.left; } return node.key; } return null; } // 同上 var maxNode = function (node) { if(node) { while(node && node.right !== null) { node = node.right; } return node.key; } return null; } // 其实这里,我真的不想说......但是我还是要'磨叽'一下...... // 这里第一个的node参数,在search中传入的是root,因为要从root开始执行逻辑。 // 还有,后面就几乎所有的传入node参数的私有方法,传入的都是root,因为要从root开始。 var searchNode = function (node,key) { // 如果是null了,返回false if(node === null) { return false; } // 这里,其实也就是根据不同的值得大小来判断递归时所需要传入的参数是什么,如果即不大也不小。bingo,说明找到了。 if(key < node.key) { return searchNode(node.left,key); } else if(key > node.key) { return searchNode(node.right,key); } else { return true; } } // 这个方法稍微复杂并且有意思一点,我们详细来说说。 var removeNode = function (node,key) { // 这个判断没什么好说的了,如果是null说明在树中没有这个键,直接返回null就可以了。 if(node === null) { return null; } // 那么这里会有三种情况的判断,如果要找的key小于当前的node.key,就递归调用函数,沿着树的左边一直找下去。 // 那么如果要找的key大于当前的node.key,就沿着树的右边一直找下去。直到找到为止。 if(key < node.key) { node.left = removeNode(node.left,key); return node; } else if(key > node.key) { node.right = removeNode(node.right,key); return node; // 这里就是找到了匹配的key的时候所处理的逻辑了 } else { // 第一种情况就是该节点是没有左右子节点的,我们直接赋值null来移除就可以了。 // 虽然该节点没有子节点,但是有一个父节点,我们需要通过返回null,来使对应的父节点的指针指向null。 // 要记得我们在remove方法中有一个root = removeNode(root,key);赋值语句,可以到下面查看。 // 就是为了让我们父节点接收到更改的指针。 if(node.left === null && node.right === null) { node = null; return node; } // 这里是第二种情况,移除有一个左侧节点或者右侧节点的节点。 // 我们只要跳过这个节点,直接将父节点的指针指向子节点就可以了。 if(node.left === null) { node = node.right; return node; } else if(node.right === null) { node = node.left; return node; } // 最后是第三种情况,稍微复杂些,其实也就是我们要做的操作多一些。 // 首先,我们在找到了需要移除的节点后,需要找到它右边子树种最小的节点。(要移除节点的继承者,也就是说在移除了匹配的节点后,这个值会替换被移除的节点) // 这里findMinNode跟min方法是一样的,只不过返回值稍有不同 var aux = findMinNode(node.right); // 然后用aux去更新要移除节点的值,这个时候,我们已经改变了要移除节点的值,也就相应的移除了该节点。 node.key = aux.key; // 但是这个时候就有两个相同的键了,所以我们要移除aux也就是node.right指向的节点。 node.right = removeNode(node.right,aux.key); // 返回更新后的引用。 return node; // 最后,要提醒大家一个需要注意的地方,移除一个树中的节点,并没有移除该节点下的所有子树或者子节点,这是一个比较容易让人迷惑的误区。 // 比如说,我有一棵下面这样的树 /* A B C D E F G */ // 我想要移除C,并没有把F,G也同时移除,只是单纯的移除了C这个节点,所以我们需要依照二叉搜索树的规则,找到一个合理的值代替这个位置(也就是F)。 // 那么我们用F替换C,并把C移除,更改对应的指针。也就完成了第三种情况的移除操作。 } } var findMinNode = function (node) { while(node && node.left !== null) { node = node.left; } return node; } // 其实这个方法很简单,一看就明白了。 // 如果root是null,说明是一个空树,我们直接让newNode为root就可以了,如果不是,我们再调用insertNode那个私有方法。 this.insert = function (key) { var newNode = new Node(key); if(root === null) { root = newNode; } else { insertNode(root,newNode); } } this.inOrderTraverse = function (callback) { inOrderTraverseNode(root,callback); } this.preOrderTraverse = function (callback) { preOrderTraverseNode(root,callback); } this.postOrderTraverse = function (callback) { postOrderTraverseNode(root,callback); } this.min = function () { return minNode(root); } this.max = function () { return maxNode(root); } this.search = function (key) { return searchNode(root,key); } this.remove = function (key) { root = removeNode(root,key); } } //这个就是callback了 function printNode (value) { console.log(value); } var tree = new BinarySearchTree(); tree.insert(11); tree.insert(7); tree.insert(15); tree.insert(5); tree.insert(3); tree.insert(9); tree.insert(8); tree.insert(10); tree.insert(13); tree.insert(12); tree.insert(14); tree.insert(20); tree.insert(18); tree.insert(25); tree.insert(6); tree.inOrderTraverse(printNode);//3,5,6,7,8,9,10,11,12,13,14,15,18,20,25 tree.remove(15); console.log("--------------") tree.inOrderTraverse(printNode);//3,5,6,7,8,9,10,11,12,13,14,18,20,25 tree.insert(100); console.log("--------------"); tree.inOrderTraverse(printNode);//3,5,6,7,8,9,10,11,12,13,14,18,20,25,100 console.log(tree.min(),"min");//3,“min” console.log(tree.max(),"max");//100,"max" console.log(tree.search(66))//false console.log(tree.search(8))//true console.log("--------------"); tree.preOrderTraverse(printNode);//11,7,5,3,6,9,8,10,18,13,12,14,20,25,100 console.log("--------------"); tree.postOrderTraverse(printNode);//3,6,5,8,10,9,7,12,14,13,100,25,20,18,11 那么我们二叉搜索树就实现完成了。其实如果大家看过前面的文章,这里的BinarySearchTree的实现其实并没有多复杂,而需要注意的是remove一个节点时在对不同的情况的处理方法。说到底,二叉搜索树也就是在插入元素也就是节点的时候要按照必要的规则,所以我们在对树进行操作的时候依照这种规则就可以了。 本来到这里就应该结束了,但是我觉得有必要给大家上几幅图片,解释解释我们上面代码中每一步的执行,在BinarySearchTree是如何操作的。 上图展示了我们依次插入各个数字的时候,二叉搜索树会根据数值的大小来安排它的位置,之后我们做了一个移除15这个节点的操作,那么在移除之后它看起来就是这样的: 具体的解释在代码的注释中已经有了,这里就不再重复的去啰嗦了。 好了,到这里我们已经基本完成了二叉搜索树的基本实现,那么下一篇文章我们会简单的介绍一下其它类型的树结构。比如自平衡二叉搜索树,红黑树,堆积树等。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
上一篇写了如何实现简单的Map结构,因为东西太少了不让上首页。好吧。。。 这一篇文章说一下散列表hashMap的实现。那么为什么要使用hashMap?hashMap又有什么优势呢?hashMap是如何检索数据的?我们一点一点的来解答。 在我们学习一门编程语言的时候,最开始学习的部分就是循环遍历。那么为什么要遍历呢?因为我们需要拿到具体的值,数组中我们要遍历数组获取所有的元素才能定位到我们想要的元素。对象也是一样,我们同样要遍历所有的对象元素来获取我们想要的指定的元素。那么无论是array也好,object也好,栈还是队列还是列表或者集合(我们前面学过的所有数据结构)都需要遍历。不然我们根本拿不到我们想要操作的具体的元素。但是这样就有一个问题,那就是效率。如果我们的数据有成百万上千万的数据。我们每一次循环遍历都会消耗大量的时间,用户体验可以说几乎没有。(当然,前端几乎不会遇到这种情况,因为大数据量的情况都通过分页来转化了)。 那么,有没有一种快速有效的定位我们想要的元素的数据结构呢?答案就是hashMap。当然,应该也有其它更高效的数据处理方式,但是我暂时不知道啊。。。。 那么hashMap是如何存取元素的呢?首先,hashMap在存储元素的时候,会通过lose lose散列函数来设置key,这样我们就无需遍历整个数据结构,就可以快速的定位到该元素的具体位置,从而获取到具体的值。 什么是lose lose散列函数呢?其实lose lose散列函数就是简单的把每个key中的所有字母的ASCII码值相加,生成一个数字,作为散列表的key。当然,这种方法并不是很好,会生成很多相同的散列值。下面会具体的讲解如何解决,以及一种更好的散列函数djb2。 那么我们开始实现我们的hashMap: // 这里我们没在重复的去写clear,size等其他的方法,因为跟前面实在是没啥区别。 function HashMap() { // 我们使用数组来存储元素 var list = []; //转换散列值得loselose散列函数。 var loseloseHashCode = function (key) { var hash = 0; // 遍历字符串key的长度,注意,字符串也是可以通过length来获取每一个字节的。 for(var i = 0; i < key.length; i++) { hash += key.charCodeAt(i) } //对hash取余,这是为了得到一个比较小的hash值, //但是这里取余的对象又不能太大,要注意 return hash % 37; } //通过loselose散列函数直接在计算出来的位置放入对应的值。 this.put = function (key,value) { var position = loseloseHashCode(key); console.log(position + "-" + key); list[position] = value; } //同样的,我们想要得到一个值,只要通过散列函数计算出位置就可以直接拿到,无需循环 this.get = function (key) { return list[loseloseHashCode(key)]; } //这里要注意一下,我们的散列表是松散结构,也就是说散列表内的元素并不是每一个下标index都一定是有值, //比如我存储两个元素,一个计算出散列值是14,一个是20,那么其余的位置仍旧是存在的,我们不能删除它,因为一旦删除,我们存储元素的位置也会改变。 //所以这里要移除一个元素,只要为其赋值为undefined就可以了。 this.remove = function (key) { list[loseloseHashCode(key)] = undefined; } this.print = function () { for(var i = 0; i < list.length; i++) { // 大家可以把这里的判断去掉,看看到底是不是松散的数组结构。 if(list[i] !== undefined) { console.log(i + ":" + list[i]); } } } } //那么我们来测试一下我们的hashMap var hash = new HashMap(); hash.put("Gandalf",'www.gandalf.com'); hash.put("John",'www.john.com'); hash.put("Tyrion",'www.tyrion.com'); //因为我们在put代码中加了一个console以便我们更好的理解代码,我们看一下输出 // 19-Gandalf // 29-John // 16-Tyrion console.log(hash.get('John'));//www.john.com console.log(hash.get("Zaking"));//undefined //那么我们来移除一个元素John hash.remove("John"); console.log(hash.get("John"));//undefined 那么我们就实现并且简单测试了一下我们自定义的hashMap,发现还不错哦。但是元素太少,没有代表性。我们再多测试几个数据看看会如何? var conflictHash = new HashMap(); conflictHash.put("Gandalf",'www.Gandalf.com');//19-Gandalf conflictHash.put("John",'www.John.com');//29-John conflictHash.put("Tyrion",'www.Tyrion.com');//16-Tyrion conflictHash.put("Aaron",'www.Aaron.com');//16-Aaron conflictHash.put("Donnie",'www.Donnie.com');//13-Donnie conflictHash.put("Ana",'www.Ana.com');//13-Ana conflictHash.put("Jonathan",'www.Jonathan.com');//5-Jonathan conflictHash.put("Jamie",'www.Jamie.com');//5-Jamie conflictHash.put("Sue",'www.Sue.com');//5-Sue conflictHash.put("Mindy",'www.Mindy.com');//32-Mindy conflictHash.put("Paul",'www.Paul.com');//32-Paul conflictHash.put("Nathan",'www.Nathan.com');//10-Nathan conflictHash.print(); /* 5:www.Sue.com 10:www.Nathan.com 13:www.Ana.com 16:www.Aaron.com 19:www.Gandalf.com 29:www.John.com 32:www.Paul.com */ 我们发现后来的把前面相同散列值得元素给替换了。那么之前的元素也就随之丢失了,这绝不是我们想要看到的样子。这才十几个元素就有这么多相同的,如果数据量极大那还了得。。。这啥用没有啊。。。所以,我们需要解决这样的问题,我们这里介绍两种解决这种冲突的方法。分离链接和线性探查。 1、分离链接 分离链接,其实核心就是为散列表的每一个位置创建一个链表,并将元素存储在里面。它可以说是解决冲突的最简单的方法,但是,它占用了额外的存储空间。之前的例子,如果用分离链接来解决冲突的话,那么看起来就是这个样子。 那么我们就需要重写hashMap,我们来看看分离链接下的hashMap是如何实现的。由于我们要重写hashMap类中的方法,所以我们重新构建一个新的类:SeparateHashMap。 function LinkedList() {//...链表方法} // 创建分离链接法下的hashMap。 function SeparateHashMap () { var list = []; //loselose散列函数。 var loseloseHashCode = function (key) { var hash = 0; for(var i = 0; i < key.length; i++) { hash += key.charCodeAt(i) } return hash % 37; } //这里为什么要创建一个新的用来存储键值对的构造函数? //首先我们要知道的一点是,在分离链接下,我们元素所存储的位置实际上是在链表里面。 //而一旦在该散列位置下的链表中有多个值,我们仍旧需要通过key去找链表中所对应的元素。 //换句话说,分离链接下的存储方式是,首先通过key来计算散列值,然后把对应的key和value也就是ValuePair存入linkedList。 //这就是valuePair的作用了。 var ValuePair = function (key,value) { this.key = key; this.value = value; this.toString = function () { return "[" + this.key + "-" + this.value + "]"; } } //同样的,我们通过loselose散列函数计算出对应key的散列值。 this.put = function (key,value) { var position = loseloseHashCode(key); //这里如果该位置为undefined,说明这个位置没有链表,那么我们就新建一个链表。 if(list[position] == undefined) { list[position] = new LinkedList(); } //新建之后呢,我们就通过linkedList类的append方法把valuePair加入进去。 //那么如果上面的判断是false,也就是有了链表,直接跳过上面的判断执行加入操作就好了。 list[position].append(new ValuePair(key,value)); } this.get = function (key) { var position = loseloseHashCode(key); //链表的操作前面相应的链表文章已经写的很清楚了。这里就尽量简单说清 //如果这个位置不是undefined,那么说明存在链表 if(list[position] !== undefined) { //我们要拿到current,也就是链表中的第一个元素进行链表中的遍历。 var current = list[position].getHead(); //如果current.next不为null说明还有下一个 while(current.next) { //如果要查找的key是当前链表元素的key,就返回该链表节点的value。 //这里要注意一下。current.element = ValuePair噢! if(current.element.key === key) { return current.element.value; } current = current.next; } //那么这里刚开始让我有些疑惑。为啥还要单独判断一下? //我们回头看一下,我们while循环的条件是current.next。没current什么事啊...对了。 //所以,这里我们还要单独判断一下是不是current。 //总结一下,这段get方法的代码运行方式是从第一个元素的下一个开始遍历,如果到最后还没找到,就看看是不是第一个,如果第一个也不是,那就返回undefined。没找到想要得到元素。 if(current.element.key === key) { return current.element.value; } } return undefined; } //这个remove方法就不说了。跟get方法一模一样,get方法是在找到对应的值的时候返回该值的value,而remove方法是在找到该值的时候,重新赋值为undefined,从而移除它。 this.remove = function (key) { var position = loseloseHashCode(key); if(list[position] !== undefined) { var current = list[position].getHead(); while(current.next) { if(current.element.key === key) { list[position].remove(current.element); if(list[position].isEmpty()) { list[position] = undefined; } return true; } current = current.next; } if(current.element.key === key) { list[position].remove(current.element); if(list[position].isEmpty()) { list[position] = undefined; } return true; } } return false; }; this.print = function () { for(var i = 0; i < list.length; i++) { // 大家可以把这里的判断去掉,看看到底是不是松散的数组结构。 if(list[i] !== undefined) { console.log(i + ":" + list[i]); } } } } var separateHash = new SeparateHashMap(); separateHash.put("Gandalf",'www.Gandalf.com');//19-Gandalf separateHash.put("John",'www.John.com');//29-John separateHash.put("Tyrion",'www.Tyrion.com');//16-Tyrion separateHash.put("Aaron",'www.Aaron.com');//16-Aaron separateHash.put("Donnie",'www.Donnie.com');//13-Donnie separateHash.put("Ana",'www.Ana.com');//13-Ana separateHash.put("Jonathan",'www.Jonathan.com');//5-Jonathan separateHash.put("Jamie",'www.Jamie.com');//5-Jamie separateHash.put("Sue",'www.Sue.com');//5-Sue separateHash.put("Mindy",'www.Mindy.com');//32-Mindy separateHash.put("Paul",'www.Paul.com');//32-Paul separateHash.put("Nathan",'www.Nathan.com');//10-Nathan separateHash.print(); /* 5:[Jonathan-www.Jonathan.com]n[Jamie-www.Jamie.com]n[Sue-www.Sue.com] 10:[Nathan-www.Nathan.com] 13:[Donnie-www.Donnie.com]n[Ana-www.Ana.com] 16:[Tyrion-www.Tyrion.com]n[Aaron-www.Aaron.com] 19:[Gandalf-www.Gandalf.com] 29:[John-www.John.com] 32:[Mindy-www.Mindy.com]n[Paul-www.Paul.com] */ console.log(separateHash.get("Paul")); /* www.Paul.com */ console.log(separateHash.remove("Jonathan"));//true separateHash.print(); /* 5:[Jamie-www.Jamie.com]n[Sue-www.Sue.com] 10:[Nathan-www.Nathan.com] 13:[Donnie-www.Donnie.com]n[Ana-www.Ana.com] 16:[Tyrion-www.Tyrion.com]n[Aaron-www.Aaron.com] 19:[Gandalf-www.Gandalf.com] 29:[John-www.John.com] 32:[Mindy-www.Mindy.com]n[Paul-www.Paul.com] */ 其实,分离链接法,是在每一个散列值对应的位置上新建了一个链表以供重复的值可以存储,我们需要通过key分别在hashMap和linkedList中查找值,而linkedList中的查找仍旧是遍历。如果数据量很大,其实仍旧会耗费一些时间。但是当然,肯定要比数组等这样需要遍历整个数据结构的方式要效率的多。 下面我们来看看线性探查法。 2、线性探查 什么是线性探查呢?其实就是在hashMap中发生冲突的时候,将散列函数计算出的散列值+1,如果+1还是有冲突那么就+2。直到没有冲突为止。 其实分离链接和线性探查两种方法,多少有点时间换空间的味道。 我们还是来看代码。 function LinearHashMap () { var list = []; var loseloseHashCode = function (key) { var hash = 0; for(var i = 0; i < key.length; i++) { hash += key.charCodeAt(i) } return hash % 37; } var ValuePair = function (key,value) { this.key = key; this.value = value; this.toString = function () { return "[" + this.key + "-" + this.value + "]"; } } this.put = function (key,value) { var position = loseloseHashCode(key); //同样的,若是没有值。就把该值存入 if(list[position] == undefined) { list[position] = new ValuePair(key,value); } else { // 如果有值,那么久循环到没有值为止。 var index = ++position; while(list[index] != undefined) { index++ } list[index] = new ValuePair(key,value); } } this.get = function (key) { var position = loseloseHashCode(key); if(list[position] !== undefined) { if(list[position].key === key) { return list[position].value; } else { var index = ++position; while(list[index] === undefined || list[index].key !== key) { index ++; } if(list[index] .key === key) { return list[index].value } } } return undefined; } this.remove = function (key) { var position = loseloseHashCode(key); if(list[position] !== undefined) { if(list[position].key === key) { list[index] = undefined; } else { var index = ++position; while(list[index] === undefined || list[index].key !== key) { index ++; } if(list[index] .key === key) { list[index] = undefined; } } } return undefined; }; this.print = function () { for(var i = 0; i < list.length; i++) { // 大家可以把这里的判断去掉,看看到底是不是松散的数组结构。 if(list[i] !== undefined) { console.log(i + ":" + list[i]); } } } } var linearHash = new LinearHashMap(); linearHash.put("Gandalf",'www.Gandalf.com');//19-Gandalf linearHash.put("John",'www.John.com');//29-John linearHash.put("Tyrion",'www.Tyrion.com');//16-Tyrion linearHash.put("Aaron",'www.Aaron.com');//16-Aaron linearHash.put("Donnie",'www.Donnie.com');//13-Donnie linearHash.put("Ana",'www.Ana.com');//13-Ana linearHash.put("Jonathan",'www.Jonathan.com');//5-Jonathan linearHash.put("Jamie",'www.Jamie.com');//5-Jamie linearHash.put("Sue",'www.Sue.com');//5-Sue linearHash.put("Mindy",'www.Mindy.com');//32-Mindy linearHash.put("Paul",'www.Paul.com');//32-Paul linearHash.put("Nathan",'www.Nathan.com');//10-Nathan linearHash.print(); console.log(linearHash.get("Paul")); console.log(linearHash.remove("Mindy")); linearHash.print(); LinearHashMap与SeparateHashMap在方法上有着相似的实现。这里就不再浪费篇幅的去解释了,但是大家仍旧要注意其中的细节。比如说在位置的判断上的不同之处。 那么HashMap对于冲突的解决方法这里就介绍这两种。其实还有很多方法可以解决冲突,但是我觉得最好的办法就是让冲突的可能性变小。当然,无论是使用什么方法,冲突都是有可能存在的。 那么如何让冲突的可能性变小呢?很简单,就是让计算出的散列值尽可能的不重复。下面介绍一种比loselose散列函数更好一些的散列函数djb2。 var djb2HashCode = function(key) { var hash = 5831; for(var i = 0; i < key.length; i++) { hash = hash * 33 + key.charCodeAt(i); } return hash % 1013; } 大家可以把最开始实现的HashMap的loselose散列函数换成djb2。再去添加元素测试一下是否冲突的可能性变小了。 djb2散列函数中,首先用一个hash变量存储一个质数(只能被1和自身整除的数)。将hash与33相乘并加上当前迭代道德ASCII码值相加。最后对1013取余。就得到了我们想要的散列值。 到这里,hashMap就介绍完了。希望大家可以认真的去阅读查看。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
我们这篇文章来说说Map这种数据结构如何用js来实现,其实它和集合(Set)极为类似,只不过Map是【键,值】的形式存储元素,通过键来查询值,Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以是任何引用类型的数据,但key不能重复,而集合以【值,值】的形式存储元素。字典也可以叫做映射。在ES6中同样新增了Map这种数据结构。我们今天要实现的Map跟前面所实现的Set是十分相似的。只不过在对应的映射关系时会有些修改。 那么这篇文章我们快速的完成Map类的相关代码: function Map () { var items = {}; this.has = function (key) { return key in items; } this.set = function (key,value) { items[key] = value; } this.delete = function (key) { if(this.has(key)) { delete items[key]; return true; } return false; } this.get = function (key) { return this.has(key) ? items[key] : undefined; } this.values = function () { var values = []; for(var k in items) { if(this.has(k)) { values.push(items[k]) } } return values; } this.keys = function () { return Object.keys(items); } this.getItems = function () { return items; } this.clear = function() { items = {}; } this.size = function () { return Object.keys(items).length; } } var map = new Map(); map.set("zak","fat"); map.set("lily","thin"); map.set("david","big"); map.set("jams","small"); console.log(map.has("jams"));//true console.log(map.has("zaking"));//false console.log(map.size());//4 console.log(map.keys());//["zak", "lily", "david", "jams"] console.log(map.values());//["fat", "thin", "big", "small"] console.log(map.get("zak"));//fat map.delete("zak"); console.log(map.has("zak"));//false console.log(map.getItems());//{lily: "thin", david: "big", jams: "small"} 这样我们就实现了自己的Map类。 ES6中的Map类,小伙伴们也可以用上面的测试方式来测试ES6原生Map,跟ES6原生的Set堪比兄弟结构。所以这里也不再多说。大家最好自己去敲一遍代码。相信小伙伴们一起学到这里的话,肯定对数据结构不在陌生。其实数据结构的面纱并不神秘。 这一篇代码着实不多,但是对下面要讲的散列表(hashMap)又十分必要。所以就单独拆出来了一章。独立的简单说明了一番。 除了后面要讲的散列表外,还剩下两个数据结构要讲讲,那就是树和图, 其中还会加入一些相关算法的介绍和说明。 然后,hashMap个人觉得十分重要,是一种存储元素及快速查找元素十分便捷一种数据结构。这里不多说。下一篇会详细的讲解hashMap。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
说到集合,第一个想到的就是中学学到的那个数学概念:集合。在我们开始集合相关的js实现前,我们有必要来了解一下什么是集合以及集合的数学概念。 好吧,我们一起来复习一下早就被我们遗忘的集合。 集合是由一组无序且唯一的项组成的。集合这个数据结构使用了与有限集合相同的数学概念。在数学中,集合是指具有某种特定性质的具体的或抽象的对象汇总成的集体,这些对象称为该集合的元素。 比如,一个包括0到9十个数字的集合表示为:N = {0,1,2,3,4,5,6,7,8,9}。集合中的对象列表用{}(大括号)包围。还有一个概念叫做空集,也就是该集合中不包含任何元素,也就是{},空集是任何集合的子集。 除了集合的基本概念,还有一些简单的集合操作,比如并集、交集、差集和子集等。在后面会详细的介绍这些集合的操作。 那么集合的数据概念就简单介绍完了。我们看看如何去创建一个集合类(set)。 function Set() { let items = {}; } 嗯,这就是set的骨架,哎??我记得好像ES6中就有set这个东东啊?嗯...是的,我们会在后面(下一篇)简单介绍下ES6原生的set类。 这里我们使用对象而不是数组来表示集合。其实用数组也是可以的。那么是不是说,前面学过的栈和队列也都可以用对象来实现?是的,不要怀疑可行性。因为其实我们在改进这两个数据结构的时候用的就是weapMap这种ES6新增的结构。 那么接下来要说一下set类有哪些可用的方法。 1、add(value):向集合中添加一个新的项。 2、delete(value):从集合移除一个值。 3、has(value):如果值在集合中,返回true,否则返回false。 4、clear():清空集合中的所有元素。 5、size():返回集合所包含元素的数量。 6、values():返回一个包含集合中所有值的数组。 function Set() { let items = {}; //判断该set实例中是否存在该value this.has = function (value) { //检查它(或其原型链)上是否包含具有指定名称的属性的对象。但是in运算符会查找其原型链上的属性。所以我们用下面的方法更好 //return value in items; //hasOwnProperty方法可以用来检测一个对象是否含有特定的自身属性;和 in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。 //所以我们也可以用hasOwnProperty来判断一个对象的自身属性是否存在 return items.hasOwnProperty(value); } this.add = function (value) { //通过我们上面写的has方法来判断这个值是否存在,如果不存在就添加进去,存在就返回false if(!this.has(value)) { items[value] = value; return true; } return false; } //同样的道理,判断该set中是否有要删除的对象,如果有就删除,没有就返回false this.remove = function (value) { if(this.has(value)) { delete items[value]; return true; } return false; } //直接充值items为空,就变相的清空了items中的所有属性 this.clear = function() { items = {}; } //Object.keys是ES6中为对象新增的原生方法,它会返回一个数组,其中包含对象的所有元素,这样我们就可以获取其元素的个数了。 this.size = function () { return Object.keys(items).length; } //上面我们用ES6新方法来获取items的长度,但是或许有些浏览器的兼容性不是很好。所以我们也可以用循环遍历计数的方式来完成这个功能 this.sizeLegacy = function () { let count = 0; for(let key in items) { if(items.hasOwnProperty(key)) ++count; } return count; } this.values = function () { let values = []; for(let i = 0,keys = Object.keys(items);i < keys.length; i++) { values.push(items[keys[i]]); } return values; } this.valuesLegacy = function () { let values = []; for(let key in items) { if(items.hasOwnProperty(key)) { values.push(items[key]) } } return values; } } var set = new Set(); set.add(1); console.log(set.values());//[1] set.add(2); console.log(set.values());//[1, 2] console.log(set.size());//2 set.remove(2); console.log(set.values());//[1] 这样我们就完整的实现了我们自定义的set类,发没发现我的注释越来越少了,越到后面的学习也就越简单了。因为其实很多东西都是类似的,有它共同的point。 好了,集合的实现我们已经完成了。下一篇文章会介绍一下集合的几种操作方法以及ES6原生set的一些简单用法介绍。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
其实无论在任何语言中,一种数据结构往往会有很多的延伸和变种以应对不同场景的需要。其实前面我们所学过的栈和队列也是可以用链表来实现的。有兴趣的小伙伴可以自己尝试着去实现以下。 有点跑题了...,我们还是说回链表,在基础链表之外,还有双向链表和循环链表和双向循环链表。这篇文章会详细的介绍一下双向链表,但是不会详细的去讲解循环链表。因为其实真的没有太大的区别。链表和循环链表的唯一的区别在于,最后一个元素指向下一个元素的指针不是null,而是head。 其实循环链表只能从头到尾的循环,而双向循环链表可以两个方向循环,想怎么玩怎么玩。 嗯...又跑题了,我们还是来说双向链表吧。 顾名思义...双向链表就是....双向链表!额...开个玩笑...咱们进入正题吧.... 其实简单说双向链表与链表的区别就在于,双向链表不仅仅有一个指向下一个节点元素的指针,还同时拥有一个指向上一个节点元素的指针。前后都可以链接,故,称之为双向链表。 那么既然是双向的指针,所以我们的代码需要新增一些东西。 由于双向链表内的一些方法与链表无异,所以这里只说明一下那些区别明显有重要意义的地方。不再贴上所有的代码。 function DoublyLinkedList() { let Node = function (element) { this.element = element; this.next = null; //在双向链表中,这里多了个指向前一个节点元素的指针prev this.prev = null; } let length = 0; let head = null; //同样的这里多了一个保存链表最后一项节点的引用变量,为什么要加这个变量? //因为是双向链表,普通链表只能从头到尾的迭代各节点元素,一方面是因为普通链表中只有一个存储头部节点元素的head变量。 //但是双向链表可以从尾部开始迭代,这就是tail的意义。 let tail = null; } 这就是双向链表的类的变动(不包括其中的方法),我们可以看到只是多了node节点元素中prev(前一个)节点元素的指针,还有tail变量对尾部节点元素的引用。 那么下面我们来看看insert方法的变化。 //我们来看看双向链表中insert方法,普通链表中,我们只需要控制next指针就可以了,但是在双向链表中,在控制next指针的同时,我们还要控制prev指针 this.insert = function (position,element) { //在普通链表中在任意位置添加元素有两种情况,一个是添加到头部,另外一个是除了头部以外的其他位置, //在双向链表中除了这两种情况,还多了一种,添加在链表尾部 if(position >= 0 && position <= length) { let node = new Node(element); let current = head; let previous; let index = 0; //添加到头部的情况 if(position === 0) { //这里,如果head为null,也就是说该链表是没有任何节点元素的情况,那么加入的这个节点元素在链表中是唯一的 //所以,head引用为node,tail的引用也为node if(!head) { head = node; tail = node; //那么如果,head不为null,说明链表中存在至少一个元素。 } else { //由于current就是head,那么要插入节点元素的话只要把node的next指针指向current,就说明我们在current前面插入了该节点元素。 node.next = current; //因为是双向列表,我们还要给current.prev一个指向。 current.prev = node; //那么既然我们在current前面插入了元素,这里也就要改变head的引用,变为我们插入的node head = node; } //如果我们想要插入尾部的情况 } else if(position === length) { //这里稍微有趣一点,这里我们要在尾部加入元素,不用像普通链表那样迭代到最后一项再操作。 //我们只需要把current直接置为tail的引用就可以了,方便快捷 current = tail; //那么我们已经拿到了最后一项节点元素的引用并且设置为了current。 //我们只需要把current(tail)的next指向node节点元素,并且把node的prev只想current。 //其实就是说,current节点的next指针不再是null了,因为我们在它的后面增加了一个“插入元素”,所以它的next指针为node //而此时node的prev指针也就理所当然的指向了current。 current.next = node; node.prev = current; //插入元素完成,但是我们此时的tail其实是current不是node,所以更改一下tail的引用。 tail = node; } else { while(index++ < position) { //依次往后移动...不多说 previous = current; current = current.next; } //在移动到需要插入节点元素的位置时。 //我们要插入在current的前面,自然就会有下面的结果了 node.next = current; previous.next = node; //但是我们由于是双向链表,我们不仅仅要修改next指针,还要修改prev指针 current.prev = node; node.prev = previous; } length++; return true; } else { return false; } } 其实insert方法在双向链表中,只是多了一种尾部情况的判断以及prev指针的改变,注释已经说的很详细了,不多说废话,我们继续看看removeAt方法在双向链表中的实现。 this.removeAt = function (position) { if(position > -1 && position < length) { let current = head,previous,index = 0; if(position === 0) { head = current.next; if(length === 1) { tail = null } else { head.prev = null; } } else if(position === length - 1) { current = tail; tail = current.prev; tail.next = null; } else { while(index++ < position) { previous = current; current = current.next; } previous.next = current.next; current.next.prev = previous; } length --; return current.element; } else { return null; } } 这是双向链表的removeAt方法,我不想解释了,因为我觉得如果你认真的阅读了这两篇文章,这个方法你绝对可以看懂了。如果你还是看不懂,请从头再来! 这里我们就基本介绍完了双向链表...等等...不是还有其它的方法么?怎么不说了? insert可以在任意位置插入元素,removeAt可以在任意位置移除元素,想要实现其它方法就不难了吧。。。。。再说下去也是重复前面说过的内容了。实在无须如此.... 那么我们对于链表的了解就告一段落,下一篇文章我们一起来看看集合这个东东。感觉会比链表好玩一些。嗯...对,跟数学中的集合有关系。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
前面讲解了数组,栈和队列。其实大家回想一下。它们有很多相似的地方。甚至栈和队列这两种数据结构在js中的实现方式也都是基于数组。无论增删的方式、遵循的原则如何,它们都是有序集合的列表。在js中,我们新建一个数组并不需要限定他的大小也就是长度,但是实际上,数组的底层仍旧为初始化的数组设置了一个长度限制。我们想要在数组中任意的插入和删除元素的成本很高,虽然在js中我们有便捷的方法可以操作数组,但是其底层原理仍旧是这样的。只是我们对它并没有感觉,比如在java中,声明一个数组是必须要限制它的长度的。并且在扩容的情况下,操作起来也不是十分方便。这就需要用到其它的数据结构来应对我们不同的需要,比如链表。 链表存储有序的元素的集合,但是和数组不同的是,链表中的元素在内存中的存储并不是连续的。每一个链表元素都包含了一个存储元素本身的节点和一个指向下一个元素的引用。看起来就像这样: 相对于传统的数组,链表的一个好处就是增删的时候无需移动其它元素,只要更改指针的指向就可以了。但是缺点就是如果想要访问链表中的元素,需要从头开始循环迭代到你想要的元素。 那么简单介绍了什么是链表之后,我们看看如何用js来实现链表,同样的链表有其自身的几种方法。 1、append(element),向列表尾部添加一个新的元素,注意这里所指的列表并不是我们想象中的有序列表,链表是无序的。 2、insert(position,element),在链表的指定位置插入一个新的元素。 3、remove(element),从列表中移除一项。 4、indexOf(element),返回该元素在列表中的索引,如果列表中没有该元素就返回-1。 5、removeAt(position),从列表的指定位置移除元素。 6、isEmpty(),判断该链表是否为空 7、size(),返回该链表包含的元素个数。 8、toString(),返回链表元素的字符串值。 以上描述了链表包含的各种方法,其实说到底也就是增删改查,任何的数据结构的方法种类也就几乎如此。下面我们来看下具体的实现。 function LinkedList(){ let Node = function (element) { this.element = element; this.next = null; } let length = 0; let head = null; this.append = function (element) {}; this.insert = function (position,element){}; this.removeAt = function (position) {}; this.remove = function (element) {}; this.indexOf = function (element) {}; this.isEmpty = function () {}; this.size = function () {}; this.getHead = function () {}; this.toString = function () {}; this.print = function () {}; } 这是整个LinkedList类的基本架子,其中Node类就是我们链表中的每一个节点元素,每一个节点元素都包含一个自身的值(element)和指向下一个节点的指针(next),length自然就是我们记录链表长度的变量,而head是指向第一个元素的指针,初始值跟next是一样的,都是null。 既然架子搭完了,我们接下来看看如何实现每一个具体的方法。链表的方法要比栈或队列的实现稍微复杂些,希望大家仔细阅读。 代码着实有点长,注释是重点,如果你认真读下来,链表的基本构成和原理想必你也就理解了。 // 下面的所有的注释所解释的语句都是注释下面的语句。以下所有的“节点元素”都代表node function LinkedList(){ //node才是链表中的单独元素,但是这个元素中又包含自身的值和指向下一个node的指针 let Node = function (element) { //node的自身元素 this.element = element; /* 这个next要特别注意,它在理论上是指向链表下一个节点元素的指针,但是在js的实现中,其实这个指针不过是一个对象的索引,而这个索引所包含的就是下一个node 就像是这样{element:1,next:{element,next:{element:3,next...}}},这种对象的一层层嵌套,这样也可以解释了为什么在中间插入链表元素时, 需要一层一层的迭代到需要插入的位置。 */ /* 换句话说,这里的next指针,指向的是下一个node节点元素的整体,不单单只是node中的element元素。 */ this.next = null; } let length = 0;//链表长度初始化 let head = null;//在链表中,我们需要存储第一个节点元素的引用,也就是head,在没有节点元素的时候初始化为null。 // append方法类似于js数组的push,向链表的尾部添加节点元素。 // 在append方法中有两种情况,一种是没有节点元素,链表的长度是0,另一种是已经存在了至少一个节点元素,应对这两种不同的情况会有不同的操作。 this.append = function (element) { //声明变量,append添加的element应该是node,所以通过Node类进行包装 let node = new Node(element); //这里就存在了一个问题,那么就是我们在给链表添加节点元素的时候只有head的引用,也就是我们只知道head是什么,但是其他的我们一概不知。 //所以这里声明一个current变量,用来存储我们当前的节点是什么。 let current; //这里,如果head是null,说明该链表是没有节点元素的,因为有节点元素的话head不可能为null(head会指向第一个节点元素),那么既然如此,我们的head=node就可以了。 //还有,这里的“=”,实在是让人很迷茫,既然是指针,为什么要“赋值”? //因为无论是head、node.next(链表节点元素的指针)还是current还是下面会声明的previous。都是存储当前位置信息的一个存储器。 //也就是说,这些变量所代表的是一个值信息的存储,他们存储的值代表他们所指向的节点元素。 //嗯,,,,希望我说明白了。。。。 console.log(head);//你可以看到head以及链表在js中展现大概是什么样的。 if(head === null) { head = node; } else { //这里,如果head!=null,说明该链表至少有一个节点元素,那么当前的current自然就是head,因为我们要从head开始迭代到结尾。 current = head; //这里容易让人疑惑的地方是current.next是啥? //上面current已经是head了,那么无论是只有一个节点元素还是多个节点元素,最后一个节点元素的next必为null,别问我为啥了。 //所以这里只要current.next不为null(也就是有实际意义的值),那么就循环到current.next是null为止。 //因为只有这样才说明当前的current是链表中的最后一个节点元素 while(current.next) { current = current.next; } //既然我们找到了链表中的最后一个节点元素,那么把该节点元素的next=node就好了。 //那么这里还要说的是,每一个新node的next必然是null,嗯,就是这么定义的,没有为啥。 //所以在我们将current.next指向node的时候,链表最后一个节点元素的指向自然就是null了。 current.next = node; } // 嗯...别忘了增加一个单位长度 length++; }; // 在链表的任意“合法”位置插入节点元素,position代表要插入的位置,element不多说,代表要插入的元素。 this.insert = function (position,element){ // 这个判断比较有趣,如果position小于0并且大于该链表的长度,说明这个position不合法。直接返回false //如果在大于等于0并且小于等于length,OK,插入位置合法,继续... if(position >= 0 && position <= length) { //同样的,要建个node let node = new Node(element), // 同样的,当前的current是head current = head, // 新增了一个previous,这个previous是为了衔接需要插入的节点元素的。 previous, // 这个index不是length,它是为了记录限定循环的计数器,作用类似于current和previous。 index = 0; // 这里,如果position是0,意味着我要在头部插入元素。 if(position === 0) { // 那么自然,新建的节点元素的指针(next)就指向了当前元素。而head自然就是新建的节点元素(node)了。 node.next = current; head = node; } else { // 那么如果想要在除了第一个元素的其他位置插入元素。 // 在没有到达想要插入的位置的时候,我们需要迭代替换previous和current,使其依次的往后移动。 while (index++ < position) { //这里就是每一次的移动,前一个等于当前,当前的又变成了下一个(就这样依次移动到指定的position位置) previous = current; current = current.next; } //那么在到达了这个位置后,我们需要把新建的node节点元素插入近previous和current。 //也就是改变node节点元素和previous的指针。使node节点元素指向当前的current。而previous的指针指向node。 //这样也就完成了节点元素在指定位置的插入 node.next = current; previous.next = node; } //插入成功,长度增加一个单位并返回true length ++ ; return true; } else { return false; } }; // 这个方式是移除制定位置的节点元素。 this.removeAt = function (position) { //同样的合法值范围限制。 if(position > -1 && position < length) { //同样的变量声明。 let current = head,previous,index = 0; //这里比较有趣,如果是要移除第一个节点元素,那么直接把head的指针指向当前节点元素(current)的下一个(.next)就可以了。 //因为我们中断了head和current的链接,直接使current不存在于链表中了,这样我们无论如何迭代都获取不到此时的current。 //这样操作之后,我们只要等待js垃圾回收器回收它就好了。 if(position === 0) { head = current.next; } else { //同样的迭代移动 while (index++ < position) { previous = current; current = current.next; } //这里我们迭代到了我们想要移除的元素的位置,同样中断了current的在链表中的链接。也就删除了该节点元素 previous.next = current.next; } //长度减少一个单位。 length --; //返回删除的元素值 return current.element; } else { return null; } }; //获取该元素在链表中的位置 this.indexOf = function (element) { let current = head,index = 0;//不解释了,这里index为0,因为要从链表的第一个0位开始遍历 //那么这里又比较有趣了,这里的current无非两种情况,null或者一个具体的值。 //如果是null,说明该链表是空的,不然current=head不可能为null //如果不为null,继续判断 while(current) { //那么如果current不为null,并且如果element和current的element相等。说明找到了,直接返回index if(element === current.element) { return index; } //这里其实可以是为上面if判断的else分支,如果不相等,那么计数器index就加一个单位,并且current指针往后移动。 index ++; current = current.next; } return -1; }; // 我们既然有了indexOf和removeAt,这个remove方法我就不多说了。 this.remove = function (element) { let index = this.indexOf(element); console.log(index) return this.removeAt(index); }; // 下面的方法也都很简单,无需多说 this.isEmpty = function () { return length === 0; }; this.size = function () { return length; }; this.getHead = function () { return head; }; this.toString = function () { let current = head,string = ''; while(current) { string += current.element + (current.next ? 'n' : ''); current = current.next; } return string; }; this.print = function () { console.log(this.toString()); }; } var list = new LinkedList(); list.append(1); list.append(2); list.append(3); list.append(4); list.append(5); list.print();//1n2n3n4n5 list.insert(2,99); list.print();//1n2n99n3n4n5 list.removeAt(1); list.print();//2n3n4n5 这就是基本的链表实现方式了。大家在实践的时候可以先去掉注释,自己思索一遍敲一遍代码,然后回过头来带着疑问看注释。我相信会有不小的帮助。 那么这一篇尽量不写的那么长。到这里就告一段落。下一篇文章会详细的介绍一下双向链表以及其实现的方式。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
其实队列跟栈有很多相似的地方,包括其中的一些方法和使用方式,只是队列使用了与栈完全不同的原则,栈是后进先出原则,而队列是先进先出(First In First Out)。 一、队列 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。 队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表。 我们对队列有了基本的了解,那么我们来看看如何实现队列。其实跟栈的实现极为类似,只是入队和出队的方法稍有不同,那么我们来看看一个完整的队列需要哪些方法: 1、enqueue(element(s)),入队,向队列尾部添加一个或者多个元素。 2、dequeue(),出队,移除队列中的第一个元素,也就是队列最前面的元素,并返回该元素。 3、front(),获取队列最前面的元素,返回队列中第一个元素(最先被添加,也是最先被移除的元素)。队列并不移除该元素。 4、isEmpty(),判断队列是否不包含任何元素。 5、size(),返回队列的元素总数。 //声明Queue类 function Queue() { //声明并初始化一个用来存放队列元素的数组。 let items = []; //添加队列元素 this.enqueue = function (element) { items.push(element) }; //移除并返回该队列元素 this.dequeue = function () { return items.shift(); }; //获取队列头部元素 this.front = function () { return items[0]; }; //判断队列元素是否为空 this.isEmpty = function () { return items.length == 0; }; //获取队列元素个数 this.size = function () { return items.length; }; //打印该队列 this.print = function () { console.log(items.toString()) }; } const queue = new Queue(); console.log(queue.isEmpty()); // outputs true queue.enqueue('John'); queue.enqueue('Jack'); queue.print(); // John,Jack queue.enqueue('Camila'); queue.print(); // John,Jack,Camila console.log(queue.size()); // outputs 3 console.log(queue.isEmpty()); // outputs false queue.dequeue(); // remove John queue.dequeue(); // remove Jack queue.print(); // Camila 上面我们就已经实现了队列这种数据结构,同样的,我们是否可以稍微改造一下,让队列的实现看起来更加优美一点。 let Queue = (function () { const items = new WeakMap(); class Queue { constructor () { //强调一下,这里items是WeakMap类型的数据,而WeakMap是键值对,有专属的set和get方法来获取和设置值, //所以这里给this设置了[],即以this为键名,[]为值,所以该方法形成的队列仍旧是对数组的操作 items.set(this,[]); } enqueue(element) { let q = items.get(this);//这里的q就相当于是[] q.push(element); } dequeue() { let q = items.get(this); let r = q.shift(); return r; } front() { return items.get(this)[0]; } isEmpty() { return items.get(this).length == 0; } size() { return items.get(this).length; } print() { console.log(items.get(this)); } } return Queue; })() 其实到这里队列就基本介绍完了,但是感觉实在有点糊弄人啊。所以就把剩下的内容都在这一篇文章写完吧。 二、优先队列 我们说完了队列,那么我们看看什么是优先队列。普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。就像是我们在窗口买票,机场排队,正常来说我们都是依照排队的顺序从队列的最前面开始依次进入,但是有规定老人孩子军人等优先,那么就赋予了该老人(孩子军人)插队的权利。而优先队列,同样就是给特定元素赋予插队(优先级)的权利。我想要入队,并不一定是直接到尾部。而是根据我设定的优先级来插入队列。 其实优先队列在实现上不同的地方是队列元素的设定和入队方法的不同(这里其实有两种实现方式,一个是按照优先级入列,一个是按照优先级出列,这里我们只用第一种方式实现)。下面我们就看看如何实现优先队列吧。 //声明Queue类 function PriorityQueue() { //声明并初始化一个用来存放队列元素的数组。 let items = []; //创建一个拥有优先级的元素类 function QueueElement(element,priority) { this.element = element; this.priority = priority; } //添加队列元素 this.enqueue = function (element,priority) { let queueElement = new QueueElement(element,priority); let added = false; //遍历队列元素,1的优先级最高,一次类推,如果当前元素优先级大于items[i],那么就把该元素放在items[i]前面。 //splice方法的第二的参数如果为0,那么则把第三个参数添加到i前面。 for(let i = 0; i < items.length; i++) { if(queueElement.priority < items[i].priority) { items.splice(i,0,queueElement); added = true;break; } } // 通过added判断是否可以直接把元素入列。 if(!added) { items.push(queueElement); } }; //移除并返回该队列元素 this.dequeue = function () { return items.shift(); }; //获取队列头部元素 this.front = function () { return items[0]; }; //判断队列元素是否为空 this.isEmpty = function () { return items.length == 0; }; //获取队列元素个数 this.size = function () { return items.length; }; //循环打印元素及其优先级“``”是ES6的模板字符串 this.print = function () { for(let i = 0; i < items.length; i++) { console.log(`${items[i].element} - ${items[i].priority}`); } }; } const queue = new PriorityQueue(); console.log(queue.isEmpty()); // outputs true queue.enqueue('zaking',2); queue.enqueue('linbo',6); queue.enqueue('queue',5); queue.enqueue('ada',3); queue.enqueue('John',1); queue.enqueue('Jack',2); queue.enqueue('Camila',3); queue.enqueue('zak',3); queue.print(); 主要的更改在于队列元素的设置和enqueue方法,由于需要为每一个循环队列的元素设置优先级,所以这里稍微更改了一下队列的元素,使其带有两个参数(元素自身和优先级),那么既然要根据不同的优先级来插入队列,所以循环队列的enqueue方法也就需要循环整个队列去判断要插入到哪里。 其实这个优先队列的实现并不是很好,比如我不传第二优先级参数,那么队列打印的时候该参数就是undefined,而且在不传参数的时候应该默认为最末优先级。大家可以试着去修改一下代码,使其看起来更符合实际。 三、循环队列 除了优先队列以外还有循环队列,循环队列的一个比较容易想象的例子就是击鼓传花游戏,游戏规则就不说了大家小时候都玩过,我们看看如何实现击鼓传花游戏。 function hotPotato(nameList, num) { const queue = new Queue(); //把所有的名单(nameList)依次入列 for (let i = 0; i < nameList.length; i++) { queue.enqueue(nameList[i]); } //声明当前被淘汰的人员名称 let eliminated = ''; //如果队列中的元素大于一个,说明还没有最后的赢家,如果只剩下一个,就出列该最后赢家 while (queue.size() > 1) { //循环当前队列num次,把队列头部的“出列元素”再入列。 for (let i = 0; i < num; i++) { queue.enqueue(queue.dequeue()); } //循环结束后,出列当前队列的元素,也就是淘汰者。 eliminated = queue.dequeue(); queue.print(); console.log(eliminated + "被淘汰"); } return queue.dequeue(); } let names = ["zak","zaking","james","lili","bole","londo","fali"] console.log(hotPotato(names,7)) 上面的方法,每次循环都会依次把头部元素放入到尾部,就实现了一个圈,然后我们以循环结束后,队列最前端的元素视为淘汰,直到最后只剩下一个元素,也就真实的模拟了击鼓传花游戏。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢! 一只想要飞得更高的小菜鸟
上一篇文章我们一起实现了栈,那么这一篇文章我们一起来用栈解决问题。看看如何用栈来解决进制转换,平衡圆括号以及汉诺塔问题,使我们对栈有更为深入的理解。 1、进制转换 我们先来看看十进制如何转换成二进制,十进制整数转换为二进制整数采用"除2取余,逆序排列"法。具体做法是:用2整除十进制整数,可以得到一个商和余数;再用2去除商,又会得到一个商和余数,如此进行,直到商为0时为止,然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。简单来说就是拿十进制数去除以二,如果整除了,那么余数为0,放入栈中,如果没有整除,余数就是1,放入栈中,直至相除的结果为0。依据所得到的结果,后得到的余数排列在最前面。也就是栈顶元素从左到右排列。 我们已经知道了十进制如何转换成二进制,那么我们看看代码是怎么实现的吧。 function decimalToBinary(decNumber) { //声明一个stack对象 const remStack = new Stack(); // 需要转换的数字 let number = decNumber; //每次相除后所得的余数 let rem; //转换后的二进制字符串 let binaryString = ''; //当number为0的时候结束循环 while (number > 0) { //对余数向下取整,因为这里不取整的话会出现小数,js没有浮点或者整形这一说。 rem = Math.floor(number % 2); // 存储当前的余数 remStack.push(rem); //因为上面对number取余只是拿到了最后余数的结果,number本身并没有除以二,所以这里除以二是为了保证后面可以再一次取余的结果正确性 number = Math.floor(number / 2); } //这里的意思是如果栈中还有元素,那么就循环拼接字符串。 while (!remStack.isEmpty()) { binaryString += remStack.pop().toString(); } return binaryString; } console.log(decimalToBinary(100));//1100100 console.log(decimalToBinary(1));//1 console.log(decimalToBinary(2));//10 console.log(decimalToBinary(111));//1101111 console.log(decimalToBinary(65));//1000001 那么上面就实现了十进制转换二进制的方法,那么我想可不可以使它更完善一点,实现十进制转换成二进制,八进制,十六进制等。 function baseConverter(decNumber, base) { const remStack = new Stack(); // 这是转换的对比字典,大家知道在十位以后的禁止转换中,10用A代表,11用B代表。 //所以当我们在转换的时候需要使余数的数字被字母所替换。 const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; let number = decNumber; let rem; let baseString = ''; //这里只做2到36位之间的转换,因为理论上来讲可以进行任何位数之间的互相转换。 //但是在不同位数之间转换的时候会有更为复杂的情况 if (!(base >= 2 && base <= 36)) { return ''; } while (number > 0) { rem = Math.floor(number % base); remStack.push(rem); number = Math.floor(number / base); } while (!remStack.isEmpty()) { baseString += digits[remStack.pop()]; } return baseString; } console.log(baseConverter(100,16));//64 console.log(baseConverter(1,16));//1 console.log(baseConverter(12,8));//14 console.log(baseConverter(122323123,36));//20TT4J console.log(baseConverter(111111111,28));//6CLF9R console.log(baseConverter(99,16));//63 我们发现其实对于十进制转换成其它进制,貌似只是多了一个对照表digits,本质上并没有什么区别。 2、平衡圆括号 function parenthesesChecker(symbols) { const stack = new Stack(); const opens = '([{'; const closers = ')]}'; let balanced = true; let index = 0; let symbol; let top; while (index < symbols.length && balanced) { // 从0开始(index的初始值),给symbol赋值 symbol = symbols.charAt(index); // 这里判断symbol是否在opens中存在,即opens.indexOf的返回值不为负数。 if (opens.indexOf(symbol) >= 0) { // 如果存在,那么说明是开始符号,入栈。 stack.push(symbol); //那么之前判断了是否存在开始符号,如果不存在的话就不会入栈,所以stack就没有元素,自然stack为空,balanced返回false。 //因为如果一开始的第一个符号就是尾部符号一定是无法对称平衡的。 } else if (stack.isEmpty()) { balanced = false; } else { // 获取栈顶元素并移除 top = stack.pop(); // 这个else其实是当symbols中对应的下标没有值得时候,就说明是closer中的值之一。 //所以这里的symbol其实是closer,所以获取最近入栈的值进行比较,就能判断出是否是平衡的。 if (!(opens.indexOf(top) === closers.indexOf(symbol))) { balanced = false; } } // 继续循环 index++; } // 这里,如果是balanced的,并且stack为空,那么说明我们的所有元素必然是一一对称的。 //因为如果不对称是无法完全出栈的 if (balanced && stack.isEmpty()) { return true; } return false; } console.log(parenthesesChecker("(()()())")) console.log(parenthesesChecker("(({})){[")) console.log(parenthesesChecker("{}()[]")) console.log(parenthesesChecker("{{{}}}}")) console.log(parenthesesChecker("[][][][")) 其实这个方法的核心在于,判断当前下标的参数是头部分还是尾部分,头部就入栈,如果是尾部就出栈头部并和当前尾部对比。正是利用了栈的特性。 3、汉诺塔 什么是汉诺塔?我相信很多人小时候都玩过,有图有真相,没图不BB。 在开始玩汉诺塔游戏之前,我先给大家说一下汉诺塔游戏的规则: 规则一:每次操作只能移动一个圈圈,把它从一个柱子移到另一个柱子上。 规则二:大圈圈不能架在小圈圈的上面。 这是游戏的规则,那么换作程序的话,规则是这样的:假设这里有三根相邻的柱子,标号为A,B,C,A柱子上由下到上按金字塔状叠放着n个不同大小的圆盘,要把所有盘子一个一个移动到柱子B上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方,请问至少需要移动多少次? 我的理解,1、目的是把这个汉诺塔从一个柱子依照由下到上的顺序完整的移动到另一个柱子上, 2、大圈不能在小圈之下,但是可以隔层放置大小圈,比如八号最大,越往上越小,那么在移动的过程中,5号是可以放在7号上面的。 3、可以往回放。 如果还有不清楚规则的地方,可以去这里亲自玩一下这个游戏。 我们已经对汉诺塔有了简单的了解,那么我们看看如何用栈来实现这个游戏吧: //plates:盘子数量,source源柱子,helper暂存柱子,dest目标柱子,sourceName源柱子名称,helperName暂存柱子名称,destName目标柱子名称,moves步数(若不传值则默认为一个数组) function towerOfHanoi(plates, source, helper, dest, sourceName, helperName, destName, moves = []) { console.log(moves.length) //如果盘子的数字不大于0 ,那么直接返回moves,终止递归的条件。 if (plates <= 0) return moves; if (plates === 1) { dest.push(source.pop()); const move = {}; move[sourceName] = source.toString(); move[helperName] = helper.toString(); move[destName] = dest.toString(); moves.push(move); } else { //递归调用自身。并且将盘子的数量减少一个,这里交换了dest和helper的位置,是为了dest.push中存入的栈是helper栈,也就是说是为了存入对应的柱子。 towerOfHanoi(plates - 1, source, dest, helper, sourceName, destName, helperName, moves); //从源柱子拿出最顶层的一个放入目标柱子(如果dest和helper互换了位置,那么其实这里的dest实际上代表的是helper) dest.push(source.pop()); //声明常量,用来存储当前各个柱子的盘子栈况 const move = {}; move[sourceName] = source.toString(); move[helperName] = helper.toString(); move[destName] = dest.toString(); // 存入moves moves.push(move); towerOfHanoi(plates - 1, helper, source, dest, helperName, sourceName, destName, moves); } return moves; } function hanoiStack(plates) { // 创建每一个柱子的栈对象,source是最开始拥有所有圈圈的柱子,dest是目标柱子,helper是中间的暂存柱子 const source = new Stack(); const dest = new Stack(); const helper = new Stack(); //倒序循环把每一个圈圈序号放入source栈 for (let i = plates; i > 0; i--) { source.push(i); } //通过return调用towerOfHanoi计算方法。 return towerOfHanoi(plates, source, helper, dest, 'source', 'helper', 'dest'); } //这个方法是计算在汉诺塔的层数为plates的时候,每一个是从哪个柱子拿到哪个柱子的 function hanoi(plates, source, helper, dest, moves = []) { if (plates <= 0) return moves; if (plates === 1) { moves.push([source, dest]); } else { hanoi(plates - 1, source, dest, helper, moves); moves.push([source, dest]); hanoi(plates - 1, helper, source, dest, moves); } return moves; } console.log(hanoiStack(2)) console.log(hanoi(8, 'source', 'helper', 'dest')); 到这里,我们对栈有了一定的了解,相信大家在今后无论什么情况下遇到“栈”这个词都不会再陌生和懵懂了。那么对栈的学习到这里就基本结束了。下一篇文章会跟大家一起学习一下队列这个数据结构。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
其实说到底,在js中栈更像是一种变种的数组,只是没有数组那么多的方法,也没有数组那么灵活。但是栈和队列这两种数据结构比数组更加的高效和可控。而在js中要想模拟栈,依据的主要形式也是数组。 从这篇文章开始,可能会接触到一些原型,原型链,类,构造函数等相关的js概念,但是这里并不会过多的介绍这些概念,必要的时候会进行一些简要的说明,推荐大家去看看汤姆大叔的深入理解Javascript系列,王福朋大神的深入理解Javascript原型和闭包系列。都是极为不错的深度好文,推荐大家可以深入学习。 要想实现一个数据结构,首先你要明白它的基本原理,那么栈是什么?又是如何工作的呢? 栈(stack)是一种遵循后进先出(Last In First Out)原则的有序集合。新添加的元素和待删除的元素都保存在栈的同一端,称为栈顶,另一端就叫做栈底。在栈里,新元素都接近栈顶,旧元素都靠近栈底。其实可以把栈简单理解成往一个木桶里堆叠的放入物品,最后放进去的在桶的顶端,也是可以最先拿出来的,而最先放进去的却在桶的底部,只有把所有上面的物品拿出来之后才可以拿走底部的物品。 对于数组来说,可以添加元素,删除元素,获取数组的长度以及返回对应下标得到值,那么在开始构造一个栈之前,我们需要了解一下栈都有哪些基本操作。 1、压栈,也称之为入栈,也就是把元素加入栈中。就像是数组中的push一样。 2、出栈,移除栈顶的元素。就像是数组中的pop一样。 3、获取栈顶的元素,不对栈做任何其他操作。就像是在数组中通过下标获取对应的值一样。 4、判断栈是否为空。就像是判断数组的长度是否为0一样。 5、清空栈,也就是移除栈里的所有元素。就像是把数组的长度设置为0一样。 6、获取栈里的元素个数。就像是数组的length属性一样。 那么,我相信我大家已经对栈有了一个基本的了解,那么我们接下来就看看如何通过构造函数来实现一个自己的js栈。 function Stack () { var items = []; //首先,我们来实现一个入栈的方法,这个方法负责往栈里加入元素,要注意的是,该方法只能添加元素到栈顶,也就是栈的尾部。 this.push = function (ele) { items.push(ele) } } var stack = new Stack(); 我们声明一个构造函数,并且在构造函数中生命一个私有变量items,作为我们Stack类储存栈元素的基本支持。然后,加入一个push方法,通过this来使其指向调用该方法的实例。下面我们还会通过这样的方式依次添加其他的方法。 function Stack () { var items = []; //首先,我们来实现一个入栈的方法,这个方法负责往栈里加入元素,要注意的是,该方法只能添加元素到栈顶,也就是栈的尾部。 this.push = function (ele) { items.push(ele); } //然后我们再添加一个出栈的方法,同样的,我们只能移除栈顶的元素。 this.pop = function (ele) { return items.pop(); } //查看栈顶,也就是栈的尾部元素是什么 this.peek = function () { return items[items.length - 1]; } //检查栈是否为空 this.isEmpty = function () { return items.length == 0; } //检查栈的长度 this.size = function () { return items.length; } //清空栈 this.clear = function () { items = []; } //打印栈内元素 this.print = function () { console.log(items.toString()) } } 这样我们就通过构造函数完整的创建了一个栈。我们可以通过new命令实例化一个Stack对象来测试一下我们的栈好不好用。 var stack = new Stack(); console.log(stack.isEmpty());//true stack.push(1); stack.print(); stack.push(3); stack.print(); console.log(stack.isEmpty());//false console.log(stack.size());//2 stack.push(10); stack.print(); stack.pop(); stack.print(); stack.clear(); console.log(stack.isEmpty());//true 我们发现我们的Stack类执行的还算不错。那么还有没有其他的方式可以实现Stack类呢?在ES6之前我可能会遗憾懵懂的对你Say No。但是现在我们可以一起来看看ES6带我们的一些新鲜玩意。 在开始改造我们的Stack类之前,需要先说一下ES6的几个概念。Class语法,Symbol基本类型和WeakMap。简单解释一下,以对后面的改造不会一脸懵逼,而大家想要更深入的了解ES6新增的各种语法,可以去自行查阅。 Class语法简单来说就是一个语法糖,它的功能ES5也是完全可以实现的,只是这样看写起来更加清晰可读,更像是面向对象的语法。 Symbol是ES6新增的一个基本类型,前面几篇文章说过,ES5只有6中数据类型,但是在ES6中又新增一种数据类型Symbol,它表示独一无二的值。 WeakMap,简单来说就是用于生成键值对的集合,就像是对象({})一样,WeakMap的一个重要用处就是部署私有属性。 当然,上面的简单介绍可不仅仅是这样的,真正的内容要比这些多得多。 那么在大家知道了它们的一些基本意义。咱们开始改造一下Stack类 class Stack { constructor() { this.items = []; } push(element) { this.items.push(element); } pop() { return this.items.pop(); } peek() { return this.items[this.items.length - 1]; } isEmpty() { return this.items.length === 0; } size() { return this.items.length; } clear() { this.items = []; } toString() { return this.items.toString(); } print() { console.log(this.items.toString()) } } 这是用class来实现的Stack类,其实我们可以看一下,除了使用了constructor构造方法以外,其实并没有什么本质上的区别。 那么我们还可以使用Symbol数据类型来实现,简单改造一下: const _items = Symbol('stackItems'); class Stack { constructor() { this[_items] = []; } push(element) { this[_items].push(element); } pop() { return this[_items].pop(); } peek() { return this[_items][this[_items].length - 1]; } isEmpty() { return this[_items].length === 0; } size() { return this[_items].length; } clear() { this[_items] = []; } print() { console.log(this.toString()); } toString() { return this[_items].toString(); } } 使用Symbol也没有大的变化,只是声明了一个独一无二的_items来代替构造方法中的数组。 但是这样的实现方式有一个弊端,那就是ES6新增的Object.getOwnPropertySymbols方法可以读取到类里面声明的所有Symbols属性。 const stack = new Stack(); const objectSymbols = Object.getOwnPropertySymbols(stack); stack.push(1); stack.push(3); console.log(objectSymbols.length); // 1 console.log(objectSymbols); // [Symbol()] console.log(objectSymbols[0]); // Symbol() stack[objectSymbols[0]].push(1); stack.print(); // 1, 3, 1 不知道大家注意没有,我们定义的Symbol是在构造函数之外的,因此谁都可以改动它。所以这样的方式还不是很完善的。那么我们还可以使用ES6的WeakMap,然后用闭包实现私有属性。 //通过闭包把声明的变量变成私有属性 let Stack = (function () { //声明栈的基本依赖 const _items = new WeakMap(); //声明计数器 const _count = new WeakMap(); class Stack { constructor() { //初始化stack和计数器的值,这里的set是WeakMap的自身方法,通过set和get来设置值和取值,这里用this作为设置值的键名,那this又指向啥呢?自行console! _count.set(this, 0); _items.set(this, {}); } push(element) { //在入栈之前先获取长度和栈本身 const items = _items.get(this); const count = _count.get(this); //这里要注意_count可是从0开始的噢 items[count] = element; _count.set(this, count + 1); } pop() { //如果为空,那么则无法出栈 if (this.isEmpty()) { return undefined; } //获取items和count,使长度减少1 const items = _items.get(this); let count = _count.get(this); count--; //重新为_count赋值 _count.set(this, count); //删除出栈的元素,并返回该元素 const result = items[count]; delete items[count]; return result; } peek() { if (this.isEmpty()) { return undefined; } const items = _items.get(this); const count = _count.get(this); //返回栈顶元素 return items[count - 1]; } isEmpty() { return _count.get(this) === 0; } size() { return _count.get(this); } clear() { /* while (!this.isEmpty()) { this.pop(); } */ _count.set(this, 0); _items.set(this, {}); } toString() { if (this.isEmpty()) { return ''; } const items = _items.get(this); const count = _count.get(this); let objString = `${items[0]}`; for (let i = 1; i < count; i++) { objString = `${objString},${items[i]}`; } return objString; } print() { console.log(this.toString()); } } return Stack; })() const stack = new Stack(); stack.push(1); stack.push(3); stack.print(); // 1, 3, 1 这是最终比较完善的版本了。那么不知道大家注没注意到一个小细节,前面我们只是声明一个变量,先不管他是不是私有的,就是数组,整个Stack构造函数都是基于items数组来进行各种方法的。 但是这里通过WeakMap作为基本,我们却多用了一个_count,前面说了_count是计数器,那么为啥要用计数器?因为WeakMap是键值对的“对象类型”,本身是没有像数组这样的长度之说的,所以需要一个计数器来代替数组的下标,以实现基于Stack的各种方法。 到这里基本上就完成了我们的栈,下一篇文章会看看如何用我们写好的栈去做一些有趣事情。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢! 一只想要飞得更高的小菜鸟
终于,这是有关于数组的最后一篇,下一篇会真真切切给大家带来数据结构在js中的实现方式。那么这篇文章还是得啰嗦一下数组的相关知识,因为数组真的太重要了!不要怀疑数组在JS中的重要性与实用性。这篇文章分为两部分,第一部分会给大家简单说一下数组原生的排序方法sort和reverse。第二部分会给大家简单说一下二维和三维数组。都不难,仔细看,很简单的。 1、reverse() reverse比较容易理解,颠倒数组中元素的顺序,也就是第一个变成最后一个,最后一个呢变成第一个。 var nums = [0,1,2,3,4,5,6,7]; nums.reverse(); console.log(nums)//[7, 6, 5, 4, 3, 2, 1, 0] reverse方法只是无差别的对数组进行倒叙,可能很多时候并不适用于我们的应用场景,所以才出现了sort()方法。 2、sort() sort()方法可以说是js数组中不太容易记忆和使用的方法,但是sort()是十分重要的一个方法,那么下面我就详细的说明一下,sort()的应用场景和使用方法。 sort()方法允许传入一个匿名函数作为排序的依据,也可以不传参数,但是我想大家都知道,在不传参数的情况下,很多时候的结果并不是我们想要的,比如: var nums = [0,2,4,9,10,11,20,32,3,6,7,8,15,26]; var newNums = nums.sort(); console.log(newNums)//[0, 10, 11, 15, 2, 20, 26, 3, 32, 4, 6, 7, 8, 9] console.log(nums)//[0, 10, 11, 15, 2, 20, 26, 3, 32, 4, 6, 7, 8, 9] 上面的代码说明,sort会改变原来的数组,而不是生成一个排序后的新数组,大家看到上面的排序并不是依据数字从大到小排序的。这是因为,sort在排序的时候会默认把数组中的各个元素转换成字符串,并且依据字符串对应的ASCII码值来比较的,那ASCII是什么?ASCII是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。 显然这不是我们想要的结果,所以就需要给sort传入一个函数(compareFunction)来使排序得到我们想要的结果或者自定义排序的方式。 在大多数实际工作的排序中,我们都会依据id值得大小,或者一些依据数值大小来排序的场景。那么我们看看如何使用compareFunction来使sort排序可以得到正确的结果: var nums = [0,2,4,9,10,11,20,32,3,6,7,8,15,26]; nums.sort(function (a,b) { return a - b; }) console.log(nums)// [0, 2, 3, 4, 6, 7, 8, 9, 10, 11, 15, 20, 26, 32] 我在刚接触sort时候看到这样的写法,第一个反应就是a和b是什么?他所代表的参数是什么意义?为什么要用a和b?我用其他的参数可以么? 首先,a和b在这里只是代表数组中任意两个元素的值,你可以使用任何两个参数来代表它。 那么再给大家看一个: var nums = [0,2,4,9,10,11,20,32,3,6,7,8,15,26]; var i = 0; nums.sort(function (m,n) { console.log(m,n) console.log(++i) return m - n; }) console.log(nums)// [0, 2, 3, 4, 6, 7, 8, 9, 10, 11, 15, 20, 26, 32] 上面的代码,给大家留下一个疑问自己去找答案。这里不会多说,看看再不同数组长度下sort会循环多少次?m,n每一次的值又是什么样的?其实一个sort方法就包含很多更深层次的问题。 那么还有一种场景,比如说后端传给我一个包含对象元素的数组,我需要依照id的大小来给这个数组排序,要怎么做呢? var objArr = [{name:"zaking",id:0},{name:"json",id:6},{name:"undefined",id:11},{name:"obj",id:10},{name:"number",id:4},{name:"string",id:5}]objArr.sort(function(a,b){ var m = a.id; var n = b.id; return m - n; }) console.log(objArr) console出来的结果是这样的: 那么这样就实现了我们在工作场景中排序的大多数的情况。那么如果你想要倒叙排序怎么办呢?a-b变成b-a。其实a-b这样的方式是简写。 nums.sort(function (a,b) { if(a<b) { return -1 } if(a>b) { return 1 } return 0 }) 或许这样更清晰一点,判断a和b值得大小来决定是否要调换两个值得位置,如果a<b那么a就放在b的后面,如果a>b,那么久把a放在b的前面,如果a既不大于b,也不小于b,那么说明a=b,则不改变两个值得位置。 这里有一篇文章,大家可以去看一下,JS基础篇--sort()方法的用法,参数以及排序原理。 3、多维数组的使用及场景 其实多维数组在平时的工作中还是很常见的,最普通的要说是二维数组了,也可以叫做矩阵。但是其实js是不支持二维或者多维数组的,但是好在js够灵活,我没有的,都可以模拟出来。那么我们也可以通过数组嵌套数组的方式来模拟多维数组。 我们先来看看二维数组: var matrix = [["a","b","c","d"],["A","B","C","D"],[1,2,3,4]]; 这就是一个简单的二维数组形式,我们通常需要遍历数组获取其中的每一个值,其实我们可以把二维数组视为行和列,第一层循环每一行,第二层循环每一行的每一列,这样就可以得到二维数组中的每一个元素。 var matrix = [["a","b","c","d"],["A","B","C","D"],[1,2,3,4]]; for (var i = 0; i < matrix.length; i++) { document.write("</br>") for(var j = 0; j < matrix[i].length;j++) { document.write(matrix[i][j]) } } 这样我们就得到了一个二维数组内所有的元素。 其实三维数组也是一样的,只是再多循环一层,我们来看一下。 var matrix3 = []; for (var i = 0; i < 3; i++) { matrix3[i] = []; for (var j = 0; j < 3; j++) { matrix3[i][j] = []; for (var k = 0; k < 3; k++) { matrix3[i][j][k] = i + j + k; } } } console.log(matrix3) 数组的介绍到这里就基本结束了,下一篇文章会跟大家一起来看看如何用JS来实现栈这种数据结构。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
上一篇文章简单的介绍了一下js的类型,以及数组的增删方法。这一篇文章,我们一起来看看数组还有哪些用法,以及在实际工作中我们可以用这些方法来做些什么。由于其中有部分内容并不常用,所以我尽量缩小篇幅。在这篇文章内介绍完大部分的数组方法,加快我们实现其它数据结构的脚步。 1、concat() 合并数组,可以合并一个或多个数组。会按照参数顺序依次合并进想要合并的数组。 //concat的参数并不是只能传入数组,字符串,数字,布尔值,对象等都可以传入。 var arr = [0,1,2,3,4,5,6]; var a = "7"; var b = true; var c = {a:1} var d = [9,10] var newArr = arr.concat(a,b,c,"d",{b:2}) //[0, 1, 2, 3, 4, 5, 6, "7", true, {a:1}, "d", {b:2}] 需要注意的是,concat方法会生成一个新的数组,并不会改变原数组的值。那么这里还是要说一下,concat的主要作用在于合并数组!而且前面也说过,并不建议在数组中存入不同类型的参数,所以上面例子合并的参数只是为了测试可以这么做,但是不要这么做,到时候会有意想不到的乱子! 2、join() 把所有的数组元素依照分隔符(也就是参数)链接成一个字符串。如果不传入参数则以","逗号分隔。该方法同样会生成一个新的字符串结果。 var arr = ["z","a","k","i","n","g"]; var arrStr = arr.join("-"); //["z", "a", "k", "i", "n", "g"] //z-a-k-i-n-g 3、some()和every() some()方法,会遍历数组中的每个元素,直到返回false结束!而every()呢,与some()相反,直到返回true结束!下面简单举两个例子: var isEven = function (val) { return (val % 2) == 0 ? true : false; } var nums = [1,2,3,4,5,6,7,8,9,10]; var judgeA= nums.every(isEven) //false var judgeB = nums.some(isEven) //true 解释一下,其实简单来说,some用来判断本数组中是否存在(至少有一个)符合传入函数的条件的值,而every则判断是否本数组中每一个值都符合条件。 那么在上面的例子中,some方法确定数组中存在符合条件的值,所以返回true,后面有没有符合条件的跟我没关系了。只要找到找一个符合条件的就说明我可以返回true了。如果some方法遍历了整个数组还没有找到符合条件的值,则会遗憾的返回false。 而every方法,跟some方法其实刚好相反。我只要发现一个不符合条件,我就高高兴兴的返回false,只有在遍历了整个数组元素发现都符合条件,才会可怜兮兮的返回true。 如果要记忆区分这两种方法,其实并不是很难,every(每一个),说明只有所有都对才算是true。而some(一些),说明你有一个就行啦,我就给你返回true。 4、forEach(),map()和filter() 敲黑板!这是重点!重点! forEach(): forEach()方法,它接受一个方法(function)作为参数,该方法中可以有三个参数(item,index,arr)分别是调用forEach数组中的每一项元素,每一项元素的下标,调用forEach方法的数组。该方法会遍历数组中的每一项,为每一项执行你想做的事,不更改原数组并且没有返回值。但是我们可以自己通过数组的索引来修改原来的数组。 var nums = [1,2,3,4,5,6,7,8,9,10]; var newNumsB = []; var newNumsA = nums.forEach(function (item,index,arr){ newNumsB.push(item + 100) arr[index] = item + 10; }) console.log(nums) //[11, 12, 13, 14, 15, 16, 17, 18, 19, 20] console.log(newNumsB) //[101, 102, 103, 104, 105, 106, 107, 108, 109, 110] console.log(newNumsA) //undefined forEach方法并不会改变原数组,如果你想要操作调用方法所修改后的值,需要把他重新赋值给一个空数组,或者,如果修改原数组是你想要的结果,那么可以通过匿名函数的第三个参数来获取到原数组从而更改他。 map(): map()方法,其实简单来说就是一个映射,但是map必须要有返回值,并且map会返回一个新数组。同样的,map也可以有三个参数,跟forEach是一样的。但是,你却无法向forEach那样来通过匿名函数的第三个参数来改变原数组,因为map需要return! var nums = [1,2,3,4,5,6,7,8,9,10]; var newMapNums = nums.map(function (item,index,arr) { console.log(item,index,arr); return item * item; }) console.log(newMapNums) // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] filter(): 还有一个filter()方法,也就是“过滤”。filter会返回一个调用该方法数组的一个子集,什么意思呢,就是说filter的参数是一个函数,该函数是用来逻辑判断的(类似于every和some的那种判定),如果判断结果返回true或者可以作为true值返回,那么就会成为这个子集中的一个元素。简单说就是,你(调用filter方法的数组中每一个元素)是否能通过我(filter的function方法)的判定,如果可以就会成为我(返回的新数组)的一员。 var nums = [1,2,3,4,5,6,7,8,9,10]; var newNums = nums.filter(function(item,index,arr){ return item > 5 }) console.log(newNums) //[6, 7, 8, 9, 10] 这里要注意的是,原数组仍旧是无法更改的,跟map一样。因为它有返回值,是通过返回值来组织新的数组的。 5、reduce() 英文的解释是缩减,刚好,咱们js中reduce方法差不多就是这个意思。该方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。 reduce的参数有点多,我还是这样写吧reduce(function(total,item,index,arr){},initalVal)。其中item,index,arr我就不说了,大家都知道是啥。total是啥呢?initalVal能猜到是什么么? var nums = [1,2,3,4,5,6,7,8,9,10]; var newNums = nums.reduce(function(total,item,index,arr){ return total + item*100 },100) console.log(newNums) //5600 total其实就是指你之前所累加的值,而initalVal就是你最初的total是多少,也就是你在即将开始reduce方法时初始值是多少(从什么值开始累加),可以不传或者传为0。那我就有点小问题了,reduce只能做数值的计算么?能不能加字符串,布尔值设置数组呢?咱们来小试一下。 var strs = ["a","b","c","d","e","f"]; var newStrs = strs.reduce(function(total,item,index){ return total + item + "-" }) console.log(newStrs) //ab-c-d-e-f- 哎?竟然会是这样的结果,那么咱们来看看为什么。首先咱们没有传initalVal,那么就是从strs的第一项开始加,total的第一个值为字符串”a“,而后面呢我们又return了item + ”-“,也就说是从数组的第二项开始得到了我们想要的结果,最后我们给数组的最后一个元素”f“,也加上了”-“,这就是为啥会有这样的结果的原因了。所以,如果大家确实一定不得不想要做这样的操作,请你用join方法! 那么,我们可不可以在数组中加入其它元素呢?这里不再赘述,你们要自己去试试噢。 6、toString(),把数组中所有元素作为字符串输出。 toString有点类似于join,但是join是数组方法,toString却适用于几乎所有的javascript对象。这里不多说,点一下就好。 7、valueOf() 与toString和join在数组中的使用方法是一样的,也同样是返回以逗号分隔的字符串对象。 但是这里不会多说但是会强调,toString和valueOf都不仅仅只是数组的方法,他们几乎适用于所有的原生JS对象。而且依照对象的不同会有不同的展现形式! 8、indexOf()和lastIndexOf() 这两个东西不太好解释,我看看怎么简单说明一下呢~~~。 indexOf是返回与参数匹配的第一个元素的索引,lastIndexOf返回与参数匹配的最后一个元素的索引。 var nums = [0,1,2,3,4,5,6,6,7,"a",9,10,{name:"zaking"},["b","c"]]; var a = nums.indexOf(7); var b = nums.lastIndexOf(7); var c = nums.indexOf(6); var d = nums.lastIndexOf(6); var e = nums.indexOf(100); var f = nums.lastIndexOf(100); console.log(a,b,c,d,e,f) //8 8 6 7 -1 -1 var x = nums.indexOf("a"); var y = nums.lastIndexOf("a"); console.log(x,y); //9 9 var m = nums.indexOf({name:"zaking"}); var n = nums.lastIndexOf({name:"zaking"}); console.log(m,n) //-1,-1 var i = nums.indexOf("b"); var j = nums.indexOf(["b","c"]) console.log(i,j) //-1,-1 其实可以说indexOf是从数组的头部开始搜索,搜索到了匹配的第一个值就停止搜索并返回该值的下标。而lastIndexOf则是从尾部开始,搜索到了第一个匹配的值就停止并返回该值的下标。 这里有一个令人不容易注意但是却十分容易混乱的事情,就是,无论从头还是从尾来搜索数组,数组的下标计算方式是永远不会变的,所以,不要认为从尾部搜索就是尾部的第一个数组元素下标为0,不是这样的! 那么再说,如果搜索的参数在数组中并不存在,那么则返回-1,两个方法都是一样的。 而且我们还可以从上面的简单测试代码中发现,这两个方法的参数只适用于基本类型,如果对数组中的引用类型元素进行索引查找是不可以也通常是不会应用的! 那么这篇文章就到这里了,简单介绍了ES5数组的几乎所有的方法,其实ES6对数组进行了不错的扩展并且新增了很多方法,但是这里不会多说,因为那样的话又会多出几篇的篇幅,还有一个原因就是阮一峰大神的ES6入门已经介绍的十分详细,大家如果有兴趣可以自行查阅。下一篇文章会介绍一下多维数组(也就二维三维...)和数组的简单排序。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢! 一只想要飞得更高的小菜鸟
在开始正式的内容之前,不得不说说js中的数据类型和数据结构,以及一些比较容易让人混淆的概念。那么为什么要从数组说起?数组在js中是最常见的内存数据结构,数组数据结构在js中拥有很多的方法,很多初学者记不清数组的大多数用法,只知道push,pop,shift等最基本的几个。所以,本系列(数组篇)会尽可能的让大家对数组有一个透彻的了解。也方便后面其他数据结构的学习和使用。 可能很多web前端开发者都会有一个疑问,那就是,数组和对象究竟是数据类型?还是数据结构?那么我们就带着这样的疑问,开始下面的学习,希望看完这篇文章之后,你模糊的概念会变得清晰一些。 首先,在js中,数据类型分为两种,基本类型(原始类型)和复杂类型,其中,基本类型是:String(字符串),Number(数值),Boolean(布尔值),还有undefined和null。复杂类型是Objecct(对象)。 说到这里大家可能会有些疑问,只有这六种类型?那数组(Array),正则(RegExp),日期(Date)算是什么?其实他们都是Object(对象)的一个分支,换句话说它们都属于Object类型,这也正是js与众不同的地方——万物皆对象。而后面要聊的包括队列,栈,链表,集合,树,图等数据结构在js中的展现方式,也都是通过对象和原型来实现的。本文无意去详细的描述数据类型和数据结构的种类以及在js中的体现形式。所以点到为止。 故事已经开始,请大家系好安全带,跟着我驰骋在在这篇广阔的土地上——数组。 先解释一下什么是数组吧,所谓数组,是有序的元素序列。 若将有限个类型相同的变量的集合命名,那么这个“名”称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。用于区分数组的各个元素的数字编号称为下标。数组是在程序设计中,为了处理方便, 把具有相同类型的若干元素按无序的形式组织起来的一种形式。这些无序排列的同类数据元素的集合称为数组。简单来说数组就是用于储存多个相同类型数据的集合。(当然,js中的数组也可以存储不同类型数据,但是!不建议这样做!) 一、数组的创建和初始化 相信很多小伙伴都知道创建一个数组十分容易: var arr = []; 这样我们就创建了一个数组,我们还可以用new关键字来创建并初始化一个数组: //创建一个空数组 var newArr = new Array(); //创建一个指定长度的数组 var newLenArr = new Array(4); //创建一个具有指定参数的数组 var numArr = new Array(1,2,3,4); 当然,通过new关键字创建并初始化数组的方式并不推荐,这里只是给大家介绍一下。其实我们通过上面第一种方式来创建数组的本质就是通过new来实例化一个Array对象。OK,这里不多说它的实现原理,还是回到数组本身来吧。 那么我们如何读取数组中的数据呢?很简单,我就一句话带过了,也就是通过中括号([ ])arr[2],来传递数值的位置,获取到对应位置的值,也可以通过这种方式来重新赋值。 二、数组的增删 接下来说说如何使用js数组自带的方法来实现数组头尾的增删:push(数组尾部插入元素),unshift(数组头部插入元素),pop(数组尾部删除元素)和shift(数组头部删除元素) 1、push方法 如果我不想使用push方法,有没有什么方式可以在数组的尾部插入一个元素呢?其实很简单,我们只需要把值赋给数组中最后一个空位上的元素就可以了。 var nums = [0,1,2,3,4]; nums[nums.length] = 5; 我们通过length属性,获取该数组的长度是5,但是我们数组对应的下标是从0开始的,通过这样的方式,也就给数组的尾部插入了一个新的元素。当然,其实我们可以更方便的使用push来给数组的尾部插入一个元素: var nums = [0,1,2,3,4]; nums.push(5); 也可以得到同样的结果。当然,push也可以传入多个参数,依次的从尾部插入数组: var nums = [0,1,2,3,4]; nums.push(5,6,6); //[0,1,2,3,4,5,6,6] 2、unshift方法 那么同样的,如何在不使用原生方法的前提下给数组的头部添加一个元素呢? var nums = [0,1,2,3,4,5,6]; for(var i = nums.length;i >= 0;i--){ nums[i] = nums[i - 1]; } //[undefined, 0, 1, 2, 3, 4, 5, 6] nums[0] = -1; //[-1, 0, 1, 2, 3, 4, 5, 6] 实际上,我们通过循环遍历,把nums数组中的每一位所对应的下标增加一个,也就是向后移动一位,那么这就导致了头部的位置空出(它的位置是存在的),但是此时我们并没有给空出的位置所对应的下标赋值,所以它的长度增加了值确实undefined,赋值之后,才会得到我们想要的结果。 下面我们还是用unshift方法来给数组的头部插入新值: var nums = [0,1,2,3,4,5]; nums.unshift(-1); //[-1, 0, 1, 2, 3, 4, 5] nums.unshift(-2,-3); //[-2, -3, -1, 0, 1, 2, 3, 4, 5] 那么要注意一点,在使用unshift传入多个参数的时候,他会把第一个参数放在数组的头部(以此类推),也就是说unshift方法会把所有的参数依照顺序插入数组,并不是我们想当然的那样从第一个参数依次添加进数组。 3、pop方法 如果我想要删除数组尾部的元素,我们可以使用pop方法,其实我们还是可以用js来模拟一下pop: var nums = [0,1,2,3,4,5]; nums.length = nums.length - 1; //[0, 1, 2, 3, 4] 我们可以通过手动让数组的长度减少一位,就可以实现删除数组尾部的元素,当然也可以减少两位三位等。 实际上,在日常开发中通常都会使用pop方法来删除数组尾部的元素(pop()方法没有参数,只是删除数组尾部的元素。): var nums = [0,1,2,3,4,5]; nums.pop() // [0, 1, 2, 3, 4] 4、shift方法 那么接下来我们看看如何从数组的首位删除元素: var nums = [0,1,2,3,4,5]; for(var i = 0; i < nums.length; i++) { nums[i] = nums[i + 1] } // [1, 2, 3, 4, 5, undefined] 可以看到,我们最后一位是undefined,也就是说在最后一次的循环里,i + 1引用了一个数组里还未初始化的位置(开辟了空间但是未赋值),所以,这样的方式只是依次覆盖了上一位的值,并没有真正的删除元素。如果想要删除首位的元素,这就需要用到shift方法了。 var nums = [0,1,2,3,4,5]; nums.shift(); // [1, 2, 3, 4, 5] 5、splice方法 最后,我们看看如何使用splice()方法,在数组的任意位置添加和删除元素: var nums = [0,1,2,3,4,5,6,7]; nums.splice(2); //[0, 1] //如果只加一个参数,说明删除从下标2开始的所有的后面的元素 var nums = [0,1,2,3,4,5,6,7]; nums.splice(2,1); //[0, 1, 3, 4, 5, 6, 7] //如果加入两个参数,则为删除从下标2开始的后面的几个元素。 var nums = [0,1,2,3,4,5,6,7]; nums.splice(2,1,"a","b","c"); //[0, 1, "a", "b", "c", 3, 4, 5, 6, 7] //三个或多个参数,意味着删除从下标2(第一个参数)开始的后面的1个(第二个参数)元素,并在下标2的后面加入从第三个参数开始的后面的所有参数,把第二个参数设置为0就可以不删除元素从而实现从任意位置添加元素 再多说一点,我们还可以使用delete操作符来删除数组中的元素,但是实际上,delete只是删除了对应下标上所存储的值,并没有同时把存储值得空间也删除掉,会导致对应位置上的值为undefined: var nums = [0,1,2,3,4,5,6,7]; delete nums[2]; //[0, 1, undefined, 3, 4, 5, 6, 7] splice()方法是修改了原数组的。 6、slice() slice()方法,会返回参数选定的范围的数组。该方法有两个参数,start(必选)和end(可选)。这两个值可以为负数,如果为负数则默认从尾部的第一个参数算起,也就是说-1就是数组的最后一个元素,-2就是数组的倒数第二个元素,以此类推。如果不传end,则默认从start开始直到数组最后一个元素都会被截取。 var nums = [0,1,2,3,4,5,6,6,7,"a",9,10,{name:"zaking"},["b","c"]]; var a = nums.slice(1,5); console.log(a);//[1, 2, 3, 4] var b = nums.slice(-1,5); console.log(b);//[] var c = nums.slice(-1,-5); console.log(c);//[] var d = nums.slice(-5,-1); console.log(d);// ["a", 9, 10, {…}] var f = nums.slice(5,1); console.log(f);//[] var x = nums.slice(5); console.log(x);// [5, 6, 6, 7, "a", 9, 10, {…}, Array(2)] var y = nums.slice(-5); console.log(y)//["a", 9, 10, {…}, Array(2)] 希望大家仔细看一下这个例子,当然,我还是一句话说明一下吧。 其实主旨就是,你所传的参数无论正负,参数所限定的范围必须是包含数组元素的。 那么数组的一些基本用法就先介绍到这里,后面应该还有两篇左右的长度来介绍数组。花费如此的篇幅,实在是数组真的极为重要,还请大家不要着急。当真正的学会了数组之后,再去看栈,队列这种数据结构,其实就很简单了。 最后,由于本人水平有限,能力与大神仍相差甚远,若有错误或不明之处,还望大家不吝赐教指正。非常感谢!一只想要飞得更高的小菜鸟
写这一系列的文章,本意是想要梳理一下自己凌乱的webpack知识,只是使用过vue-cli,修改过其中的一部分代码,但是对于一个简单项目从0开始搭建webpack的流程和其中的依赖并不是十分清楚。所以写了这样的一系列文章,一方面巩固自己的知识,一方面也可以给小伙伴们一点点入口。但是无奈运气不好,偏偏恰逢webpack4热火朝天的上线了,在webpack3时代的一部分使用方法已经没办法照葫芦画瓢了。所以一边查看文档和github,给大家找到了一些并不是很完美的解决方案。这是这个系列的最后一篇,说一说webpack一些可以优化的地方。也算是给这个系列一个6分的答卷吧。 先给大家介绍一个webpack自带的小插件,BannerPlugin,我们在前面的文章中说过,webpack自带的插件需要引入webpack,前面已经引入过了,这里就不再重复说明,然后我们只需要在plugins中加上一段代码就可以了: new webpack.BannerPlugin('author:zaking') 这样在你打包生成的文件中都会带上你的标记,主要的作用就是在工作中找到这么垃圾的代码是谁写的。 下面我们来看看如何将静态资源集中打包在一个文件夹下,这样会方便我们的开发和维护。想要将静态资源集中,我们需要一个插件copy-webpack-plugin。 如何安装和引入就不多说了,直接上代码: new copyWebpackPlugin([{ from:__dirname+'/src/public', to:'./public' }]) 安装和引入完成之后,在plugins配置中加入如上代码,也就是说从你的src/public目录下的所有文件都会打包到dist/public目录下,那么你现在如果打包的话会有报错,因为你的src下并没有public目录,所以我们新建一个目录,并且把src/images下的图片改个名字放进去,然后在打包就会发现完全没问题了。 那么下面再说说如何读取json文件,我们偷点懒,直接使用package.json,看看如何读取其中的内容。 首先,我们在index.html中新建一个div,名为jsonDIV: <!-- 读取json数据的容器 --> <div id="jsonDiv"></div> 然后再main.js中加入一段代码: //读取json var json =require('../package.json'); document.getElementById("jsonDiv").innerHTML= json.name; 然后我们npm run dev一下,就可以看到页面中已经显示了我们的项目名称,说明读取成功。 回头看了下这篇文章有点短小,但是想来想去实在不知道还有什么可以写在这系列的最后一篇文章中。可能有人会问,怎么webpack这么多的功能你没什么说的了?不是的,而是再说下去也是重复之前说过的,各种loader,各种插件,所以也就不说了。如果大家对webpack更多的功能和用法有兴趣,可以去查看官方文档,那里还是比较详细的。而目前webpack4的发布,使很多小伙伴都迫不及待地想要尝尝鲜,但是webpack3仍旧是当前的主流,而且4的文档并不全面,很多功能如果英文不太好的话还是很难的。所以个人建议大家在工作中没必要使用4。当然,想要课后学习一下是值得肯定的。 那么最后,非常感谢大家阅读!github代码已经同步更新!一只想要飞得更高的小菜鸟
正文之前,由于这是一个系列的文章,可能第一次看到的看官老爷们会觉得突兀,如果你是webpack新手,我建议你先从前几篇文章看起,如果你对webpack有一些了解,也希望可以在github上下载代码,对照着看会更有效果 在当代的前端开发中,很少会用原生JS来开发页面,最基本的都会使用jQuery来节省我们开发的时间和效率,而angular,vue,react的出现更是为前端开发者带来了福音。那么这篇文章就说说如何用webpack来打包引入第三方框架(类库)。如果单纯的引入jQuery或者其他第三方类库,在打包的时候webpack会把它一起打包进我们的main.js,也就说,如果我们引入两三个框架,两三个UI库,那么我们的集成包会很大,页面的加载时间也会很长,这是我们不愿见到的结果。所以在学会引入的同时,还要知道如何把第三方类库从我们的业务逻辑包中抽离出来。 那么我们先来安装一下jQuery,因为在生产环境我们仍旧是需要jQuery作为依赖的,所以我们需要用npm install jquery --save来安装它。安装完成之后,我们在main.js中增加两行代码: //引入jQuery import $ from 'jquery'; //编写jQuery代码 $('#jqueryDiv').html('Hello Zaking,jQuery is useful'); 我们还需要在index.html中增加一个div容器: <!-- jQuery代码容器 --> <div id="jqueryDiv"></div> 然后,我们npm run dev看一下,会发现页面中已经有jQuery代码所生成的文字,说明我们引入jQuery成功了。你可以在每一个需要jQuery的页面这样引入。但是这样会很麻烦,我们如果通过这样的方式引入了jQuery,但是整个项目中却并没有写一行jQuery代码,那么webpack也是会把它打包进去的。而且每一个需要jQuery的页面都需要引入的话好麻烦,那么我们可以使用ProvidePlugin来实现一次引入全局使用,而且通过插件的方式来引入第三方类库,如果你不使用它的话,webpack就不会打包它,还不错吧。由于ProvidePlugin是webpack自带的插件,我们不需要引入他,但是需要引入一下webpack: //引入webpack const webpack = require('webpack'); 我们还需要在入口文件引入jQuery,还记得前面模块化的时候把入口文件单独拆分成了一个entry.js,所以我们在其中增加一点代码,现在看起来应该是这样的: entry.path={ main:'./src/main.js', jquery:'jquery' } 然后我们在plugins项下配置一下这个插件,就像这样: //创建一个webpack下的ProvidePlugin插件的实例,使全局都可以使用jQuery new webpack.ProvidePlugin({ $:"jquery" }) 这样就可以了,但是别忘了把main.js中通过import引入的jQuery代码删除,然后再npm run dev试一下。会发现也同样出现了jQuery生成的文字。 那么下面我们就学习一下,如何抽离第三方类库,使其存储在一个单独的文件夹下,在webpack3时代,通常是使用CommonsChunkPlugin,CommonsChunkPlugin也是webpack自带的插件。官方文档是这样解释他的:CommonsChunkPlugin 插件,是一个可选的用于建立一个独立文件(又称作 chunk)的功能,这个文件包括多个入口chunk 的公共模块。通过将公共模块拆出来,最终合成的文件能够在最开始的时候加载一次,便存到缓存中供后续使用。这个带来速度上的提升,因为浏览器会迅速将公共的代码从缓存中取出来,而不是每次访问一个新页面时,再去加载一个更大的文件。 简单来说,就是将公共模块拆分出来以便使浏览器加载速度更快。但是在webpack4时代,已经取消了这个插件取而代之的是splitChunks 和runtimeChunk ,那么我们就来看一下,在webpack4的环境下,如何抽离多个第三方类库。 我们先来安装一下vue,跟jQuery的安装方式一样,就不多说了,同样的我们也需要在入口文件处引入vue: entry.path={ main:'./src/main.js', //引入jQuery和vue jquery:'jquery', vue:"vue" } 然后再ProvidePlugin中加入vue这个选项: new webpack.ProvidePlugin({ $:"jquery", vue:"vue" }) 然后我们在plugins中增加一个配置: new webpack.optimize.SplitChunksPlugin({ chunks: "all", minSize: 20000, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, name: true }) 然后我们npm run build一下,会发现在dist的根目录下已经生成了vue.js和jQuery.js。说明我们打包成功了。那么我们想要测试一下,vue究竟能用了么? 我们在main.js中引入vue,并且加上vue官网例子中最简单的一个: import Vue from 'vue' //vue代码 var app = new Vue({ el: '#vueApp', data: { message: 'Hello Vue!' } }) 然后,我们在index.html创建一个div容器: <!-- vue容器 --> <div id="vueApp"> {{message}} </div> 然后我们试一下npm run dev,页面打开之后我们发现没有hello vue,f12看下控制台,发现报错了。不出意外的话,你的报错信息是这样的: 什么意思呢,官方解释是:运行时构建不包含模板编译器,因此不支持 template 选项,只能用 render 选项,但即使使用运行时构建,在单文件组件中也依然可以写模板,因为单文件组件的模板会在构建时预编译为 render 函数。就是说,如果我们想使用template,我们不能直接在客户端使用npm install之后的vue。 那么我们需要新增一点配置: resolve: { alias: { 'vue$': 'vue/dist/vue.esm.js' } } //resolve是在最外层的,与plugins等同级 这样就可以了,然后运行一下,会发现Hello Vue已经出现在我们的页面之中了。这两天一直加班,所以更新的有点慢,这个系列的文章还有最后一篇,尽量在这两天就可以完成。一只想要飞得更高的小菜鸟
初级的文章和demo已经基本完成了,代码也已经上传到了我的github上,如果你对webpack的使用并不是十分了解,那么建议你回头看下走近系列,里面包括了当前项目中使用频繁的插件,loader的讲解。以及基本的webpack配置,相关依赖等。如果你已经有了一定的webpack使用经验。那么你直接看这篇文章也是完全没问题的。 这一系列会着重讲解webpack的进阶使用方法,前面文章讲解过的一些部分,就不会再去重复的解释。 那么,还是先交代一下环境以及目录结构,这些你可以直接从github上获取到: 下面是当前的环境配置版本: 在实际的工作当中,我们会区分不同的环境来执行不同的webpack配置代码,以实现不同环境的要求,当前的主要环境其实就两个,一个开发环境,一个生产环境。开发环境更倾向于便捷的调试,开发的方便,比如热加载等。而生产环境希望代码的体积更小,http请求更少,页面的加载速度更快。甚至有些时候两个环境的要求是互斥的。所以才需要根据不同的环境来配置不同的代码。 废话不多说,咱们直接进入正题吧。 在前面的文章中,为了使静态资源找到正确的路径,我们设置了一个变量webpath,那么如果要区分环境,变量的值肯定是不同的,那么我们如何根据命令来使webpath获得不同的值呢,其实很简单: 修改一下package.json中的build命令和dev命令,加上一个参数,然后我们在webpack.config.js中可以通过process.env来获取到这个参数。这样就可以区分不同的环境了。 "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "set type=build&webpack --mode production", "dev": "set type=dev&webpack-dev-server --mode development", "version": "webpack -v" } 那么修改完package.json中的命令后,我们还需要修改一下webpack.config.js中webpath那段代码: /*根据参数的不同来区分不同的环境*/ if(process.env.type == "build"){ var webpath={ /*这个地址目前是随便写的,只是为了区别于开发环境,真实上线的话要改成你上线的地址*/ publicPath:"http://www.zaking.com/" } }else{ var webpath={ publicPath:"http://192.168.199.124:9090/" } } 这样就可以了,运行不同的命令来试试效果如何吧。这里简单说明一下,process.env是什么,process是node的一个全局环境变量,process.env.type也就是你设置在scripts命令中的type值。更详细的内容不在这里多说,有强迫症的小伙伴可以去看看这里:https://nodejs.org/dist/latest-v8.x/docs/api/process.html#process_process_env。 那么环境拆分说完了,如何模块化配置webpack呢?其实也很简单,听起来比较高逼格罢了。简单说就是把通用变量放在一个单独的js文件中,然后通过export暴露接口,require引入接口而已!比如你在使用vue中一定写过很多这种东西,再简单也要说一下滴。 我们新建一个与webpack.config.js同级的文件夹,名字就叫做entry.js(入口)。然后我们在entry.js中写入入口配置的代码: const entry ={}; //声明路径属性 entry.path={ main:'./src/main.js' } //导出该变量 module.exports = entry; 然后在webpack.config.js中引入该模块,并且修改下入口处的配置代码: /*在这里引入entry文件的路径*/ const entry = require("./entry.js"); /*入口文件*/ entry:entry.path 这样就实现了所谓的模块化,当然这里只是举一个简单的例子,复杂的配置项目可能会有逻辑复杂的模块化配置。比如vue-cli那样的,现在你再去看看vue-cli的代码,应该也可以看懂一些了,只是它的功能更为复杂,模块的关联更强。那么这篇文章就暂时写到这里。下一篇会带大家一起看看如何打包第三方类库等更贴近生活的实用技能。本篇文章的代码也已经同步更新到github上了,以后随着文章的更新会实时同步代码,方便大家学习。一只想要飞得更高的小菜鸟
这一章咱们来说一下如何使用babel以及如何用webpack调试代码。这是基础篇的最后一章,这些文章只是罗列的给大家讲解了在一些场景中webpack怎样使用,这章结束后会给大家讲解一下如何在我们实际的开发及上线的工作环境中自如的使用webpack。 既然我们要使用babel,那babel是什么呢?一句话,babel能让你使用当前浏览器还暂时或者无法支持的“js”,比如es6,es7,JSX等。 那么来安装一下吧: npm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react /*babel-core是babel的核心功能包,babel-loader就不用说了,转换目标代码的babel包,babel-preset-env也就是转换es6语法的包,babel-preset-react就是转换JSX的包*/ 安装完成之后,我们需要在loader中配置一下: { test:/\.(jsx|js)$/, use:{ loader:'babel-loader', options:{ presets:[ "env","react" ] } }, exclude:/node_modules/ } 让我们简单修改一下src/entry.js中的代码,就改成这样吧: let name = "Hello zaking" document.getElementById('title').innerHTML=name; npm run server,发现完全没问题。 那么做一个小小的修改,我们在根目录下新建一个.babelrc文件,其实babel的配置项很多,为了让config.js看起来更清爽,咱们有关于babel的配置都写在这里(只是目前咱们没用到多少)。 .babelrc的内容这样写: { "presets":["react","env"] } 然后修改一下config.js中的babel-loader配置: { test:/\.(jsx|js)$/, use:{ loader:'babel-loader' }, exclude:/node_modules/ } /*也就是删除了options配置项*/ 修改完成之后,我们再来npm run server试一下。没问题! 那么打包完成之后我们开发的时候如何调试代码呢?我们可以通过配置devtool生成map,来使调试变得更简单。map其实就是源文件和打包后生成文件的一种映射。 在配置devtool时,webpack给我们提供了四种选项。 source-map:在一个单独文件中产生一个完整且功能完全的文件。这个文件具有最好的source map,但是它会减慢打包速度; cheap-module-source-map:在一个单独的文件中产生一个不带列映射的map,不带列映射提高了打包速度,但是也使得浏览器开发者工具只能对应到具体的行,不能对应到具体的列(符号),会对调试造成不便。 eval-source-map:使用eval打包源文件模块,在同一个文件中生产干净的完整版的sourcemap,但是对打包后输出的JS文件的执行具有性能和安全的隐患。在开发阶段这是一个非常好的选项,在生产阶段则一定要不开启这个选项。 cheap-module-eval-source-map:这是在打包文件时最快的生产source map的方法,生产的 Source map 会和打包后的JavaScript文件同行显示,没有影射列,和eval-source-map选项具有相似的缺点。 四种打包模式,有上到下打包速度越来越快,不过同时也具有越来越多的负面作用,较快的打包速度的后果就是对执行和调试有一定的影响。 (更多配置信息可以查看webpack文档:https://doc.webpack-china.org/configuration/devtool/) 个人意见是,如果大型项目可以使用source-map,如果是中小型项目使用eval-source-map就完全可以应对,需要强调说明的是,source map只适用于开发阶段,上线前记得修改这些调试设置。 使用方法也非常简单,只要在webpack.config.js中加一个配置项就可以了: 记住一个很必要的事情,就是如果你使用了uglifyjs-webpack-plugin,记得在该插件的配置中写入sourceMap:true字段。这样才可以正常的生成sourceMap,详情请查看https://github.com/webpack-contrib/uglifyjs-webpack-plugin OK,至此,webpack的基本配置和使用方法就讲解完了。这个项目的相关demo已经上传到我的github。大家可以查阅学习。但是我还是建议大家一定要自己跟着教程多练习。不然是没什么实际效果的。一只想要飞得更高的小菜鸟
我们前面已经学了很多webpack基本的处理情况,一句话总结就是,一个优秀的webpack项目,主要的核心用法就是整合loader和plugin去处理你想要的任何需求。 下面,咱们一起来学学如何用webpack来处理less,sass等预编译器。先看看如何用webpack处理less。 国际惯例,第一步是安装: npm install less less-loader --save-dev 第二步,配置loader项: { test: /\.less$/, use: [{ loader: "style-loader" },{ loader: "css-loader" },{ loader: "less-loader" }] } 最后,咱们来写一个less试试,直接在src/css/目录下,新建一个pink.less文件并写一些代码: @base :pink; #lessDiv{ width:300px; height:300px; background-color:@base; } 还要在src/index.html中加入一个id名为#lessDiv的div,最重要的一点,不要忘了在src/entry.js中引入pink.less,引入的方法跟引入css文件是一样的。 不知道大家还记不记的咱们在处理css文件的时候做过一件事情,也就是把css从js中分离出来,因为webpack打包是把css打包进js的,所以我们修改一下loader的配置方式,跟前面的css是一样的,这里不再赘述,直接上代码: { test: /\.less$/, use: extractTextPlugin.extract({ use: [{ loader: "css-loader" }, { loader: "less-loader" }], fallback: "style-loader" }) } 你可以试试,这两种代码打包之后会有什么样的区别,OK,我们npm run server一下,会发现页面中已经出现了一个粉色的盒子。 那么说完了less,继续说一下sass。仍旧是安装,配置。。。不多说,直接上代码: npm install node-sass --save-dev npm install sass-loader --save-dev /*如果安装报错,可以试一下cnpm或者分开单独安装*/ 配置loader: { test: /\.scss$/, use: [{ loader: "style-loader" // creates style nodes from JS strings }, { loader: "css-loader" // translates CSS into CommonJS }, { loader: "sass-loader" // compiles Sass to CSS }] } 跟less一样编写一个scss文件写入sass代码并且在entry.js中引入,别忘了在index.html中写个div: // css/blue.scss $color: blue; #sassDiv { $width: 100%; width: $width; height:200px; background-color: $color; } // index.html加入 <div id="sassDiv"></div> //entry.js中引入 import sass from './css/blue.scss'; 同样的,把sass从js中分离,修改loader配置: { test: /\.scss$/, use: extractTextPlugin.extract({ use: [{ loader: "css-loader" }, { loader: "sass-loader" }], fallback: "style-loader" }) } npm run server后发现蓝色的div已经出现。其实大家可以自己去试着写一下sass的配置。因为跟less甚至之前的css几乎是一摸一样的。没有什么大的区别。那么下面咱们一起看看css3属性自动加上前缀,极大的方便我们的开发。 自动加前缀,我们需要用到postCss和autoprefixer,那么安装一下吧: npm install --save-dev postcss-loader autoprefixer 我们新建一个postcss.config.js,与webpack.config.js同级,并且写入相关代码,也就是引入autoprefixer: module.exports = { plugins: [ require('autoprefixer') ] } 然后把我们之前写过的css-loader的配置修改一下: { test: /\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader", options: { modules: true } }, { loader: "postcss-loader" } ] } 分离: { test: /\.css$/, use: extractTextPlugin.extract({ fallback: 'style-loader', use: [ { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader' ] }) } 那么,我们随便在index.css中加上点css3的样式试试吧。至此,当你npm run build 之后多半会发现既不报错,但是index.css中也没有加上相应的前缀。那么你需要在package.json中写入如下配置: "browserslist": [ "> 1%", "last 2 versions", "not ie <= 8" ] 什么意思呢?也就是全球浏览器使用率大于1%,最新的两个版本并且是ie8以上的浏览器。还有更多的参数可以查看这里https://github.com/ai/browserslist#queries。 增加了这个配置之后,再打包就完全没问题啦。 那么我们下面学习一下如何消除未使用的css,在实际项目中,我们可能会引入很多样式库,组件库,或者随着项目需求的增加,我们可能会添加一些新的css,而以前的css又不知道有没有用,也不敢去动,害怕牵一发而动全身。所以我们可以使用一个插件,在打包的时候自动去除未使用的css样式: /*PurifyCSS-webpack要依赖于purify-css这个包,所以这两个都安装一下,-D是--save-dev的简写,i是install的简写*/ npm i -D purifycss-webpack purify-css 然后我们引入glod和purifycss-webpack插件: const glob = require('glob'); const PurifyCSSPlugin = require("purifycss-webpack"); /*引入插件就不多说了,glob是node的一个对象,我们需要在检查html模板以确定是否用到了css的时候用到glob*/ 然后,在plugins里这样配置: new PurifyCSSPlugin({ paths: glob.sync(path.join(__dirname, 'src/*.html')), }) /*glob.sync同步获取指定文件夹下的文件,这里的意思就是获取src下的所有html文件。有关node的知识不在这里多说,有兴趣的大家可以自行去学习*/ 配置成功,我们在src/index.css中写点没用的代码试试吧。发现打包后的css中并没有我们新加的没用的代码,成功!
上一章,咱们学了如何用webpack来打包css,压缩js等。这一篇文章咱们来学习一下如何用webpack来处理图片。废话不多说,咱们开始吧。 首先,咱们随便找一张你喜欢的图片放到src/images目录下,然后把图片设置为背景图片,代码是这个样子。 src/index.html: <div id="title"></div> <div id="name"></div> <div id="img"></div> <script src="./entry1.js"></script> <script src="./entry2.js"></script> src/css/index.css: body{ background:red; } #title{ background:orange; color:blue; } #img{ background: url(../images/dog.jpg); width: 500px; height: 500px; } ok,我们写完了代码,现在webpack是无法解析的,我们可以打包试一下,发现会报错。嗯,没有加入loader肯定会报错的! 那么,接下来我们来安装一下打包图片需要用到的loader: npm install --save-dev file-loader url-loader 在等待安装之际,我们先解释一下这两个loader分别都是做什么的: file-loader:解决引用路径的问题,拿background样式用url引入背景图来说,我们都知道,webpack最终会将各个模块打包成一个文件,因此我们样式中的url路径是相对入口html页面的,而不是相对于原始css文件所在的路径的。这就会导致图片引入失败。这个问题是用file-loader解决的,file-loader可以解析项目中的url引入(不仅限于css),根据我们的配置,将图片拷贝到相应的路径,再根据我们的配置,修改打包后文件引用路径,使之指向正确的文件。 url-loader:如果图片较多,会发很多http请求,会降低页面性能。这个问题可以通过url-loader解决。url-loader会将引入的图片编码,生成dataURl。相当于把图片数据翻译成一串字符。再把这串字符打包到文件中,最终只需要引入这个文件就能访问图片了。当然,如果图片较大,编码会消耗性能。因此url-loader提供了一个limit参数,小于limit字节的文件会被转为DataURl,大于limit的还会使用file-loader进行copy。其实简单来说,url-loader的作用就是根据配置来判断图片是否需要转换成字符串编码,来使项目的性能和速度更快。 那么,接下来我们在module中配置一下loader,现在我们的module看起来是这样的,其中limit参数就是用来判断需要把图片转换成字符串编码的大小范围,单位是B。 module:{ rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] },{ test:/\.(png|jpg|gif)/ , use:[{ loader:'url-loader', options:{ limit:500000 } }] } ] }, 唉?不对啊,你安装了两个loader,为什么只用了url-loader啊?!因为url-loader内置了file-loader。这里安装file-loader是为了方便下面用到的时候不用再安装了。 OK,咱们来打包一下看看会发生什么吧。打开页面发现我们引入的图片已经显示了。 下面说一下怎么把css从js中分离出来,我们上面的css都是通过import引入到js中再进行打包的,这样不利于维护,也违背了js,css,html互相分离的基本原则。但是一旦分离了css,那么原本的图片路径就会出现问题。我们下面来解决一下。但是webpack官方认为css就应该打包进js中以减少http请求。所以,仁者见仁智者见智,怎么做看具体情况来选择吧。 其实要解决这个问题很简单,用插件,extract-text-webpack-plugin。怎么安装就不说了,已经说了好多遍了,跟上面的安装方法一样,改个名字而已。 既然是插件,我们就需要在config中引入并且new一个实例之后才可以使用: module:{ rules: [ { test: /\.css$/, /*修改了一下使用的方式*/ use: extractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) },{ test:/\.(png|jpg|gif)/ , use:[{ loader:'url-loader', options:{ limit:500000 } }] } ] }, //插件,用于生产模版和各项功能 plugins:[ new uglify(), new htmlPlugin({ minify:{ removeAttributeQuotes:true }, hash:true, template:'./src/index.html' }), /*这里new了一个插件的实例*/ new extractTextPlugin("css/index.css") ], ok,我们兴致勃勃地去打包一下,竟然报错了,报错的主要原因是extract-text-webpack-plugin当前还没有支持webpack4.x,那么我们该怎么办呢?别急,咱们想想办法解决: npm install --save-dev extract-text-webpack-plugin@next /*更新版本*/ 这样就可以打包了,但是我们打包完成之后发现index.html并没有引入相应的css,页面没有任何css,别急,我们来进行下一步解决这个问题。 我们在config中定义一个路径变量: var webpath={ publicPath:"http://192.168.199.124:9090/" } /*就是你在devServer中定义的host和端口*/ 然后在output属性中定义一个publicPath属性: output:{ path:path.resolve(__dirname,'dist'), filename:'[name].js', publicPath:webpath.publicPath }, 并且要把之前配置在devServer中的host修改成你自己的本机host,我的是http://192.168.199.124,如果你不知道,可以在cmd中使用ipconfig来查询。 然后,我们npm run server一下,发现打开的页面已经有图片和样式了。 那么我们就学会了如何处理css中的图片问题,下面我们学习一下如何处理html中的图片(也是用插件,各种插件,你可以去github随便找一个你喜欢的可以处理这中问题类型的插件): 这里我们使用html-withimg-loader,然后在config中如下配置: npm install html-withimg-loader --save /*因为在生产环节中也需要用到,所以使用--save命令*/ { test: /\.(htm|html)$/i, use:[ 'html-withimg-loader'] } 完事了。。。。。。简单吧。 前边的内容,打包后的图片并没有放到images文件夹下,要放到images文件夹下,其实只需要配置我们的url-loader选项就可以了。前面也说到了,要限制limit的大小,不然图片在小于limit的范围内会进行转码,咱们来小小的修改下代码就可以了: { test:/\.(png|jpg|gif)/ , use:[{ loader:'url-loader', options:{ limit:500, outputPath:'images/' } }] } 我们在src/index.html中加入一个图片的引入: <div id="title"></div> <div id="name"></div> <div id="img"></div> <!-- 通过src引入图片 --> <img id="htmlImg" src="./images/dog.jpg"> 然后在src/css/index.css下写上这样的css: body{ background:red; } #title{ background:orange; color:blue; } #img{ background: url(../images/dog.jpg); width: 500px; height: 500px; } /*设置图片大小*/ #htmlImg{ width: 500px; height: 500px; } 然后我们npm run server一下看看结果吧。这里说明一下,可能细心的小伙伴会问,为什么以前打包用npm run build 而这里用npm run server呢? 通常情况下,我们有两种环境,一种是dev,一种是prod,也就是开发环境和生产环境,一般在开发环境下,我们本地会通过webpack-dev-server通过express起一个node服务器,而不是真正的打包,而我们以前所一起学习的webpack使用方法即包含了开发环境下的需求,又有生产环境下的内容,当我们要把整个项目打包上线的时候,我们会压缩js,压缩图片,整合资源以减少http请求,但是我们在开发环境下的时候,往往需要更多的功能以使代码更容易阅读和debug。再有就是,我们在引入图片的时候,用的是绝对地址,也就是node起服务器后你本地的ip地址,如果不通过npm run server起本地服务器,是无法找到图片的。有兴趣的小伙伴可以试试npm run build然后手动打开dist下的html看看效果。 至此,图片的处理方式就结束了。下一章咱们来看看怎么处理less啊,sass这样的css预编译语言,毕竟现在很少用css来写样式了。 一只想要飞得更高的小菜鸟
前面的文章介绍了webpack的devServer以及多入口多出口文件的配置,咱们继续往下学。 在开始学习接下来的知识之前,我们先回顾一下,前文提到了webpack的简单配置方法,但是只详细说了下入口和出口文件的配置,并没有更多的去解释其他选项的配置,比如loader,plugin等。那么咱们现在就来学一下module的配置。前面说过,module的主要作用就是通过loaders来配置,解析,转换不同类型的模块。比如说,可以把less,sass转换成css,可以把es6甚至es7语法转换成大部分浏览器可以运行的js代码。所有的loaders都需要在npm中单独安装并且在module中配置后才可以使用。loader的主要配置只有test和use两种,简单来说就是。你要匹配的文件是什么,用test来过滤。用use来确定你要用什么loader来转换你匹配到的文件。下面咱们开始第一个loader的使用。 首先我们来安装两个loader,css-loader和style-loader。运行如下代码: npm install style-loader css-loader --save-dev 其中,style-loader主要用于将css插入到页面的style标签中等。css-loader主要用于处理css中的url()。 然后我们查看package.json中的devDependecies中多了两个配置项,也就是我们安装的css-loader和style-loader说明安装成功。 然后,我们在src文件夹下新建一个css文件夹,并且新建一个index.css文件。 此时,你的文件目录结构应该是这样的: 在文件中我们写上如下的代码。 body{ background:red; } #title{ background:orange; color:blue; } 只是这样还不行,我们需要在src/entry.js中引入这个css文件,代码如下: import idxcss from './css/index.css' 最后一步,也是最重要的一步,我们在webpack.config.js中的module中配置一下我们已经安装好的loader: module:{ rules: [ { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] } ] }, 然后,让我们npm run build一下!打开index.html,我们发现css已经被写入了。 下面我们介绍一下loader的其他几种写法,意思都一样,你们喜欢哪个用哪个。 /*第一种写法*/ module:{ rules:[ { test:/\.css$/, use:['style-loader','css-loader'] } ] } /*第二种写法*/ module:{ rules:[ { test:/\.css$/, loader:['style-loader','css-loader'] } ] } /*第三种写法*/ module:{ rules:[ { test:/\.css$/, use: [ { loader: "style-loader" }, { loader: "css-loader" } ] } ] } ok,下面我们来学一下如何压缩JS,我们需要用到uglifyjs-webpack-plugin,一个压缩JS的插件,没错,插件,plugins。那么看一下我们如何使用他呢。 uglifyjs-webpack-plugin已经集成在webpack中,所以我们不用下载安装了,直接在config.js中引入: const uglify = require('uglifyjs-webpack-plugin'); 然后在module的plugin选项下new一个uglify就可以了。 下面我们npm run build一下就打包成功了。JS压缩通常都是用在生产环境中的。下面来看看html文件是如何打包的。 先把dist目录下的index.html复制到src目录下,然后把dist目录下的文件都删除。 html文件的打包需要用到另一个插件,html-webpack-plugin。我们先安装一下吧: npm install --save-dev html-webpack-plugin 安装完成之后,我们需要引入这个插件,所有的插件使用都是这三步,安装(除了webpack集成的可以省去安装这一步),引入,配置。 然后,我们需要在plugins下配置一下这个插件。代码如下: new htmlPlugin({ /*压缩文件,removeAttributeQuotes指去掉属性的双引号,目前你随便不用也行*/ minify:{ removeAttributeQuotes:true }, /*加入hash值,为了避免浏览器缓存js*/ hash:true, /*要打包的html文件的路径及名称*/ template:'./src/index.html' }) 配置完成,npm run build一下会发现dist目录下已经生成了三个文件。至此我们就学会了打包css,压缩js和打包生成html文件。如果稍微细心一点你会发现,其实webpack主要的作用就在于loader和plugin,也正是如此,webpack才有了它多样化个性化的配置方法。下一篇文章,我们一起学一下如何用webpack来处理图片。 一只想要飞得更高的小菜鸟
上一篇文章留下了一些问题,如果你没看过上一篇文章,可以在我的博客里查找,或者直接从这篇文章开始也是没问题的。 const path = require('path'); module.exports={ entry:{ entry:'./src/entry.js' }, output:{ path:path.resolve(__dirname,'dist'), /*上面的代码中还有path以及__dirname的意思就是指获取到当前项目的绝对路径,涉及到了node.js的相关知识,这里不做过多的解释,有兴趣的小伙伴可以自己去找一下资料。*/ filename:'[name].js' /*这里name的意思就是指获取入口文件的名称用来作为出口文件的名称,这样你就不用有几个入口就写几个出口名称啦*/ }, module:{}, plugins:[], devServer:{} } 这是上一篇文章中使用但是没有详细讲解的代码片段。现在我们来一一做一个了解。 entry:配置入口文件,也就是你想要打包的文件路径。可以是单一的,也可以是多入口文件。下面会详细的讲解。‘ output:配置出口文件,也就是你想要在那个文件夹下面生成打包后的文件。同样的,既然入口可以是多文件,那么出口也可以是多出口的。 module:模块配置,主要用于一些loader的使用,用于转换编译less,sass,图片等文件。 plugins:配置插件,如果想要更多的功能满足项目的需求,那么你需要使用到插件。 devServer:配置开发服务功能,后面会更详细的介绍。 那么简单配置介绍完了,下面我们一起看一下多入口以及多出口文件是如何应用的 首先我们在dist目录下的index.html做一下简单的修改,我们引入另外一个名称为entry2.js的文件,并且把body中的代码修改成下面的这个样子。 <div id="title"></div> <div id="name"></div> <script src="./entry1.js"></script> <script src="./entry2.js"></script> 然后,我们在src目录下新建一个entry2.js,里面写上这样的代码。 document.getElementById('name').innerHTML="我是入口2的文件哦,看我打包成功了没?"; 第三步,在webpack.config.js中entry的配置项下增加一个入口链接, entry:{ entry:'./src/entry.js', entry2:'./src/entry2.js', //这里我们又引入了一个入口文件 } OK,至此我们的项目就修改完成,试一下在命令行中输入npm run build之后的结果吧。 此时我们的目录结构看起来是这样的: 在浏览器中打开index.html,会发现出现了两句话。那么就说明打包成功咯。 至此,你已经学会了入口出口,以及多入口多出口文件的配置。接下来我们来学一下devServer能做些什么,要怎么做。 首先我们来安装一下webpack-dev-server, 运行下面的命令,等待完成就可以了,完成后你会看到package.json中devDependencies项下面多了一个webpack-dev-server及其相应的版本号。 npm install webpack-dev-server --save-dev 那么,webpack-dev-server究竟是用来做什么的呢?它主要是启动了一个使用express的http服务器,用来伺服资源文件。 OK,安装完成之后,我们需要在webpack.config.js的devServer选项中写几行简单的代码: /*设置基本目录结构,也就是你想要使用服务的目录地址*/ contentBase:path.resolve(__dirname,'dist'), /*服务器的IP地址,可以使用IP也可以使用localhost*/ host:'localhost', /*服务端压缩是否开启,目前开不开都行,想关你就关*/ compress:true, /*配置服务端口号,建议别用80,很容易被占用,你要是非要用也是可以的。*/ port:9090 然后,在package.js中的scripts选项中再配置一个命令脚本,它看来应该是这样子滴: 好了,现在该写的代码都写完了,咱们来看看效果。 在命令行中输入npm run server,唉?!有趣的事情发生了,竟然报错了。错误说让你安装webpack-cli,行吧,那么咱们再安装一下webpack-cli。 npm install webpack-cli -D 安装完webpack-cli,我们兴致勃勃地去npm run server,发现又TM错了,咋个回事。 先别急,我们来看一下报错信息: 不想看的小伙伴可以不看,这都什么啊!在你安装完webpack-cli后,会提示你一些安装包(webpack-cli,webpack-dev-server等)需要webpack版本在4.0以上,如果你不当回事继续npm run server,就会报错了。回头看一下发现咱们的webpack版本是3.6.0。有码为证,确实是。 既然是这样那就升级一下webpack版本吧。咱们直接在上面的代码上修改,当前最新版本的webpack是4.1.1。咱们就用最新的!改好了之后是这样滴! 咱们再npm install重新安装一下。 安装完成,咱们再!我保证最后一次!npm run server一下下。 我想这样子应该是成功了。咱们打开浏览器输入http://localhost:9090/看一下,呦呵!出来了。 看起来好像不错的样子,我们在src下的随便一个entry里面修改两句代码,发现已经可以热更新咯。一只想要飞得更高的小菜鸟
在前端工作的过程中,只要你接触过vue,angular,react,gulp就一定知道webpack或者听说过或者使用过webpack,但是或许你对webpack的使用方法并不是十分了解,只是会用写好的构建项目如:vue-cli,angular-cli等,那么这一系列的文章就是带你一点点走近webpack,真正弄懂webpack到底可以做什么,怎么做。 在真正的正文开始之前,我们先做一个小demo,就像所有的故事背景那样,这篇文章也作为该系列的故事背景。废话不多说,咱们开始吧。 一、安装(环境) 首先要安装node.js,这里就不多说怎么安装啦,去官网下载,然后就像安装其他软件那样安装它,git也是一样,安装好了试试下面的命令。 $ npm -v 4.2.0 $ node -v v7.8.0 接下来,我们开始安装webpack,首先webpack可以全局安装,也可以在你的项目下安装。但是你会发现,所有的文档甚至官方说明都不推荐全局安装webpack,因为全局安装会把webpack锁定到指定的版本,无法自由的根据情况去选择webpack版本,也有可能你从git上clone的webpack版本与你本地安装的版本不同,会产生意想不到的问题。 那么,如何安装webpack呢?代码如下: npm install webpack -g /*全局安装最新版本的webpack*/ npm install webpack --save-dev /*安装最新版本的webpack到当前项目*/ npm install webpack@3.6.0 --save-dev /*安装指定的webpack版本到当前项目*/ 在安装webpack到本地之前,需要做一个重要的操作,也就是 npm init 这样做是为了生成package.json,记录你当前项目所用到的依赖及一些其他相关信息。 OK,至此,我们的webpack就已经安装成功了。当前的webpack版本已经到了4.1.1,咱们暂时先使用npm install webpack@3.6.0 --save-dev命令。安装指定版本的webpack,后面必要的时候会升级webpack版本。 那么,接下来,我们开始我们第一个webpack打包demo。 我们先创建两个文件夹,分别命名为dist和src。 当前的目录结构,看起来应该是这样的: 然后,我们在index.html中写上如下的代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>zaking webpack</title> </head> <body> <div id="title"></div> <script src="./entry.js"></script> </body> </html> entry.js中的代码比较简单: document.getElementById('title').innerHTML="Hello webpack,I'am Zaking"; 然后在package.json中加入如下的命令脚本: 在git命令行中输入npm run xx,你会发现dist目录下多了一个entry.js文件。然后用浏览器打开index.html,你会发现写在html文档可以正常打开并且运行。 这里的xx是随便输入的,npm run xx的意思就是执行xx所对应的脚本语句,通常会写为build、start、dev等具有语义的命令,这里是为了让大家方便理解,并不是一定要写死。 那这句脚本语句的意思就是打包src/entry.js文件到dist/entry.js文件。 至此,你已经学会了最基本的webpack打包方式,但是你一定会问,要是webpack只是这样用的话多麻烦。根本没有达到我们想要的自动化,热加载等使项目开发方便,打包灵活,减少http请求等优化项目的目的。 是的,上面的打包方式在我们的实际开发和应用中基本上是不会用到的。写在这里,只是为了让大家有一个简单清晰的认识。那么让我们来看看最简单的wbepack项目应该是什么样子。 目录结构不变,我们在根目录下新建一个文件,叫做webpack.config.js,他就是webpack的配置文件。 现在,你的目录看起来应该是这个样子:哦对了,别忘了把dist/entry.js删除,等下还要生成这个文件的。 我们在webpack.config.js写上如下代码: const path = require('path'); module.exports={ entry:{ entry:'./src/entry.js' }, output:{ path:path.resolve(__dirname,'dist'), filename:'[name].js' }, module:{}, plugins:[], devServer:{} } 然后,我们在package.json的脚本语句那里写上 然后,在命令行中执行 npm run build,神奇的事情出现了。 > test@1.0.0 build C:\Users\lenovo\Desktop\test > webpack Hash: c39ddabfd4bb98624a2e Version: webpack 3.6.0 Time: 52ms Asset Size Chunks Chunk Names entry.js 2.54 kB 0 [emitted] entry [0] ./src/entry.js 71 bytes {0} [built] 出现了这样的信息,说明你的文件打包成功。 OK,至此,本文就结束了,哦你可能会问webpack.config.js的那些js是什么意思。下一章我们再一起探究他具体的含义。并且下一章会真正的带大家走近webpack。一只想要飞得更高的小菜鸟
该部分主要为CSS3新增的选择器 接上一篇 CSS(CSS3)选择器(1) 一.通用兄弟选择器: 24:E ~ F,匹配任何E元素之后的同级F元素。 div ~ p{ background-color:#00FF00; } 二.属性选择器: 25:E[att ^= val],匹配属性att的值以”val“开头的元素。 [id ^= start]{ background-color:red; ] /*匹配以id属性的值为start开头的,如id="start1",id="start2",id="start3"的元素*/ 26:E[att $= val],匹配属性att的值以”val“结尾的元素。 [id $= end]{ background-color:red; ] /*匹配以id属性的值为end结尾的,如id="1end",id="2end",id="3end"的元素*/ 27:E[att *= val],匹配属性att的值包含”val“字符串的元素。 [id $= hass]{ background-color:red; ] /*匹配以id属性的值包含hass的,如id="1hass",id="hass2",id="3hass444"的元素*/ 三.结构性伪类选择器: 28:E:root,匹配文档的根元素,对于HTML文档,就是HTML元素。(也就是说可能存在其他文档形式时使用,选中的是该文档类型的根元素) :root{ background:red; } /*经测试,像div:root这样的写法是无效的*/ 29:E:not,匹配不符合当前选择器的任何元素。 h1:not(.name) { color: red; } /*其含义是,匹配所有h1元素的类名不为name的h1元素,如果:not选择器前面不带指明的元素是无效的*/ 30:E:empty,匹配一个不包含任何子元素的元素,包括文本节点。 .box:empty{ background:pink; } 31:E:target,匹配文档中特定”id“,点击后的效果。 :target{ background: red; } /*通常用于锚点定位后,定位的目标点样式*/ 32:E:last-child,匹配父元素的最后一个子元素。 li:last-child{ background-color:red; } 33:E:nth-child(n),匹配其父元素的第n个子元素,从1开始。 li:nth-child(2){ background-color:red; } li:nth-child(odd){ background-color:red; } 34:E:nth-last-child(n),匹配其父元素的倒数第n个子元素,倒数第一个的index为1。 li:nth-last-child(2){ background-color:red; } li:nth-last-child(even){ background-color:red; } 35:E:nth-of-type(n),与:nth-child()作用类似,但是仅匹配同类型的元素。 h2:nth-of-type(odd){ background:red; } 36:E:nth-last-of-type(n),与:nth-last-child() 作用类似,但是仅匹配同类型的元素。 h2:nth-last-of-type(even){ background:red; } 37:E:first-of-type,匹配父元素下使用同种标签的第一个子元素。 h2:first-of-type{ background-color: yellow; } 38:E:last-of-type,匹配父元素下使用同种标签的最后一个子元素。 h2:last-of-type{ background-color: yellow; } 39:E:only-child,匹配父元素下仅有的一个子元素,等同于:first-child:last-child或 :nth-child(1):nth-last-child(1)。 li:only-child{ background-color: yellow; } 40:E:only-of-type,匹配父元素下使用同种标签的唯一一个子元素,等同于:first-of-type:last-of-type或 :nth-of-type(1):nth-last-of-type(1)。 li:only-of-type{ background-color: yellow; } 四.UI元素状态伪类选择器: 41:E:enabled,匹配表单中激活的元素。 input[type="text"]:enabled{ background-color:yellow; } 42:E:disabled,匹配表单中禁用的元素。 input[type="text"]:disabled{ background-color:purple; } 43:E:read-only,指定当元素处于只读状态时的样式。 input[type="text"]:read-only{ background-color: gray; } 44:E:read-write,指定当元素处于非只读状态时的样式。 input[type="text"]:read-write{ background-color: greenyellow; } 45:E:checked,匹配表单中被选中的radio(单选框)或checkbox(复选框)元素。 input[type="checkbox"]:checked { outline:2px solid blue; } 46:E:default,指定但页面打开时默认处于选取状态的单选框或复选框控件的样式。需要注意的是,即使用户将该单选框或复选框的选取状态设定为非选取状态,该样式仍然有效。 input[type="checkbox"]:default { outline:2px solid blue; } 47:E:indeterminate,指定当页面打开时,一组单选框中没有任何一个单选框被设定为选取状态时,整组单选框的样式,如果用户选取了任何一个单选框,那么该样式则取消。 input[type="radio"]:indeterminate{ outline: solid 3px blue; } 48:E::selection,用来指定该元素处于选中状态时的样式。 p::selection{ background:red; color:#FFF; } input[type="text"]::selection{ background:gray; color:#FFF; } 49:E:invalid,用来指定元素的内容无法通过H5元素的属性所指定的检查(required)或元素的内容不符合规定的格式(type=Email等)。 input[type="text"]:invalid{ background-color: red; } 50:E:valid,用来指定元素的内容可以通过H5元素的属性所指定的检查(required)或元素的内容不符合规定的格式(type=Email等)。 input[type="text"]:valid{ background-color: white; } 51:E:required,用来指定允许使用required属性,并且已经指定了required属性的input,select,textarea元素的样式。 input[type="text"]:required{ border-color: red; border-width:3px; } 52:E:optional,用来指定允许使用required属性,并且未指定了required属性的input,select,textarea元素的样式。 input[type="text"]:optional{ border-color: black; border-width:3px; } 53:E:in-range,用来指定当元素的有效值被限定在一定范围之内(通常通过min属性值或者max属性值来限定),且实际输入值在该范围内时使用的样式。 input[type="number"]:in-range{ background-color: white; } 54:E:out-of-range,用来指定当元素的有效值被限定在一定范围之内(通常通过min属性值或者max属性值来限定),且实际输入值不在该范围内时使用的样式。 input[type="number"]:out-of-range{ background-color: red; } 55:E::placeholder,用来改变文字占位符的样式。 input::placeholder{ color:red; } 至此,CSS(CSS3)选择器的简单说明笔记就到这里结束了,其实这些内容包含了CSS(CSS3)世界的绝大多数常用选择器,当然,还有些不常用的如果大家有兴趣,可以自行搜索资料。 参考:css选择器笔记,30个你必须熟记的css选择器,MDN-docs-选择器介绍,HTML5和CSS3权威指南(第3版下册-庐陵牛)第十九章,before和after伪元素的用法。一只想要飞得更高的小菜鸟
这篇文章主要用于存储CSS以及CSS3的选择器部分知识,以便日后查阅及记忆. 该内容分为两部分,第一部分为css选择器的一些基本知识。第二部分为CSS3新增加的选择器。 在开始之前,先简单介绍一下选择器,选择器的作用就是定位我们想要样式化的网页HTML元素。选择器可以分为以下几种类型。 1、简单选择器,通过元素类型,class或id匹配一个或多个元素。 2、属性选择器,通过属性/属性值 匹配一个或多个元素。 3、伪类,匹配处于确定状态的一个或多个元素。(比如鼠标指针悬停的元素、当前被选中或未被选中的复选框、元素是DOM树中一父节点的第一个子节点等) 4、伪元素,匹配处于相关的确定位置的一个或多个元素。(例如每个段落的第一个字,或者某个元素之前生成的内容) 5、组合器,这里不仅仅是选择器本身,还有以有效的方式组合两个或者更多的选择器用于非常特定的选择的方法。 6、多用选择器,这些也不是单独的选择器。这个思路是将以逗号分隔开的多个选择器放在一个CSS规则下面, 以将一组声明应用于由这些选择器选择的所有元素。 那么下面就具体的选择器来一一做一定的解释,有不足的地方还希望不吝赐教。嘿嘿。 一.基本选择器: 1:*,通配符,匹配所有元素。 *{ padding:0; margin:0; } /*个人建议,请不要在你的CSS代码中出现通配符,通配符(*)是一种效率极低甚至会有在大型网站中使用导致页面渲染变慢的可能。所以,请尽可能的不要使用。*/ 2:#id,id选择器,匹配所有id属性为"id"的元素,id属性具有唯一性。 #text { font-size: 16px; } /*一个ID名称在文件中必须是唯一的,若是ID名称重复,则可能会出现不可预知的情况,所以一定要避免ID名称的重复*/ 3:.class,class选择器,匹配所有class属性中包含"class"的元素。文档中的多个元素可以具有相同的类名,而单个元素可以有多个类名(以空格分开多个类名的形式书写) .demo{ width: 100px; height: 100px; background: red; } 4:E(element),标签选择器,匹配所有使用E标签的元素。 span{ color: red; background: pink; font-style: 20px; } 二.多元素组合选择器: 5:E,F,多元素选择器,匹配所有E元素和F元素,E和F之间用逗号(,)分隔。 span,p,div{ color: red; background: pink; font-style: 20px; } 6:E F,后代元素选择器,匹配所有属于E元素后代的F元素,E和F之间用空格分隔。 .demo .text{ color: gray; } 7:E > F,子元素选择器,匹配所有E元素的子元素F。 .demo>.text{ color: gray; } 8:E + F,毗邻元素选择器,匹配所有紧随E元素之后的同级元素F。 .demo + .text{ color: gray; } 三.属性选择器: 9:E[att],匹配所有具有att属性的E元素,不考虑它的值。(E在此处可以省略,如“[checked]”,下同) [title]{ color:red; } 10:E[att=val],匹配所有att属性等于"val"的元素。 [title=name]{ border:5px solid blue; } img[title=bgimg]{ width:100px; height:100px; } 11:E[att~=val],匹配所有att属性具有多个空格分隔的值、其中一个值等于"val"的E元素。 [title~=hello]{ color:red; } 12:E[att |= val],匹配所有att属性具有多个连字号分隔(hyphen-separated)的值、其中一个值以"val"开头的E元素。(主要用于lang属性,比如"en"、"en-us"、"en-gb"等等) [lang|=en] { color:red; } 四.伪类选择器: 13:E:first-child,匹配父元素的第一个子元素。 .demo:first-child{ color: pink; } 14:E:link,匹配所有未被点击的链接。 .demo:link{ font-weight: 900; } 15:E:visited,匹配所有已被点击的链接。 .demo:visited{ font-weight: 900; } 16:E:active,匹配鼠标已经在其上按下,还没有释放的E元素。 .demo:active{ font-weight: 900; } 17:E:hover,匹配鼠标悬停其上的E元素。 .demo:hover{ color: orange; font-weight:900; } 18:E:focus,匹配获得当前焦点的E元素。 input:focus{ color:red; } 19:E:lang(c),匹配lang属性等于c的元素。 html:lang(zh){ color:lime; background:red; } :lang(en) > span{ color:pink; } 五.伪元素选择器: 20:E:first-line,匹配E元素的第一行。 .demo:first-line{ color:red; } 21:E:first-letter,匹配E元素的第一个字母。 .demo:first-letter{ font-weight: border; } 22:E:before,在E元素之前插入生成的内容。 .num:before{ content:"(" attr(href) ")", } 23:E:after,在E元素之后插入生成的内容。 .clearfix:after { content: ""; display: block; height: 0; clear: both; } /*after清除浮动,这里只对after和before的选择器写法做一个简单说明,并不具体涉及属性及其用法*/ 最后想说,这些东西只是简单的罗列和解释,算是方便在使用的时候快速查找,如果想要更深入的了解还需要的更多的练习和代码量,就算是最基本的选择器知识,其实也要比这些多得多。只是还有很多用处并不是特别多,比如上面的E:lang(c),E:visited,E:active等,还有很多未写在文章内的,如果想要更深入的学习,大家可以去下面的相关链接查看。 参考:css选择器笔记,30个你必须熟记的css选择器,MDN-docs-选择器介绍,HTML5和CSS3权威指南(第3版下册-庐陵牛)第十九章,before和after伪元素的用法。 一只想要飞得更高的小菜鸟