HTTP 牌的 Cookie

今天要介绍的 Cookie ,不是皇冠丹麦牌的哦,而是我们 HTTP 牌的~

HTTP Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。

由上面的介绍,提炼成简短的一句话就是——Cookie是一段数据MDN 的介绍已非常清晰易懂。目前使用最广泛的是由网景公司在其制定的标准上进行扩展后的产物。

能吃吗?

HTTP Cookie 当然不能吃,但是!它可以用于识别用户身份记录用户操作历史

  • 识别用户身份
    例如,当用户登录一个网站时,如果用户需要记住登录,那么服务器就会发送一个包含此用户登录凭证(用户名和密码的某种加密形式)的 Cookie 到用户本地(将会被存储在计算机的硬盘中),那么在 Cookie 的有效期内再次访问这个网站,浏览器就会自动带上这个 Cookie 去访问服务器,服务器识别到这个 Cookie ,就可以免去用户登录这个操作。
  • 记录操作历史
    假设现有一个用户在网上购物,由于 HTTP 协议是无状态的,即服务器不知道用户上一次做了什么,如果不依靠其他的手段,服务器是无法记录到用户买了什么的,而 Cookie 就可以弥补这一缺陷——用户每选购一个商品,服务器就可以向用户发送一段 Cookie ,记录那个商品的信息。这样每当用户访问新的商品页面,浏览器就会把 Cookie 发给服务器,服务器在这个 Cookie 里追加信息。最后,结账的时候,服务器读取接受到的 Cookie 就可以知道用户一共选购了什么商品。

MDN 中详细描述了 Cookie 的使用场景:

  • 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
  • 个性化设置(如用户自定义设置、主题等)
  • 浏览器行为跟踪(如跟踪分析用户行为等)

有什么口味?

没有榛仁味,也没有果干味。按它的储存位置,可以分为内存Cookie硬盘Cookie

  • 内存 Cookie 由浏览器维护,保存在内存中,浏览器关闭后就消失了,其存在时间是短暂的。
  • 硬盘 Cookie 保存在硬盘里,有一个过期时间,除非用户手动清理或到了过期时间,硬盘Cookie不会被删除,其存在时间是长期的。

所以,按存在时间,还可分为会话期Cookie持久性Cookie

  • 会话期 Cookie 不需要指定过期时间( Expires )或者有效期( Max-Age )。需要注意的是,有些浏览器提供了会话恢复功能,这种情况下即使关闭了浏览器,会话期 Cookie 也会被保留下来,就好像浏览器从来没有关闭一样。
  • 持久性 Cookie 可以指定一个特定的过期时间( Expires )或有效期( Max-Age )。

制作的关键工序

  • 后端人员在响应头中设置 Cookie
    Node.js 下的后端代码如下:
    1
    response.setHeader("Set-Cookie", "user=caijialinxx@foxmail.com");
    如果要设置多个 Cookies ,那么使用数组包含每一个元素即可,即:
    1
    response.setHeader("Set-Cookie", ["user=caijialinxx@foxmail.com", "language=javascript"]);
  • 用户在浏览器中修改 Cookie
    一些浏览器自带或安装开发者工具允许用户查看、修改或删除特定网站的 Cookies 信息。这也带来了一个问题,用户可以伪造 Cookie 来操作网页。

保质期多久?

后端开发人员可以加多点防腐剂,让 Cookie 的有效期长一点,也可以让它几分钟之后就过期,取决于后端人员如何设置。一般默认的有效期是20分钟,每个浏览器的默认时长可能不同。

这个防腐剂就是上面提到的 Expires 或者 Max-Age 属性。

  • Expires 属性
    它可以指定一个具体的到期时间,到了指定时间之后浏览器就不会再保存这个 Cookie 。例如:
    1
    response.setHeader("Set-Cookie", "user=caijialinxx@foxmail.com; Expires=Thu, 12 Jul 2018 02:54:00 GMT")
    那么,可以看到收到的响应头中含有
    1
    Set-Cookie: user=caijialinxx@foxmail.com; Expires=Thu, 12 Jul 2018 04:00:00 GMT
    响应头中的Set-Cookie字段
    且新增了这条 Cookie 如下图所示:
    设置了Expires的Cookie
    但是由于这个时间是依赖本地时间来决定 Cookie 具体何时过期,所以没有办法保证 Cookie 一定会在服务器指定的时间过期。
  • Max-Age 属性
    它可以规定从现在开始,在指定的时间后 Cookie 到期,单位为秒。例如:
    1
    response.setHeader("Set-Cookie", "user=caijialinxx@foxmail.com; Max-Age=60")
    那么,可以看到收到的响应头中含有
    1
    Set-Cookie: user=caijialinxx@foxmail.com; Max-Age=60
    响应头中的Set-Cookie字段
    我们可以看到 Cookie 在 2018-07-12T03:30:24.539Z 后过期,这个时间是根据我们启动服务器后,增加60s后得到的时间。
    a1d3d7198a21adbb2c8248a80f20590.png

食品安全认证

在第4节中我们知道了,用户可以借助浏览器的开发者工具修改 Cookie ,那么这意味着我们可以篡改网站的 Cookie 如下图所示:
在开发者工具中读写Cookie
具体的危害可以查看偷窃Cookies和脚本攻击——维基百科
为了避免这种情况,我们可以添加 HttpOnly 标记:

  • HttpOnly 可以使 Cookie 无法通过 JavaScript 的 document.cookie 读写。
    1
    response.setHeader("Set-Cookie", "user=caijialinxx@foxmail.com; HttpOnly")
    响应头中含有
    1
    Set-Cookie: user=caijialinxx@foxmail.com; HttpOnly
    但可以在 Application 中更改,如下图所示。
    标记了HTTPOnly后Cookie仍可被更改

此外,还有 Secure 标记:

  • Secure 可以使 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务器,如果不是 HTTPS 协议,那么浏览器将不会接受这个 Cookie 。
    1
    response.setHeader("Set-Cookie", "user=caijialinxx@foxmail.com; Secure")
    响应头中含有
    1
    Set-Cookie: user=caijialinxx@foxmail.com; Secure
    响应头返回的Set-Cookie中含有Secure标记
    因为此网站是 http://localhost:8888 ,所以在请求头中浏览器无法发送 Cookie 。
    请求头中无Cookie字段
    但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为Cookie有其固有的不安全性,Secure 标记也无法提供确实的安全保障。从 Chrome 52 和 Firefox 52 开始,不安全的站点(http:)无法使用 Cookie 的 Secure 标记。

它的不美味之处

  • Cookie 会被附加在每个 HTTP 请求中,所以无形中增加了流量,带来额外的开销。
  • 由于在 HTTP 请求中的 Cookie 是明文传递的,所以安全性成问题,除非用 HTTPS 。
  • Cookie 的大小一般限制在 4KB 左右,对于复杂的存储需求来说是不够用的。
  • 识别不精确, Cookie 只能定位到用户、浏览器和计算机这三者的组合,一旦有一个不同, Cookie 的内容就会不同或者是存储位置不一样。
  • 不准确的状态——浏览器的“回退”按键会使 Cookie 无法记录上一次的用户操作。
  • Cookies 在某种程度上说已经严重危及用户的隐私和安全,它会被利用于投放广告。

