通过理解错误的编码方式,可以更好地了解如何正确地进行编码。当然,编写 Asynchronous JavaScript™ + XML(Ajax)有正确的方法,也有错误的方法。本文将讨论一些需要避免的常见编码实践。

如 果人们在第一次就能够将所有事情全部做对,那么这个世界将变得完全不同。Ajax 也是如此。我做了大量的工作以支持 Ajax 开发人员(包括我自己),包括编码、撰写文章和演讲。通过这些工作,我学到了很多关于正确和错误编写 Ajax 的知识。在我的上一篇文章 “五种常见 Ajax 模式:可立即使用这些非常有用的 Ajax 设计模式” 中,我介绍了五种用于正确编写 Ajax 应用程序的模式。在这篇文章中,我将介绍 Ajax 代码中常见的五种反模式。

请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。

您可能会问,什么是反模式(anti-pattern)反模式 就是频繁出现的应用程序设计缺陷,已经成为所有人都应该注意的问题。我在这里将从较高的层次进行讨论,而不涉及语法错误和链接问题。

大 多数开发人员听说过关于反模式的一个很好的例子:结构化查询语言(Structured Query Language,SQL)的错误使用导致 Web 站点受到 SQL 注入攻击。这种反模式使得公司损失惨重,并暴露了客户记录,而且不幸的是没有一种编程语言可以幸免。因此,我们有必要了解这种模式发生的原理和原因,以及 如何避免。

Ajax 反模式也是如此。我并不是说它们将造成公司损失数十亿的收入,但是它们可以搞垮服务器或者提供糟糕的用户体验,这种代价不仅昂贵,而且令人沮丧。

如 果理解了发生错误的内容,您将学到很多知识。很多时候,人们仅仅把 Ajax 看作是一种在加载页面后从服务器取回 XML 的方式。这种观点非常狭隘,并且如果被错误使用,将引发应用程序的性能问题。在本文中,我将解释这种观点之所以错误的原因,以及如何修复这种错误。

在没有必要的时候轮询计时器

我见到的很多 Ajax 问题都和滥用 JavaScript 语言内置的计时器功能有关。其中的关键方法是 window.setInterval()。只要看到这种方法,就需要稍微提高警惕;为什么要使用一个计时器呢?当然,计时器有其用途 —— 比如,动画。

window.setInterval() 方法告诉页面以特定的时间间隔回调某个函数(比如每秒)。大多数浏览器对使用这些计时器总是说得多,做得少,主要是因为 JavaScript 语言是单线程的语言。如果您要求的时间间隔为 1 秒,那么获得的回调时间间隔可能是 1 秒、1.2 秒、9 秒或任何其他时间。

绝对不需要使用计时器的一种情况就是等待 Ajax 请求的完成。以 清单 1 为例。

清单 1. Antipat1a_polling.html

                        <html><script>                        var req = null;                        function loadUrl( url ) {                        if(window.XMLHttpRequest) {                        try { req = new XMLHttpRequest();                        } catch(e) { req = false; }                        } else if(window.ActiveXObject) {                        try { req = new ActiveXObject('Msxml2.XMLHTTP');                        } catch(e) {                        try { req = new ActiveXObject('Microsoft.XMLHTTP');                        } catch(e) { req = false; }                        } }                        if(req) {                        req.open('GET', url, true);                        req.send('');                        }                        }                        window.setInterval( function watchReq() {                        if ( req != null && req.readyState == 4 && req.status == 200 ) {                        var dobj = document.getElementById( 'htmlDiv' );                        dobj.innerHTML = req.responseText;                        req = null;                        }                        }, 1000 );                        var url = window.location.toString();                        url = url.replace( /antipat1a_polling.html/, 'antipat1_content.html' );                        loadUrl( url );                        </script><body>                        Dynamic content is shown between here:<br/>                        <div id="htmlDiv" style="border:1px solid black;padding:10px;">                        </div>And here.</body></html>                        

