主頁 > 企業開發 > Vue2原始碼決議-原始碼除錯與核心流程梳理圖解

Vue2原始碼決議-原始碼除錯與核心流程梳理圖解

2022-07-15 07:13:39 企業開發

現在VUE3已經有一段時間了,也慢慢普及起來了,不過因為一直還在使用VUE2的原因還是去了解和學了下它的原始碼,畢竟VUE2也不會突然就沒了是吧,且VUE3中很多原理之類的也是類似的,然后就準備把VUE3搞起來了是吧,VUE2原始碼使用的是roullup進行打包的,還使用了Flow進行靜態型別檢測(該庫使用的已經不多了,且VUE3已經使用TypeScript進行開發了,有型別檢測了),若是沒怎么接觸過Vue2,直接Vue3會更劃算些,結構之類的也更清晰了

篇幅有限只探討了核心的一些程序,


VUE2專案結構與入口

主要目錄結構:

vue2原始碼倉庫:https://github.com/vuejs/vue
clone后可以看到大概如下結構:

|----benchmarks 性能測驗
|----scripts 腳本檔案
|----scr 原始碼
|??|----compiler 模板編譯相關
|??|----core vue2核心代碼
|??|----platforms 平臺相關
|??|----server 服務端渲染
|??|----sfc 決議單檔案組件
|??|----shared 模塊間共享屬性和方法

package.json入口:

// package.json 中指定了roullup的組態檔及打包引數
"scripts": {
    "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
    "dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs-dev",
    "dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm",
}
// 在/scripts/config.js 可以看到在接受到引數后 打包入口最終在 /src/platforms下檔案中

構建引數與版本的說明

可以看到rollup打包或者除錯的時候后面更了很多引數,不同引數就能生成不同內容的版本,引數說明如下:

  • web-runtime: 運行時,無法決議傳入的template
  • web-full:運行時 + 模板編譯
  • web-compiler:僅模板編譯
  • web-runtime-cjs web-full-cjs:cjsCommonJS打包
  • web-runtime-esm web-full-esm :esm 語法(支持import export)
  • web-full-esm-browser:瀏覽器中使用
  • web-server-renderer:服務端渲染

注:在使用CLI腳手架開發時,一般都是選擇web-runtime是因為,腳手架中有vue-loader會將模板轉為render函式了,所以不需要再模板編譯了,

入口深入與原始碼的構建,除錯

我們可以在/platforms目錄下找到,最外層的入口,但這個入口有經過層層包裝,添加了些方法后,最后才會到創建VUE實體的入口,以entry-runtime-with-compiler.js為例,
entry-runtime-with-compiler 重寫了$mount,主要增加了對模板的處理方法,:

  • 沒有template則嘗試從el中取dom作template
  • template則直接使用傳入的template
  • 沒則將template轉化為render函式,放在$options

它的Vue又是從./runtime/index導進來的,runtime/index.js有公共的$mount方法,還增加了:

  • directives (全域指令:model,show)
  • components (全域組件:transition,transitionGroup)
  • patch(瀏覽器環境)

詳細流程如下圖:
image

開啟除錯:

package.json項中增加sourcemap配置,如:

"scripts": {
    "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev",
	.......
 }

然后npm run dev就可以在原始碼中debugger進行除錯了,


VUE基本代碼的執行在原始碼中的核心流程

比如在頁面中有如下代碼,它主要涉及到Vue中的技術有:模板語法,資料雙向系結,計算屬性,偵聽器,

點擊查看主要代碼
<div id="app">
  <p>{{fullName}}:{{fullName}}-{{formBY}}</p>
</div>

const vm = new Vue({
    el: "#app",
    data() {
        return {
            firstName: "Shiina",
            lastName: "Mashiro",
            formBY: "flytree-cnblogs",
            arr: [1, 2, 3, ["a"]],
        };
    },
    computed: {
        fullName() {
            return this.firstName + this.lastName;
        }
    },
    watch: {
        firstName(newValue, oldValue) {
            console.log(newValue, oldValue)
        }
    }
});