总结

  1. Cookie 是一段数据,它由服务器发送给浏览器并保存到本地,存储着用户的一些信息(如用户登录状态、个性化设置等),在此浏览器下一次访问此服务器时,这段 Cookie 会被附加在 HTTP 请求中发送给服务器。
  2. Cookie 的大小一般为 4KB ,默认有效期一般为 20mins ,但可以通过 Expires (UTC格式)或 Max-Age (秒数)来设置其过期时间。
  3. Node.js 设置 Cookie 的 API : response.setHeader("Set-Cookie", "name=value")
  4. 浏览器读写 Cookie 的 API : document.cookie 。若要阻止用户通过 JavaScript 来读写 Cookie ,只需要添加 HttpOnly 标记。
  5. Secure 标记可以使 Cookie 只能通过 HTTPS 加密过的请求发送服务器,如果不是 HTTPS 请求,浏览器会自动忽略。
  6. 由于 Cookie 会被附加在 HTTP 请求中,且是明文传递,所以会增加流量以及有安全问题。建议使用 Web storage API ( sessionStoragelocalStorage )或 IndexedDB 。

扩展阅读

在这篇笔记写到一半的时候,我看到了 MDN 的介绍,我觉得它写得已经很好了,一度有不想写直接抛出链接的想法……..
然鹅,这篇笔记可是我自己消化的东西啊。不管怎么样,都还是应该要自己写出来。只不过我还是会把 MDN 的链接放在下面作为扩展阅读,当然还有其他一些优秀的博客笔记。

MVC 是什么?

MVC 是什么

我说

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

这是来自百度百科的介绍。看上面这段话,我帮你们提取出关键字,再翻译成我们能理解的话,就是:

MVC 是一种代码组织形式,它可以将代码按照 Model (数据层)、 View (视图层)和 Controller (控制层)这三种形式组织代码,每层互相独立、各司其职,却又相互联系、相互依赖。

这样是不是好理解多了,那么这三层的作用如下所述:

  • Model 负责数据管理,包括数据逻辑、数据请求、数据存储等功能。例如,前端的 Model 主要负责 AJAX 请求或 LocalStorage 的存储
  • View 负责用户界面的呈现。例如,前端的 View 主要负责 HTML 页面的渲染
  • Controller 负责处理 View 层的事件,并更新 Model 中的数据;也负责监听 Model 层的数据变化,并及时更新到 View 层。

代码说

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
let model = {
data: null,
init() {
// model 的初始化,一般是要与数据库建立连接,所以包含一些验证信息(id, key)
},
fetch() {
// 获取数据库中的 data(s)
},
add() {
// 向数据库中添加 data(s)
},
update() {
// 更新数据库中的 data(s)
},
delete() {
// 删除数据库中的 data(s)
}
}

let view = {
init() {
// view 获取需要被操作的对象,供 controller 操作
}
}

let controller = {
view: null,
model: null,
init(view, model) {
// controller 初始化本地 view 或 model ,并执行相关逻辑操作
this.view = view
this.model = model
this.bindEvents()
},
bindEvents() {
// 处理 view 层的事件,如果数据有变化就更新 model 层
},
render() {
// 监听 model 层的数据变化,更新到 view 层
}
}

controller.init(view, model)

图说

由上面的文字和代码,我们可以已经可以知道 ModelViewController 三者的关系了。再用图总结一下,就如下所示:
图说MVC.png

MVC 有什么用

WHAT!? 上面的例子还不能让你知道 MVC 的好处吗?

想象一下,你要做了一个网页应用,某一天,某个功能不够好想完善,或者出了 BUG ,假设你把所有 JS 代码全部集中在一个文件里,而且没有整理代码的习惯,是的,你需要在几百行甚至上千行的代码中找到那个功能的代码,………….(不如先给自己准备一瓶眼药水)

好了,不废话了,在你的代码量大的时候,使用 MVC 来组织封装代码,那么在你要找代码的时候,就可以快速定位了。

例如,你想改变某个控件在被点击时的动画效果——好的,这是 Controller 层的事,那么直接找到 Controller 层中那个控件的 click 事件做修改就可以了;

或者你发现同一个文档中有两个拥有相同 id 的元素,你已经在 HMTL 中改正,现在只需要去对应的 JS 中改变 View 层中使用错误 id 获取的这个元素的正确 id 即可。

…被我绕晕了吗?相信聪明的你,可以看懂,哈哈哈哈哈。

总结(其实可以直接看这个)

  1. MVC 是一种
    • 按照 ModelViewController 三层结构
    • 来组织代码
    • 的形式 / 理念 / 典范
  2. 它可以
    • 提高代码的可重用性
    • 减少三层结构之间的互相干扰
    • 提高程序的可扩展性和可维护性

本文到此结束,谢谢观赏,敬请勘误。
E-mail: caijialinxx@foxmail.com

一些常见的 HTTP 状态码

1xx Informational responses

  • 100 Continue
    服务器已接收到请求报头,客户端应继续发送请求报文。
  • 101 Switching Protocols
    服务器已根据客户端的请求切换协议。

2xx Success

  • 200 OK
    请求已成功。
  • 201 Created
    请求已完成,新的资源已被创建,且其 URI 已随着响应行返回。
  • 202 Accepted
    请求已被接受处理,但处理尚未完成。该请求最终可能会也可能不会被执行。该状态码在异步操作的情况下很方便。
  • 204 No Content
    服务器已成功处理请求,没有返回任何实体内容。
  • 205 Reset Content
    服务器已成功处理请求,且没有返回任何内容。但与 204 不同的是,包含此状态码的响应要求请求者重置文档视图。

3xx Redirection

  • 301 Moved Permanently
    被请求的资源被永久移动到返回的新的 URI 中。
  • 302 Found
    告诉客户端浏览另一个 URL 。 302 已被 303307 所取代。
  • 303 See Other
    使用 GET 方法在另一个 URI 下找到请求对应的响应。
  • 304 Not Modified
    所请求的资源未修改,服务器返回此状态码时,表示已执行 GET 请求,且由于文件无变化于是不会返回任何资源。
  • 305 Use Proxy
    请求的资源只能通过代理服务器获得,响应中提供其代理服务器的地址。
  • 307 Temporary Redirect
    请求暂时由另一个 URI 响应,而未来的请求应仍继续使用原始的 URI 。

