TML表格如何從中間分開?
大家好,這節課講解一下如何在html中把表格從中間分開。可以使用到表格中的單元格合并方式,比如可以看到html里面加入了兩行四列的表格,它的邊框設置為兩個px,通過合并,通過"colspan"設置分隔線來跨列合并,使其達到表格分開的效果。
運行一下代碼,可以看到表格第二列和第三列已經進行合并了,當然也可以通過實現跨行合并達到合并單元格創建表格分割線的效果。跨行合并,運行一下代碼,可以看到表格的第一行和第二行已經合并了(口誤是行不是列),并且在中間創造了一條分割線。
以上就是今天所講解的html如何把表格從中間分開。
者|David Gilbertson
譯者|無明
出處丨前端之巔
一個網站該如何以最佳的方式向用戶發送資源文件?有很多不同的場景,不同的技術和不同的術語。在這篇文章里,我希望能夠讓你明白:哪種文件分割策略最適合你的網站和用戶,以及如何實現。
根據 Webpack 術語表,有兩種不同的文件分割類型。它們看起來似乎可以互換,但顯然不行:
第二種方法看起來更有吸引力,不是嗎?事實上,有很多文章似乎都假設這是拆分 JavaScript 文件唯一有價值的方案。但我想要告訴你的是,對于很多網站來說,第一種方法更有價值,而且它應該是你首先要考慮的。
捆綁拆分背后的想法非常簡單。如果你有一個巨大的文件,哪怕只是修改了一行代碼,用戶也必須再次下載整個文件。但是,如果你將它分成兩個文件,那么用戶只需要下載被修改的那個文件,瀏覽器會從緩存中獲取另一個文件。
捆綁拆分與緩存有關,因此對于首次訪問網站的用戶來說,有沒有拆分其實并沒有什么不同。
對于頻繁訪問網站的用戶來說,要衡量捆綁拆分所帶來的性能提升可能也很棘手,但我們必須這樣做!
我需要一個表格來記錄性能數據。下面是上述提到的場景:
假設我們的 JavaScript 包大小是 400 KB,只包含 main.js 單個文件。
我們的 Webpack 配置如下(我省略了不相關的配置):
const path=require('path'); module.exports={ entry: path.resolve(__dirname, 'src/index.js'), output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[contenthash].js', }, };
每個禮拜,當我們做出一些變更時,這個包的 contenthash 就會發生變化。因此,每周 Alice 訪問我們的網站時必須下載新的 400 KB 文件。
我們把這些數字記錄在表格中,它看起來就像這樣。
下載量總共是 4.12 MB,為期 10 周。
但我們可以做得更好。
拆分 vendor 包
現在,我們將包拆分為 main.js 和 vendor.js 文件。
這很簡單:
const path=require('path'); module.exports={ entry: path.resolve(__dirname, 'src/index.js'), output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[contenthash].js', }, optimization: { splitChunks: { chunks: 'all', }, }, };
Webpack 4 努力為你做最好的事情,甚至都不需要告訴它你想要如何拆分捆綁包。
有人說,“這樣看起來很整潔,不錯,Webpack!”
也有人說,“你都對我的包做了什么?”
設置 optimization.splitChunks.chunks='all'意味著“將 node_modules 所有內容都放入名為 vendors~main.js 的文件中”。
經過這個基本的捆綁拆分,Alice 每次訪問網站時仍然需要下載 200 KB 的 main.js 新文件,然后分別在第 1 周,第 8 周和第 5 周下載 200 KB 的 vendor.js 文件。
現在的下載量總共是 2.64 MB。
減少了 36%。在配置中加了五行代碼,效果還不錯。
這樣的性能提升似乎有點微不足道,因為它是 10 周加起來的總和,但不管怎樣,向用戶發送的字節數確確實實減少了 36%,我們應該為自己感到自豪。
但我們可以做得更好。
vendors.js 遇到了與原來 main.js 文件相同的問題——對文件的一部分做出變更就必須重新下載整個文件。
那么為什么不為每個 npm 包提供單獨的文件呢?這很容易做到。
所以讓我們將 react、lodash、redux 和 moment 等拆分成不同的文件:
const path=require('path'); const webpack=require('webpack'); module.exports={ entry: path.resolve(__dirname, 'src/index.js'), plugins: [ new webpack.HashedModuleIdsPlugin(), // so that file hashes don't change unexpectedly ], output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[contenthash].js', }, optimization: { runtimeChunk: 'single', splitChunks: { chunks: 'all', maxInitialRequests: Infinity, minSize: 0, cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name(module) { // get the name. E.g. node_modules/packageName/not/this/part.js // or node_modules/packageName const packageName=module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]; // npm package names are URL-safe, but some servers don't like @ symbols return `npm.${packageName.replace('@', '')}`; }, }, }, }, }, };
Webpack 的文檔(https://webpack.js.org/guides/caching/)對此做出了很好的解釋,我會大致解釋一下 groovy 的部分,因為我在這個上面花了很多時間:
Alice 每周仍然會重新下載 200 KB 的 main.js 文件,并且在她第一次訪問網站時仍然會下載 200 KB 的 npm 軟件包,但她絕不會下載相同的軟件包兩次。
現在的下載總量是 2.24 MB,與基線相比減少了 44%。
我在想是否有可能減少 50%?
現在讓我們回到可憐的 Alice 一次又一次下載的 main.js 文件。
我之前提到過,我們的網站上有兩個截然不同的部分:產品列表頁面和產品詳細信息頁面。每個部分不一樣的代碼為 25 KB(共享代碼為 150 KB)。
“產品詳細信息”頁面現在并沒有發生太大變化,因此,如果我們將其變為單獨的文件,大多數時候可以從緩存中獲取它。
另外,我們有一個巨大的內聯 SVG 文件用于渲染圖標,大小為 25 KB,而且很少會發生改動。
我們應該對此做些什么。
我們手動添加了一些條目,告訴 Webpack 為每一項創建一個文件。
module.exports={ entry: { main: path.resolve(__dirname, 'src/index.js'), ProductList: path.resolve(__dirname, 'src/ProductList/ProductList.js'), ProductPage: path.resolve(__dirname, 'src/ProductPage/ProductPage.js'), Icon: path.resolve(__dirname, 'src/Icon/Icon.js'), }, output: { path: path.resolve(__dirname, 'dist'), filename: '[name].[contenthash:8].js', }, plugins: [ new webpack.HashedModuleIdsPlugin(), // so that file hashes don't change unexpectedly ], optimization: { runtimeChunk: 'single', splitChunks: { chunks: 'all', maxInitialRequests: Infinity, minSize: 0, cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name(module) { // get the name. E.g. node_modules/packageName/not/this/part.js // or node_modules/packageName const packageName=module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]; // npm package names are URL-safe, but some servers don't like @ symbols return `npm.${packageName.replace('@', '')}`; }, }, }, }, }, };
Webpack 還會為 ProductList 和 ProductPage 之間共享的內容創建文件,這樣我們就不會得到重復的代碼。
這樣就可以為親愛的 Alice 節省 50 KB 的下載量。
現在的總下載量只有 1.815 MB!
我們已經為 Alice 節省了高達 56%的下載量,在我們的理論場景中,這種情況可以一直持續下去。
截止到目前,我們只是通過修改 Webpack 配置來實現這一切——我們沒有對應用程序代碼進行任何更改。
我們的目標是將應用程序拆分為合理的小文件,讓用戶下載更少的代碼。
因此,接下來我們要進入代碼拆分,但首先我想要解決你現在想到的三個問題。問題 1:大量的網絡請求不是更慢嗎?
對于這個問題,答案是一個非常響亮的“不”。
在 HTTP/1.1 時代或許是這種情況,但對于 HTTP/2 來說并非如此。
盡管一些著名的文章得出“即使使用 HTTP/2,下載太多文件仍然較慢”的結論,但在這些文章中,他們所謂的“太多”文件是指“數百個”文件。所以請記住,如果你有數百個文件,可能會達到并發上限。
問題 2:每個 Webpack 捆綁包中不是有樣板代碼?
是的。
問題 3:如果有多個小文件,不就失去了壓縮的優勢了嗎?
是的。
好吧,這就是說:
接下來讓我們做一下量化,這樣就可以確切地知道性能被磨損了多少。
我做了一個測試,將 190KB 的文件拆分成 19 個小文件,這樣發送給瀏覽器的總字節數大約增加了 2%。
在第一次訪問時增加 2%,但在以后訪問可以減少 60%,所以可以說完全沒有磨損。
我針對 1 個文件和 19 個文件分別進行了測試,并基于不同的網絡,包括 HTTP/1.1。
這是結果表格,我想這足以說明“更多文件會更好”:
在 3G 和 4G 網絡上,當有 19 個文件時,總的加載時間縮短了 30%。
當然,這些數據帶有一定的噪音。例如,第二次在 4G 網絡上的加載時間為 646 毫秒,過了兩次之后需要 1116 毫秒——多了 73%。因此,聲稱 HTTP/2“快 30%”似乎有點心虛的感覺。
我制作這張表來是想要量化 HTTP/2 的差異,但看來我唯一能說的是“它可能沒有顯著差異”。
真正的驚喜是最后兩行,我原本認為舊的 Windows 和 HTTP/1.1 會很慢。
這就是我要說的有關捆綁拆分的一切。我認為這種方法的唯一缺點是要不斷地說服人們,加載大量小文件是沒有問題的。
現在,讓我們談談另一種類型的文件拆分。
這種方法可能只對某些網站有用。
我發明了 20/20 規則:如果你的網站的某些部分只有 20%的用戶訪問,而這部分超過了整個網站 20%的 JavaScript,那么你應該按需加載這些代碼。
顯然,因為存在更復雜的場景,所以這個數字顯然需要做出調整。但關鍵在于,肯定存在一個平衡點,到了這個平衡點,代碼拆分對于你的網站來說可能就沒有意義了。
如何找到這個平衡點?
假設你有一個購物網站,你想知道是否應該對“結帳”代碼進行拆分,因為只有 30%的用戶會進行這個操作。
你需要弄清楚有多少代碼是只與結賬這個功能有關的。因為在進行“代碼拆分”之前已經進行了“捆綁拆分”,因此你可能已經知道這部分究竟有多少代碼。
只與結帳有關的代碼是 7 KB,其余部分是 300 KB。看到這個我會說,我不會想去拆分這個代碼,原因如下:
現在讓我們來看看兩個需要代碼拆分的例子。
我之所以從這里開始講起,是因為它適用于大多數網站,而且介紹起來相對簡單。
我在網站上使用了很多花哨的功能,有一個文件導入了所有需要的 polyfill。其中包括以下八行:
require('whatwg-fetch'); require('intl'); require('url-polyfill'); require('core-js/web/dom-collections'); require('core-js/es6/map'); require('core-js/es6/string'); require('core-js/es6/array'); require('core-js/es6/object');
我在 index.js 的頂部導入了這個文件。
import './polyfills'; import React from 'react'; import ReactDOM from 'react-dom'; import App from './App/App'; import './index.css'; const render=()=> { ReactDOM.render(<App />, document.getElementById('root')); } render(); // yes I am pointless, for now
根據之前的捆綁拆分的 Webpack 配置,polyfill 將自動被拆分為四個不同的文件,因為這里有四個 npm 包。它們總共約 25 KB,但 90%的瀏覽器都不需要它們,所以有必要進行動態加載。
使用 Webpack 4 和 import() 語法(不要與 import 語法混淆)可以很方便地實現 polyfill 的條件加載。
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App/App'; import './index.css'; const render=()=> { ReactDOM.render(<App />, document.getElementById('root')); } if ( 'fetch' in window && 'Intl' in window && 'URL' in window && 'Map' in window && 'forEach' in NodeList.prototype && 'startsWith' in String.prototype && 'endsWith' in String.prototype && 'includes' in String.prototype && 'includes' in Array.prototype && 'assign' in Object && 'entries' in Object && 'keys' in Object ) { render(); } else { import('./polyfills').then(render); }
如果瀏覽器支持所有功能,那么就渲染頁面,否則的話就導入 polyfill,然后渲染頁面。在瀏覽器中運行這些代碼時,Webpack 的運行時將負責加載這四個 npm 包,在下載和解析它們之后,將調用 render()……
順便說一句,要使用 import(),需要 Babel 的 dynamic-import 插件 (https://babeljs.io/docs/en/babel-plugin-syntax-dynamic-import/)。 另外,正如 Webpack 文檔解釋的那樣,import() 使用了 promise,所以你需要單獨對其進行 polyfill。
這個很簡單,對嗎?下面來點稍微有難度的。
回到 Alice 的例子,我們假設網站有一個“管理”功能,賣家可以登錄并管理他們的商品。
這部分有很多精彩的功能,大量的圖表,需要很多 npm 大圖表庫。因為已經在進行了捆綁拆分,所以它們都是 100 KB 左右的文件。
目前,我的路由設置是當用戶訪問 /admin 時,將會渲染<AdminPage>。當 Webpack 將所有內容捆綁在一起時,它會找到 import AdminPage from ./AdminPage.js,然后說,“我需要將它包含在初始化代碼中”。
但我們不希望它這樣。我們需要使用動態導入,例如 import(‘/AdminPage.js’),這樣 Webpack 就知道要進行動態加載了。
這很酷,不需要做任何配置。
因此,我可以創建另一個組件,當用戶訪問 /admin 時就會渲染這個組件,而不是直接引用 AdminPage。它看起來可能像這樣:
import React from 'react'; class AdminPageLoader extends React.PureComponent { constructor(props) { super(props); this.state={ AdminPage: null, } } componentDidMount() { import('./AdminPage').then(module=> { this.setState({ AdminPage: module.default }); }); } render() { const { AdminPage }=this.state; return AdminPage ? <AdminPage {...this.props} /> : <div>Loading...</div>; } } export default AdminPageLoader;
這個概念很簡單。在加載這個組件時(意味著用戶在訪問 /admin),我們將動態加載./AdminPage.js,然后在 state 中保存對該組件的引用。
在等待<AdminPage>加載時,我們只是在 render() 方法中渲染<div> Loading... </div>,或者在加載完成時渲染<AdminPage>,并保存在 state 中。
我自己這樣做是為了好玩,但在現實世界中,你可以使用 react-loadable,正如 React 文檔(https://reactjs.org/docs/code-splitting.html) 中關于代碼拆分的描述那樣。
以上就是所有我想說的話,簡單地說就是:
如果用戶會多次訪問你的網站,請將你的代碼拆分為很多小文件。如果你的網站有些部分是大部分用戶不會訪問到的,請動態加載這些代碼。
英文原文
https://hackernoon.com/the-100-correct-way-to-split-your-chunks-with-webpack-f8a9df5b7758
日客戶要求表內的數據依據某種分組生成HTML頁面進行展示,一般處理這種需求直接上編程工具就好了,從數據庫里讀取數據,根據規則生成字符串,最后將字符串寫出到文件。由于需求比較急,作為數據庫編程系列文章,如果能用SQL實現首選還是SQL,這樣處理既直接又快速,不過針對SQL要真的有耐心和信心寫完,調試更是崩潰。由于要寫出文件到硬盤,最后還是選擇MySQL作為數據庫工具,Navicat作為開發工具。
有兩張表計劃表、市縣表,二者依靠市縣編碼(sxbm)進行等值連接,計劃表內含有各個學校投放在各個市縣的專業代號(zydh),專業名稱(zymc)、招生備注(bz)、學制(xz)、要求的學歷(xl)、計劃數(jh)等字段組成的計劃信息,院校編碼(yxbm)為學校的兩位數編碼,院校代號(yxdh)為院校編碼(yxbm)+市縣編碼(sxbm)組成的四位數編碼,院校代號其實可以區分出學校在哪個市縣的投檔的專業計劃。要求以學校為單位創建HTML頁面,頁面首先要以市縣作為表格分割,然后根據專業代號排序。具體實現過程如下:
CREATE TABLE `zzjh2019v` ( `YXDH` varchar(9) COMMENT '學校代號', `YXMC` varchar(54) COMMENT '學校名稱', `ZYDH` varchar(2) COMMENT '專業代號', `ZYMC` varchar(28) COMMENT '專業名稱', `XZ` varchar(3) COMMENT '學制', `XL` varchar(4) COMMENT '學歷', `JH` varchar(6) COMMENT '招生計劃數', `BZ` varchar(200) COMMENT '備注', `yxbm` char(2) COMMENT '學校編碼', `sxbm` char(2) COMMENT '市縣編碼' ) ENGINE=InnoDB CHARACTER SET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=Compact;
CREATE TABLE `sx` ( `sxbm` char(2) COMMENT '市縣編碼', `sxmc` varchar(20) COMMENT '市縣名稱' ) ENGINE=InnoDB CHARACTER SET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=Compact;
糾結了很久這個東西怎么寫,最后采取游標、拼接字符串、字符串聚合,動態SQL,寫文件等一些列操作完成需求,創建的存儲過程如下:
CREATE DEFINER=`root`@`localhost` PROCEDURE `splitjh`() BEGIN declare done INT DEFAULT 0; declare pyxbm char(2); declare psxmc varchar(10); declare pyxmc varchar(50); declare pjhall int; declare pjhrows TEXT; declare yxjh cursor for select yxbm,yxmc,sum(jh) jhall from zzjh2019v a,sx b where a.sxbm=b.sxbm group by yxbm,yxmc order by yxbm; declare CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1; open yxjh; fetch yxjh into pyxbm,pyxmc,pjhall; while done !=1 do select group_concat(jhrow separator '') into pjhrows from (select concat('<tr class="subtitle"><td>',yxdh,'</td><td>',yxmc,'在 <span><font color="red">',b.sxmc,'</font></span> 招生計劃如下</td><td>',sum(jh),'</td><td></td><td></td></tr>',group_concat('<tr class="jhrow"><td>',zydh,'</td><td>',zymc,'(',bz,')</td><td>',jh,'</td><td>',xz,'</td><td>',xl,'</td></tr>' order by zydh separator '')) jhrow from zzjh2019v a,sx b where yxbm=pyxbm and a.sxbm=b.sxbm group by yxdh order by yxdh,zydh) jhs; set @pfilename=concat('''d:/32/1/1/jh11',pyxbm,'.html'''); set @sql=concat('select concat(''<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><link rel="stylesheet" type="text/css" href="zsjh.css" ><title>3+2計劃</title></head><body><h3></h3><table><tr class="subtitle"><th>代號</th><th>專業及名稱備注</th><th>人數</th><th>學制</th><th>學歷</th></tr>'',''',pjhrows,''',''</body></html>'') from dual into outfile ',@pfilename); prepare execsql from @sql; execute execsql; DEALLOCATE PREPARE execsql; fetch yxjh into pyxbm,pyxmc,pjhall; end while; close yxjh; END;
首先看效果,執行過程
call splitjh();
在磁盤形成的HTML文件效果如下圖(數據有一定的敏感性,進行了遮擋處理):
文件展示頁面
生成的文件列表如下圖:
生成的文件列表
這里一共有87所學校,所以生成了87的文件,添加CSS樣式文件,讓表格呈現如前圖所示。
技術點
1)MySQL的游標,以及循環讀取游標的方法,涉及的語句如下:
declare yxjh cursor for select yxbm,yxmc,sum(jh) jhall from zzjh2019v a,sx b where a.sxbm=b.sxbm group by yxbm,yxmc order by yxbm;#游標定義 declare CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;#游標循環條件,注意此句一定要定義在游標之后,才起作用 open yxjh;#打開游標 fetch yxjh into pyxbm,pyxmc,pjhall;#將游標行內容賦值給變量。
2)執行動態SQL,由于MySQL into outfile 后接的文件名不能為變量,所以必須使用動態SQL的方法,涉及的語句如下:
prepare execsql from @sql;#從一個變量準備一個動態sql,注意execsql不用提前定義 execute execsql;#執行準備好的語句 DEALLOCATE PREPARE execsql;#銷毀語句
綜上就是使用MySQL數據庫,并借用MySQL寫文件的方式將數據從數據庫內按照需求導出文件,為何不用navicat導出呢?因為無法達到要求,又是聚合、又是格式,所以只能自己編寫過程通過SQL語句拼接字符串的方式來實現。沒有太多的技術難度,主要是想法和調試難度。后續在此基礎上又開發了以市縣為單位創建HTML文件,各招生學校作為分割的過程。本案例是實際需求催生出來的做法,在遇到這樣的需求前你是先想到SQL還是先想到開發工具呢?從實際效果看使用SQL這種方式更加靈活。這樣的SQL實現的字符串拼接是不是有點極限呢?
*請認真填寫需求信息,我們會在24小時內與您取得聯系。