setTimeout(() => {
    vm.firstName = 'flytree'
}, 1000);

我們可以把核心(細節后面再展開,先有個整體把握)的執行流程梳理下如下圖:
image

創建回應式資料

要實作資料的雙向系結,就要創建回應式資料,原理就是重寫了data中每項資料的gettersetter,這樣就可以攔截到每次的取值或者改值的操作了,取值的時候收集依賴,改值的時候通知notify:

點擊查看代碼
// 路徑 /scr/core/observer/index.js
export function defineReactive() {
    const dep = new Dep()

    const property = Object.getOwnPropertyDescriptor(obj, key)
    if (property && property.configurable === false) {
        return
    }

    // cater for pre-defined getter/setters
    const getter = property && property.get
    const setter = property && property.set
    if ((!getter || setter) && arguments.length === 2) {
        val = obj[key]
    }

    let childOb = !shallow && observe(val)
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            const value = https://www.cnblogs.com/flytree/archive/2022/07/14/getter ? getter.call(obj) : val
            if (Dep.target) {
                dep.depend()
                if (childOb) {
                    childOb.dep.depend()
                    if (Array.isArray(value)) {
                        dependArray(value)
                    }
                }
            }
            return value
        },
        set: function reactiveSetter(newVal) {
            const value = getter ? getter.call(obj) : val
            /* eslint-disable no-self-compare */
            if (newVal === value || (newVal !== newVal && value !== value)) {
                return
            }
            /* eslint-enable no-self-compare */
            if (process.env.NODE_ENV !=='production' && customSetter) {
                customSetter()
            }
            // #7981: for accessor properties without setter
            if (getter && !setter) return
            if (setter) {
                setter.call(obj, newVal)
            } else {
                val = newVal
            }
            childOb = !shallow && observe(newVal)
            dep.notify()
        }
    })
}

模板編譯

compileToFunctions進行模板編譯,主要流程就是:

  1. 使用正則決議模板,然后將其轉化衛AST抽象語法樹,
  2. 然后根據AST抽象語法樹拼裝render函式,

比如上面的代碼

template: "<div id=\"app\">\n      <p>{{fullName}}:{{fullName}}-{{formBY}}</p>\n

image

生成的render函式:
image

"with(this){
  return _c('div',{attrs:{"id ":"app "}},
        [_c('p',[_v(_s(fullName)+": "+_s(fullName)+" - "+_s(formBY))])])
}"

使用with,vue實列執行到這個方法時,則會去找當前實體的屬性,
_c,_s,_v等函式是用來將對應型別節點轉換位虛擬dom的,render執行后就能生成對應的虛擬dom樹了,

依賴收集

在看依賴收集前,可以想下以下問題:

什么時候進行依賴收集? data中項被取值(其getter執行)
什么時候執行getter? _render函式執行
什么時候執行_render? _update函式執行
什么時候執行_update? data項中getter執行
什么時候執行data項中get方法? 模板中取值

這時我們再看下get的來源和去處,看下具體的流程:
image

可以看到:
1.取值:在模板中取值的時候它就會進行依賴收集,執行dep.depend(), 最后會去重的watcher存在依賴的subs[]中,去重是,如果模板中重復取了兩次值,那也不會重復收集watcher
2.改值:在值發生變更的時候,就會觸發dep.notify(),會遍歷執行其dep.subs中的所有watcher.update(),最后還是會執行到watcher.get(),那么就執行了_update(_render())把變化更新到dom上了,

Dep類原始碼:

點擊查看代碼
export default class Dep {
  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub) {
    this.subs.push(sub)
  }

  removeSub (sub) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    if (process.env.NODE_ENV !== 'production' && !config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort((a, b) => a.id - b.id)
    }
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