4xx Client errors

  • 400 Bad Request
    服务器无法处理请求,因为客户端的请求有明显的错误(例如请求语法错误、请求过大等)。
  • 401 Unauthorized
    当前请求未通过认证。响应必须包含一个适用于被请求资源的 WWW-Authenticate 信息头来询问用户信息。
  • 403 Forbidden
    请求已被理解但是服务器拒绝执行。与 401 不同的是,用户无法通过身份验证来得到请求资源的许可。
  • 404 Not Found
    请求的资源目前无法被找到。
  • 405 Method Not Allowed
    请求的方法无法支持请求的资源。例如,一个本应通过 POST 请求来获取数据的表单使用了 GET 请求(a GET request on a form that requires data to be presented via POST),或者是一个只读的资源使用了 PUT 请求(or a PUT request on a read-only resource)。该响应必须返回一个 Allow 头信息用以表示出当前资源能够接受的请求方法的列表。
  • 406 Not Acceptable
    请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体。
  • 410 Gone
    被请求的资源在服务器上已不再可用,并且没有任何已知的转发地址。
  • 422 Unprocessable Entity
    请求的格式是正确的,但是由于语义错误,服务器无法响应。

5xx Server errors

  • 500 Internal Server Error
    服务器内部错误,无法完成请求。一般来说,这个问题会在服务器端的源代码错误时出现。
  • 502 Bad Gateway
    服务器充当网关或代理时,收到来自上游服务器的无效响应。
  • 503 Service Unavailable
    服务器当前不可用,原因可能是服务器超载或正在维护。
  • 504 Gateway Timeout
    服务器充当网关或代理时,没有及时收到来自上游服务器的响应。

本文完。

参考资料:List of HTTP status codes —— 维基百科

浏览器的同源策略及规避方法

同源策略

In computing, the same-origin policy is an important concept in the web application security model. Under the policy, a web browser permits scripts contained in a first web page to access data in a second web page, but only if both web pages have the same origin.

这是来自维基百科的解释。大意就是两个网页在相同的来源的前提下,允许A网页包含的脚本访问B网页的数据。这保证了用户的信息安全,防止某个网站的恶意脚本通过操作 DOM 来获取到敏感数据。

同源的条件

所谓的“同源”,需要满足三个条件:

  1. 协议相同
  2. 域名相同
  3. 端口相同

只有同时满足了这三个条件,才能是同源。拿 http://www.example.com/dir/page.html 来举个例子,它的协议是 http:// ,域名是 www.example.com ,端口号是默认的 80 ,那么以下例子中

1
2
3
4
5
http://www.example.com/dir2/other.html  (同源)
http://username:password@www.example.com/dir2/other.html (同源)
https://www.example.com/dir/other.html (不同源,协议不同)
http://example.com/dir/other.html (不同源,域名不同)
http://www.example.com:81/dir/other.html (不同源,端口号不同)

有同学可能会说 https://baidu.comhttps://www.baidu.com 访问结果相同啊,为什么是会域名不同呢?注意,域名相同是指完全相同而不是相似,baidu.com 是一级域名(此说法有误,但绝大部分人都这么叫,《计算机网络》中将其解释为二级域名),而 www.baidu.com 为二级域名(若前一个是二级域名那么这个对应的就是三级域名),当然是不同的两个域名。访问结果相同是因为百度公司设置的 DNS 解析的关系,这篇文章或许能够解答你,但不能因为结果相同就说它们是一样的。不信可以去访问一下 http://www.12306.cnhttp://12306.cn ,就能让你“眼见为实”了。

带来的问题

  1. 一级域名相同,只是二级域名不同的同一所有者的网页被限制(Cookie、LocalStorage、IndexDB的读取)
  2. 无法跨域发送 AJAX 请求
  3. 无法操作 DOM
  • Q:为什么 Form 表单可以跨域发送请求,而 AJAX 不可以。

    A:因为 Form 表单提交之后会刷新页面,所以即使跨域了也无法获取到数据,所以浏览器认为这个是安全的。而 AJAX 最大的优点就是在不重新加载整个页面的情况下,更新部分网页内容。如果让它跨域,则可以读取到目标 URL 的私密信息,这将会变得非常危险,所以浏览器是不允许 AJAX 跨域发送请求的。

规避方法

  1. document.domain属性
  2. CORS
  3. JSONP
  4. Cross-document messaging
  5. WebSocket

document.domain

假设你已登录并正浏览腾讯网 http://www.qq.com/ ,此时你突然想看腾讯视频,于是你点击腾讯网提供的快捷链接“视频”,浏览器加载 https://v.qq.com/ 。你想查看自己以往的观看记录,因为这两个网页的二级域名不同,v.qq.com 无法获取到你在 www.qq.com 的登录信息,所以无法查看观看记录,于是你不得不重新登录一次。
这一趟操作下来,是不是很不人性化?如果能避免这个同源限制,直接读取 Cookie 自动登录就好了。腾讯当然也是这么想的。

If two windows (or frames) contain scripts that set domain to the same value, the same-origin policy is relaxed for these two windows, and each window can interact with the other. – document.domain property, Wiki

从上面的例子中,我们只要将这两个网页的设置相同的域名,即可共享 Cookie 。即

1
document.domain = 'qq.com'

CORS

This standard extends HTTP with a new Origin request header and a new Access-Control-Allow-Origin response header. – Cross-Origin Resource Sharing, Wiki

跨源资源共享(Cross-Origin Resource Sharing,简称 CORS ),它是 W3C 标准,通过一个新的 Origin 请求头和一个新的 Access-Control-Allow-Origin 响应头来扩展 HTTP ,即可解决 AJAX 跨源请求的问题。
假设 http://www.qq.com 要向 https://v.qq.com 发送 AJAX 请求,那么他们需要这样设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* http://www.qq.com 前端发起 AJAX 请求 */
let req = new XMLHttpRequest()
req.onreadystatechange = () => {
if(req.readyState === 4) {
if(req.status >=200 && req.statue < 400) {
console.log('success')
} else {
console.log('failed')
}
}
}
req.open('GET', 'https://v.qq.com')
req.send()
1
2
3
4
/* https://v.qq.com 被请求的后台 */
...
响应.setHeader('Access-Control-Allow-Origin', 'http://www.qq.com')
...

那么我们在 http://www.qq.com 下中打开 Chrome 的开发者工具的 Network ,即可看到 https://v.qq.com 的请求状态码为 200 ,同时请求头中添加了:

1
Origin: http://www.qq.com

以及响应头中添加了:

1
Access-Control-Allow-Origin: http://www.qq.com

JSONP

JSONP 是一种使用模式,它通过动态创建 <script> 标签来向跨源网址发送请求,其中这个请求的查询字符串中含有 callback 参数,用来指定回调函数的名字。服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* http://example1.com 发送请求 */
let script = document.createElement('script')
let functionName = `c${parseInt(Math.random() * 10000, 10)}`

window[functionName] = (res) => {
alert(res)
}

script.src = `http://example2.com/?callback=${functionName}`
script.onload = (e) => {
e.target.remove()
delete window[functionName]
}
script.onerror = (e) => {
e.target.remove()
delete window[functionName]
}