要查看真实环境的演示,请查看这个在线版本 antipat1a_polling.html.

在进行 setInterval 调用之前,所有一切看上去都工作得不错。这个调用将设置监视请求状态的计时器,然后使用下载的资源设置页面内容。

我将展示一个更好的解决方案,用来计算出什么时候请求能够完成。同时,清单 2 展示了页面正在请求的文件。

清单 2. Antipat1_content.html

                        <b>Hello there</b>                        

要查看真实环境的演示,请查看这个在线版本 antipat1_content.html.

同时 图 1 显示了在我的浏览器中看到的页面。

图 1. 放置在 HTML 文档中的内容

所 以,您可能会问自己,“它现在可以工作,不是吗?如果没有出现故障的话,为什么要修复呢?” 实际上已经出现故障了,因为程序运行得非常慢。计时器将时间间隔设置为 1 秒,随着时间的流逝,请求完全超过了时间间隔。所以,您将看到页面首先出现一个空的框,然后再等待一秒钟,忽然出现大量的内容。多么糟糕!

如何解决呢?Ajax 天生就是异步的。难道不需要进行轮询循环就能查看何时完成请求吗?

结果证明,并非如此。正如我在 清单 3 中展示的一样,XMLHTTPRequest 对象所提供的全部内容是一个名为 onreadystatechange 的回调机制。(多么好听的名字,让人想起了 VAX PDP/11s)。

清单 3. Antipat1a_fixed.html

                        <html><script>                        var req = null;                        function processReqChange() {                        if (req.readyState == 4 && req.status == 200 ) {                        var dobj = document.getElementById( 'htmlDiv' );                        dobj.innerHTML = req.responseText;                        }                        }                        function loadUrl( url ) {                        ...                        if(req) {                        req.onreadystatechange = processReqChange;                        req.open('GET', url, true);                        req.send('');                        }                        }                        var url = window.location.toString();                        url = url.replace( /antipat1a_fixed.html/, 'antipat1_content.html' );                        loadUrl( url );                        </script>                        ...                        

要查看真实环境的演示,请查看在线版本 antipat1a_fixed.html.

这个新代码只是查看请求对象是否发生改变,以响应 onreadystatechange 回调。然后,在完成后更新页面。

最后的结果是一个加载神速的页面。页面出现后,新的内容几乎是立即填充了页面框。为什么呢?因为请求完成后就立即调用了代码,然后填充页面。没有必要将时间浪费在无聊的计时器上。

轮询反模式的另一个变体是:页面反复向服务器发送请求,即使请求没有发生变化。请看 清单 4 所示的搜索页面。

清单 4. Antipat1b_polling.html

                        <html><script>                        var req = null;                        function processReqChange() {                        if (req.readyState == 4 && req.status == 200 ) {                        var dobj = document.getElementById( 'htmlDiv' );                        dobj.innerHTML = req.responseText;                        }                        }                        function loadUrl( url ) {                        ...                        }                        window.setInterval( function watchSearch() {                        var url = window.location.toString();                        var searchUrl = 'antipat1_content.html?s='+searchText.value;                        url = url.replace( /antipat1b_polling.html/, searchUrl );                        loadUrl( url );                        }, 1000 );                        </script><body><form>                        Search <input id="searchText" type="text">:<br/>                        <div id="htmlDiv" style="border:1px solid black;padding:10px;">                        </div></form></body></html>                        

要查看真实环境的演示,请查看在线版本 antipat1b_polling.html.

您可以看到浏览器中的页面能够发挥作用,如 图 2 所示。

图 2. 具有动态响应区域的搜索区域

多么美妙。这样看来,页面非常合理。当我改变搜索文本时,显示结果的区域也将根据新的搜索条件改变(也许并不完全如此,但是如果为请求安装一个真正的搜索引擎,它就会这样做)。

