js语法+dom+js图片库+最佳实践+图片库改进版
【2】js语法
【2.2.4】数据类型
类型1)字符串
var mood = 'happy';
var moood = "happy";
类型2)数值;
var age = 33.24;
类型3)布尔值;
var married = true;
【2.2.5】数组
1)填充方式
填充方式1)
var beatles = Array();
var beatles = Array(4);
beatles[0] = "0";
填充方式2)
var beatles = ["0", "1"];
填充方式3)可以填入3种类型的混合数据;
var beatles = ["0", 0, true];
2)二维数组
var lennon = ["0", "1"];
var beatles = [];
beatles[0] = lennon;
3)关联数组:下标是字符串;
var lennon = Array();
lennon["name"]="john";
【2.2.6】对象
var lennon = new Object();
lennon.name = "john";
lennon.year = 1999;
lennon.living = false;
还可以这样:
var lennon = {name:"1", age:2};
【2.6】函数
1)变量作用域
全局变量:可以在脚本的任意位置使用;
局部变量:只可以在声明它的那个函数内部使用;
注意:如果在函数内部不小心使用了全局变量,即使本意是使用局部变量,javascript 还是会认为引用那个全局变量;
2)如何声明全局变量和局部变量:
在某个函数中使用var,那个变量是局部变量;如果没有使用var,则该变量是全局变量;
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><title>p29</title></head><body><script src="p29.js"></script></body>
</html>
function square(num) {total = num * num;return total;
}
var total = 50;
var number = square(20);
alert(total);
【2.7】对象:由属性和方法组合在一起的数据实体;
var num = 7.1;
var num = Math.round(num);
alert(num);
var current_date = new Date();
var today = cur_date.getDay();
【2.7.2】 宿主对象
1)定义:由浏览器提供的预定义对象被称为宿主对象;
2)宿主对象有:Form, Image 和 Element 等;
补充:宿主的含义: 指为寄生生物提供生存环境的生物;
【3】dom
【3.0】写在前面:
1)5个常用的DOM方法: getElementById, getElmentsByTagName, getElementsByClassName, getAttribute 和 setAttribute 等;
【3.1】 文档: DOM中的 D: document, 把网页文档转换为一个文档对象;
【3.2】对象:DOM中的O:
1)js的3种对象,如下:
用户定义对象;由程序员自行创建的对象;
内建对象;内建在js里的对象; 如Array, Math 和 Date;
宿主对象;如 Form, Image 和 Element , window 等;
window对象对应着浏览器窗口本身;这个对象的属性和方法称为 BOM 浏览器对象模型,提供了 windom.open 和 windom.blur 等方法;
【3.3】模型:Model, DOM把一个网页文档当做一棵树;
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><title>p34</title></head><body><h1>what to buy</h1><p title="a gentel reminder"> dont forget to buy this stuff.</p><ul id="purchases"><li>a tin of beans</li><li class="sale">cheese</li><li class="sale important">milk</li></ul><script src="p29.js"></script></body>
</html>
【3.4】3种节点类型
1)元素节点:dom的原子; 如 <body> <p> <ul>
2)文本节点:<p>元素节点包含的文本 dont forget to buy this stuff 就是文本节点;
3)属性节点:<p>元素中的 title="a gentel reminder" 就是属性节点;
【3.4.4】CSS
cascading style sheet 层叠样式表;
1)设置样式如下:
body {
color:white;
background-color:black;
}
2) class属性 和 id属性如下:
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><title>p34</title><style>body {color:white;background-color:black;}</style><link rel="stylesheet" href="p37.css" /></head><body><h1 class="special">what to buy</h1><p class="special" title="a gentel reminder"> dont forget to buy this stuff.</p><ul id="purchase"><li>a tin of beans</li><li class="sale">cheese</li><li class="sale important">milk</li></ul><script src="p29.js"></script></body>
</html>
body {color:white;background-color:black;}
.special { font-style:italic;
}
h1.special {text-transform:uppercase;
}#purchase {border:1px solid white;background-color:#333;color:#ccc;padding:1em;
}
#purchase {font-weight:bold;
}
【3.4.5】获取元素:有3种dom方法获取元素节点,通过元素id, 标签名字和类名类获取;
1) getElementById
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><title>p39</title><link rel="stylesheet" href="p39.css" /></head><body><h1 class="special">what to buy</h1><p class="special" title="a gentel reminder"> dont forget to buy this stuff.</p><ul id="purchase"><li>a tin of beans</li><li class="sale">cheese</li><li class="sale important">milk</li></ul><script>alert(typeof document.getElementById("purchase"));</script></body>
</html>
2)getElementsByTagName 返回一个对象数组;
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><title>p39</title><link rel="stylesheet" href="p39.css" /></head><body><h1 class="special">what to buy</h1><p class="special" title="a gentel reminder"> dont forget to buy this stuff.</p><ul id="purchase"><li>a tin of beans</li><li class="sale">cheese</li><li class="sale important">milk</li></ul><script>alert(typeof document.getElementById("purchase"));var items = document.getElementsByTagName("li");for (var i=0; i<items.length; i++) {alert(typeof items[i])}</script></body>
</html>
2.2)如果想知道某个元素包含多少个列表项,用如下方法:
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><title>p39</title><link rel="stylesheet" href="p39.css" /></head><body><h1 class="special">what to buy</h1><p class="special" title="a gentel reminder"> dont forget to buy this stuff.</p><ul id="purchase"><li>a tin of beans</li><li class="sale">cheese</li><li class="sale important">milk</li></ul><script>alert(typeof document.getElementById("purchase"));var items = document.getElementsByTagName("li");for (var i=0; i<items.length; i++) {alert(typeof items[i])}items = document.getElementsByTagName("*");alert(items.length);</script></body>
</html>
3)getElementsByClassName: 通过类名来获取元素;
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><title>p39</title><link rel="stylesheet" href="p39.css" /></head><body><h1 class="special">what to buy</h1><p class="special" title="a gentel reminder"> dont forget to buy this stuff.</p><ul id="purchase"><li>a tin of beans</li><li class="sale">cheese</li><li class="sale important">milk</li></ul><script src="p42.js"> </script></body>
</html>
// 找出node 的名为 className的元素;
function getElementsByClassName(node, className) {if (node.getElementsByClassName) {return node.getElementsByClassName(className);} else {var results = new Array();var elements = node.getElementsByTagName("*");for (var i=0; i<elements.length; i++) {if (elements[i].className.indexOf(className) != -1) {results[results.length] = element[i];} }}return results;
}
var shopping = document.getElementById("purchase");
var sales = getElementsByClassName(shopping, "sale");
alert(sales.length);
【3.5】获取和设置属性
【3.5.1】getAttribute 方法;
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><title>p44</title><link rel="stylesheet" href="p39.css" /></head><body><h1 class="special">what to buy</h1><p class="special" title="a gentel reminder"> dont forget to buy this stuff.</p><ul id="purchase"><li>a tin of beans</li><li class="sale">cheese</li><li class="sale important">milk</li></ul><script>var param = document.getElementsByTagName("p");for (var i=0; i<param.length; i++) {var title_text = param[i].getAttribute("title"); // 获取属性if (title_text) alert(title_text);}</script></body>
</html>
【3.5.2】setAttribute 设置属性
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><title>p45</title><link rel="stylesheet" href="p39.css" /></head><body><h1 class="special">what to buy</h1><p class="special" title="a gentel reminder"> dont forget to buy this stuff.</p><ul id="purchase"><li>a tin of beans</li><li class="sale">cheese</li><li class="sale important">milk</li></ul><script>var param = document.getElementsByTagName("p");for (var i=0; i<param.length; i++) {var title_text = param[i].getAttribute("title"); // 获取属性if (title_text){param[i].setAttribute("title", "brand new title text"); // 设置属性alert(param[i].getAttribute("title"));} }</script></body>
</html>
补充:查找带有多个类名的元素,中间使用空格分隔,如下:
document.getElementsByClassName("class1 class2");
【4】案例研究:javascript图片库
【4.1】标记
(1)
<!DOCTYPE html>
<html lang="en">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><title>Image Gallery</title>
</head>
<body><h1>Snapshots</h1><ul><li> <a href="images/fireworks.jpg" title="A fireworks display">Fireworks</a></li><li> <a href="images/coffee.jpg" title="A cup of black coffee">Coffee</a></li><li> <a href="images/rose.jpg" title="A red, red rose">Rose</a></li><li> <a href="images/bigben.jpg" title="The famous clock">Big Ben</a></li></ul>
</body>
</html>
【4.2】 javascript
【4.2.1】 非dom解决方案
1)改变属性值的方法,如下:
element.value = "the new value";
element.setAttribute("value", "the new value");
2)运行结果:
(2)
<!DOCTYPE html>
<html lang="en">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><title>Image Gallery</title>
</head>
<body><h1>Snapshots</h1><ul><li> <a href="images/fireworks.jpg" οnclick="showPic(this);return false"title="A fireworks display">Fireworks</a></li><li> <a href="images/coffee.jpg" οnclick="showPic(this);return false"title="A cup of black coffee">Coffee</a></li><li> <a href="images/rose.jpg" οnclick="showPic(this);return false"title="A red, red rose">Rose</a></li><li> <a href="images/bigben.jpg" οnclick="showPic(this);return false"title="The famous clock">Big Ben</a></li></ul><img id="placeholder" src="imges/placeholder.jpg" alt="my image gallery" /><script> function showPic(whichpic){var source = whichpic.getAttribute("href");var placeholder = document.getElementById("placeholder");placeholder.setAttribute("src", source); }</script>
</body>
</html>
分析: 在 onclick 事件处理函数所触发的 javascript代码里增加一条return false , 就可以防止用户被带到 目标链接窗口;,即不访问href属性值的url地址;
【4.4】对这个函数进行扩展
【4.4.1】childNodes属性: 获取body元素的全部子元素;
var body_element = document.getElementsByTagName("body")[0];
body_element.childNodes 获取body元素的全部子元素;function countBodyChildren() {
var body_element = document.getElementsByTagName("body")[0];
alert(body_element.childNodes.length);
}
window.onload = countBodyChildren;
我想让 countBodyChildren 这个函数在页面加载时就执行, 这需要使用 onload 事件处理函数;
(3)
<!DOCTYPE html>
<html lang="en">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><title>Image Gallery</title>
</head>
<body><h1>Snapshots</h1><ul><li> <a href="images/fireworks.jpg" οnclick="showPic(this);return false"title="A fireworks display">Fireworks</a></li><li> <a href="images/coffee.jpg" οnclick="showPic(this);return false"title="A cup of black coffee">Coffee</a></li><li> <a href="images/rose.jpg" οnclick="showPic(this);return false"title="A red, red rose">Rose</a></li><li> <a href="images/bigben.jpg" οnclick="showPic(this);return false"title="The famous clock">Big Ben</a></li></ul><img id="placeholder" src="imges/placeholder.jpg" alt="my image gallery" /><script> function countBodyChildren() {var body_element = document.getElementsByTagName("body")[0];alert(body_element.childNodes.length);}window.onload = countBodyChildren;</script>
</body>
</html>
【4.4.2】nodeType属性
1)childNodes属性返回的数组包含所有类型的节点,不仅仅是元素节点;
2)nodeType总共有12种可取值,但其中仅3种具有使用价值:
元素节点的nodeType属性值为1;
属性节点的nodeType属性值为2;
文本节点的nodeType属性值为3;
【4.4.5】nodeValue属性
1)改变一个文本节点的值,使用dom提供的 nodeValue属性,来得到节点的值;
【4.4.6】firstChild 和 lastChild 属性:第一个元素和最后一个元素;
node.firstChild 等价于 node.childNodes[0];
node.lastChild 等价于 node。childNodes[node.childNodes.length-1]
【4.4.7】利用 nodeValue属性刷新这段描述
1)
(4)
源码:
function showPic(whichpic) {var source = whichpic.getAttribute("href");var placeholder = document.getElementById("placeholder");placeholder.setAttribute("src",source);var text = whichpic.getAttribute("title");var description = document.getElementById("description");// 利用nodeValue属性刷新这段描述description.firstChild.nodeValue = text;
}
<!DOCTYPE html>
<html lang="en">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><title>Image Gallery</title><script type="text/javascript" src="scripts/showPic.js"></script><link rel="stylesheet" href="styles/layout.css" type="text/css" media="screen" />
</head>
<body><h1>Snapshots</h1><ul><li><a href="images/fireworks.jpg" title="A fireworks display" οnclick="showPic(this); return false;">Fireworks</a></li><li><a href="images/coffee.jpg" title="A cup of black coffee" οnclick="showPic(this); return false;">Coffee</a></li><li><a href="images/rose.jpg" title="A red, red rose" οnclick="showPic(this); return false;">Rose</a></li><li><a href="images/bigben.jpg" title="The famous clock" οnclick="showPic(this); return false;">Big Ben</a></li></ul><img id="placeholder" src="data:images/placeholder.gif" alt="my image gallery" /><p id="description">Choose an image.</p>
</body>
</html>
【5】最佳实践
【5.1】
1)js使用window对象的open()方法来创建新的浏览器窗口,
window.open(url, name, features),
【荔枝】
function popup(url){
window.open(url, "popup", "width=320,height=480");
}
【5.2.2】内嵌的事件处理函数
<a href="#" οnclick="popup('http://www.baidu.com')">;
return false;"> example </a>
更加简单的写法是:
<a href="https://www.baidu.com/" οnclick="popUp(this.href);return false;">baidu</a>
【5.4】分离js 与 html;
1)当window对象触发onload事件时,document对象已经存在;
【html】
<!DOCTYPE html>
<html lang="en">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><title>Example</title>
<script type="text/JavaScript" src="p69.js">
</script>
</head>
<body><a href="https://www.baidu.com/" class="popup">baidu</a>
</body>
</html>
【js】
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;}}}
}
function popUp(url){window.open(url, "baidu", "width=320, height=480");
}
1)压缩脚本:指的是把脚本文件不必要的字节,如空格和注释,统统删除,从而达到压缩的目的;
2)脚本压缩工具:
工具1) JSMin
工具2)YUI Compressor
工具3)Closure Compiler
【6】图片库改进版
【6.3.1】添加事件处理函数
(1)
<!DOCTYPE html>
<html lang="en">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><title>Image Gallery</title><script type="text/javascript" src="scripts/showPic.js"></script>
</head>
<body><h1>Snapshots</h1><ul id="imagegallery"><li><a href="images/fireworks.jpg" title="A fireworks display">Fireworks</a></li><li><a href="images/coffee.jpg" title="A cup of black coffee" >Coffee</a></li><li><a href="images/rose.jpg" title="A red, red rose">Rose</a></li><li><a href="images/bigben.jpg" title="The famous clock">Big Ben</a></li></ul><img id="placeholder" src="data:images/placeholder.gif" alt="my image gallery" /><p id="description">Choose an image.</p>
</body>
</html>// 改变占位符的图片和标题
function showPic(whichpic) {if (!document.getElementById("placeholder")) return true;var source = whichpic.getAttribute("href");var placeholder = document.getElementById("placeholder");placeholder.setAttribute("src",source);if (!document.getElementById("description")) return false;if (whichpic.getAttribute("title")) {var text = whichpic.getAttribute("title");} else {var text = "";}var description = document.getElementById("description");if (description.firstChild.nodeType == 3) {description.firstChild.nodeValue = text;}return false;
}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);}links[i].onkeypress = links[i].onclick;}
}
// 添加函数func 到 window.onload 函数的执行列表;// onload 函数会在 document加载完毕后执行;function addLoadEvent(func) {var oldonload = window.onload;if (typeof window.onload != 'function') {window.onload = func;} else {window.onload = function() {oldonload();func();}}
}addLoadEvent(prepareGallery);
【6.3.2】共享onload事件
1)addLoadEvent函数: 计划在页面加载完毕时执行传入参数表示的函数;
function addLoadEvent(func) {var oldonload = window.onload;if (typeof window.onload != 'function') {window.onload = func;} else {window.onload = function() {oldonload();func();}}
}
2)
(2)
<!DOCTYPE html>
<html lang="en">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><title>Image Gallery</title><script type="text/javascript" src="scripts/showPic.js"></script><link rel="stylesheet" href="styles/layout.css" type="text/css" media="screen" />
</head>
<body><h1>Snapshots</h1><ul id="imagegallery"><li><a href="images/fireworks.jpg" title="A fireworks display"><img src="data:images/thumbnail_fireworks.jpg" alt="Fireworks" /></a></li><li><a href="images/coffee.jpg" title="A cup of black coffee" ><img src="data:images/thumbnail_coffee.jpg" alt="Coffee" /></a></li><li><a href="images/rose.jpg" title="A red, red rose"><img src="data:images/thumbnail_rose.jpg" alt="Rose" /></a></li><li><a href="images/bigben.jpg" title="The famous clock"><img src="data:images/thumbnail_bigben.jpg" alt="Big Ben" /></a></li></ul><img id="placeholder" src="data:images/placeholder.gif" alt="my image gallery" /><p id="description">Choose an image.</p>
</body>
</html>
function showPic(whichpic) {if (!document.getElementById("placeholder")) return true;var source = whichpic.getAttribute("href");var placeholder = document.getElementById("placeholder");placeholder.setAttribute("src",source);if (!document.getElementById("description")) return false;if (whichpic.getAttribute("title")) {var text = whichpic.getAttribute("title");} else {var text = "";}var description = document.getElementById("description");if (description.firstChild.nodeType == 3) {description.firstChild.nodeValue = text;}return false;
}
// 加载函数
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);}links[i].onkeypress = links[i].onclick;}
}
// 把函数func 添加到 页面加载完毕时执行的函数列表中;
// window.onload 用于绑定点击事件与响应函数;
function addLoadEvent(func) {var oldonload = window.onload;if (typeof window.onload != 'function') {window.onload = func;} else {window.onload = function() {oldonload();func();}}
}addLoadEvent(prepareGallery);
【6.5】 js的优化版本
// 【荔枝】用到了firstChild 和 nodeType的js
// 鼠标点击响应函数
function showPic(whichpic) {if (!document.getElementById("placeholder")) return true;var source = whichpic.getAttribute("href");var placeholder = document.getElementById("placeholder");placeholder.setAttribute("src",source);if (!document.getElementById("description")) return false;if (whichpic.getAttribute("title")) {var text = whichpic.getAttribute("title");} else {var text = "";}var description = document.getElementById("description");// 用到了 firstChild 和 nodeType;if (description.firstChild.nodeType == 3) { // 3 表示文本节点description.firstChild.nodeValue = text;}return false;
}
// 被加载的函数
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);}links[i].onkeypress = links[i].onclick; // 不推荐使用 onkeypress 属性}
}
// 在页面加载完成后,被加载函数。
//
function addLoadEvent(func) {var oldonload = window.onload;if (typeof window.onload != 'function') {window.onload = func;} else {window.onload = function() {oldonload();func();}}
}
addLoadEvent(prepareGallery);
js语法+dom+js图片库+最佳实践+图片库改进版相关推荐
- 05 JS基础DOM
05 JS基础DOM JS的window对象定时器: window下一些方法: <script>弹出window.alert('hello')返回布尔值var ret = window.c ...
- 前端性能优化最佳实践(转)
转载请注明: 转载自WEB前端开发(www.css119.com)-关注常见的WEB前端开发问题.最新的WEB前端开发技术(webApp开发.移动网站开发).最好的WEB前端开发工具和最全的WEB前端 ...
- 3.9 Docker最新入门教程-Docker入门-构建镜像最佳实践
3.9 构建镜像最佳实践 安全扫描 构建镜像后,最好使用docker scan命令扫描它以查找安全漏洞.Docker 与Snyk合作提供漏洞扫描服务. 笔记 您必须登录到 Docker Hub 才能扫 ...
- 关于 JS 模块化的最佳实践总结
模块化开发是 JS 项目开发中的必备技能,它如同面向对象.设计模式一样,可以兼顾提升软件项目的可维护性和开发效率. 模块之间通常以全局对象维系通讯.在小游戏中,GameGlobal 是全局对象.在小程 ...
- React.js 2016 最佳实践 徬梓阅读 1584收藏 71
为什么80%的码农都做不了架构师?>>> 译者按:近几个月React相关话题依旧火热,相信越来越多的开发者在尝试这样一项技术,我们团队也在PC和移动端不断总结经验.2016来了 ...
- Node.js CLI 工具最佳实践
为什么写这篇文章? 一个糟糕的 CLI 工具会让用户觉得难用,而构建一个成功的 CLI 需要密切关注很多细节,同时需要站在用户的角度,创造良好的用户体验.要做到这些特别不容易. 在这个指南中,我列出了 ...
- Vue.js最佳实践
Vue.js最佳实践 第一招:化繁为简的Watchers 场景还原: created(){this.fetchPostList() },watch: {searchInputValue(){this. ...
- vue.js+boostrap最佳实践
一.为什么要写这篇文章 最近忙里偷闲学了一下vue.js,同时也复习了一下boostrap,发现这两种东西如果同时运用到一起,可以发挥很强大的作用,boostrap优雅的样式和丰富的组件使得页面开发变 ...
- JS最佳实践——红皮书
最佳实践 前言 1 可维护性 2 降低耦合 2.1 将css从js中抽离 2.2 模板文本写注释 2.3 应用逻辑 / 事件处理程序分离 2.3.1 概念 2.3.2 Demo 2.4 松散耦合原则 ...
最新文章
- 【工业互联网】全球工业互联网十大最具成长性技术展望(2019-2020年)
- iOS10 UI设计基础教程
- make for arm
- QTP- 对输入格式的检查
- 673. 最长递增子序列的个数
- 利用FSMT进行文件服务器迁移及整合
- oracle 全局搜索字符串,oracle操作字符串:拼接、替换、截取、查找 _ 学编程-免费技术教程分享平台...
- Linux常用命令(补充)--其他
- 阿里云镜像加速Docker
- 86 arduino 电机yl_Arduino 直流电机
- nginx 反向代理及负载均衡
- 软件测试计划和测试方案的区别
- w5500跨网段_串口转以太网模块—W5500S2E-S1如何与计算机建立局域网
- 不需要下载7-zip 解压 7z.001 7z.002 7z.003
- 总结2016,展望2017
- ubuntu14.04 安装五笔输入法(fcitx)
- 我的蓝色之路有多远?
- 【学习笔记】《卓有成效的管理者》 第三章 我能贡献什么
- Linux入门学习 —— 常用的基本命令(上)
- live555服务器性能,使用Live555 HTTP容量作为信号服务器