document.body.appendChild(script)
1
2
3
4
5
6
7
8
/* http://example2.com 发送响应 */
...
let callbackName = 请求方URL.callback
响应.statusCode = 200
响应.setHeader('Content-Type', 'application/javascript')
响应.write(`${callbackName}.call(undefined, "Success")`)
响应.end()
...

请求方 http://example1.com 动态创建 <script> 之后,其 src 属性指向被请求方的路径 http://example2.com 并在尾部加上 callback 参数,如 ?callback=xxx 。被请求方获取到请求方的 callback 参数,并发回响应构造函数调用,如 xxx.call(undefined, 'yyy') 。那么请求方将会弹窗显示“yyy”。

Cross-document messaging

Cross-document messaging allows a script from one page to pass textual messages to a script on another page regardless of the script origins… A script in one page still cannot directly access methods or variables in the other page, but they can communicate safely through this message-passing technique. – Cross-document messaging, Wiki

跨文档通信(Cross-document messaging),维基百科解释得很清楚了,它允许一个网页的脚本向另一个网页传递文本消息,尽管他们不同源。不过它们也只能通过此技术安全地传递文本消息而已,仍无法直接访问另一个页面的方法和变量。
这是 HTML5 引入的新 API ,它为 window 对象新增了 window.postMessage() 方法。它允许用户跨窗口通信,不管是否同源。

例子如下:
我在父窗口 JS Bin 中打开一个新窗口,子窗口的 URL 指向百度 https://www.baidu.com ,并且在百度页面的控制台中定义了 window.onmessage 事件,使其输出 event 对象。
在jsbin的代码.png

1
2
<button id='btn'>postMessage</button>
<script src="//code.jquery.com/jquery-2.1.1.min.js"></script>
1
2
3
4
5
6
7
8
9
// 父窗口 http://js.jirengu.com/wirixavoxo/1/edit?js,output
var x = window.open('https://www.baidu.com', 'baidu');
$(btn).click(function() {
x.postMessage('from father', 'https://www.baidu.com');
});

window.addEventListener('message', function(e) {
console.log(e);
}, false);
1
2
3
4
// 子窗口 https://www.baidu.com/
window.addEventListener('message', function(e) {
console.log(e)
}, false)

当我点击父窗口中的按钮时,触发其 click 事件,向子窗口发送了一条消息,在子窗口监听 message 事件的结果如下图所示:
在百度中监听的message事件结果.png
我们可以看到 e.data 就是父窗口中发送的消息 "from father"e.origin 为父窗口的 URI "http://js.jirengu.com" ,且 e.type"message"

然后我在子窗口中分别声明了 String 变量、 Function 变量和 Object 变量,其中 Object 变量是获取了百度页面的搜索按钮。根据结果可以发现,在子窗口定义的 Function 类型和 HTMLElement 类型(即使这个HTML元素没有被插入到页面中)不被允许发送,根据控制台的报错提示,是因为它们“不能被克隆”。
在百度中尝试给jsbin发送消息.png

1
2
3
4
5
6
7
8
9
10
11
// 子窗口 https://www.baidu.com/
var str = 'this is a string!'
window.opener.postMessage(str, 'http://js.jirengu.com/wirixavoxo/1/edit?js,output')

var func = function() {
console.log('this is a function')
}
window.opener.postMessage(func, 'http://js.jirengu.com/wirixavoxo/1/edit?js,output')

var btn = document.getElementById('su')
window.opener.postMessage(btn, 'http://js.jirengu.com/wirixavoxo/1/edit?js,output')

相应地,在父窗口中我们可以看到结果也是只显示了 str 这个变量的输出结果。e.origin 是子窗口 "https://www.baidu.com"
在jsbin中监听的message事件结果.png
若想知道其他类型的输出结果,请自行运行测试哦。有些结果我没有放出来,但结论已经在上面了。再阐述一遍就是:

  1. Function 类型无法传输
  2. HTMLElement 类型,无论是获取页面的元素,还是自行新创建但还没被插入到页面的,也无法传输
  3. 只要是能传输的类型,无论你是 StringNumberundefinednullBooleanObject 等类型,传到父窗口后查看 typeof e.data ,结果都是 string 。至于为什么,我也不清楚,我猜想是 message 事件中对那些数据类型进行了强制转换,或者是因为这个功能还在开发中,所以这个 API 并不成熟。

WebSocket

Modern browsers will permit a script to connect to a WebSocket address without applying the same-origin policy. However, they recognize when a WebSocket URI is used, and insert an Origin: header into the request that indicates the origin of the script requesting the connection. To ensure cross-site security, the WebSocket server must compare the header data against a whitelist of origins permitted to receive a reply. – WebSockets, Wiki

下面这个例子来自维基百科,客户端的请求报头如下:

1
2
3
4
5
6
7
8
9
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

服务器收到这个请求之后,查询 Origin 字段的 URL 是否在其白名单内,如果是就返回以下报文:

1
2
3
4
5
6
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

一旦连接建立成功,客户端和服务器就可以以全双工模式来回传输数据。

AJAX 的规避方法

AJAX 适用的规避方法有 CORS(跨源资源共享)、 JSONP 及 WebSocket。

CORS 与 JSONP 的区别

  1. CORS 比 JSONP 要更强大一点,因为 JSONP 只支持的 GET 请求,而 CORS 支持所有 HTTP 请求。
  2. 但对于老式浏览器的支持上, JSONP 更占优势,且能向不支持 CORS 的网站发送请求。

参考资料

  1. AJAX
  2. 跨域资源共享 CORS 详解
  3. 跨域多个域名白名单
  4. DNS解析过程详解
  5. HTML5 postMessage 和 onmessage API 详细应用

DOM 事件模型的发展

DOM Level 1 (背景)

W3C 为了统一各浏览器,梳理了微软和网景两大巨头的浏览器的事件监听模型,整合成第一个版本的标准(DOM Level 1)并发布。所以其实 DOM Level 1 只是标准出来之前已有的事件监听的集合。现在有以下代码,我们来尝试为这些按钮绑定事件,使得当点击这些按钮时,弹窗显示“hi”。