问题是 JavaScript 代码使用 window.setInterval 来不断地生成请求,即使搜索字段没有发生更改。这将消耗网络带宽,并消耗服务器时间。对于一个流行的站点来说,这可是一个致命的组合。

解决方法就是对搜索框使用事件回调,如 清单 5 所示。

清单 5. Antipat1b_fixed.html

                        <html><script>                        var req = null;                        function processReqChange() { ... }                        function loadUrl( url ) { ...  }                        var seachTimer = null;                        function runSearch()                        {                        if ( seachTimer != null )                        window.clearTimeout( seachTimer );                        seachTimer = window.setTimeout( function watchSearch() {                        var url = window.location.toString();                        var searchUrl = 'antipat1_content.html?s='+searchText.value;                        url = url.replace( /antipat1b_fixed.html/, searchUrl );                        loadUrl( url );                        seachTimer = null;                        }, 1000 );                        }                        </script><body><form>                        Search <input id="searchText" type="text" οnkeyup="runSearch()">:<br/>                        <div id="htmlDiv" style="border:1px solid black;padding:10px;">                        </div></form></body></html>                        

要查看真实环境的演示,请查看在线版本 antipat1b_fixed.html.

这里,我将 runSearch() 函数与搜索框的 onkeyup() 方法关联起来。通过这样做,当用户在搜索框中输入内容时,我将得到回调。

runSearch() 函数执行得非常漂亮。它为调用服务器并实际运行搜索的另一个方法设置了一个超时。如果在设置之前还没有超时,那么将清除该超时。为什么?因为这将允许用户 输入大量的文本;然后,当用户按下最后一个键时,第二种方法将运行搜索。通过这种方法,用户将不再被不断闪烁的显示打扰。


回页首

没有检查回调返回的结果

许多 Ajax 反模式源于对 XMLHTTPRequest 对象机制的误解。我经常看到的一种情况就是,用户没有在回调中检查对象的 readyStatestatus 字段。请查看 清单 6 以理解我所说的含义。

清单 6. Antipat2_nocheck.html

                        <html><script>                        var req = null;                        function processReqChange() {                        var dobj = document.getElementById( 'htmlDiv' );                        dobj.innerHTML = req.responseText;                        }                        ...                        </code>                        <p>Everything looks okay. And on small requests, and on some browsers, it's                        probably fine. But many requests are large enough to call several calls                        to the <code type="inline">onreadystatechange</code> handler before they                        finish. So, your callback might be working with incomplete data.</p>                        <p>The right way to do it is shown in <a href="#list7">Listing 7</a>.</p>                        <code type="section">                        <heading refname="list7" type="code">Listing 7. Antipat2_fixed.html</heading>                        <html><script>                        var req = null;                        function processReqChange() {                        if (req.readyState == 4 && req.status == 200 ) {                        var dobj = document.getElementById( 'htmlDiv' );                        dobj.innerHTML = req.responseText;                        }                        }                        ...                        

要查看真实环境的演示,请查看在线版本 antipat2_nocheck.html.

没有太多的代码,并且它可以在所有浏览器上工作。

我注意到,和其他浏览器相比,这个问题在 Windows® Internet Explorer® 7 上尤为突出。Internet Explorer 7 对 onreadystatechange 进行多次回调 —— 我的意思是说即使对小的请求也多次进行回调。因此,需要正确编写处理程序。


回页首

在使用 HTML 更合适的时候却传送复杂的 XML

在我工作的一家公司中,所有的谈论都是关于 “在网络的边缘实现智能化”。关于这个简单思想的另一个比较有趣的说法就是:通过在桌面上实现浏览器的智能化工作来替代服务器中的全面处理。

但 是在页面中实现智能化意味着在其中使用大量的 JavaScript 代码。这样做有一个很大的弊端:浏览器兼容性。确实需要在每个流行的浏览器上测试 JavaScript 代码中每个关键行 —— 或者至少,对客户最可能使用的浏览器进行测试。所有这些都意味着大量的工作。以 清单 8 所示的复杂 Ajax 代码为例。

