這篇“VUE響應(yīng)式原理實例代碼分析”文章的知識點大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“VUE響應(yīng)式原理實例代碼分析”文章吧。
站在用戶的角度思考問題,與客戶深入溝通,找到保靖網(wǎng)站設(shè)計與保靖網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗,讓設(shè)計與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個性化、用戶體驗好的作品,建站類型包括:網(wǎng)站設(shè)計制作、成都網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機端網(wǎng)站、網(wǎng)站推廣、域名申請、網(wǎng)站空間、企業(yè)郵箱。業(yè)務(wù)覆蓋保靖地區(qū)。
1.defineProperty 的應(yīng)用
在Vue2.X響應(yīng)式中使用到了 defineProperty 進行數(shù)據(jù)劫持,所以我們對它必須有一定的了解,那么我們先來了解它的使用方法把, 這里我們來使用 defineProperty來模擬 Vue中的 data。
2.defineProperty修改多個參數(shù)為響應(yīng)式
修改多個參數(shù)
看了上面的方法只能修改一個屬性,實際上我們 data中數(shù)據(jù)不可能只有一個,我們何不定義一個方法把data中的數(shù)據(jù)進行遍歷都修改成響應(yīng)式呢
3.Proxy
在Vue3中使用 Proxy來設(shè)置響應(yīng)式的屬性
先來了解下 Proxy的兩個參數(shù)
new Proxy(target,handler)
target
:要使用 Proxy
包裝的目標對象(可以是任何類型的對象,包括原生數(shù)組,函數(shù),甚至另一個代理)
handler
:一個通常以函數(shù)作為屬性的對象,各屬性中的函數(shù)分別定義了在執(zhí)行各種操作時代理 p
的行為
其實 和 Vue2.X實現(xiàn)的邏輯差不多,不過實現(xiàn)的方法不一樣
那么就放上代碼了
觸發(fā)set和 get的方法
// 觸發(fā)了set方法 vm.msg = 'haha' // 觸發(fā)了get方法 console.log(vm.msg)
4.發(fā)布訂閱模式
在Vue 響應(yīng)式中應(yīng)用到了 發(fā)布訂閱模式 我們先來了解下
首先來說簡單介紹下 一共有三個角色
發(fā)布者、 訂閱者、 信號中心 舉個現(xiàn)實中例子 作者(發(fā)布者)寫一篇文章 發(fā)到了掘金(信號中心) ,掘金可以處理文章然后推送到了首頁,然后各自大佬(訂閱者)就可以訂閱文章
在Vue 中的例子 就是EventBus$on
$emit
那么我們就簡單模仿一下 Vue 的事件總線吧
之前代碼縮進4個單位有點寬,這里改成2個
5.觀察者模式
與 發(fā)布訂閱 的差異
與發(fā)布訂閱者不同 觀察者中 發(fā)布者和訂閱者(觀察者)是相互依賴的 必須要求觀察者訂閱內(nèi)容改變事件 ,而發(fā)布訂閱者是由調(diào)度中心進行調(diào)度,那么看看觀察者模式 是如何相互依賴,下面就舉個簡單例子
6.模擬Vue的響應(yīng)式原理
這里來實現(xiàn)一個小型簡單的 Vue主要實現(xiàn)以下的功能
接收初始化的參數(shù),這里只舉幾個簡單的例子 eldataoptions
通過私有方法 _proxyData把data注冊到 Vue中 轉(zhuǎn)成gettersetter
使用 observer把 data中的屬性轉(zhuǎn)為 響應(yīng)式 添加到 自身身上
使用 observer方法監(jiān)聽 data的所有屬性變化來 通過觀察者模式 更新視圖
使用 compiler編譯元素節(jié)點上面指令 和 文本節(jié)點差值表達式
在這里獲取到 el
data
通過 _proxyData把 data的屬性 注冊到Vue并轉(zhuǎn)成 gettersetter
/* vue.js */ class Vue { constructor(options) { // 獲取到傳入的對象 沒有默認為空對象 this.$options = options || {} // 獲取 el this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el // 獲取 data this.$data = options.data || {} // 調(diào)用 _proxyData 處理 data中的屬性 this._proxyData(this.$data) } // 把data 中的屬性注冊到 Vue _proxyData(data) { Object.keys(data).forEach((key) => { // 進行數(shù)據(jù)劫持 // 把每個data的屬性 到添加到 Vue 轉(zhuǎn)化為 getter setter方法 Object.defineProperty(this, key, { // 設(shè)置可以枚舉 enumerable: true, // 設(shè)置可以配置 configurable: true, // 獲取數(shù)據(jù) get() { return data[key] }, // 設(shè)置數(shù)據(jù) set(newValue) { // 判斷新值和舊值是否相等 if (newValue === data[key]) return // 設(shè)置新值 data[key] = newValue }, }) }) } }
在這里把 data中的 屬性變?yōu)轫憫?yīng)式加在自身的身上,還有一個主要功能就是 觀察者模式在 第 4.dep.js
會有詳細的使用
/* observer.js */ class Observer { constructor(data) { // 用來遍歷 data this.walk(data) } // 遍歷 data 轉(zhuǎn)為響應(yīng)式 walk(data) { // 判斷 data是否為空 和 對象 if (!data || typeof data !== 'object') return // 遍歷 data Object.keys(data).forEach((key) => { // 轉(zhuǎn)為響應(yīng)式 this.defineReactive(data, key, data[key]) }) } // 轉(zhuǎn)為響應(yīng)式 // 要注意的 和vue.js 寫的不同的是 // vue.js中是將 屬性給了 Vue 轉(zhuǎn)為 getter setter // 這里是 將data中的屬性轉(zhuǎn)為getter setter defineReactive(obj, key, value) { // 如果是對象類型的 也調(diào)用walk 變成響應(yīng)式,不是對象類型的直接在walk會被return this.walk(value) // 保存一下 this const self = this Object.defineProperty(obj, key, { // 設(shè)置可枚舉 enumerable: true, // 設(shè)置可配置 configurable: true, // 獲取值 get() { return value }, // 設(shè)置值 set(newValue) { // 判斷舊值和新值是否相等 if (newValue === value) return // 設(shè)置新值 value = newValue // 賦值的話如果是newValue是對象,對象里面的屬性也應(yīng)該設(shè)置為響應(yīng)式的 self.walk(newValue) }, }) } }
在html中引入的話注意順序
然后在vue.js中使用 Observer
/* vue.js */ class Vue { constructor(options) { ... // 使用 Obsever 把data中的數(shù)據(jù)轉(zhuǎn)為響應(yīng)式 new Observer(this.$data) } // 把data 中的屬性注冊到 Vue _proxyData(data) { ... } }
看到這里為什么做了兩個重復(fù)性的操作呢?重復(fù)性兩次把 data的屬性轉(zhuǎn)為響應(yīng)式
在obsever.js中是把 data的所有屬性 加到 data自身 變?yōu)轫憫?yīng)式 轉(zhuǎn)成 gettersetter方式
在vue.js中 也把 data的 的所有屬性 加到 Vue上,是為了以后方面操作可以用 Vue的實例直接訪問到 或者在 Vue中使用 this訪問
使用例子:
這樣在Vue
和 $data
中都存在了 所有的data屬性了 并且是響應(yīng)式的
comilper.js在這個文件里實現(xiàn)對文本節(jié)點 和 元素節(jié)點指令編譯 主要是為了舉例子 當然這個寫的很簡單 指令主要實現(xiàn) v-textv-model
/* compiler.js */ class Compiler { // vm 指 Vue 實例 constructor(vm) { // 拿到 vm this.vm = vm // 拿到 el this.el = vm.$el // 編譯模板 this.compile(this.el) } // 編譯模板 compile(el) { // 獲取子節(jié)點 如果使用 forEach遍歷就把偽數(shù)組轉(zhuǎn)為真的數(shù)組 let childNodes = [...el.childNodes] childNodes.forEach((node) => { // 根據(jù)不同的節(jié)點類型進行編譯 // 文本類型的節(jié)點 if (this.isTextNode(node)) { // 編譯文本節(jié)點 this.compileText(node) } else if (this.isElementNode(node)) { //元素節(jié)點 this.compileElement(node) } // 判斷是否還存在子節(jié)點考慮遞歸 if (node.childNodes && node.childNodes.length) { // 繼續(xù)遞歸編譯模板 this.compile(node) } }) } // 編譯文本節(jié)點(簡單的實現(xiàn)) compileText(node) { // 核心思想利用把正則表達式把{{}}去掉找到里面的變量 // 再去Vue找這個變量賦值給node.textContent let reg = /\{\{(.+?)\}\}/ // 獲取節(jié)點的文本內(nèi)容 let val = node.textContent // 判斷是否有 {{}} if (reg.test(val)) { // 獲取分組一 也就是 {{}} 里面的內(nèi)容 去除前后空格 let key = RegExp.$1.trim() // 進行替換再賦值給node node.textContent = val.replace(reg, this.vm[key]) } } // 編譯元素節(jié)點這里只處理指令 compileElement(node) { // 獲取到元素節(jié)點上面的所有屬性進行遍歷 ![...node.attributes].forEach((attr) => { // 獲取屬性名 let attrName = attr.name // 判斷是否是 v- 開頭的指令 if (this.isDirective(attrName)) { // 除去 v- 方便操作 attrName = attrName.substr(2) // 獲取 指令的值就是 v-text = "msg" 中msg // msg 作為 key 去Vue 找這個變量 let key = attr.value // 指令操作 執(zhí)行指令方法 // vue指令很多為了避免大量個 if判斷這里就寫個 uapdate 方法 this.update(node, key, attrName) } }) } // 添加指令方法 并且執(zhí)行 update(node, key, attrName) { // 比如添加 textUpdater 就是用來處理 v-text 方法 // 我們應(yīng)該就內(nèi)置一個 textUpdater 方法進行調(diào)用 // 加個后綴加什么無所謂但是要定義相應(yīng)的方法 let updateFn = this[attrName + 'Updater'] // 如果存在這個內(nèi)置方法 就可以調(diào)用了 updateFn && updateFn(node, key, this.vm[key]) } // 提前寫好 相應(yīng)的指定方法比如這個 v-text // 使用的時候 和 Vue 的一樣 textUpdater(node, key, value) { node.textContent = value } // v-model modelUpdater(node, key, value) { node.value = value } // 判斷元素的屬性是否是 vue 指令 isDirective(attr) { return attr.startsWith('v-') } // 判斷是否是元素節(jié)點 isElementNode(node) { return node.nodeType === 1 } // 判斷是否是 文本 節(jié)點 isTextNode(node) { return node.nodeType === 3 } }
寫一個Dep類 它相當于 觀察者中的發(fā)布者 每個響應(yīng)式屬性都會創(chuàng)建這么一個 Dep對象 ,負責收集該依賴屬性的Watcher對象 (是在使用響應(yīng)式數(shù)據(jù)的時候做的操作)
當我們對響應(yīng)式屬性在 setter中進行更新的時候,會調(diào)用 Dep中 notify方法發(fā)送更新通知
然后去調(diào)用 Watcher中的 update實現(xiàn)視圖的更新操作(是當數(shù)據(jù)發(fā)生變化的時候去通知觀察者調(diào)用觀察者的update更新視圖)
總的來說 在Dep(這里指發(fā)布者) 中負責收集依賴 添加觀察者(這里指Wathcer),然后在 setter數(shù)據(jù)更新的時候通知觀察者
說的這么多重復(fù)的話,大家應(yīng)該知道是在哪個階段 收集依賴 哪個階段 通知觀察者了吧,下面就來實現(xiàn)一下吧
先寫Dep類
/* dep.js */ class Dep { constructor() { // 存儲觀察者 this.subs = [] } // 添加觀察者 addSub(sub) { // 判斷觀察者是否存在 和 是否擁有update方法 if (sub && sub.update) { this.subs.push(sub) } } // 通知方法 notify() { // 觸發(fā)每個觀察者的更新方法 this.subs.forEach((sub) => { sub.update() }) } }
在 obsever.js中使用Dep
在 get中添加 Dep.target(觀察者)
在 set中 觸發(fā) notify(通知)
/* observer.js */ class Observer { ... } // 遍歷 data 轉(zhuǎn)為響應(yīng)式 walk(data) { ... } // 這里是 將data中的屬性轉(zhuǎn)為getter setter defineReactive(obj, key, value) { ... // 創(chuàng)建 Dep 對象 let dep = new Dep() Object.defineProperty(obj, key, { ... // 獲取值 get() { // 在這里添加觀察者對象 Dep.target 表示觀察者 Dep.target && dep.addSub(Dep.target) return value }, // 設(shè)置值 set(newValue) { if (newValue === value) return value = newValue self.walk(newValue) // 觸發(fā)通知 更新視圖 dep.notify() }, }) } }
**watcher **的作用 數(shù)據(jù)更新后 收到通知之后 調(diào)用 update進行更新
/* watcher.js */ class Watcher { constructor(vm, key, cb) { // vm 是 Vue 實例 this.vm = vm // key 是 data 中的屬性 this.key = key // cb 回調(diào)函數(shù) 更新視圖的具體方法 this.cb = cb // 把觀察者的存放在 Dep.target Dep.target = this // 舊數(shù)據(jù) 更新視圖的時候要進行比較 // 還有一點就是 vm[key] 這個時候就觸發(fā)了 get 方法 // 之前在 get 把 觀察者 通過dep.addSub(Dep.target) 添加到了 dep.subs中 this.oldValue = vm[key] // Dep.target 就不用存在了 因為上面的操作已經(jīng)存好了 Dep.target = null } // 觀察者中的必備方法 用來更新視圖 update() { // 獲取新值 let newValue = this.vm[this.key] // 比較舊值和新值 if (newValue === this.oldValue) return // 調(diào)用具體的更新方法 this.cb(newValue) } }
那么去哪里創(chuàng)建 Watcher呢?還記得在 compiler.js中 對文本節(jié)點的編譯操作嗎
在編譯完文本節(jié)點后 在這里添加一個 Watcher
還有 v-textv-model指令 當編譯的是元素節(jié)點 就添加一個 Watcher
/* compiler.js */ class Compiler { // vm 指 Vue 實例 constructor(vm) { // 拿到 vm this.vm = vm // 拿到 el this.el = vm.$el // 編譯模板 this.compile(this.el) } // 編譯模板 compile(el) { let childNodes = [...el.childNodes] childNodes.forEach((node) => { if (this.isTextNode(node)) { // 編譯文本節(jié)點 this.compileText(node) } ... } // 編譯文本節(jié)點(簡單的實現(xiàn)) compileText(node) { let reg = /\{\{(.+)\}\}/ let val = node.textContent if (reg.test(val)) { let key = RegExp.$1.trim() node.textContent = val.replace(reg, this.vm[key]) // 創(chuàng)建觀察者 new Watcher(this.vm, key, newValue => { node.textContent = newValue }) } } ... // v-text textUpdater(node, key, value) { node.textContent = value // 創(chuàng)建觀察者2 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } // v-model modelUpdater(node, key, value) { node.value = value // 創(chuàng)建觀察者 new Watcher(this.vm, key, (newValue) => { node.value = newValue }) // 這里實現(xiàn)雙向綁定 監(jiān)聽input 事件修改 data中的屬性 node.addEventListener('input', () => { this.vm[key] = node.value }) } }
當 我們改變 響應(yīng)式屬性的時候 觸發(fā)了 set()方法 ,然后里面 發(fā)布者 dep.notify方法啟動了,拿到了 所有的 觀察者 watcher實例去執(zhí)行 update方法調(diào)用了回調(diào)函數(shù) cb(newValue)方法并把 新值傳遞到了 cb()當中 cb方法是的具體更新視圖的方法 去更新視圖
比如上面的例子里的第三個參數(shù) cb方法
new Watcher(this.vm, key, newValue => { node.textContent = newValue })
還有一點要實現(xiàn)v-model的雙向綁定
不僅要通過修改數(shù)據(jù)來觸發(fā)更新視圖,還得為node添加 input事件 改變 data數(shù)據(jù)中的屬性
來達到雙向綁定的效果
7.測試下自己寫的
到了目前為止 響應(yīng)式 和 雙向綁定 都基本實現(xiàn)了 那么來寫個例子測試下
{{msg}}
{{age}}
8.五個文件代碼
這里直接把5個文件個代碼貼出來 上面有的地方省略了,下面是完整的方便大家閱讀
vue.js
/* vue.js */ class Vue { constructor(options) { // 獲取到傳入的對象 沒有默認為空對象 this.$options = options || {} // 獲取 el this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el // 獲取 data this.$data = options.data || {} // 調(diào)用 _proxyData 處理 data中的屬性 this._proxyData(this.$data) // 使用 Obsever 把data中的數(shù)據(jù)轉(zhuǎn)為響應(yīng)式 new Observer(this.$data) // 編譯模板 new Compiler(this) } // 把data 中的屬性注冊到 Vue _proxyData(data) { Object.keys(data).forEach((key) => { // 進行數(shù)據(jù)劫持 // 把每個data的屬性 到添加到 Vue 轉(zhuǎn)化為 getter setter方法 Object.defineProperty(this, key, { // 設(shè)置可以枚舉 enumerable: true, // 設(shè)置可以配置 configurable: true, // 獲取數(shù)據(jù) get() { return data[key] }, // 設(shè)置數(shù)據(jù) set(newValue) { // 判斷新值和舊值是否相等 if (newValue === data[key]) return // 設(shè)置新值 data[key] = newValue }, }) }) } }
obsever.js
/* observer.js */ class Observer { constructor(data) { // 用來遍歷 data this.walk(data) } // 遍歷 data 轉(zhuǎn)為響應(yīng)式 walk(data) { // 判斷 data是否為空 和 對象 if (!data || typeof data !== 'object') return // 遍歷 data Object.keys(data).forEach((key) => { // 轉(zhuǎn)為響應(yīng)式 this.defineReactive(data, key, data[key]) }) } // 轉(zhuǎn)為響應(yīng)式 // 要注意的 和vue.js 寫的不同的是 // vue.js中是將 屬性給了 Vue 轉(zhuǎn)為 getter setter // 這里是 將data中的屬性轉(zhuǎn)為getter setter defineReactive(obj, key, value) { // 如果是對象類型的 也調(diào)用walk 變成響應(yīng)式,不是對象類型的直接在walk會被return this.walk(value) // 保存一下 this const self = this // 創(chuàng)建 Dep 對象 let dep = new Dep() Object.defineProperty(obj, key, { // 設(shè)置可枚舉 enumerable: true, // 設(shè)置可配置 configurable: true, // 獲取值 get() { // 在這里添加觀察者對象 Dep.target 表示觀察者 Dep.target && dep.addSub(Dep.target) return value }, // 設(shè)置值 set(newValue) { // 判斷舊值和新值是否相等 if (newValue === value) return // 設(shè)置新值 value = newValue // 賦值的話如果是newValue是對象,對象里面的屬性也應(yīng)該設(shè)置為響應(yīng)式的 self.walk(newValue) // 觸發(fā)通知 更新視圖 dep.notify() }, }) } }
compiler.js
/* compiler.js */ class Compiler { // vm 指 Vue 實例 constructor(vm) { // 拿到 vm this.vm = vm // 拿到 el this.el = vm.$el // 編譯模板 this.compile(this.el) } // 編譯模板 compile(el) { // 獲取子節(jié)點 如果使用 forEach遍歷就把偽數(shù)組轉(zhuǎn)為真的數(shù)組 let childNodes = [...el.childNodes] childNodes.forEach((node) => { // 根據(jù)不同的節(jié)點類型進行編譯 // 文本類型的節(jié)點 if (this.isTextNode(node)) { // 編譯文本節(jié)點 this.compileText(node) } else if (this.isElementNode(node)) { //元素節(jié)點 this.compileElement(node) } // 判斷是否還存在子節(jié)點考慮遞歸 if (node.childNodes && node.childNodes.length) { // 繼續(xù)遞歸編譯模板 this.compile(node) } }) } // 編譯文本節(jié)點(簡單的實現(xiàn)) compileText(node) { // 核心思想利用把正則表達式把{{}}去掉找到里面的變量 // 再去Vue找這個變量賦值給node.textContent let reg = /\{\{(.+?)\}\}/ // 獲取節(jié)點的文本內(nèi)容 let val = node.textContent // 判斷是否有 {{}} if (reg.test(val)) { // 獲取分組一 也就是 {{}} 里面的內(nèi)容 去除前后空格 let key = RegExp.$1.trim() // 進行替換再賦值給node node.textContent = val.replace(reg, this.vm[key]) // 創(chuàng)建觀察者 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } } // 編譯元素節(jié)點這里只處理指令 compileElement(node) { // 獲取到元素節(jié)點上面的所有屬性進行遍歷 ![...node.attributes].forEach((attr) => { // 獲取屬性名 let attrName = attr.name // 判斷是否是 v- 開頭的指令 if (this.isDirective(attrName)) { // 除去 v- 方便操作 attrName = attrName.substr(2) // 獲取 指令的值就是 v-text = "msg" 中msg // msg 作為 key 去Vue 找這個變量 let key = attr.value // 指令操作 執(zhí)行指令方法 // vue指令很多為了避免大量個 if判斷這里就寫個 uapdate 方法 this.update(node, key, attrName) } }) } // 添加指令方法 并且執(zhí)行 update(node, key, attrName) { // 比如添加 textUpdater 就是用來處理 v-text 方法 // 我們應(yīng)該就內(nèi)置一個 textUpdater 方法進行調(diào)用 // 加個后綴加什么無所謂但是要定義相應(yīng)的方法 let updateFn = this[attrName + 'Updater'] // 如果存在這個內(nèi)置方法 就可以調(diào)用了 updateFn && updateFn.call(this, node, key, this.vm[key]) } // 提前寫好 相應(yīng)的指定方法比如這個 v-text // 使用的時候 和 Vue 的一樣 textUpdater(node, key, value) { node.textContent = value // 創(chuàng)建觀察者 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } // v-model modelUpdater(node, key, value) { node.value = value // 創(chuàng)建觀察者 new Watcher(this.vm, key, (newValue) => { node.value = newValue }) // 這里實現(xiàn)雙向綁定 node.addEventListener('input', () => { this.vm[key] = node.value }) } // 判斷元素的屬性是否是 vue 指令 isDirective(attr) { return attr.startsWith('v-') } // 判斷是否是元素節(jié)點 isElementNode(node) { return node.nodeType === 1 } // 判斷是否是 文本 節(jié)點 isTextNode(node) { return node.nodeType === 3 } }
dep.js
/* dep.js */ class Dep { constructor() { // 存儲觀察者 this.subs = [] } // 添加觀察者 addSub(sub) { // 判斷觀察者是否存在 和 是否擁有update方法 if (sub && sub.update) { this.subs.push(sub) } } // 通知方法 notify() { // 觸發(fā)每個觀察者的更新方法 this.subs.forEach((sub) => { sub.update() }) } }
watcher.js
/* watcher.js */ class Watcher { constructor(vm, key, cb) { // vm 是 Vue 實例 this.vm = vm // key 是 data 中的屬性 this.key = key // cb 回調(diào)函數(shù) 更新視圖的具體方法 this.cb = cb // 把觀察者的存放在 Dep.target Dep.target = this // 舊數(shù)據(jù) 更新視圖的時候要進行比較 // 還有一點就是 vm[key] 這個時候就觸發(fā)了 get 方法 // 之前在 get 把 觀察者 通過dep.addSub(Dep.target) 添加到了 dep.subs中 this.oldValue = vm[key] // Dep.target 就不用存在了 因為上面的操作已經(jīng)存好了 Dep.target = null } // 觀察者中的必備方法 用來更新視圖 update() { // 獲取新值 let newValue = this.vm[this.key] // 比較舊值和新值 if (newValue === this.oldValue) return // 調(diào)用具體的更新方法 this.cb(newValue) } }
以上就是關(guān)于“VUE響應(yīng)式原理實例代碼分析”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。