1
2
3
4
5
6
<button id='A'>A</button>
<button id='B'>B</button>
<button id='C'>C</button>
<script>
function sayHi(){ alert('hi') }
</script>
  • 利用 HTML 事件属性来分配事件

    1
    2
    3
    4
    5
    6
    <!-- A -->
    <button id='A' onclick='sayHi'>A</button>
    <!-- B -->
    <button id='B' onclick='sayHi()'>B</button>
    <!-- C -->
    <button id='C' onclick='sayHi.call()'>C</button>

    代码结果:

    在上述代码中,只有 BC 才能得到我们想要的结果。DOM Level 1 规定,当使用 HTML 事件属性绑定事件名时,必须带上英文括号。因为事件属性的值是指要运行的代码,一旦用户点击按钮,浏览器就 eval('sayHi()') 或者 eval('sayHi.call()')sayHi 只是一个函数变量名,如果是按钮A,那么浏览器执行的是 eval('sayHi') ,这样只是运行了这个变量,并没有执行这个函数。所以切记正确写法为后两者。

  • 利用 HTML DOM 来分配事件

    现在,让我们来看看在 js 中,我们又该如何利用 HTML DOM 来得到我们想要的结果。

    1
    2
    3
    4
    5
    6
    function meetAgain(){
    alert('hi')
    }
    A.onclick = meetAgain //A:正确
    B.onclick = meetAgain() //B:错误
    C.onclick = meetAgain.call() //C:错误

    上述代码中,只有 A 才是正确的。因为在 JavaScript 中, onclick 是一个属性, meetAgain 是一个函数对象, A.onclick = meetAgain 是将 meetAgain 函数的对象地址赋给 onclick 属性。而 meetAgain()meetAgain.call() 表示执行函数,返回的结果是 undefined ,那么 B 和 C 的 onclick 的属性值为 undefined ,很显然这并不是我们要的结果。

所以 HTML 事件属性 和 HTML DOM 两者的区别:

HTML 事件属性中分配事件时,需要在函数名后加上“()”,表示当用户点击时就执行这个函数;HTML DOM中为事件属性分配事件时,不需要在函数名后加“()”,表示将函数的对象地址赋给事件属性。

DOM Level 2 (发展)

在 DOM Level 1 中我们可以看到,要为一个元素绑定事件,只能用 onclickonmouseup 等属性,这就带来了一个问题,一个属性只能绑定一个事件。DOM Level 2 中最重要的更新就是添加了 DOM 事件模型,使得一个属性可以绑定多个事件。此外,这是我们目前最常用的 DOM 标准

  • 重要的API:target.addEventListener(type, listener, options)

    具体看以下代码,来理解 addEventListener()onclick 绑定事件的区别( HTML 代码仍为本文开头的第一段代码):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    A.addEventListener('click', function (){
    console.log('A1')
    })
    A.addEventListener('click', function (){
    console.log('A2')
    })
    B.onclick = function (){
    console.log('B1')
    }
    B.onclick = function (){
    console.log('B2')
    }

    当用户分别点击 A按钮 和 B按钮 时,运行结果为:

    > "A1"
    > "A2"
    > "B2"
    

    我们发现,A 的 click 事件的两个结果都运行出来了,而 B 只运行了在后位绑定的事件函数。这是因为 onclick 属性是唯一的,若绑定了两个及以上的事件处理函数,则先绑定的会被新绑定的覆盖。而 addEventListener() 会让事件进入到事件监听队列(eventListeners)中,执行顺序依照队列先进先出的特性。所以不建议直接使用 onclick 属性来绑定事件处理函数,以免被无意覆盖。

  • addEventListener() 对应的是 removeEventListener() ,它可以将指定函数移出事件监听队列。

    1
    2
    3
    4
    5
    function func(){
    console.log('C')
    }
    C.addEventListener('click', func)
    C.removeEventListener('click', func)

    此时,点击 C按钮 时,并不会输出任何内容,因为 func 在进入事件监听事件队列后又马上被移出了,等不到用户点击。

事件流(Event flow): IE 提出的事件冒泡(Event bubbling)、网景提出的事件捕获(Event capture)。

1
2
3
4
5
6
7
8
9
<div id='head'>

<div id='face'>

<div id='nose'>
鼻子
</div>
</div>
</div>
  • Q1:当点击“鼻子”时,头和脸有没有被点击到?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    head.addEventListener('click', function (){
    console.log('头');
    })
    face.addEventListener('click', function (){
    console.log('脸');
    })
    nose.addEventListener('click', function (){
    console.log('鼻子');
    })

    根据上述代码,当点击“鼻子”时,控制台输出的结果是:

    > "鼻子"
    > "脸"
    > "头"
    

    由此可见,当点击“鼻子”时,“脸”和“头”也被点击到了。因为“鼻子”是“脸”和“头”的一部分,顺序是由里到外,由小到大。

  • Q2:当点击“鼻子”时,三者的事件处理顺序是怎么样的?

    在上一个例子中,我们看到顺序是由里到外(由小到大)的,那么顺序只能是这一种吗? W3C 给出了两种事件流机制,分别是冒泡流和捕获流。上一个例子就是冒泡流的例子。而要采用冒泡流还是捕获流,就由 addEventListener() 的第三个参数 options 决定 —— 当 options 为 false 时就是冒泡流(默认),为 true 时就是捕获流。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /* 冒泡阶段,忽略第三个参数则默认为 undefined ,undefined 为 false */
    head.addEventListener('click', function (){
    console.log('头:冒泡');
    })
    face.addEventListener('click', function (){
    console.log('脸:冒泡');
    })
    nose.addEventListener('click', function (){
    console.log('鼻子:冒泡');
    })

    /* 捕获阶段 */
    head.addEventListener('click', function (){
    console.log('头:捕获');
    }, true)
    face.addEventListener('click', function (){
    console.log('脸:捕获');
    }, true)
    nose.addEventListener('click', function (){
    console.log('鼻子:捕获');
    }, true)

    运行结果:

    > "头:捕获"
    > "脸:捕获"
    > "鼻子:冒泡"
    > "鼻子:捕获"
    > "脸:冒泡"
    > "头:冒泡"
    

    由这个运行结果,我们可以看出捕获阶段与冒泡阶段的执行顺序是相反的,它是由外到里,由大到小的,且“头”和“脸”都是先捕获后冒泡。而例外的是,“脸”却是先冒泡后捕获。

    其实,捕获阶段的事件是先于冒泡阶段处理的。而当事件流完成祖先元素及父元素的捕获阶段后,到达目标元素(target)时,事件处理顺序遵循函数绑定的顺序处理(即按顺序执行代码)而不是先捕获后冒泡,这个阶段被称为“目标阶段”。理解过程如下图:
    event-flow.png

移动端的适配

腾讯曾经出过一个面试题:移动端该如何做适配?有位同学分享了他那次的面试经历,有兴趣可以了解一下

移动端在用户群中占了很大的比重,所以做好移动端的适配,基本是前端们必备的技能之一。移动端和PC端的最重要的区别就是

  1. 视口大小

    移动端的视口宽度基本在 1000px 以内,手机端在 320px 至 425px 之间,若将PC端的页面在手机端中展示,最初的做法是用手机端的视口宽度去模拟PC端的 980px ,那么内容会被缩放,用户浏览时需要自行放大。

  2. 交互方式

    • 移动端没有 hover 事件,PC端页面的 hover 在手机端中无法展现
    • 移动端有 touch 事件

那么,要想使得网页在移动端也能很好地展现出来,大概有以下几种方法:

移动端的适配总结

把总结提到最前写是为了方便复习。

  1. viewport元标签
  2. 媒体查询,自动探测屏幕宽度来加载相应的CSS
  3. 尽量不使用绝对单位(如px),而使用相对单位(如rem、em)

Viewport元标签

在移动端中常常会添加 meta viewport 来规定网页的显示形式。