清单 8. Antipat3_complex.html

                        <html><head><script>                        var req = null;                        function processReqChange() {                        if (req.readyState == 4 && req.status == 200 && req.responseXML ) {                        var dtable = document.getElementById( 'dataBody' );                        var nl = req.responseXML.getElementsByTagName( 'movie' );                        for( var i = 0; i < nl.length; i++ ) {                        var nli = nl.item( i );                        var elYear = nli.getElementsByTagName( 'year' );                        var year = elYear.item(0).firstChild.nodeValue;                        var elTitle = nli.getElementsByTagName( 'title' );                        var title = elTitle.item(0).firstChild.nodeValue;                        var elTr = dtable.insertRow( -1 );                        var elYearTd = elTr.insertCell( -1 );                        elYearTd.innerHTML = year;                        var elTitleTd = elTr.insertCell( -1 );                        elTitleTd.innerHTML = title;                        } } }                        function loadXMLDoc( url ) {                        if(window.XMLHttpRequest) {                        try { req = new XMLHttpRequest();                        } catch(e) { req = false; }                        } else if(window.ActiveXObject) {                        try { req = new ActiveXObject('Msxml2.XMLHTTP');                        } catch(e) {                        try { req = new ActiveXObject('Microsoft.XMLHTTP');                        } catch(e) { req = false; }                        } }                        if(req) {                        req.onreadystatechange = processReqChange;                        req.open('GET', url, true);                        req.send('');                        }                        }                        var url = window.location.toString();                        url = url.replace( /antipat3_complex.html/, 'antipat3_data.xml' );                        loadXMLDoc( url );                        </script></head><body>                        <table cellspacing="0" cellpadding="3" width="100%"><tbody id="dataBody">                        <tr>                        <th width="20%">Year</th>                        <th width="80%">Title</th>                        </tr>                        </tbody></table></body></html>                        

要查看真实环境的演示,请查看在线版本 antipat3_complex.html.

这段代码从 清单 9 所示的 XML 文件中读取数据,然后将它变为表格格式。

清单 9. Antipat3_data.xml

                        <movies>                        <movie>                        <year>1993</year>                        <title>Jurassic Park</title>                        </movie>                        <movie>                        <year>1997</year>                        <title>The Lost World: Jurassic Park</title>                        </movie>                        <movie>                        <year>2001</year>                        <title>Jurassic Park III</title>                        </movie>                        </movies>                        

可以看到如 图 3 所示的结果。

图 3. 复杂的电影清单页面

这其实不是糟糕的代码。只不过是用大量的代码执行一个实际上相当简单的任务。产生的页面一点儿都不复杂。它不能在客户端对页面进行排序和搜索。事实上,几乎没有理由对 XML 和 HTML 进行复杂的转换。

难道不能像 清单 10 那样让服务器返回 HTML 而不是 XML,从而变得更简单点儿吗?

清单 10. Antipat3_fixed.html

                        <html><script>                        var req = null;                        function processReqChange() {                        if (req.readyState == 4 && req.status == 200 ) {                        var dobj = document.getElementById( 'tableDiv' );                        dobj.innerHTML = req.responseText;                        }                        }                        function loadUrl( url ) { ... }                        var url = window.location.toString();                        url = url.replace( /antipat3_fixed.html/, 'antipat3_content.html' );                        loadUrl( url );                        </script><body><div id="tableDiv"></div></body></html>                        

事实上,这样更加简单。所有创建复杂表行和单元格的代码被替换为页面中 <div> 标记的一组简单的 innerHTML。 Voilà!

从服务器返回的 HTML 如 清单 11 所示。

