JavaScript DOM 编程艺术 (第二版)学习之5-6章

第五章 最佳实践

5.1 过去的错误

        与HTML语言相比,JavaScript语言的生存环境的要求要苛刻得多。如果JavaScript代码不符合语法规定,JavaScript解释器(对Web应用而言就是浏览器)将拒绝执行它们并报错。而浏览器在遇到不符合语法规定的HTML代码时,则会千方百计地将其呈现出来。

5.2 平稳退化

        如果正确使用了JavaScript脚本,就可以让访问者在它们的浏览器不支持JavaScript的情况下仍能顺利地浏览你的网站。这就是所谓的平稳退化(graceful degradation),就是说,虽然某些功能无法使用,但最基本的操作仍能顺利完成。

注意:应该只在绝对必要的情况下才使用弹出窗口,因为这将牵涉到网页的可访问性问题。如果网页上的某个链接将弹出新窗口,最好在这个链接本身的文字中予以说明。

JavaScript使用window对象的open()方法来创建新的浏览器窗口。
window.open(url,name,features)

说明

  • 第一个参数是想在新窗口里打开的网页的URL地址。如果省略,将弹出一个空白浏览器窗口。
  • 第二个参数是新窗口的名字,可以在代码里通过这个名字与新窗口进行通信。
  • 第三个参数是一个以逗号分隔的字符串,其内容是新窗口的各种属性,如新窗口尺寸等。新窗口的浏览功能要少而精。
  • open方法的功能对文档的内容无任何影响,只与浏览环境(具体到这个例子,window对象)有关。