<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0">

上面这行代码的意思是,网页宽度默认等于屏幕宽度(width=device-width),不允许用户缩放(user-scalable=no),网页初始大小占屏幕面积的100%(initial-scale=1.0)。

  • width 设置视口宽度,device-width指设备宽度
  • user-scalable 是否允许用户缩放, no 为不允许, yes 为允许
  • initial-scale 设置页面地初始缩放比例
  • maximum-scale 允许的最大缩放比例
  • minimum-scale 允许的最小缩放比例

媒体查询(Media Queries)

  • 样式表中的媒体查询。例如:

    1
    2
    3
    4
    5
    @media (max-width: 980px) {
    body {
    background-color: red;
    }
    }

    这段 CSS 代码规定了当媒体的宽度 ≤980px 时,背景色变成红色。

  • <link> 标签中的媒体查询。例如:

    1
    <link rel='stylesheet' href='style.css' media='(max-width: 980px)'>

    这段代码表示当满足媒体查询的宽度 ≤980px 时, style.css 才会生效(页面加载时已下载好,等待条件满足时使用)。

    相较之下,第二个例子的媒体查询方法会更方便,即不同媒体查询结果下,引用不同的 CSS 文件,它能避免样式覆盖的问题。

  • CSS 文件中的媒体查询。例如:

    1
    @import url("tinyScreen.css") (max-width: 400px);

Click here to learn more about Media Queries.

触摸屏设备的交互事件

  • 没有 :hover 伪类

    因为在触摸屏设备中,用户无法像在 PC 端上一样,把鼠标移动到某个带有 :hover 伪类的元素上就能做出回应,所以我们需要为触摸屏设备以 click 事件来模拟 PC 端中的 hover 事件。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!-- PC 端 -->
    <!DOCTYPE html>
    <html>
    <head>
    <style>
    #xxx {
    width: 50px;
    height: 50px;
    background-color: red;
    }
    #xxx:hover {
    background-color: blue;
    }
    </style>
    </head>
    <body>
    <div id="xxx"></div>
    </body>
    </html>

    这段代码实现的效果是:

      

    要想在触摸屏设备中实现类似效果,那么代码应该改写成:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- 触摸屏设备 -->
    <!-- 相同部分已省略,script 加到 body 内最后 -->
    <script type="text/javascript">
    xxx.onclick = function(){
    if(xxx.style.backgroundColor === 'red'){
    xxx.style.backgroundColor = 'blue'
    } else {
    xxx.style.backgroundColor = 'red'
    }
    }
    </script>
  • touch 事件

    在这里就不做深入研究,有兴趣可以看 MDN 的触摸事件例子,以及演示效果(请在触控设备中打开)。

REM

rem 是一个相对单位,这个单位代表根元素 <html>font-size 大小

例如定义了如下样式,那么 <body> 中的字体大小实际为 18px 。它不仅适用于字体大小,还可以用在 widthmargin 等的样式上。此外,它还可以与其他单位共存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html>
<head>
<style>
html {
font-size: 12px;
}
body {
font-size: 1.5rem;
}
#div {
width: 6rem;
height: 6rem;
border: 1px solid #999;
padding: 5px;
}
</style>
</head>
<body>
这是一段在 body 内的文字。
<div id='div1'>这是一个在 body 内的 div 。</div>
</body>
</html>

代码结果:

这是一段在 body 内的文字。
这是一个在 body 内的 div 。

插一个拓展知识点:有趣的是,若上面例子中, div1 的宽高的单位改为 em ,则它的实际宽高由 72px 变成 108px 。这是因为 em 表示元素的 font-size 计算值,div1 继承了父元素 <body>font-size 值(18px),所以它的 1em 实际上等于 18px 。 Click here to learn more about length units.

Sass —— 强大的 CSS 扩展语言

使用 Sass 来开发 CSS 可以方便样式的编写。它不仅完全兼容 CSS ,还允许我们使用变量、嵌套等,甚至还可以声明函数来使用。

下面是一个 scss 文件,我们可以简单看看它的语法规则大概是怎么样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@function px2rem( $px ){  //自定义函数
@return $px/$deviceWidth*10 + rem;
}
$deviceWidth: 640; //变量以$符号开头

ul {
width: px2rem(256);
padding: px2rem(32);
background-color: #eee;
list-style: none;
font-size: 1.2em;

// 嵌套
li {
padding: 4px;
border-bottom: 1px solid #ddd;
}
li:nth-child(3) {
border-bottom: none;
}
}

经过转换,上面的 scss 文件对应的 css 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ul {
width: 4rem;
padding: 0.5rem;
background-color: #eee;
list-style: none;
font-size: 1.2em;
}
ul li {
padding: 4px;
border-bottom: 1px solid #ddd;
}
ul li:nth-child(3) {
border-bottom: none;
}

Click here to learn more about Sass.
或者,需要中文的话,这里有一篇阮一峰老师学习Sass的博客笔记,讲得很详细也一眼就能看懂。

结语:后期还会为本文章更新 Flex 布局。爸爸要去吃饭了。

使用原生JS实现一个jQuery的API

原生JS操作

1
2
3
<ul style='border: 1px solid #666'>
<li id='li'>Hi</li>
</ul>

代码结果:

  • Hi

现在,我想为 <li> 标签添加一个样式,

1
.red { color: red; }

并改变文本内容为“Thank you~”。用原生JS的写法就是:

1
2
li.classList.add('red')
li.textContent = 'Thank you~'

代码结果:

  • Thank you~

模仿jQuery实现一个API

如果要模仿jQuery,实现一个jQuery的API,那么实现方法如下:

  1. 封装函数。将每个功能封装成一个函数,需要使用到时调用这个函数即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /* 封装添加样式的函数addClass */
    function addClass(node, className) {
    node.classList.add(className)
    }

    /* 封装设置内容的函数setText */
    function setText(node, text) {
    node.textContent = text
    }

    addClass(li, 'red')
    setText(li, 'Thank you~')
  2. 将第一个参数(节点)提取出来变成 node.functionName() 的形式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function jQuery(node){
    return {
    addClass: function(className){
    node.classList.add(className)
    },
    setText: function(text){
    node.textContent = text
    }
    }
    }

    var node = jQuery(li)
    node.addClass('red')
    node.setText('Thank you~')
  3. 处理多个节点的情况。

    1
    2
    3
    4
    <ul style='border: 1px solid #666'>
    <li>Hi</li>
    <li>Welcome</li>
    </ul>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function jQuery(nodeOrSelector){
    var elements = (typeof nodeOrSelector === 'string') ? document.querySelectorAll(nodeOrSelector) : { 0: nodeOrSelector, length: 1 }
    return {
    addClass: function(className){
    for(let i=0; i<elements.length; i++){
    elements[i].classList.add(className)
    }
    },
    setText: function(text){
    for(let i=0; i<elements.length; i++){
    elements[i].textContent = text
    }
    }
    }
    }

    var node = jQuery('li')
    node.addClass('red')
    node.setText('Thank you~')
  4. 简化jQuery函数名

    1
    2
    3
    window.$ = jQuery;
    $('li').addClass('red')
    $('li').setText('Thank you~')

    最终运行结果:

    • Thank you~
    • Thank you~