清单 11. Antipat3_content.html

                        <table cellspacing="0" cellpadding="3" width="100%">                        <tbody id="dataBody">                        <tr>                        <th width="20%">Year</th>                        <th width="80%">Title</th>                        </tr>                        <tr>                        <td>1993</td>                        <td>Jurassic Park</td>                        </tr>                        <tr>                        <td>1997</td>                        <td>The Lost World: Jurassic Park</td>                        </tr>                        <tr>                        <td>2001</td>                        <td>Jurassic Park III</td>                        </tr>                        </tbody>                        </table>                        

要查看真实环境的演示,请查看在线版本 antipat3_content.html.

对 于所有任务,选择是在服务器上处理,还是在客户机上处理取决于任务的需求。本文的例子相当简单:提供电影表。如果任务更复杂的话 —— 可能会进行分类、搜索、添加、删除或动态交互(单击电影名将出现更多信息)—— 那么可以在客户端使用更加复杂的代码。事实上,在本文的结尾我将演示在客户机上进行排序,从而反面论证在服务器上施加大量负载的情形。

也许所有示例中最好的一个就是 Google Maps。Google Maps 执行了很好的任务 —— 将富客户端的代码与服务器端的智能映射引擎结合了起来。我将使用这个服务作为例子,说明如何确定在哪里执行什么样的处理。


回页首

在应该传送 JavaScript 代码的时候却传送 XML

所有关于使 Web 浏览器读取 XML 数据源并动态呈现它们的夸大其辞,可能让您觉得这是惟一可用的方法。然而,这种想法是错误的,因为非常聪明的工程师已经使用过 Ajax 传送技术来发送 JavaScript 代码而不是 XML。请看 清单 12 所示的电影表示例。

清单 12. Antipat4_fixed.html

                        <html><head><script>                        var req = null;                        function processReqChange() {                        if (req.readyState == 4 && req.status == 200 ) {                        var dtable = document.getElementById( 'dataBody' );                        var movies = eval( req.responseText );                        for( var i = 0; i < movies.length; i++ ) {                        var elTr = dtable.insertRow( -1 );                        var elYearTd = elTr.insertCell( -1 );                        elYearTd.innerHTML = movies[i].year;                        var elTitleTd = elTr.insertCell( -1 );                        elTitleTd.innerHTML = movies[i].name;                        } } }                        function loadXMLDoc( url ) { ... }                        var url = window.location.toString();                        url = url.replace( /antipat4_fixed.html/, 'antipat4_data.js' );                        loadXMLDoc( url );                        </script></head><body>                        <table cellspacing="0" cellpadding="3" width="100%">                        <tbody id="dataBody"><tr>                        <th width="20%">Year</th>                        <th width="80%">Title</th>                        </tr></tbody></table></body></html>                        

要查看真实环境的演示,请查看在线版本 antipat4_fixed.html.

这个示例没有从服务器读取 XML,它读取的是 JavaScript 代码。然后使用 JavaScript 代码中的 eval() 函数获取数据,然后再使用这些数据快速构建表。

清单 13 展示了 JavaScript 代码。

清单 13. Antipat4_data.js

                        [ { year: 1993, name: 'Jurassic Park' },                        { year: 1997, name: 'The Lost World: Jurassic Park' },                        { year: 2001, name: 'Jurassic Park III' } ]                        

这个功能要求服务器使用 JavaScript 语言进行通信。不过这通常不是什么大问题。大多数流行的 Web 语言已经支持 JavaScript Object Notation(JSON)输出。

优 势是明显的。在这个示例当中,通过使用 JavaScript 语言,下载到客户机的数据减少了 52%。同样,性能也得到了提升。读取 JavaScript 代码的速度快了 9%。9% 可能看上去不是很大,但是要记住这是个非常基础的示例。更大的数据块或者更复杂的结构需要更多 XML 解析代码,而所需的 JavaScript 代码数量不会变。


回页首

服务器负载过重

在服务器上执行很少的任务的反面论证是在其上执行大量的操作。正如我在前面提到的,这是一个需要权衡的问题。但是,我想说明的是如何在客户机上对电影表执行排序,从而为服务器减轻负载。