5.2.1 “javascript:”伪协议

        “真”协议用来在因特网上的计算机之间传输数据包,如HTTP协议(http://)、FTP协议(ftp://)等,伪协议是一种非标准化的协议。“javascript:”伪协议让我们通过一个链接来调用JavaScript函数。
下面是通过“javascript:”伪协议调用popUp()函数的具体做法。

<a href = " javascript:popUp('http://www.example.com/');"> example </a>
5.2.2 内嵌的事件处理函数
  • 把onclick事件处理函数作为属性嵌入<a>标签,该事件处理函数将在onclick事件发生时调用图片切换函数。
  • 在某个链接里用onclick事件处理函数去打开新窗口时,这个链接的href属性似乎没有什么用处。
<a href="#" onclick="popUp('http://www.example.com'); return false ;"> example</a>
  • #号是一个仅供文档内部使用的链接记号。把href属性的值设置为“#”只是为了创建一个空链接,实际工作全部由onclick属性负责完成。
  • 与使用“javascript:”伪协议调用JavaScript代码的做法同样糟糕,因为它们都不能平稳退化。
5.2.3 谁关心这个

        具体到popUp()函数,为其中的JavaScript代码预留出退路很简单:在链接里把href属性设置为真实存在的URL地址,使其成为一个有效的链接。

<a href="http://www.example.com/" onclick="popUp('http://www.example.com'); return false ;"> example</a>

精简化

<a href="http://www.example.com/" onclick="popUp(this.getAttribute('href')); return false ;"> example</a>
<a href="http://www.example.com/" onclick="popUp(this.href); return false ;"> example</a>

        把href属性的设置为真实存在的URL地址后,即使JavaScript已被禁用,这个链接仍然可用。虽然这个链接在功能上打了点儿折扣,它没有打开一个新窗口,因为popUp失效。

5.3 向CSS学习

5.3.1 结构与样式分离

        作为CSS技术的突出优点,文档结构与文档样式的分离可以确保网页都能平稳退化。

5.3.2 渐进增强
  • 所谓“渐进增强”就是用一些额外的信息层去包裹原始数据。按照“渐进增强”原则创建出来的网页几乎(如果不是全部的话)都符合“平稳退化”原则。
  • 类似于CSS,JavaScript和DOM提供的所有功能也应该构成一个额外的指令层。CSS代码负责提供关于“表示”的信息,JavaScript代码负责提供关于“行为”的信息。行为层的应用方式与表示层一样。

5.4 分离JavaScript

        负责实际完成各项任务的JavaScript函数都已存入外部文件,问题出现在内嵌的事件处理函数中。在HTML文档里使用诸如onclick之类的属性也是一种既没有效率又容易引发问题的做法。
实现
(1)把文档里的所有链接全放入一个数组里。
(2)遍历数组
(3)如果某个链接的class属性等于popup,就表示这个链接在被点击时应该调用popUp函数。
于是,
A.把这个链接的href属性值传递给popUp()函数
B.取消这个链接的默认行为,不让这个链接把访问者带离当前窗口。
代码

	var links = document.getElementsByTagName("a");
	for(var i = 0; i < links.length; i++){
		if (links[i].getAttribute("class") == "popup"){
			links[i].onclick = function(){
				popUp(this.getAttribute("href"));
				return false;
			}
		}
	}

        以上代码将把调用popUp()函数的onclick事件添加到有关的链接上。只要把它们存入一个外部JavaScript文件,就等于是把这些操作从HTML文档里分离出来了。而这就是“分离JavaScript”的含义。

        把以上代码存入外部JS文件,它们将无法正常运行。因为这段代码的第一行是:

	var links = document.getElementsByTagName("a");
  • 这条语句将在JavaScript文件被加载时立刻执行。如果JavaScript文件是从HTML文档的<head>部分用<script>标签调用的,它将在HTML文档之前加载到浏览器里。
  • 同样,如果<script>标签位于文档底部</body>之前,就不能保证哪个文件最先结束加载(浏览器可能一次加载多个)。因为脚本加载时文档可能不完整,所以模型也不完整。没有完整的DOM,getElementsByTagName等方法就不能正常工作。
  • 必须让这些代码在HTML文档全部加载到浏览器之后马上开始执行。还好,HTML文档全部加载完毕时将触发一个事件,这个事件有它自己的事件处理函数。
  • 文档将被加载到一个浏览器窗口里,document对象又是window对象的一个属性。当window对象触发onload事件时,document对象已经存在。
    代码如下:
window.onload = prepareLinks;
function prepareLinks (){
	var links = document.getElementsByTagName("a");
	for(var i = 0; i < links.length; i++){
		if (links[i].getAttribute("class") == "popup"){
			links[i].onclick = function(){
				popUp(this.getAttribute("href"));
				return false;
			}
		}
	}
}

window.onload 在浏览器完成对象的装载后立即触发。

5.5 向后兼容

5.5.1 对象检测

检测浏览器对JavaScript的支持程度。

  • 只要把某个方法打包在一个if语句里,就可以根据这条if语句的条件表达式的求值结果是true(这个方法 存在)还是false(这个方法不存在)来决定应该采取怎样的行动。这种检测称为对象检测(object detection)。
  • 几乎所有的东西(包括各种方法在内)都可以被当做对象来对待,而这意味着我们可以容易的把不支持某个特定DOM方法的浏览器检测出来。
  • 在使用对象检测时,一定要删掉方法名后面的圆括号,如果不删掉,测试的将是方法的结果,无论方法是否存在。

if(method){
      statements
}

把测试条件改为“如果你不理解这个方法,请离开”则更简单。

if(!method){
      return false;
}

测试多个方法或属性是否存在:
>if(!methodA || !methodB ){
      return false;
}

window.onload = prepareLinks;
function prepareLinks (){
	if(!document.getElementsByTagName){
		return false;
	}
	
	var links = document.getElementsByTagName("a");
	for(var i = 0; i < links.length; i++){
		if (links[i].getAttribute("class") == "popup"){
			links[i].onclick = function(){
				popUp(this.getAttribute("href"));
				return false;
			}
		}
	}
}

虽然只是一条简单的if语句,但它可以确保那些“古老的”浏览器不会因为我的脚本代码而出问题。
这么做是为了让脚本有良好的向后兼容性。

5.5.2 浏览器嗅探技术

      在JavaScript脚本代码里,在使用某个特定的方法或属性之前,先测试它是否真实存在是确保向后兼容性最安全和最可信的方法,但它并不是唯一的方法。
      浏览器嗅探(browser sniffing)指通过提取浏览器供应商提供的信息来解决向后兼容问题。但这种技术风险非常大。
      首先,浏览器有时会“撒谎”。因为历史原因,有些浏览器会把自己报告为另外一种浏览器,还有一些浏览器允许用户任意修改这些信息。
      其次,为了适用于多种不同的浏览器。浏览器嗅探脚本会变得越来越复杂。
      最后,许多浏览器嗅探脚本在进行这类测试时要求浏览器的版本号必须得到精确地匹配。因此,每当市场上出现新版本时,就不得不修改这些脚本。
      浏览器嗅探技术正被对象检测技术所取代。

5.6 性能考虑

        为保证应用流畅地运行,在为文档编写和应用脚本时,需要注意一些问题。

5.6.1 尽量少访问DOM和尽量减少标记

        使用了两次getElementsByTagName方法去执行相同的操作,浪费了一次搜索。

	if(document.getElementsByTagName("a").length > 0{
		var links = document.getElementsByTagName(“a”);
		for(var i = 0 ; i < links.length ; i++){
		//对每个链接做点处理
		}
	}

        把第一次搜索的结果保存在一个变量中,,然后在循环里重用该结果。

	var links = document.getElementsByTagName("a");
	if(links .length > 0{
		for(var i = 0 ; i < links.length ; i++){
		//对每个链接做点处理
		}
	}

        这样一来,代码功能没有变,但搜索DOM的次数由两次降低到了一次。
        另一方面,要尽量减少文档中的标记数量。过多不必要的元素只会增加DOM树的规模,进而增加遍历DOM树以查找特定元素的时间。

5.6.2 合并和放置脚本
  • 包含脚本的最佳方式就是使用外部文件,因为外部文件与标记能清晰地分离开,而且浏览器也能对站点中的多个页面重用缓存过的相同脚本。
  • 最好把多个js文件合并到一个脚本文件中。这样就可以减少加载页面时发送请求的数量。而减少请求数量通常都是在性能优化时首先要考虑的。
  • 脚本在标记中的位置对页面的初次加载时间也有很大影响。当把脚本放在文档的<head>区域时,位于<head>块中的脚本会导致浏览器无法并行加载其他文件(如图像或其他脚本)。
  • 一般来说,根据HTTP规范,浏览器每次从同一个域名中最多只能同时下载两个文件。而在下载脚本期间,浏览器不会下载其他任何文件,即使是来自不同域名的文件也不会下载,所有其他资源都要等脚本加载完毕后才能下载。
  • 把所有<script>标签都放到文档的末尾,标签之前,就可以让页面变得更快。
5.6.3 压缩脚本

      压缩脚本文件也可以加快加载速度。所谓压缩脚本,指的是把脚本文件中不必要的字节,如空格和注释,统统删除,从而达到“压缩”文件的目的。有的精简程序甚至会重写部分代码,使用更短的变量名,从而减少整体文件大小。

function showPic(whichpic) {
	//取得图片的href属性
    var source = whichpic.getAttribute("href");
    //取得占位符
    var placeholder = document.getElementById("placeholder");
    //更新占位符
    placeholder.setAttribute("src",source);
    //使用图像的title属性更新文本描述
    var text = whichpic.getAttribute("title");
    var description = document.getElementById("description");
    description.firstChild.nodeValue = text;
}

压缩后的代码:

function showPic(a) {var b = a.getAttribute("href");
     document.getElementById("placeholder").setAttribute("src",b);
     document.getElementById("placeholder").firstChild.nodeValue =a.getAttribute("title");}

      精简后的代码虽然不容易看懂,却能大幅减少文件大小。多数情况下,应该有一个工作副本,可以修改并注释代码,有一个精简副本,用于放在站点上。为了区分这两个版本,最好在精简副本的文件名中加上min字样。
      有代表性的代码压缩工具:

  1. Douglas Crockford的JSMin
  2. 雅虎的YUI Compressor
  3. 谷歌的Closure Complier

5.7 小结

本章介绍了一些与DOM脚本编程工作有关的概念和实践,主要是
平稳退化、分离JavaScript、向后兼容、性能考虑。


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

6.1 快速回顾

之前完成的函数代码清单:

function showPic(whichpic) {
    //取得图片的href属性
    var source = whichpic.getAttribute("href");
    //取得占位符
    var placeholder = document.getElementById("placeholder");
    //更新占位符
    placeholder.setAttribute("src",source);
    //使用图像的title属性更新文本描述
    var text = whichpic.getAttribute("title");
    var description = document.getElementById("description");
    description.firstChild.nodeValue = text;
}

调用此函数的HTML片段:

	<ul>
       <li>
           <a href="images/001.jpg"  onclick = "showPic(this);return false;"title="海伦娜.弗尔曼肖像 鲁本斯 1620-1625年 79×54厘米 现存伦敦国立美术馆">《海伦娜.弗尔曼肖像》</a>
       </li>
       <li>
           <a href="images/002.jpg"  onclick = "showPic(this);return false;"title=" 无名女郎 1883年 I.N.克拉姆斯柯依 俄国 75.5cm×99cm 布 油彩 莫斯科 特列恰科夫美术馆藏 ">《无名女郎》</a>
       </li>
       <li>
           <a href="images/003.jpg"  onclick = "showPic(this);return false;"title ="吹笛少年 马奈 油画 1866年 160×98厘米">《吹笛少年》</a>
       </li>
       <li>
           <a href="images/004.jpg" onclick = "showPic(this);return false;" title="女占卜师 卡拉瓦乔 油画 1590年 99×131厘米 藏巴黎卢浮宫">《女占卜师》</a>
       </li>

   </ul>
   <img id = "placeholder" src="images/22.jpg" height="333" alt="我的美术馆"/>
   <p id = "description">Choose an image.</p>

6.2 它支持平稳退化么

  • 即使JavaScript功能已被禁用,用户也可以浏览图片库里的所有图片,网页里的所有链接也都可以正常工作。
  • 当JavaScript被禁用时,浏览器将沿着href属性给出的链接前进,用户将看到一张新图片而不是“该页无法显示”之类的出错信息。

6.3 添加事件处理函数

       由于图片库的JavaScript与HTML标记不是分离的,所以可以给整个清单设置一个独一无二的ID,把JavaScript代码与HTML文档中的有关标记关联起来。

<ul id = “imagegallery”>

添加事件处理函数
函数要完成的工作:

  1. 检查当前浏览器是否理解getElementsByTagName
  2. 检查当前浏览器是否理解getElementById
  3. 检查当前网页是否存在一个id为imagegallery的元素
  4. 遍历imagegallery元素中的所有链接
  5. 设置onclick事件,让它在有关链接被点击时完成以下操作
          把这个链接作为参数传递给showPic函数;
          取消链接被点击时的默认行为,不让浏览器打开这个链接。

1.检查点

        如果想用JavaScript给某个网页添加一些行为,就不应该让JavaScript代码对这个网页的结构有任何依赖。
        结构化程序设计原则之一:函数应该只有一个入口和一个出口。
        实际工作中,过分拘泥于这项原则往往会使代码变得难以阅读。如果为了避免留下多个出口点而去改写那些if语句的话,这个函数的核心代码就会被掩埋在一层又一层的花括号里,就像下面这样:

	function preparegallery() {
		if(document.getElementsByTagName){
			if(document.getElementById){
				if(document.getElementById("imagegallery")){
					statements go here ... ...
				}
			}
		}
	}

      如果一个函数有多个出口,只要这些出口集中出现在函数的开头部分,就是可以接受的。
出于可读性的考虑,把return false语句全部集中到preparegallery的开头部分:

	function preparegallery(){
		if (!document.getElementsByTagName) return false;
		if(!document.getElementById) return false;
		if (!document.getElementById("imagegallery")) return false;
		... ...
	}

2.变量名里有什么
创建变量gallery来简化document.getElementById(“imagegallery”)

var gallery = document.getElementById(“imagegallery”);
var links = gallery.getElementsByTagName(“a”);

注意:不要用保留字、JavaScript函数名、alert、var、if之类的单词作为变量的名字。

3.遍历

for(var i = 0; i < links.length; i++)

4.改变行为
links[i].onclick = function() {
showPic(this);
return false;
}

5.完成JavaScript函数

function preparegallery(){
	if (!document.getElementsByTagName) return false;
	if (!document.getElementById) return false;
	if (!document.getElementById("imagegallery")) return false;
	var gallery = document.getElementById("imagegallery");
	var links = gallery.getElementsByTagName("a");
	for(var i = 0; i < links.length; i++){
		links[i].onclick = function() {
			showPic(this);
			return false;
		}
	}
}

6.3.2 共享onload事件

应该让这个函数在网页加载完毕之后立刻执行。网页加载完毕时会触发一个onload事件,这个事件与window对象相关联。

window.onload = prepareGallery;

如果有两个函数functionA 与 functionB,如果想让它们俩都在页面加载时得到执行,该怎么办?
如果把它们逐一绑定到onload事件上,如下所示:

window.onload = functionA ;
window.onload = functionB;

它们当中只有最后那个才会被实际执行。
可以先创建一个匿名函数来容纳这两个函数,然后把那个匿名函数绑定到onload事件上,如下所示:

window.onload = function () {
       functionA ();
       functionB ();
}

弹性最佳解决方案:
      不管在页面加载完毕时执行多少个函数,它都可以应付自如。
addLoadEvent函数将要完成的操作:
      把现有的window.onload事件处理函数的值存入变量oldonload
      如果在这个处理函数上还没有绑定任何函数,就像平时那样把新函数添加给它。
      如果在这个处理函数上已经绑定了一些函数,就把新函数追加到现有指令的末尾。

	function addLoadEvent(func) {
		var oldonload = window.onload;
		if(typeof window.onload != 'function') {
			window.onload = func ; 	
		}else {
			window.onload = function() {
				oldonload();
				func();
			}
		}
	}

这将把那些在页面加载完毕时执行的函数创建为一个队列。如果想把刚才那两个函数添加到这个队列里去,只需写出以下代码:

addLoadEvent(functionA );
addLoadEvent(functionB );

6.4 不要做太多假设

  • 增加一些语句来检查id值等于placeholder和description的元素是否存在。
    -showPic函数负责完成两件事:一是找出id属性值是placeholder的图片并修改其src属性;
    二是找出id属性是description的元素并修改其第一个子元素(firstChild)的nodeValue属性。
  • 第一件事是函数必须完成的任务,第二件事只是一项锦上添花的补充。故把检查工作分成两个步骤以获得这样一种效果:只要placeholder图片存在,即使description元素不存在,切换显示新图片的操作也照常进行。
function showPic(whichpic) {
    if(!document.getElementById("placeholder")) {
        return false;
    }
    var source = whichpic.getAttribute("href");
    var placeholder = document.getElementById("placeholder");
    placeholder.setAttribute("src",source);
    if(document.getElementById("description"))  {
        var text = whichpic.getAttribute("title");
        var description = document.getElementById("description");
        description.firstChild.nodeValue = text;       
    }
    return true;
}

       如果把placeholder图片从标记文档里删掉并在浏览器里刷新这页面,那么无论点击imagegallery清单里的哪一个链接,都没有任何响应。
       问题在于prepareGallery函数做出了这样一个假设:showPic函数肯定会正常返回,基于这一假设,prepareGallery函数取消了onclick事件的默认行为。
       是否要返回一个false值以取消onclick事件的默认行为,其实应该由showPic函数决定。
       showPic应返回两个值:
              如果图片切换成功,返回true。
              如果图片切换不成功,返回false。
       为修正这个问题,应该在返回前验证showPic的返回值,以便决定是否阻止默认行为。

function prepareGallery(){
    if (!document.getElementsByTagName) return false;
    if (!document.getElementById) return false;
    if (!document.getElementById("imagegallery")) return false;
    var gallery = document.getElementById("imagegallery");
    var links = gallery.getElementsByTagName("a");
    for(var i = 0; i < links.length; i++){
        links[i].onclick = function() {
            return !showPic(this);
        }
    }
}

6.5 优化

检查title属性是否真的存在,当title属性不存在时把变量text的值设置为空字符串:

if (whichpic.getAttribute(“title”)){
        var text = whichpic.getAttribute(“title”);
}else{
        var text = " ";
}
完成操作的另一种方法:
var text = whichpic.getAttribute(“title”) ? whichpic.getAttribute(“title”) : " ";

检查placeholder元素是否存在,但需要假设那是一张图片,用nodeName属性来增加一项测试:

if(placeholder.nodeName != “IMG”) {
        return false;
}

注意:nodeName属性总是返回一个大写字母的值,即使元素在HTML文档里是小写字母。
假设description元素的第一个子元素(firstChild)是一个文本节点,可以对此进行检查:

if (description.firstChild.nodeType == 3){
        description.firstChild.nodeValue = text;
}

在引入了以上几项检查之后showPic函数的代码:

function showPic(whichpic) {
    if(!document.getElementById("placeholder")) {
        return false;
    }
    var source = whichpic.getAttribute("href");
    var placeholder = document.getElementById("placeholder");
    if(placeholder.nodeName != "IMG") {
        return false;
    }
    placeholder.setAttribute("src",source);
    if(document.getElementById("description"))
    {
        var text = whichpic.getAttribute("title") ? whichpic.getAttribute("title") : " ";
        var description = document.getElementById("description");
        if (description.firstChild.nodeType == 3) {
            description.firstChild.nodeValue = text;
        }
    }
    return true;
}

6.6 键盘访问

        按下键盘上任何一个按键都会触发onkeypress事件。
        如果想让onkeypress事件与onclick事件触发同样的行为,可简单地把有关指令复制一份。或者直接将onclick事件的所有功能赋给onkeypress事件:link[i].onkeypress = link[i].onclick;

小心onkeypress
        onkeypress事件处理函数很容易出问题,用户每按下一个按键都会触发它。这意味着如果绑定在onkeypress事件上的处理函数上返回的是false,那些只使用键盘访问的用户将永远无法离开当前链接。
        幸运的是,onclick事件处理函数比我们想象的聪明。在几乎所有浏览器里,用Tab键移动到某个链接然后按下回车键的动作也会触发onclick事件。
        最好不要使用onkeypress事件处理函数。onclick事件处理函数已经能满足需要,它对键盘访问的支持相当完美。

6.7 把JavaScript与CSS结合起来

从文档的<head>部分引用layout.css文件。

<link rel=“stylesheet” href=“scripts/layout.css” type=“text/css” media=“screen”/>
注意:
link: 标签链接样式表,空元素,仅包含属性,只存在于head部分,可出现任何次数。
rel: 定义当前文档与被链接文档之间的关系。
media: 规定被链接的文档将显示在什么设备上。screen是计算机屏幕。
href:要导入样式表的url.

把图片链接换成一些缩微图而不是文字,CSS也依然有效。
下面是layout.css文件的完整清单:

body{
     font-family: "微软雅黑",Serif;
     color:white;
     background-color:#993366;
     margin: 1em 10%;
 }
h1{
    color:snow;
    background-color: transparent;

}
ul img{
    height: 80px;
    border: 0;
}
a{
    color:floralwhite;
    background-color: transparent;
    font-weight: bold;
    text-decoration: none;
}
ul{
    padding: 0;
}
li{
    float: left;
    padding: 1em;
    list-style:none;
}
img{
    display:block;
    clear:both;
}
#imagery{
    list-style: none;
}
#imagery li{
    display: inline;
}

font-family: “微软雅黑”,Serif;

特定字体名 + 通用字体系列,字体名中有空格或者# $ 之类的符号,需要在font - family声明中加引号。

margin: 1em 10%;

垂直测量,值复制,上 右 下 左。

display: inline;

设置对象为行内元素显示。
内联对象的默认值(内联对象就是不自动产生换行的元素如span)。

6.8 DOM Core 和 HTML-DOM

        在编写JavaScript代码时只用到了以下几个DOM方法:

  • getElementById
  • getElementsByTagName
  • getAttribute
  • setAttribute

        这些方法都是DOM Core的核心部分。它们并不专属于JavaScript,支持DOM的任何一种程序设计语言都可以使用它们。它们的用途 也并非仅限于处理网页,还可以用来处理用任何一种标记语言(比如XML)编写出来的文档。
        onclick属性等属于HTML-DOM,它们在DOM Core很久之前就已经为人们所熟悉了。如HTML-DOM提供了一个forms对象,可把语句:document.getElementsByTagName(“form”)     
                                                  简化为:document.forms 。
        显然,HTML-DOM比DOM Core 更为简短,但HTML-DOM只能用来处理Web文档。

6.9 小结

本章完成的主要工作:

  • 尽量让JavaScript代码不再依赖于那些没有保证的假设,因此引入了许多项测试和检查。这些测试和检查使JavaScript代码能够平稳退化。
  • 没有使用onkeypress事件处理函数,使JavaScript代码的可访问性得到了保证。
  • 把事件处理函数从标记文档分离到了一个外部的JavaScript文件,使JavaScript代码不再依赖于HTML文档的内容和结构。

不太满意的地方:
        placeholder和description元素单纯是为了showPic函数而存在的,但不支持或禁用了JavaScript功能的浏览器也会把它们呈现出来,难免会给访问者带来不便甚至是困扰。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值