总结

说白了,jQuery就是一个函数,它返回一个对象,里面包含节点的属性和方法。节点可以调用这些方法来操作DOM。

PS:完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
window.jQuery = function(nodeOrSelector){
var elements = (typeof nodeOrSelector === 'string') ? document.querySelectorAll(nodeOrSelector) : {
0: nodeOrSelector,
length: 1
}
return {
addClass: function(className){
for(let i=0; i<elements.length; i++){
elements[i].classList.add(className)
}
},
setText: function(text){
for(let i=0; i<elements.length; i++){
elements[i].textContent = text
}
}
}
}
window.$ = jQuery

一些较重要的HTML标签的学习笔记

这篇博客将会记录我对一些HTML标签的学习,在本文提到的知识点基本是常用的,如果需要了解详细的标签及其属性用法,可以查询 MDN

<iframe> 标签

Usage:将另一个页面嵌入到当前页面中。详情请点击

代码示例

1
<iframe src="https://caijialinxx.github.io/archives/" name="xxx" frameborder="0" width="100%" height="400px"></iframe>

结果预览


重点总结

  1. src 属性可接网址或相对地址
  2. name 属性与 <a> 标签的 target 属性结合使用,可以使 <a> 标签的链接在 iframe 容器中呈现
  3. 一般情况下都添加 frameborder="0" 的属性,使其更美观
  4. width 属性和 height 属性可以设置容器的宽高

<a> 标签

Usage:创建一个到其他网页、文件、同一页面内的位置、电子邮件地址或任何其他URL的超链接。详情请点击

代码示例

1
2
3
<a href="//music.163.com/playlist/1998046799/1300657788?userid=1300657788" target="xxx">分享我的歌单,让你轻松下</a><br>
<a href="#a">点这里将会跳到标题 `<a>标签` </a><br>
<a href="javascript: alert('Hello, I am Here!');">点这里将会执行一个 JavaScript 脚本</a><br>

结果预览

分享我的歌单,让你轻松下(翻上去iframe里听哦)
点这里将会跳到本章节 <a>标签 处
点这里将会执行一个 JavaScript 脚本

重点总结

  1. href 属性的取值:
    • 浏览器支持的协议地址,如 http://...ftp://... 等(可以使用无协议绝对地址,自动继承当前页面的协议)
    • 相对地址,如 #xxx(定位到页面特定锚点,如果是 #top 或者 # 则直接跳到页面顶部)、 ?yyy=xxx./xxx(当前网页所在 URL 下的文件或文件夹)
    • 伪协议,如 javascript: alert(1);javascript:;(这可以实现点击<a>标签没有动作,满足一些特殊需求)
  2. target 属性的取值:
    • <iframe>name 属性值 —— 这将会使 <a> 标签的 URL 在 <iframe> 中呈现
    • _blank —— 在新的标签页打开
    • _self —— 在当前页面跳转
    • _parent —— 在父框架或浏览上下文加载,若无父框架或浏览器上下文,则此选项等同 _self
    • _top 在顶层浏览上下文加载。若没有父框架或浏览上下文,则此选项等同 _self
  3. download 属性:下载文件
  4. 发送 GET 请求(F12 打开开发者工具,查看 Network ,即可发现当点击一个 <a> 链接时,请求方式为 GET )

<form> 标签

Usage:表示了文档中的一个区域,这个区域包含有交互控制元件,用来向 web 服务器提交信息( GET 请求或 POST 请求)。详情请点击

代码示例

1
2
3
4
<form action="" method="get">
<!-- 表单中必须要有提交按钮,才能发送请求 -->
<input type="submit">
</form>

重点总结

  1. method 属性可指定为 GETPOST
  2. action 属性指定跳转路径,可以接相对路径(如./anthorpage.html)
  3. POST 请求发送后,检查 Headers 有第四部分 Form-Data ,其中第三部分 Request Headers 指定第四部分为 application/x-www-form-urlencoded
  4. target 属性同 <a> 标签
  5. 提交表单后会自动刷新页面

<input> 标签

Usage:用于创建交互式控件,以便接受来自用户的数据。详情请点击

代码示例

1
2
3
4
账号:<input type="text" name="userid"><br>
密码:<input type="password" name="pwd"><br>
性别:<input type="radio" name="sex" value="female"><input type="radio" name="sex" value="male" selected><br>
爱好:<input type="checkbox" name="hobby" value="swimming">游泳<input type="checkbox" name="hobby" value="running">跑步<input type="checkbox" name="hobby" value="riding">骑行

结果预览

账号:
密码:
性别:
爱好:游泳 跑步 骑行

重点总结

  1. type 属性的取值
    • submit —— 提交按钮
    • button —— 普通按钮
      PS:<button> 标签如果没有 type 值且 form 表单里只此一个按钮,那么 <button> 标签会自动升级为提交按钮;如果有 type="button" ,则是一个普通按钮。另外 <button> 标签可以有子标签而 <input type="button"> 标签不能。
    • checkbox —— 复选框
      若同一选项的 name 值相同,那么数据传输时将会被集合成一个数组。
    • radio —— 单选框
      在该类型下 name 属性值必须一样,使得 value 唯一。
    • password —— 密码框
    • ……
  2. id 属性
    可唯一指定一个元素,此外还可以与 <label> 标签结合使用。其 for 属性值指向 id 属性值,或直接包裹在 <input> 标签外,即可使用户点击其文本即可选中 input 焦点,例如:
    1
    2
    <input type="checkbox" id="xixi"><label for="xixi">嘻嘻</label>
    <label><input type="checkbox" id="haha">哈哈</label>
     
      
     

<select> 标签

Usage:用于创建选项菜单。详情请点击

代码示例

1
2
3
4
5
<select name="value">
<option value="" disabled>-</option>
<option value="1" selected>1</option>
<option value="2">2</option>
</select>

结果预览


重点总结

  1. multiple 属性 —— 多选,按下 Crtl 键即可多选。
  2. required 属性 —— 规定select的值不能为空。
  3. 必须有 <option> 子标签, <option> 子标签中的属性有:
    • disabled 表示不可选
    • selected 表示默认选中

<textarea> 标签

Usage:可以进行多行纯文本编辑。详情请点击

代码示例

1
<textarea name="textarea" rows="6" cols="30">Write something here...</textarea>

结果预览


重点总结

  1. 默认可调整,若添加 style="resize:none" 属性,则固定宽高
  2. cols 属性 —— 指定宽度
  3. rows 属性 —— 指定高度(若CSS行高变化则可能会不同)
  4. readonly 属性 —— 不允许用户修改元素内文本,能让用户点击和选择元素内的文本。如果在表单里,这个元素的值依旧会跟随表单一起提交。