清单 14 显示了可排序的电影表。

清单 14. Antipat5_sort.html

                        <html><head><script>                        var req = null;                        var movies = null;                        function processReqChange() {                        if (req.readyState == 4 && req.status == 200 ) {                        movies = eval( req.responseText );                        runSort( 'year' );                        } }                        function runSort( key )                        {                        if ( key == 'name' )                        movies.sort( function( a, b ) {                        if ( a.name < b.name ) return -1;                        if ( a.name > b.name ) return 1;                        return 0;                        } );                        else                        movies.sort( function( a, b ) {                        if ( a.year < b.year ) return -1;                        if ( a.year > b.year ) return 1;                        return 0;                        } );                        var dtable = document.getElementById( 'dataBody' );                        while( dtable.rows.length > 1 ) dtable.deleteRow( 1 );                        for( var i = 0; i < movies.length; i++ ) {                        var elTr = dtable.insertRow( -1 );                        var elYearTd = elTr.insertCell( -1 );                        elYearTd.innerHTML = movies[i].year;                        var elTitleTd = elTr.insertCell( -1 );                        elTitleTd.innerHTML = movies[i].name;                        }                        }                        function loadXMLDoc( url ) { ... }                        var url = window.location.toString();                        url = url.replace( /antipat5_sort.html/, 'antipat4_data.js' );                        loadXMLDoc( url );                        </script></head><body>                        <table cellspacing="0" cellpadding="3" width="100%">                        <tbody id="dataBody"><tr>                        <th width="20%"><a href="javascript: void runSort('year')">Year</a></th>                        <th width="80%"><a href="javascript: void runSort('name')">Title</a></th>                        </tr></tbody></table></body></html>                        

要查看真实环境的演示,请查看在线版本 antipat5_sort.html.

这是一个相当简单的示例。它无法处理那些很可能需要好几页显示的特别长的列表。但是它确实说明了创建一个能够快速排序的表非常简单,并且无需刷新页面,也不需要服务器来执行麻烦无聊的排序工作。


回页首

结束语

我针对 Ajax 编写了大量文章,并做了大量 Ajax 工作,同时主持 IBM developerWorks Ajax 论坛, 所以我了解一些关于 Ajax 的知识,以及其正确和错误的用法。最常见的情况就是开发人员低估了 Ajax 的复杂性,他们认为它只不过是向浏览器发送 XML、JavaScript 或 HTML 代码而已。我将 Ajax 平台视作完整的浏览器;实际上,是完整的流行浏览器集,因为您必须了解所有这些浏览器的特殊要求。

所有这些都归结到一点:有大量的有关 Ajax 的知识要学习,在这个过程中还会发生很多错误。我希望这篇文章能够帮助您避免一些这样的陷阱,或者在落入这样的圈套后帮助您解决麻烦。总之,虽然可以从成功的经验中学到很多知识,然而通常可以从错误中学到更多的东西。

