zoukankan      html  css  js  c++  java
  • 《JavaScript DOM 编程艺术》读书笔记

    《JavaScript DOM 编程艺术》读书笔记

    本科的时候也有看前端的内容但是没有系统地学习,现在有 WebGIS 相关的项目,并且发现 Android 开发也发生了一些变化: 很多应用不再单单是一个简单的原生 Android 应用,用到了跨平台技术,比如说 React Native 、 Ionic 等等,这都需要 Javascript 基础。所以打算系统地学习下 Javascript 。

    其实有这个想法很久了,包括暑假来到也有意学习前端技术,最开始是在慕课网看 前端基础 相关视频,然后也简单地在 菜鸟教程 看了相关文字内容,最后在廖雪峰的网站看 JavaScript全栈教程,这部分对后来 Node.js 后端开发很有帮助。

    但是现在发现 Javascript 水品还不行,没有接近实战的水平,所以打算再系统学习下。
    按照知乎 如何循序渐进、有效地学习JavaScript? 问题的回答,决定先学习 《JavaScript DOM 编程艺术》(第二版),后面再学习 《JavaScript高级程序设计》,最后再刷 《ECMAScript 6 入门》 学习 ES6 。

    第1章 JavaScript 简史

    本章主要介绍了 JavaScript 的起源、浏览器之间的战争、DOM 的演变史。

    DOM (Document Object Model,文档对象模型) 是一套对文档的内容进行抽象和概念化的方法。
    感觉和类的说法很类似。

    第2章 JavaScript 语法

    程序设计语言分为解释型和编译型两大类。Java或者C++等语言需要一个编译器,把用Java等高级语言编写出来的源代码翻译为计算机能直接执行的文件。
    解释型语言不需要编译器--它们仅需要解释器,对于JavaScript而言,Web浏览器负责完成有关解释和执行工作。
    浏览器的JavaScript解释器将直接读取源代码并执行,相关错误也只能在此时才能发现。

    语法

    JavaScript 语法基本上和 Java 或者 C++ 类似,下面主要介绍不同点。

    JavaScript 是一种弱类型语言,变量使用前不需要进行类型声明,但不建议这样做。

    数据类型

    JavaScript 中的数据类型主要包括 字符串、数值、布尔值 三种。

    • 字符串:单引号双引号都可以,最好根据字符内容选择。
    • 数值:不单单是整数,允许任意位小数。
    • 布尔值:true 或 false 。

    数组

    JavaScript 数组声明不必指出数组长度:

    var car = Array(5);
    var car = Array();
    var car = [];

    声明数组的同时也可以进行填充(向数组添加元素):

    var cars = new Array("Saab","Volvo","BMW");
    var cars = ["Saab","Volvo","BMW"];

    数组元素类型不必相同,甚至可以是数组或者对象:

    var cars = new Array("Saab",2017,true);
    var beatles = [];
    beatles[0] = cars;
    // beatles[0][1] 的值为 2017 。

    对象简介

    对象的声明使用 Object 关键字:

    var car = new Object();
    var car = {};
    var person = {firstname:"John", lastname:"Doe", id:5566};

    对象属性取值赋值方法:

    var person = {firstname:"John", lastname:"Doe", id:5566};
    person.lastname = "Wang";
    var name = person.lastname;
    var name = person["lastname"];

    算数操作符、条件语句、循环语句

    和 Java、C++ 基本一致,不再赘述。

    比较操作符不太一样:
    == : 表示类型转换后值是否相等;
    === :严格相等,类型必须相同。

    函数

    函数声明及调用方法:

    function myFunction(a, b) {
        return a * b;
    }
    // 调用函数
    myFunction(2,5);

    变量的作用域

    全局变量:可以在脚本的任意位置引用,包括函数内部。
    局部变量:在函数内部声明,只在函数内部有效。

    对象

    对象是自包含的数据集合,包含在对象里的数据可通过 属性(property) 和 方法(method) 访问。

    • 属性是隶属于某个特定对象的变量
    • 方法是只有某个特定对象才能调用的函数

    对象分类:

    • 自定义对象:利用 JavaScript 创建的自己的对象;
    • 内建对象:JavaScript提供的一系列预先定义好的对象。数组也可以看做是 JavaScript 的内建对象的一种。常见的还有Data对象;
    • 宿主对象:由浏览器提供的预定义对象。常见的有 windows,document 等。

    第3章 DOM

    DOM 三个字母的具体含义:
    D 是基础,没有文档(Document)DOM 也就无从谈起;
    O 是对象(Object),JavaScript 本身就可以看做是由对象构成的语言,其重要性不言而喻;
    M 是模型(Model),其含义是某种事物的表现形式。
    具体的说 DOM 把文档表示成了一颗家谱树(DOM 使用 parent、child,sibling 等记号来表明家庭成员之间的关系。

    节点

    DOM 由许多不同的节点(node)组成,节点可分为三类:
    元素节点:DOM 的原子是元素节点,可以包含其他元素。
    文本节点:元素节点的内容。
    属性节点:元素节点的描述。

    <p title="Paragraph">This is a paragraph.</p>
    /**
    * 元素节点:p
    * 文本节点:This is a paragraph.
    * 属性节点:title="Paragraph"
    */

    获取元素

    有三种方法可以获取元素节点,分别通过 id、标签名、class 。

    // document 特有函数,返回一个元素
    var x = document.getElementById("intro");
    

    // getElementsByTagName、getElementsByClassName 返回元素数组

    var y = document.getElementsByTagName("p");
    var y = x.getElementsByTagName("p");
    // 允许使用通配符
    var y = x.getElementsByTagName("*");

    var z = document.getElementsByClassName("intro");
    var z = x.getElementsByClassName("intro");
    // 允许查找带有多个类名的元素,并且类名顺序不重要
    var z = x.getElementsByClassName("import intro");

    获取和设置属性

    获取和设置属性的方法如下:
    getAttribute():该方法只能通过元素节点对象调用;
    setAttribute():该方法允许我们对属性节点的值做出修改。

    var img = document.getElementById("image");
    img.getAttribute("src");
    img.src;
    

    img.setAttribute("src","landscape.jpg");
    img.src = "landscape.jpg";

    document.getElementById("image").src="landscape.jpg";

    通过浏览器查看源代码,其属性并不会改变,也就是说 setAttribute 做出的修改不会反映到文档本身的源码里。这种“表里不一”的的现象源自 DOM 的工作模式:
    先加载文档的静态内容,再动态刷新,动态刷新不影响文档的静态内容。

    第4章 案例研究:JavaScript图片库

    事件处理函数

    事件处理函数的作用是,在特定事件发生时调用特定的代码。

     <a href="images/fireworks.jpg" title="A fireworks display" onclick="showPic(this); return false;">Fireworks</a>

    事件处理函数的工作机制:在给某元素添加事件处理函数后,一旦事件发生相应的 JavaScript 代码就会执行。
    被调用的 JavaScript 代码可以返回一个值,这个值将被传递至事件处理函数。

    例如:我们给某链接添加一个 onclick 事件处理函数,并把这个事件处理函数所触发的 JavaScript 代码返回 true 或 false 。
    这样一来,当这个链接被点击时,JavaScript 代码返回值是 true ,onclick 事件处理函数就认为 这个链接被点击了;反之,认为没有被点击。

    所以在 onclick 事件处理函数所触发的 JavaScript 代码里增加一条 return false; 语句,屏蔽掉链接的默认行为。

    对函数进行拓展

    childNodes 属性

    childNodes 属性可以获取任一元素的所有子元素。

    var body_element = document.getElementById("body")[0];
    // 得到 body 的所有子元素、数组
    body_element.childNodes;

    nodeType 属性

    childNodes 属性返回的数组包括所有类型的节点,而不仅仅是元素节点。
    事实上,文档里的每样东西都是一个节点,设置连空格和换行符都被解释为节点。

    可通过节点的 nodeType 属性区分不同的节点。

    node.nodeType

    返回值是一个数字。

    nodeType 共有 12 种取值,其中仅 3 种具有实用价值。

    元素节点:1.
    属性节点:2.
    文本节点:2.

    nodeValue 属性

    若改变 文本节点 的值,可通过 DOM 提供的 nodeValue 属性。

    node.nodeValue

    注意:对于元素节点而言,element.nodValue 得到的值并不是元素的文本值。

    正确的做法的先得到元素节点的文本节点,再取 nodeValue 属性:

    element.childNodes[0].nodeValue

    firstChild 和 lastChild 属性

    firstChild:元素的第一个子元素。
    lastChild:元素的最后一个元素。

    function showPic(whichPic) {
        var source = whichPic.getAttribute("href");//获取资源路径
        var placeholder = document.getElementById("placeholder");
        placeholder.setAttribute("src",source);
    
    <span class="token keyword">var</span> text <span class="token operator">=</span> whichPic<span class="token punctuation">.</span><span class="token method function property-access">getAttribute</span><span class="token punctuation">(</span><span class="token string">"title"</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token comment">//获取内容</span>
    <span class="token keyword">var</span> description <span class="token operator">=</span> <span class="token dom variable">document</span><span class="token punctuation">.</span><span class="token method function property-access">getElementById</span><span class="token punctuation">(</span><span class="token string">"description"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    description<span class="token punctuation">.</span><span class="token property-access">firstChild</span><span class="token punctuation">.</span><span class="token property-access">nodeValue</span> <span class="token operator">=</span> text<span class="token punctuation">;</span>
    

    }

    第5章 最佳实践

    1、在使用任何一句JavaScript代码时,都应该想想,对这个网页是否有用;
    2、平稳退化(graceful degradation):如果正确使用了 JavaScript 脚本,可以让访问者在他们的浏览器不支持 JavaScript 的情况下仍能顺利地浏览你网站。
    虽然某些功能无法使用,但是最基本的操作仍能顺利完成;
    3、渐进加强:用额外的信息层去包裹原始数据;使 CSS 代码负责提供关于“表示”的信息,JavaScript 代码负责提供关于“行为”的信息。
    4、分离 JavaScript:在 HTML 文档中使用诸如 onclick 之类的属性也是一种没有效率又容易引发问题的做法。
    如果利用像 CSS 中的 class 和 id 属性那样,把JavaScript 代码调用行为与 HTML 文档内容和结构分离,网页就会健壮不少。
    5、向后兼容:对象检测:检测浏览器对 JavaScript 的支持程度。
    用一个 if 语句的条件表达式看求值结果是 true 还是 false 来采取不同的行动。
    如在代码前加上 if(!getElementById) return false;
    6、性能考虑:
    尽量少访问DOM和尽量减少标记,不管什么时候只要是查询DOM中的某些元素,浏览器就会搜索整个DOM树,从中查找可能匹配的元素。
    在多个函数都会取得一组类似元素的情况下,可以考虑重新构建代码,把搜索结果保存在一个全局变量里,或者把一组元素以参数形式传递给函数。
    减少标记数量的目的在于,过多的不必要的元素只会增加DOM树的规模。
    7、合并和放置脚本:减少请求数量是在性能优化时首先要考虑的;
    把所以的<script>标签都放在文档的末尾,</body>标记之前,就可以让页面变得更快。
    8、压缩脚本:指的是把脚本文件中的不必要的字节,比如空格和注释,统统删除,从而达到“压缩”文件的目的;
    多数情况下应该有两个版本,一个是工作副本,可以修改代码并添加注释,另一个是精简副本,用于放在站点上,通常在精简副本的文件名上加上 min 字样。

    第6章 案例研究:图片库改进版

    本章主要是第5章内容在 图片库 上的实践。

    共享onload事件:

    假如有两个函数 firstFunction 和 secondFunction 需要在页面加载时执行:

    window.onload = firstFunction;
    window.onload = secondFunction;

    这样做的话实际上只有后一个可以执行。

    window.onload = function()
    {
      firstFunction();
      secondFunction();
    }

    这样做创建了一个匿名函数,在需要绑定的函数不是很多的场合的确很实用.

    最佳解决方案,使用 addLoadEvent 函数。
    这个方案需要额外添加一些代码,但一旦有了这些代码,绑定函数到 onload 就很方便。

    function addLoadEvent(func){
       //将现有的 window.onload 处理函数保存到 oldonload
      var oldonload = window.onload;
        //如果现有的 window.onload 上没有处理函数,将 func 添加给它
      if(typeof oldonload != 'function')
      {
        window.onload = func;
      }
      else{
        window.onload function()
        {
          oldload();
          func();
        }
      }
    }

    第7章 动态创建标记

    JavaScript 可以通过创建新元素和修改现有元素改变网页的结构。

    传统方法

    document.write

    document 的 write() 方法可以方便快速地将字符串插入到文档内。

    <body>
      <script type="text/javascript">
        document.write("<p>This is inserted.</p>");
      </script>
    </body>

    缺点就是违背了"行为与表现分离的原则",
    即使把这句语句挪到外部,还是需要在<body>里边添加<script>标签才可以调用。

    innerHTML 属性

    innerHTML 属性可以用来读写某给定元素里地 HTML 元素。

    <body>
      <div id="testdiv"></div>
    </body>
    

    window.onload = function(){
    var testdiv = document.getElementById("testdiv");
    testdiv.innerHTML="<p>I inserted <em>this</em> content.</p>";
    }

    利用此技术无法区分“插入一段内容”还是“替换一段内容”。

    DOM 方法

    createElement 方法

    var para = document.createElement("p");

    创建元素节点,只创建会出现一个文档碎片(document fragment)。
    本身并不影响页面表现,它是游荡在JavaScript世界里的一个孤儿。
    但是它已经有 nodeType 和 nodeName 属性。

    appendChild 方法

    把新创建的节点插入文档的节点树最简单方法是:让其成为某个现有节点的一个子节点。

    var para = document.createElement("p");
    var testdiv = document.getElementById("testdiv");
    testdiv.appendChild(para);

    createTextNode 方法

    创建文本节点填充元素节点的内容。
    把文本节点插入为元素节点的子节点。

    var para = document.createElement("p");
    var testdiv = document.getElementById("testdiv");
    testdiv.appendChild(para);
    var txt = document.createTextNode("Hello World");
    para.appendChild(txt);

    改变顺序,二者结果相同。

    var para = document.createElement("p");
    var txt = document.createTextNode("Hello World");
    para.appendChild(txt);
    var testdiv = document.getElementById("testdiv");
    testdiv.appendChild(para);

    重回图片库

    在已有元素前插入元素

    DOM 提供了 insertBefore() 方法,把一个元素插入到现有元素之前。

    parentElement.insertBefore(newElement,targetElement);

    其中:
    parentElement:目标元素的父元素,
    newElement:想插入的元素,
    targetElement:想插入哪个元素之前。

    var gallery = document.getElementById("imagegallery");
    gallery.parentNode.insertBefore(placeholder,gallery);

    在已有元素后插入元素

    DOM 并没有提供了 insertAfter() 方法,下面编写:

    function insertAfter (newElement,targetElement){
      var parent = targetElement.parentNode;
      if(parent.lastChild == targetElement){
        parent.appendChild(newElement);
      }else{
        parent.insertBefore(newElement,targetElement.nextSibling);
      }
    }
    

    var gallery = document.getElementById("imagegallery");
    insertAfter(placeholder,gallery);

    Ajax

    Ajax 可以做到只更新页面中的一小部分,其它内容不用重新加载。
    Ajax 的主要优势是对页面的请求以异步方式发送到服务器。

    XMLHttpRequest 对象

    Ajax 的核心是 XMLHttpRequest 对象,XMLHttpRequest 充当浏览器脚本与服务器之间的中间人的角色。
    JavaScript 可以通过这个对象自己发送请求,同时自己处理响应。

    function getnewContent (){
      var request = new XMLHttpRequest();
      if(request){
        request.open("GET","ajax_info.txt",true);
        request.onreadystatechange = function(){
          if(request.readyState == 4){
            var para = document.createElement("p");
            var txt = document.createTextNode(request.responseText);
            para.appendChild(txt);
            var testdiv = document.getElementById("testdiv");
            testdiv.appendChild(para);
          }
        };
        request.send(null);
      }
    }

    其中 readyState 属性的值,有5个可能值:
    0 表示未初始化
    1 表示正在加载
    2 表示加载完毕
    3 表示正在交互
    4 表示完成

    访问服务器返回的数据要通过两个属性完成。
    responseText:保存文本字符串形式的数据。
    responseXML:保存 Content-Type 头部指定为 "text/xml" 的数据。

    注意 异步请求,脚本在发送 XMLHttpRequest 请求之后仍然会继续执行,不会等待响应返回。

    HIjax

    HIjax 意思是渐进增强地使用 Ajax 。

    第8章 充实文档内容

    JavaScript 脚本只应该用来充实文档内容,要避免使用 DOM 技术来创建核心内容。

    function displayAbbreviations() {
      if (!document.getElementsByTagName || !document.createElement || !document.createTextNode) return false;
    // 得到所有链接
      var abbreviations = document.getElementsByTagName("abbr");
      if (abbreviations.length < 1) return false;
      var defs = new Array();
    // 遍历链接
      for (var i=0; i<abbreviations.length; i++) {
        var current_abbr = abbreviations[i];
        if (current_abbr.childNodes.length < 1) continue;
        var definition = current_abbr.getAttribute("title");
        var key = current_abbr.lastChild.nodeValue;
        defs[key] = definition;
      }
    // 创建列表
      var dlist = document.createElement("dl");
    // 遍历访问键
      for (key in defs) {
        var definition = defs[key];
        var dtitle = document.createElement("dt");
        var dtitle_text = document.createTextNode(key);
        dtitle.appendChild(dtitle_text);
        var ddesc = document.createElement("dd");
        var ddesc_text = document.createTextNode(definition);
        ddesc.appendChild(ddesc_text);
    // 添加列表项到列表中
        dlist.appendChild(dtitle);
        dlist.appendChild(ddesc);
      }
      if (dlist.childNodes.length < 1) return false;
    // 创建标题
      var header = document.createElement("h2");
      var header_text = document.createTextNode("Abbreviations");
      header.appendChild(header_text);
    // 把标题添加到页面主体
      document.body.appendChild(header);
    // 把列表添加到页面主体
      document.body.appendChild(dlist);
    }
    addLoadEvent(displayAbbreviations);

    我的理解就是把原本 HTML 的内容通过 JavaScript 操作提取出来摘要,再加到 HTML 中充实文档内容。

    第9章 CSS-DOM

    我们在浏览器看到的网页其实有三部分构成:

    • 结构层(structural layer) 由 XHTML 或者 HTML 等标记语言创建。
    • 表示层(presentation layer) 由 CSS 负责创建。
    • 行为层(behavior layer) 负责内容应该如何响应事件这一动作,这主要是由 javaScript 和 DOM 负责。

    style 属性

    在文档中每个人元素都是一个对象,每个元素都有一个 style 属性,他们也是一个对象。

    获取样式

    element.style.color;
    // 中间带连字符的 CSS 属性要使用驼峰写法
    element.style.fontFamily;

    获取样式属性的返回值与设置值采用同样的单位。
    如我们在 CSS font-size 属性时以 em 为单位,相应的 DOM fontSize 属性也以 em 为单位。

    注意 通过 style 获取属性的局限性,即只能返回 内联样式

    <p style="color:sienna;margin-left:20px">这是一个段落。</p>

    通过 link 元素引入的 CSS 文件样式不能用 DOM style 属性检索出来。

    <link rel="stylesheet" type="text/css" href="mystyle.css">

    通过 <head> 部分引入的 <style> 标签里也不能用 DOM style 属性检索出来。

    <head>
      <style>
      hr {color:sienna;}
      p {margin-left:20px;}
      </style>
    </head>

    设置样式

    style 对象的各个属性都是可读写的,可以通过元素的 style 属性获取样式,也可以通过它更新样式。

    element.style.property = value;
    // 例如:
    para.style.color = "black";

    何时该用 DOM 脚本设置样式

    在绝大多数场合还是应该使用 CSS 声明样式。
    在使用 CSS 不方便的场合,可以利用 DOM 对文档的样式做一些小的增强。

    通过 CSS 设置样式方式:

    // 1.通过标签元素
    p {
      font-size: 1em;
    }
    // 2.通过class属性
    .fineorint {
      font-size: 1em;
    }
    // 3.通过id属性
    #fineorint {
      font-size: 1em;
    }

    通过 DOM 脚本设置样式的情况:

    • 根据元素在节点数的位置来设置样式
    • 根据某种条件反复设置某种样式
    • 响应事件,即事件发生时设置有关元素的样式

    className 属性

    前面一直在使用 DOM 直接设置或者修改元素的样式,这种让行为层干表示层的活,并不是理想的工作方式。

    与其使用 DOM 直接改变某个元素的样式,倒不如通过 JavaScript 代码修改元素的 class 属性。

    通过修改 DOM 直接改变元素样式:

    function styleHeaderSiblings() {
      if (!document.getElementsByTagName) return false;
      var headers = document.getElementsByTagName("h1");
      for (var i=0; i<headers.length; i++) {
        var elem = getNextElement(headers[i].nextSibling);
        elem.style.fontWeight = "bold";
        elem.style.fontSize = "1.2em";
      }
    }

    通过修改 class 属性:

    function styleHeaderSiblings() {
      if (!document.getElementsByTagName) return false;
      var headers = document.getElementsByTagName("h1");
      for (var i=0; i<headers.length; i++) {
        var elem = getNextElement(headers[i].nextSibling);
        elem.setAttribute("class","intro");
      }
    }

    当然需要提前引入 CSS 样式:

    .intro {
      font-weight: bold;
      font-size: 1.2em;
    }

    更简单的是通过 className 属性。
    className 属性是一个可读/可写的属性,凡是元素节点都有这个属性。

    获得元素的 class 属性:
    element.className
    修改元素的 class 属性:
    element.className = value

    通过 className 属性修改样式:

    function styleHeaderSiblings() {
      if (!document.getElementsByTagName) return false;
      var headers = document.getElementsByTagName("h1");
      for (var i=0; i<headers.length; i++) {
        var elem = getNextElement(headers[i].nextSibling);
        elem.className = "intro";
      }
    }

    缺点:通过 className 属性设置元素的 class 属性将替换原有 class 设置。

    可以通过 字符串拼接 解决(注意 intro 前的空格):

    elem.className += " intro";

    第10章 用JavaScript实现动画效果

    JavaScript 能够按照预定的时间间隔重复调用一个函数,而这意味着我们可以随着时间的推移而不断地改变某个元素地样式。

    位置

    位置通常是由 CSS 负责设置的:

    element {
      position: absolute;
      top: 50px;
      left: 100px;
    }
    • static: 默认值。没有定位,元素出现在正常的流中(忽略 top, bottom, left, right 或者 z-index 声明)。
    • relative: 生成相对定位的元素,相对于其正常位置进行定位。因此,"left:20" 会向元素的 LEFT 位置添加 20 像素。
    • absolute: 生成绝对定位的元素,相对于 static 定位以外的第一个父元素进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
    • fixed: 生成绝对定位的元素,相对于浏览器窗口进行定位。元素的位置通过 "left", "top", "right" 以及 "bottom" 属性进行规定。
    • inherit: 规定应该从父元素继承 position 属性的值。

    时间

    JavaScript 函数 setTimeout 能让某个函数在经过一段预定的时间后才开始执行。

    variable = setTimeout("function",interval);

    第一个参数为函数名字,第二个参数为间隔时间。若想取消执行:

    clearTimeout(variable)

    本章主要是定义的一个位置随时间运动的函数:

    function moveElement(elementID,final_x,final_y,interval) {
      if (!document.getElementById) return false;
      if (!document.getElementById(elementID)) return false;
      var elem = document.getElementById(elementID);
      var xpos = parseInt(elem.style.left);
      var ypos = parseInt(elem.style.top);
      if (xpos == final_x && ypos == final_y) {
        return true;
      }
      if (xpos < final_x) {
        xpos++;
      }
      if (xpos > final_x) {
        xpos--;
      }
      if (ypos < final_y) {
        ypos++;
      }
      if (ypos > final_y) {
        ypos--;
      }
      elem.style.left = xpos + "px";
      elem.style.top = ypos + "px";
      var repeat = "moveElement('"+elementID+"',"+final_x+","+final_y+","+interval+")";
      movement = setTimeout(repeat,interval);
    }

    第11章 HTML5

    本章介绍了 HTML5 的新特性,包括 <canvas><audio><video> 元素及新的表单控件。

    第12章 综合示例

    结合前面的知识实现了一个综合示例,对前面的知识进行了很好的回顾。

    这本书看下来,感受最大的是 HTML 和 CSS 基础太差,JavaScript 部分还好。
    原来计划上个周末看完呢,中间又催文档啥的,这个周末(2017.10.29)才看完。

  • 相关阅读:
    iscroll.js
    HTML 第九章总结
    HTML第八章总结
    HTML第七章总结
    HTML第六章总结
    HTML第五章总结
    HTML第四章总结
    HTML第三章总结
    HTML第二章总结
    HTML第一章总结
  • 原文地址:https://www.cnblogs.com/hustshu/p/14629898.html
Copyright © 2011-2022 走看看