Watcher類原始碼:

點擊查看主要代碼
export default class Watcher {
    constructor(vm, expOrFn, cb, options, isRenderWatcher) {
        this.vm = vm
        if (isRenderWatcher) {
            vm._watcher = this
        }
        vm._watchers.push(this)
        // options
        if (options) {
            this.deep = !!options.deep
            this.user = !!options.user
            this.lazy = !!options.lazy
            this.sync = !!options.sync
            this.before = options.before
        } else {
            this.deep = this.user = this.lazy = this.sync = false
        }
        this.cb = cb
        this.id = ++uid // uid for batching
        this.active = true
        this.dirty = this.lazy // for lazy watchers
        this.deps = []
        this.newDeps = []
        this.depIds = new Set()
        this.newDepIds = new Set()
        this.expression = process.env.NODE_ENV !== 'production'
            ? expOrFn.toString()
            : ''
        // parse expression for getter
        if (typeof expOrFn === 'function') {
            // 渲染watcher時就gettr就傳入了 _update(_render())
            this.getter = expOrFn
        } else {
            this.getter = parsePath(expOrFn)
            if (!this.getter) {
                this.getter = noop
                process.env.NODE_ENV !== 'production' && warn(
                    `Failed watching path: "${expOrFn}" ` +
                    'Watcher only accepts simple dot-delimited paths. ' +
                    'For full control, use a function instead.',
                    vm
                )
            }
        }
        // 在計算屬性創建watcher的時候lazy為true
        this.value = https://www.cnblogs.com/flytree/archive/2022/07/14/this.lazy
            ? undefined
            : this.get()
    }

    /**
     * Evaluate the getter, and re-collect dependencies.
     */
    get() {
        pushTarget(this)
        let value
        const vm = this.vm
        try {
            value = this.getter.call(vm, vm)
        } catch (e) {
            if (this.user) {
                handleError(e, vm, `getter for watcher"${this.expression}"`)
            } else {
                throw e
            }
        } finally {
            // "touch" every property so they are all tracked as
            // dependencies for deep watching
            if (this.deep) {
                traverse(value)
            }
            popTarget()
            this.cleanupDeps()
        }
        return value
    }

    /**
     * Add a dependency to this directive.
     */
    addDep(dep) {
        const id = dep.id
        if (!this.newDepIds.has(id)) {
            this.newDepIds.add(id)
            this.newDeps.push(dep)
            if (!this.depIds.has(id)) {
                dep.addSub(this)
            }
        }
    }

    /**
     * Clean up for dependency collection.
     */
    cleanupDeps() {
        let i = this.deps.length
        while (i--) {
            const dep = this.deps[i]
            if (!this.newDepIds.has(dep.id)) {
                dep.removeSub(this)
            }
        }
        let tmp = this.depIds
        this.depIds = this.newDepIds
        this.newDepIds = tmp
        this.newDepIds.clear()
        tmp = this.deps
        this.deps = this.newDeps
        this.newDeps = tmp
        this.newDeps.length = 0
    }

    /**
     * Subscriber interface.
     * Will be called when a dependency changes.
     */
    update() {
        /* istanbul ignore else */
        if (this.lazy) {
            this.dirty = true
        } else if (this.sync) {
            this.run()
        } else {
            queueWatcher(this)
        }
    }

    /**
     * Scheduler job interface.
     * Will be called by the scheduler.
     */
    run() {
        if (this.active) {
            const value = https://www.cnblogs.com/flytree/archive/2022/07/14/this.get()
            if (
                value !== this.value ||
                // Deep watchers and watchers on Object/Arrays should fire even
                // when the value is the same, because the value may
                // have mutated.
                isObject(value) ||
                this.deep
            ) {
                // set new value
                const oldValue = this.value
                this.value = value
                if (this.user) {
                    const info = `callback for watcher"${this.expression}"`
                    invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
                } else {
                    this.cb.call(this.vm, value, oldValue)
                }
            }
        }
    }