<table> 标签

Usage:表示表格数据,即通过二维数据表表示的信息。详情请点击

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<table border="1">
<colgroup>
<col width="60px"><col bgcolor="red" width="80px"><col width="60px">
</colgroup>
<thead>
<tr><th>NO.</th><th>Name</th><th>Age</th></tr>
</thead>
<tbody>
<tr><th>1</th><td>Caaa</td><td>20</td></tr>
<tr><th>2</th><td>Jaff</td><td>21</td></tr>
</tbody>
<tfoot>
<tr><th>AvgAge</th><td></td><td>20.5</td></tr>
</tfoot>
</table>

结果预览

NO.NameAge
1Caaa20
2Jaff21
AvgAge20.5

重点总结

  1. 子元素有 <thead><tbody><tfoot><colgroup><tr><td> 不是它的子元素。如果省略不写子元素,不会报错,只会自动更正。
  2. 注意,上文中的决定了样式的属性已不建议使用或者已废弃,若需要改变样式请直接使用 CSS 。

CSS介绍

历史与概述

CSS(Cascading Style Sheets,层叠样式表), 是一种样式表语言,用来描述 HTML 或 XML(包括如 SVG、XHTML 之类的 XML 分支语言)文档的呈现。CSS 描述了在屏幕、纸质、音频等其它媒体上的元素应该如何被渲染的问题。

  • 1994年哈肯·利缇提出了 CSS 的最初建议,而当时伯特·波斯(Bert Bos)正在设计一个名为Argo的浏览器,于是他们决定一起设计 CSS 。
  • 1995年他们在 WWW 网络会议上 CSS 又一次被提出,波斯演示了 Argo 浏览器支持 CSS 的例子,哈肯也展示了支持 CSS 的 Arena 浏览器。
  • 1996年底,CSS 初稿已经完成,同年12月,层叠样式表的第一份正式标准(Cascading style Sheets Level 1)完成,成为 W3C 的推荐标准。
  • 1997年初,W3C 开始接管 CSS ,并组织了专门管 CSS 的工作组,其负责人是克里斯·里雷。他们开始讨论第一版中没有涉及到的问题。
  • 1998年5月 W3C 发表了 CSS 2 ,并很快又发表了 CSS 2.1 修改了 CSS 2 的一些错误,消除了其中基本不支持的内容和增加了一些已有的浏览器的扩展内容,此时 CSS 开始流行。
  • 从2011年开始,CSS 被分为多个模块单独升级,统称为 CSS 3。

引入方法

我们想要让文字居中且变红,则可以用以下四种方法实现:

内联样式

1
2
3
4
<!-- 在 CSS 还没发表之前,添加样式的方法 -->
<p><center><font color="red">你真棒!</font></center><p>
<!-- CSS 发表之后的方法 -->
<p style="text-align:center;color:red">你真棒!</p>

注意:<center><font> 标签已过时,不建议使用。

<style> 标签

1
2
3
4
5
6
7
8
9
10
11
<head>
<style>
p{
color: red;
text-align: center;
}
</style>
</head>
<body>
<p>你真棒!</p>
</body>

<link> 标签引入外部文件

假设本文件为 index.html ,它所在的文件夹下有一个用来写样式的 style.css 文件。

1
2
3
4
5
6
7
<!-- 这是 index.html 的代码 -->
<head>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<p>你真棒!</p>
</body>
1
2
3
4
5
/* 这是 style.css 的代码 */
p{
text-align: center;
color: red;
}
  • 此外,还可以在 A.css 文件中使用 @import url(./B.css) 来引入另一个 CSS 文件,例如 B.css ,这样使得 HTML 文件渲染 A.css 前会先渲染 B.css 。

假设 index.html 和 style.css 所在文件夹下还有个 anotherstyle.css 文件。现需在 style.css 中引入 anotherstyle.css ,使得 index.html 页面的背景色变为#666

1
2
3
4
5
6
/* 这是style.css的代码 */
@import url(./anotherstyle.css)
p{
text-align: center;
color: red;
}
1
2
3
4
/* 这是anotherstyle.css的代码 */
body{
backgroud-color: #666;
}

元素的高度

  • 内联元素的高度是由字体及其参数决定,无法设置宽高。line-height 的属性值可以决定内联元素的高度,例如 line-height:20px 那么 <span> 的高度也为 20px
  • 块级元素的高度是由其文档流元素高度的总和决定。

文档流指文档内元素的流动方向。
块级元素从上往下流动
内联元素从左往右流动,如果流动遇到阻碍(容器宽度不够)则换行继续从左往右流动。但如果是一个英文单词,则无论宽度是否已经超出容器宽度,都不将分割成两行,除非添加 word-break: break all 属性。


如何学习CSS




一些小重点(未完待续…)

  1. 页面默认字体大小为16px
  2. float 做横向结构需要给所有子元素添加 float:left ,给其父元素添加 .clearfix::after{ content:""; display:block; clear:both; }

空元素是什么?可替换元素又是个啥?

空元素

An empty element is an element from HTML, SVG, or MathML that cannot have any child nodes (i.e., nested elements or text nodes).

这是来自 MDN 的解释。按照我的理解,用大白话说出来就是,在 HTMLSVGMathML 中,空元素指这个元素节点里不存在子节点,这个子节点指 元素节点element node)或者 文本节点text node)。

PS:关于 DOM 节点的概念,如果你不了解,可以戳 阮一峰的《JavaScript 标准参考教程(alpha)》中 6.1 概述 来学习。

举例说明,
在 HTML 中有以下空元素:

此外,在这些空元素上使用一个闭标签是无效的。如 <input type="text"></input> 是错误的。

在 SVG 中有 <font-face-name><hkern><vkern> 等。

在 MathML 中有 <mspace><semantics> 等。

可替换元素

In CSS, a replaced element is an element whose representation is outside the scope of CSS. In other words, these are external objects whose representation is independent of the CSS formatting model.


看懂了吗?看懂了吗?不懂?那我给你看下MDN的中文解释

CSS 里,可替换元素(replaced element)的展现不是由CSS来控制的。这些元素是一类外观渲染独立于CSS的外部对象。

现在懂了?还是不懂?

Emmm…

Maybe it would help.

替换元素 是浏览器根据其标签的元素与属性来判断显示具体的内容的,CSS并不需要考虑渲染替换元素,它的展现独立于CSS,这就是“independent of the CSS formatting model”的意思。此外,不像 非替换元素 一样,它们将内容直接告诉浏览器,而检查替换元素的代码时可以发现它并没有实际的内容。

这是一个来自 W3C 关于 Replaced element 的解释,如果你看得懂英文的话,结合W3C标准的原始定义会理解得更准确一点。

另外,有人提出,把 Replaced element 翻译成可替换元素是不准确的,就英文字面意思来说, replaced 是已被替换了的, Replaced element 不由CSS负责展示渲染,不是可以被替换的概念。原文请戳这里