這篇“Node.js中的Buffer和事件循環(huán)實例分析”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Node.js中的Buffer和事件循環(huán)實例分析”文章吧。
創(chuàng)新互聯(lián)是一家專業(yè)的成都網(wǎng)站建設(shè)公司,我們專注網(wǎng)站設(shè)計、網(wǎng)站建設(shè)、網(wǎng)絡(luò)營銷、企業(yè)網(wǎng)站建設(shè),賣友情鏈接,廣告投放為企業(yè)客戶提供一站式建站解決方案,能帶給客戶新的互聯(lián)網(wǎng)理念。從網(wǎng)站結(jié)構(gòu)的規(guī)劃UI設(shè)計到用戶體驗提高,創(chuàng)新互聯(lián)力求做到盡善盡美。
數(shù)據(jù)的二進(jìn)制
計算機中所有的內(nèi)容:文字、數(shù)字、圖片、音頻、視頻最終都會使用二進(jìn)制來表示
JS
可以直接去處理非常直觀的數(shù)據(jù):比如字符串,我們通常展示給用戶的也是這些內(nèi)容
但你可能會以為JS也能夠處理圖片
事實上在網(wǎng)頁端,圖片一直是交給瀏覽器來處理的
JS
或者HTML
,只是負(fù)責(zé)告訴瀏覽器圖片的地址
瀏覽器負(fù)責(zé)發(fā)送請求獲取這個圖片,并且最終將這個圖片給渲染出來
但是對于服務(wù)端來說是不一樣的
服務(wù)端要處理的本地文件類型相對較多
比如某一個保存文本的文件并不是使用utf-8
進(jìn)行編碼的,而是用GBK
,那么我們必須讀取到他們的二進(jìn)制數(shù)據(jù),再通過GKB轉(zhuǎn)換成對應(yīng)的文字
比如我們需要讀取的是一張圖片數(shù)據(jù)(二進(jìn)制),再通過某些手段對圖片數(shù)據(jù)進(jìn)行二次的處理(裁剪、格式轉(zhuǎn)換、旋轉(zhuǎn)、添加濾鏡),Node中有一個名為sharp
的庫,就是負(fù)責(zé)讀取圖片或者傳入圖片的Buffer
對其再進(jìn)行處理的
比如在Node
中通過TCP
建立長連接,TCP傳輸?shù)氖亲止?jié)流,我們需要將數(shù)據(jù)轉(zhuǎn)成字節(jié)再進(jìn)行傳入,并且需要知道傳輸字節(jié)的大?。蛻舳诵枰鶕?jù)大小來判斷讀取多少內(nèi)容)
Buffer和二進(jìn)制
我們會發(fā)現(xiàn),對于前端開發(fā)來說,通常很少會和二進(jìn)制打交道,但是對于服務(wù)器端來說,為了實現(xiàn)很多功能,我們必須直接去操作其二進(jìn)制的數(shù)據(jù)
所以Node
為了可以方便開發(fā)者完成更多功能,提供給了我們一個名為Buffer
的類,并且他是全局的
我們前面說過,Buffer中存儲的是二進(jìn)制數(shù)據(jù),那么到底是如何存儲的呢?
我們可以將Buffer看成是一個存儲二進(jìn)制的數(shù)組
這個數(shù)組中的每一項,可以保存8
位二進(jìn)制:00000000
,剛好是一個字節(jié)
為什么是8位呢?
在計算機中,很少的情況我們會直接操作一位二進(jìn)制,因為一位二進(jìn)制存儲的數(shù)據(jù)是非常有限的
所以通常會將8位合在一起作為一個單元,這個單元稱之為一個字節(jié)(byte
)
也就是說 1 byte = 8 bit
,1kb = 1024 byte
,1M = 1024kb
, 1 G = 1024 M
比如很多編程語言中的int
類型是4
個字節(jié),long
類型是8
個字節(jié)
比如TCP
傳輸?shù)氖亲止?jié)流,在寫入和讀取時都需要說明字節(jié)的個數(shù)
比如RGB
的值分別都是255
,所以本質(zhì)上在計算機中都是用一個字節(jié)存儲的
Buffer和字符串
Buffer
相當(dāng)于是一個字節(jié)的數(shù)組,數(shù)組中的每一項對于一個字節(jié)的大小
如果我們希望將一個字符串放入到Buffer中,是怎么樣的過程呢?
將字符串直接傳入Buffer類中,然后再創(chuàng)建buffer
實例
英文字符串有個特點,每一個字符對應(yīng)一個字節(jié)的二進(jìn)制編碼
const message = 'Hello' // 使用new關(guān)鍵字創(chuàng)建buffer實例,但這種創(chuàng)建方法已經(jīng)過期了 const buffer = new Buffer(message) console.log(buffer); // <Buffer 48 65 6c 6c 6f> console.log(buffer.toString()); // Hello
中文字符串的編解碼
buffer
的默認(rèn)編碼是utf-8
,所以在下列代碼中,Buffer
類是使用了utf-8編碼對我們的字符串進(jìn)行編碼,使用的也是utf-8對我們的字符串進(jìn)行解碼
中文字符串有個特點,在utf-8編碼中,一個文字對應(yīng)3
個字節(jié)的二進(jìn)制編碼
const message = '你好啊' // 使用Buffer.from對我們的字符串進(jìn)行解碼 const buffer = Buffer.from(message) console.log(buffer); // <Buffer e4 bd a0 e5 a5 bd e5 95 8a> // buffer實例中有個toString方法可以對編碼進(jìn)行解碼 console.log(buffer.toString()); // '你好啊'
那如果編碼和解碼用的是不同形式的編碼結(jié)果會怎么樣呢?
毫無疑問,解碼出來的東西并不是我們原先編碼的字符串
const message = '你好啊' const buffer = Buffer.from(message, 'utf16le') console.log(buffer); // <Buffer 60 4f 7d 59 4a 55> console.log(buffer.toString()); // `O}YJU
Buffer的其他創(chuàng)建方式
創(chuàng)建buffer
的方式有很多,我們這里可以通過alloc
的方式創(chuàng)建Buffer
我們可以直接對buffer實例以數(shù)組的形式對每一位進(jìn)行修改
如果修改的是一個十進(jìn)制數(shù)字,那它會自動幫助我們轉(zhuǎn)化成16進(jìn)制的數(shù)字
如果修改的是一個十六進(jìn)制數(shù)字,那么就會直接寫入
// 其可以指定我們buffer的位數(shù),比如這里傳遞進(jìn)去的是8,那么創(chuàng)建出來的buffer就有8個元素,且每個元素對應(yīng)的二進(jìn)制數(shù)都是0 const buffer = Buffer.alloc(8) console.log(buffer); // <Buffer 00 00 00 00 00 00 00 00> // 賦值為十進(jìn)制數(shù)字的話,buffer會幫我們轉(zhuǎn)化為16進(jìn)制數(shù)字再寫入到對應(yīng)的位置 buffer[0] = 88 // 在js中,以0x開頭的就表示為16進(jìn)制的數(shù)字 buffer[1] = 0x88 console.log(buffer); // <Buffer 58 88 00 00 00 00 00 00>
Buffer和文件操作
1、文本文件
如果未指定字符編碼,則不進(jìn)行解碼,直接返回原始的 buffer
,也就是文件內(nèi)容結(jié)果utf-8
編碼后的二進(jìn)制數(shù)
const fs = require('fs') fs.readFile('./a.txt', (err, data) => { console.log(data); // <Buffer e5 93 88 e5 93 88> })
編碼和解碼用的都是utf-8,則可以得到文件中正確的內(nèi)容
const fs = require('fs') // encoding表示解碼所用的字符編碼,編碼默認(rèn)為utf-8 fs.readFile('./a.txt', { encoding: 'utf-8' }, (err, data) => { console.log(data); // 哈哈 })
如果編碼和解碼所用的字符編碼不同,則最終讀取出來的內(nèi)容會亂碼
const fs = require('fs') // 編碼用的是utf16le字符編碼,解碼使用的是utf-8格式,肯定是解不是正確的內(nèi)容的 fs.readFile('./a.txt', { encoding: 'utf16le' }, (err, data) => { console.log(data); // 鏥袓 }) // 以上代碼和下面代碼類似 const msg = '哈哈' const buffer = Buffer.from(msg, 'utf-8') console.log(buffer.toString('utf16le')); // 鏥袓
2、圖片文件
對圖片編碼進(jìn)行拷貝,達(dá)到復(fù)制圖片的目的
讀取圖片的時候不要指定encoding
屬性,因為字符編碼只有在讀取文本文件的時候才有用
const fs = require('fs') fs.readFile('./logo.png', (err, data) => { console.log(data); // 打印出來的是圖片文件對應(yīng)的二進(jìn)制編碼 // 我們還可以將圖片編碼寫入到另一個文件當(dāng)中,相當(dāng)于我們將該圖片拷貝了一份 fs.writeFile('./bar.png', data, err => { console.log(err); }) })
對圖片進(jìn)行翻轉(zhuǎn)、裁剪等操作,可以使用sharp
這個庫
const sharp = require('sharp') // 將logo.png這張圖片裁剪成200x300后拷貝到文件bax.png中 sharp('./logo.png') .resize(200, 300) .toFile('./bax.png', (err, info) => { console.log(err); }) // 還可以將圖片文件先轉(zhuǎn)為buffer,然后在寫入到文件中,也可以實現(xiàn)拷貝圖片的目的 sharp('./logo.png') .resize(300, 300) .toBuffer() .then(data => { fs.writeFile('./baa.png', data, err => { console.log(err); }) })
Buffer的創(chuàng)建過程
事實上我們創(chuàng)建Buffer
時,并不會頻繁的向操作系統(tǒng)申請內(nèi)存,它會默認(rèn)先申請一個8 * 1024
個字節(jié)大小的內(nèi)存,也就是8kb
等到內(nèi)存不夠或者快用完的時候才會去申請新的內(nèi)存
什么是事件循環(huán)?
事件循環(huán)是什么?
事實上我把事件循環(huán)理解成我們編寫的JS
和瀏覽器或者Node
之間的一個橋梁
瀏覽器的事件循環(huán)是一個我們編寫的JS
代碼和瀏覽器API調(diào)用(setTimeout
、AJAX
、監(jiān)聽事件
等)的一個橋梁,橋梁之間通過回調(diào)函數(shù)進(jìn)行溝通
Node的事件循環(huán)是一個我們編寫的JS代碼和系統(tǒng)調(diào)用(file system
、networ
等)之間的一個橋梁,,橋梁之間也是通過回調(diào)函數(shù)進(jìn)行溝通的
進(jìn)程和線程
進(jìn)程和線程是操作系統(tǒng)中的兩個概念:
進(jìn)程(process
):計算機已經(jīng)運行的程序
線程(thread
):操作系統(tǒng)能夠運行運算調(diào)度的最小單位,所以CPU
能夠直接操作線程
聽起來很抽象,我們直觀一點解釋:
進(jìn)程:我們可以認(rèn)為,啟動一個應(yīng)用程序,就會默認(rèn)啟動一個進(jìn)程(也可能是多個進(jìn)程)
線程:每一個進(jìn)程中,都會啟動一個線程用來執(zhí)行程序中的代碼,這個線程被稱之為主線程
所以我們也可以說進(jìn)程是線程的容器
再用一個形象的例子解釋
操作系統(tǒng)類似于一個工廠
工廠中有很多車間,這個車間就是進(jìn)程
每個車間可能有一個以上的工人在工廠,這個工人就是線程
多進(jìn)程多線程開發(fā)
操作系統(tǒng)是如何做到同時讓多個進(jìn)程(邊聽歌、邊寫代碼、邊查閱資料)同時工作呢?
這是因為CPU
的運算速度非常快,他可以快速的在多個進(jìn)程之間迅速的切換
當(dāng)我們的進(jìn)程中的線程獲取到時間片時,就可以快速執(zhí)行我們編寫的代碼
對于用戶來說是感受不到這種快速的切換的
瀏覽器和JavaScript
我們經(jīng)常會說JavaScript
是單線程的,但是JS的線程應(yīng)該有自己的容器進(jìn)程:瀏覽器或者Node
瀏覽器是一個進(jìn)程嗎,它里面只有一個線程嗎?
目前多數(shù)的瀏覽器其實都是多進(jìn)程的,當(dāng)我們打開一個tab
頁面時就會開啟一個新的進(jìn)程,這是為了防止一個頁面卡死而造成所有頁面無法響應(yīng),整個瀏覽器需要強制退出
每個進(jìn)程中又有很多的線程,其中包括執(zhí)行JavaScript代碼的線程
但是JavaScript的代碼執(zhí)行是在一個單獨的線程中執(zhí)行的
這就意味著JS
的代碼,在同一時刻只能做一件事
如果這件事是非常耗時的,就以為這當(dāng)前的線程就會被阻塞
JavaScript的執(zhí)行過程
函數(shù)要被壓入函數(shù)調(diào)用棧中后才會被執(zhí)行,下面我們來分析下代碼的執(zhí)行過程
const message = 'Hello World' console.log(message); function sum(num1, num2) { return num1 + num2 } function foo() { const result = sum(20, 30) console.log(result); } foo()
我們JS的代碼其實也可以像其它編程語言一樣可以看成是在main
函數(shù)中執(zhí)行的
那么首先我們要將main函數(shù)壓入函數(shù)調(diào)用棧中
定義變量message
執(zhí)行log
函數(shù),log函數(shù)會被放入到函數(shù)調(diào)用棧中,執(zhí)行完后出棧
調(diào)用foo
函數(shù),foo函數(shù)被壓入函數(shù)調(diào)用棧中,但是執(zhí)行過程中又需要調(diào)用sum
函數(shù)
所以sum函數(shù)會被壓入到函數(shù)調(diào)用棧中,sum函數(shù)執(zhí)行完畢后出棧
此時foo函數(shù)也得到了sum函數(shù)返回的值,并執(zhí)行了賦值操作,但又遇到了log函數(shù)
所以又要將log函數(shù)壓入到調(diào)用棧,log函數(shù)被執(zhí)行完畢,出棧后foo函數(shù)也執(zhí)行完畢,foo函數(shù)出棧
foo函數(shù)執(zhí)行完后,整個js
代碼執(zhí)行完畢,main函數(shù)出棧
瀏覽器的事件循環(huán)
如果在執(zhí)行JS
代碼的過程中,有異步操作呢?
比如中間我們插入了一個setTimeout
的函數(shù)調(diào)用
那么setTimeout這個函數(shù)被放入到調(diào)用棧中,執(zhí)行會立即結(jié)束,并不會阻塞后續(xù)代碼的執(zhí)行
那么,往setTimeout函數(shù)里面?zhèn)魅氲暮瘮?shù)(我們稱之為timer
函數(shù)),會在什么時候被執(zhí)行呢?
事實上,setTimeout是調(diào)用了web api
,瀏覽器會提前會將回調(diào)函數(shù)存儲起來,在合適的時機,會將timer函數(shù)加入到一個事件隊列中
事件隊列中的函數(shù),會被放入到函數(shù)調(diào)用棧中,在調(diào)用棧中被執(zhí)行
為什么setTimeout不會阻塞代碼的執(zhí)行呢?就是因為瀏覽器里面維護了一個非常非常重要的東西——事件循環(huán)
瀏覽器中會通過某種方式幫助我們保存setTimeout中的回調(diào)函數(shù)的,比較常用的方法就是保存到一個紅黑樹里面
等到setTimeout定時器時間到達(dá)的時候,它就會將我們的timer回調(diào)函數(shù)從保存的地方取出來并放入到事件隊列里面
事件循環(huán)一旦發(fā)現(xiàn)我們的隊列中有東西了,并且當(dāng)前函數(shù)調(diào)用棧是空的,其它同步代碼也執(zhí)行完之后,就會將我們隊列中的回調(diào)函數(shù)依次出列放入到函數(shù)調(diào)用棧中執(zhí)行(隊列中前一個函數(shù)出棧后,下一個函數(shù)才會入棧)
當(dāng)然事件隊列中不一定只有一個事件,比如說在某個過程中用戶點擊了瀏覽器當(dāng)中的某個按鈕,我們可能對這個按鈕的點擊做了一個監(jiān)聽,對應(yīng)了一個回調(diào)函數(shù),那個回調(diào)函數(shù)也會被加入到我們的隊列里面的,執(zhí)行順序按照它們在事件隊列中的順序執(zhí)行。還有我們發(fā)送ajax
請求的回調(diào),也是加入到事件隊列里面的
總結(jié):其實事件循環(huán)是一個很簡單的東西,它就是在某一個特殊的情況下,需要去執(zhí)行某一個回調(diào)的時候,它就把提前保存好的回調(diào)塞入事件隊列里面,事件循環(huán)再給它取出來放入到函數(shù)調(diào)用棧中
宏任務(wù)與微任務(wù)
但是事件循環(huán)中并非只維護一個隊列,事實上是有兩個隊列,而且隊列中的任務(wù)執(zhí)行一定會等到所有的script都執(zhí)行完畢后
宏任務(wù)隊列(macrotask queue
):ajax
、setTimeout
、setInterval
、DOM
監(jiān)聽、UI Rendering
等
微任務(wù)隊列(microtask queue
):Promise
的then
回調(diào)、Mutation Observer API
、queueMicrotask()
等
那么事件循環(huán)對于兩個隊列的優(yōu)先級是怎么樣的呢?
main script
中的代碼優(yōu)先執(zhí)行(編寫的頂層script代碼)
在執(zhí)行任何一個宏任務(wù)之前(不是隊列,是一個宏任務(wù)),都會先查看微任務(wù)隊列中是否有任務(wù)需要執(zhí)行
也就是宏任務(wù)執(zhí)行之前,必須保證微任務(wù)隊列是空的
如果不為空,那么就優(yōu)先執(zhí)行微任務(wù)隊列中的任務(wù)(回調(diào))
面試題<一>
考點:main stcipt
、setTimeout
、Promise
、then
、queueMicrotask
setTimeout(() => { console.log('set1');4 new Promise(resolve => { resolve() }).then(resolve => { new Promise(resolve => { resolve() }).then(() => { console.log('then4'); }) console.log('then2'); }) }) new Promise(resolve => { console.log('pr1'); resolve() }).then(() => { console.log('then1'); }) setTimeout(() => { console.log('set2'); }) console.log(2); queueMicrotask(() => { console.log('queueMicrotask'); }) new Promise(resolve => { resolve() }).then(() => { console.log('then3'); }) // pr1 // 2 // then1 // queueMicrotask // then3 // set1 // then2 // then4 // set2
setTimeout
會立即壓入函數(shù)調(diào)用棧,執(zhí)行完畢后立即出棧,其timer
函數(shù)被放入到宏任務(wù)隊列中
傳入Promise
類的函數(shù)會被立即執(zhí)行,其并不是回調(diào)函數(shù),所以會打印出pr1
,并且由于執(zhí)行了resolve
方法,所以該Promise的狀態(tài)會立即變?yōu)?code>fulfilled,這樣then
函數(shù)執(zhí)行的時候其對應(yīng)的回調(diào)函數(shù)就會被放入到微任務(wù)隊列中
又遇到了一個setTimeout函數(shù),壓棧出棧,其timer函數(shù)會被放入到宏任務(wù)隊列中
遇到console.log
語句,函數(shù)壓棧后執(zhí)行打印出了2
,然后出棧
這里通過queueMicrotask
綁定了個函數(shù),該函數(shù)會被放入到微任務(wù)隊列中
又遇到了new Promise語句,但是其立即就將promise的狀態(tài)改為了fulfilled,所以then函數(shù)對應(yīng)的回調(diào)也被放入到了微任務(wù)隊列中
由于同步腳本代碼已經(jīng)執(zhí)行完畢,現(xiàn)在事件循環(huán)開始要去把微任務(wù)隊列和宏任務(wù)對壘的任務(wù)按照優(yōu)先級順序放入到函數(shù)調(diào)用棧中執(zhí)行了,注意:微任務(wù)的優(yōu)先級比宏任務(wù)高,每次想要執(zhí)行宏任務(wù)之前都要看看微任務(wù)隊列里面是否為空,不為空則需要先執(zhí)行微任務(wù)隊列的任務(wù)
第一個微任務(wù)是打印then1
,第二個微任務(wù)是打印queueMicrotask,第三個微任務(wù)是打印then3
,執(zhí)行完畢后,就開始去執(zhí)行宏任務(wù)
第一個宏任務(wù)比較復(fù)雜,首先會打印set1
,然后執(zhí)行了一個立即變換狀態(tài)的new promise
語句,其then回調(diào)會被放入到微任務(wù)隊列中,注意現(xiàn)在微任務(wù)隊列可不是空的,所以需要執(zhí)行優(yōu)先級較高的微任務(wù)隊列,相當(dāng)于該then回調(diào)被立即執(zhí)行了,又是相同的new Promise語句,其對應(yīng)的then對調(diào)被放入到微任務(wù)隊列中,注意new Promise語句后面還有一個console
函數(shù),該函數(shù)會在執(zhí)行完new Promise語句后立即執(zhí)行,也就是打印then2
,現(xiàn)在微任務(wù)對壘還是有一項任務(wù),所以接下來就是打印then4
。目前為止,微任務(wù)隊列已經(jīng)為空了,可以繼續(xù)執(zhí)行宏任務(wù)隊列了
所以接下里的宏任務(wù)set2
會被打印,宏任務(wù)執(zhí)行完畢
整個代碼的打印結(jié)果是:pr1 -> 2 -> then1 -> queueMicrotask -> then3 -> set1 -> then2 -> then4 -> set2
面試題<二>
考點:main script
、setTimeout
、Promise
、then
、queueMicrotask
、await
、async
知識補充:async、await是Promise
的一個語法糖,在處理事件循環(huán)問題時
我們可以將await關(guān)鍵字后面執(zhí)行的代碼,看做是包裹在new Promise((resolve,rejcet) => { 函數(shù)執(zhí)行 })
中的代碼
await語句后面的代碼,可以看做是上一個Promise中的then(res => {函數(shù)執(zhí)行})
中的代碼
async function async1() { console.log('async1 start'); await async2() console.log('async1 end'); } async function async2() { console.log('async2'); } console.log('script start'); setTimeout(() => { console.log('setTimeout'); }, 0) async1() new Promise(resolve => { console.log('promise1'); resolve() }).then(() => { console.log('promise2'); }) console.log('script end'); // script start // async1 start // async2 // promise1 // script end // async1 end // promise2 // setTimeout
一開始都是函數(shù)的定義,不需要壓入函數(shù)調(diào)用棧中執(zhí)行,直到遇到第一個console
語句,壓棧后執(zhí)行打印script start
后出棧
遇到第一個setTimeout
函數(shù),其對應(yīng)的timer
會被放入到宏任務(wù)隊列中
async1函數(shù)被執(zhí)行,首先打印出async1 start
,然后又去執(zhí)行await
語句后面的async2
函數(shù),因為前面也說了,將await關(guān)鍵字后面的函數(shù)看成是new Promise
里面的語句,這個函數(shù)是會被立即執(zhí)行的,所以async2會被打印出來,但該await語句后面的代碼相當(dāng)于是放入到then回調(diào)中的,也就是說console.log('async1 end')
這行代碼被放入到了微任務(wù)隊列里
代碼繼續(xù)執(zhí)行,又遇到了一個new Promise語句,所以立即打印出了promise1
,then回調(diào)中的函數(shù)被放入到了微任務(wù)隊列里面去
最后一個console函數(shù)執(zhí)行打印script end
,同步代碼也就執(zhí)行完畢了,事件循環(huán)要去宏任務(wù)和微任務(wù)隊列里面執(zhí)行任務(wù)了
首先是去微任務(wù)隊列,第一個微任務(wù)對應(yīng)的打印語句會被執(zhí)行,也就是說async1 end
會被打印,然后就是promise2
被打印,此時微任務(wù)隊列已經(jīng)為空,開始去執(zhí)行宏任務(wù)隊列中的任務(wù)了
timer函數(shù)對應(yīng)的setTimeout會被打印,此時宏任務(wù)也執(zhí)行完畢,最終的打印順序是:script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2 -> setTimeout
以上就是關(guān)于“Node.js中的Buffer和事件循環(huán)實例分析”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
標(biāo)題名稱:Node.js中的Buffer和事件循環(huán)實例分析
瀏覽地址:http://www.muchs.cn/article40/ghgceo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信小程序、軟件開發(fā)、網(wǎng)站改版、建站公司、、網(wǎng)站導(dǎo)航
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)