    /**
     * Evaluate the value of the watcher.
     * This only gets called for lazy watchers.
     */
    evaluate() {
        this.value = https://www.cnblogs.com/flytree/archive/2022/07/14/this.get()
        this.dirty = false
    }

    /**
     * Depend on all deps collected by this watcher.
     */
    depend() {
        let i = this.deps.length
        while (i--) {
            this.deps[i].depend()
        }
    }

    /**
     * Remove self from all dependencies' subscriber list.
     */
    teardown() {
        if (this.active) {
            // remove self from vm's watcher list
            // this is a somewhat expensive operation so we skip it
            // if the vm is being destroyed.
            if (!this.vm._isBeingDestroyed) {
                remove(this.vm._watchers, this)
            }
            let i = this.deps.length
            while (i--) {
                this.deps[i].removeSub(this)
            }
            this.active = false
        }
    }
}

更新到dom樹的細節

從上面步驟分析下來,一般情況下,watcher實體的中的get()執行了,就能觸發,dom更新了,就是走了updateComponent

// 此方法在 core/instance/lifecycle.js
updateComponent = () => {
      vm._update(vm._render(), hydrating)
}

_render執行后會生成虛擬dom,而_update就會執行patch(__patch__)更新對比后更新dom了,

_update原始碼:

點擊查看代碼
export function lifecycleMixin (Vue) {
  Vue.prototype._update = function (vnode, hydrating) {
    const vm = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }
}

patch進行diff優化

patch匯出:

// platforms/web/runtime/patch.js
export const patch: Function = createPatchFunction({ nodeOps, modules })

最后createPatchFunction的原始碼在core/vdom/patch.js
Diff大概的流程:
判斷是否是相同節點:sameVnode判斷標簽和key是否相同,

diff演算法是用來比較兩個虛擬dom的更新情況的,而且是同級比較的
在diff演算法中有四個指標,
在新的虛擬dom中的兩個指標,新前(在前面的指標),新后(在后面的指標),
在舊的虛擬dom中的兩個指標,舊前(在前面的指標),舊后(在后面的指標),

前指標的特點:

  1. 初始位置在最前面,也就是說children陣列中的第0位,
  2. 前指標只能向后移動,

后指標的特點:

  1. 初始位置在最后面,也就是說在children陣列中的第length-1位,
  2. 后指標只能向前移動,

每次比較可能進行以下四種比較:

  1. 新前和舊前,匹配則,前指標后移一位,后指標前移一位,
  2. 新后和舊后,匹配則,前指標后移一位,后指標前移一位,
  3. 新后和舊前,匹配則,將所匹配的節點的dom移動到舊后之后,虛擬dom中將其設位undefined,指標移動,
  4. 新前和舊后,匹配則,將所匹配的節點的dom移動到舊前之前,虛擬dom中將其設位undefined,指標移動,
    匹配的步驟是按此順序從一到四進行匹配,但若之中有匹配成功的則不進行之后的匹配,比如第2種情況匹配,則不會進行3,4的匹配了,

上面四種匹配是對push, shift, pop, unshift ,reveres ,sort 操作進行優化,但若以上的四種情況都未曾匹配到,則會以新虛擬dom中為匹配的這項當作查找的目標,在舊虛擬dom中進行遍歷查找:

  1. 若查找到,則將dom中找到這項移動舊前之前,其虛擬dom中位置則設為undefined,然后新前指標移動一位,
  2. 若未找到,則將新前所指的這項(也是查找的目標項),生成dom節點,插入到舊前之前上,而后新前指標移動一位,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/499375.html

標籤:其他

上一篇:虛擬DOM的理解與總結

下一篇:從零開始制作【立體鍵盤】,畫UI免寫CSS,【盲打練習】的互動邏輯只用了10來行運算式!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more