Ajax 和 XML: 五种 Ajax 反模式(转载)相关推荐

  1. Ajax 和 XML: 五种 Ajax 反模式

    什么是反模式(anti-pattern)?反模式 就是频繁出现的应用程序设计缺陷,已经成为所有人都应该注意的问题.我在这里将从较高的层次进行讨论,而不涉及语法错误和链接问题. 大多数开发人员听说过关于 ...

  2. Ajax 和 XML: 五种常见 Ajax 模式

    Asynchronous JavaScript + XML(Ajax)无疑是 2006 年最热门的技术术语,且有望在 2007 得到进一步发展.但是对您的应用程序来说它究竟有什么意义呢?Ajax 应用 ...

  3. 五种 Ajax 反模式:避免常见的 Ajax 代码陷阱!

      developerWorks 中国  >  XML | Web development  > Ajax 和 XML: 五种 Ajax 反模式 避免常见的 Ajax 代码陷阱 文档选项 ...

  4. Linux网络编程 五种I/O 模式及select、epoll方法的理解

    近期一次面试机会让我觉得有很多地方理解可能不到位,翻翻谷歌的资料加深对一些技术的理解 五种I/O 模式: [1] 阻塞 I/O (Linux下的I/O操作默认是阻塞I/O,即open和socket创建 ...

  5. 五种I/O 模式——阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/O

    From: http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520112163171778/ 五种I/O 模式: [1]        ...

  6. 详解SOA五种基本架构模式

    目前,面向服务的架构(SOA)已成为连接复杂服务系统的主要解决方案.虽然SOA的理论很容易理解,但要部署一个设计良好.真正实用的SOA系统却非常困难.本文试图通过解析SOA的模式,提供与架构相关的技术 ...

  7. Asp.net Ajax,Jquery,ExtJs 三种Ajax技术框架比较

    1.现流行的Ajax框架技术简介 现有的Ajax从框架角度分级的话,可以有以下分类: ·零级,完成base工作,包括扩展原有对象的方法,Ajax通讯部分,比较精简. ·一级,完成effect工作,包括 ...

  8. cocos2dx3.0五种屏幕适配模式,及FIXED_WIDTH、FIXED_HEIGHT使用

    2019独角兽企业重金招聘Python工程师标准>>> 适配模式 (1)EXACT_FIT   :拉伸变形,使铺满屏幕.会出现图像拉伸 屏幕宽 与 设计宽比 作为X方向的缩放因子,屏 ...

  9. 芯梦启航五种流水灯模式

    包括流水灯呼吸灯灯功能实现. void Delay1(unsigned int t) {     while(t--); } unsigned char Time,i; int j=0,l=0,m=0 ...

最新文章

  1. windows codeblocks clang 3.7.0
  2. matplotlib各个部分
  3. 【English】Grab-Verify Your Email Address
  4. Oracle 11g 内存结构
  5. django URL路由基础
  6. 虚拟主机 webdav php,ubuntu 搭建 webdav 文件服务器 及客户端配置 详解
  7. java jdbc rowset_JDBC RowSet的使用
  8. 虚拟机讲只读文件变为可读可写文件_Linux虚拟机文件系统突然变成只读
  9. Delphi XE2 之 FireMonkey 入门(41) - 控件基础: TListBox
  10. 三星笔记文件存储路径_《那些年JavaWeb踩过的坑》ssh框架整合配置文件路径(错误笔记)...
  11. Web前端-HTTP Cache-control
  12. ipv6访问文件服务器,ipv4客户端如何访问ipv6服务器
  13. 自考c语言程序设计02600,自考02600《C语言程序设计》模拟试卷十一
  14. ESP32 调试SHT20踩的坑
  15. 及时复盘的好处_及时复盘,促进成长
  16. python-docx处理word文档功能详细说明
  17. 电脑预装Office2016打开Word时点击保存弹出“word无法启动转换器RECOVR32.CNV”对话框问题的修复方法
  18. 使用Qt学习C语言编程2(加入工具链)
  19. 【python 接口开发】如何用python开发自己的接口
  20. upperbound找不到_关于lower_bound( )和upper_bound( )的常见用法

热门文章

  1. LPC1758积累--boot程序bin文件校验
  2. 一份完整的app产品运营推广方案,app推广运营的方法(一)
  3. unity 输入框弹出输入法_国产输入法那么多,我为什么选择了「不接地气」的 Gboard?...
  4. 小程序源码:后台版本趣味测试微信小程序源码下载支持自定义问题等等
  5. a4纸在html的像素,A4纸网页打印中对应像素的设定跟换算
  6. C4D基础学习(二)-移动与捕捉工具
  7. 第九篇论文读后总结-相似森林
  8. 当今app行业 比较流行的 简称 汇总
  9. 手把手教你如何抵制法国货
  10. 2021-10-20-Flask-01- 基本运行框架及配置文件