Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 亚洲国产精品第一页,久久99国产亚洲高清观看首页,色噜噜在线视频

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          5個相見恨晚的寶藏網站,擁有它們,資源在手,再不求人

          5個相見恨晚的寶藏網站,擁有它們,資源在手,再不求人

          天小編給大家帶來了5個實用寶藏網站,常用、簡潔、方便、無廣告,滿足你日常工作需求!讓你做到資源在手,再不求人!做辦公室最閃亮的那一顆Star!

          一、實用類

          1.在線工具

          在線工具,顧名思義,線上的工具箱。里面的工具大多是比較偏向生活化的,而且網站上面的功能各種各樣,具有多樣性。比較生活化的,就類似格字帖生成、數字轉大寫、各國首都列表、高校查詢、尺碼對照表、地圖坐標系等等。

          當然比較專業的也有,像在線運行代碼、css工具、html工具、php工具等等碼農必備。畢竟這個網站給自己的定位是程序員的工具箱。網站優點在于總共匯總了近百款工具,而且完全免費,不用注冊,隨用隨關!十分良心!

          2.阿貓阿狗導航

          阿貓阿狗網址導航是產品經理和運營人必備的導航站,整個網站多達17個大類別。涵蓋了行業資訊、區塊鏈、數據分析、前端框架、域名注冊等等。收錄產品運營必備工具、國外優秀網站等相關網站。無論是新媒體還是自媒體,無論是產品汪還是運營喵,這都是必須Ctrl+D的一個網站~

          3.迅捷畫圖

          作為一款可以直接在線繪制各種思維導圖、流程圖等多種圖表的在線網站,完全可以滿足上班族的日常需要。網站上的模板種類繁多,并且可以直接套用進行在線編輯。上手簡單,功能齊全。同時支持多種格式的導出與保存,對新手小白很友好的一個在線繪圖網站。最重要一點!這個網站導出.xsd格式是免費的,沒有任何限制,而且后續還可以隨時修改。如果想要提高工作效率,這個網站也是不錯的選擇。

          二、搜索類

          1.蟲部落快搜

          這個網站雖然作為一個搜索網站,倒不如說是搜索引擎的集合體。當你打開這個網站的時候,你會發現,啊!原來搜索還可以這么玩!界面左邊布滿了各種搜索類別,你可以直接按需搜索。其中涵蓋了英文電子書、姓氏家譜、漢典、古文獻、MBA智庫、商品歷史價格等等類別。棒極了!立馬Ctrl+D!

          2.鳩摩搜索

          這個網站是一個電子書搜索網站,由一名電子書愛好者建立的。果然愛好使人強大!整個界面十分簡潔、可愛。該網站通過搜索引擎抓取網絡上現有的電子書,并且支持多種格式下載。擁有這個網站就可以隨心所欲下載電子書了~書蟲寶寶們不能錯過的一款搜索網站。

          以上就是今天小編要分享給大家的寶藏網站啦!上面的網站大家有用過嗎?還是大家有更好的網站呀,不要吝嗇!歡迎分享給小編哦~有好用的話后續小編也可以收錄匯總一下!

          大家好,今年的金三銀四已經來了,也有人說是銅三鐵四,不過我想說的是這重要嗎,環境只是一個因素,它確實會影響大家找工作或者跳槽漲薪,但是影響不多,最重要的一個因素在于自己是否已經做好了準備。我呢為大家準備了百道面試題,為大家保駕護航,后續也會持續更新吧,畢竟還有金九銀十與新技術的出現。

          希望大家可以點個贊支持一下,整理不易

          因為內容太多所以需要完整版PDF的小伙伴可關注+私信【學習】自行查看

          1.Vue3.0性能提升主要是體現在哪些方面

          1.響應式系統

          - Vue.js 2.x 中響應式系統的核心是 Object.defineProperty,劫持整個對象,然后進行深度遍歷所有屬性,給每個屬性添加`getter`和`setter`,實現響應式
          - Vue.js 3.x 中使用 Proxy 對象重寫響應式系統
          	- 可以監聽動態新增的屬性
          	- 可以監聽刪除的屬性
          	- 可以監聽數組的索引和length屬性 
          
          * 實現原理:
          
            * 通過Proxy(代理): 攔截對象中任意屬性的變化, 包括:屬性值的讀寫、屬性的添加、屬性的刪除等。
          
            * 通過Reflect(反射): 對源對象的屬性進行操作。
          
            * MDN文檔中描述的Proxy與Reflect:
          
              * Proxy:[Proxy - JavaScript | MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy "Proxy - JavaScript | MDN")
          
              * Reflect:[Reflect - JavaScript | MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect "Reflect - JavaScript | MDN")
                                                     
                 new Proxy(data, {
                    // 攔截讀取屬性值
                    get (target, prop) {
                        return Reflect.get(target, prop)
                    },
                    // 攔截設置屬性值或添加新屬性
                    set (target, prop, value) {
                        return Reflect.set(target, prop, value)
                    },
                    // 攔截刪除屬性
                    deleteProperty (target, prop) {
                        return Reflect.deleteProperty(target, prop)
                    }
                })
            
                proxy.name='tom'   ![]
          


          2.編譯階段

          - Vue.js 2.x 通過標記靜態節點,優化 diff 的過程
          - Vue.js 3.x 
            *   vue.js 3.x中標記和提升所有的靜態節點,diff的時候只需要對比動態節點內容;
            *   Fragments(升級vetur插件): template中不需要唯一根節點,可以直接放文本或者同級標簽
            *   靜態提升(hoistStatic),當使用 hoistStatic 時,所有靜態的節點都被提升到 render 方法之外.只會在應用啟動的時候被創建一次,之后使用只需要應用提取的靜態節點,隨著每次的渲染被不停的復用。
            *   patch flag, 在動態標簽末尾加上相應的標記,只能帶 patchFlag 的節點才被認為是動態的元素,會被追蹤屬性的修改,能快速的找到動態節點,而不用逐個逐層遍歷,提高了虛擬dom diff的性能。
            *   緩存事件處理函數cacheHandler,避免每次觸發都要重新生成全新的function去更新之前的函數
          markdown


          3.源碼體積

          - 相比Vue2,Vue3整體體積變小了,除了移出一些不常用的AP
          - tree shanking
            - 任何一個函數,如ref、reavtived、computed等,僅僅在用到的時候才打包
            - 通過編譯階段的靜態分析,找到沒有引入的模塊并打上標記,將這些模塊都給搖掉
          


          2.vue3有哪些新的組件

          1.Fragment

          *   在Vue2中: 組件必須有一個根標簽
          
          *   在Vue3中: 組件可以沒有根標簽, 內部會將多個標簽包含在一個Fragment虛擬元素中
          
          *   好處: 減少標簽層級, 減小內存占用
          markdown


          2.Teleport

          什么是Teleport?—— Teleport 是一種能夠將我們的組件html結構移動到指定位置的技術。

          <teleport to="移動位置">
              <div v-if="isShow" class="mask">
                  <div class="dialog">
                      <h3>我是一個彈窗</h3>
                      <button @click="isShow=false">關閉彈窗</button>
                  </div>
              </div>
          </teleport>
          


          3.Suspense

          • 等待異步組件時渲染一些額外內容,讓應用有更好的用戶體驗
          • 使用步驟:
            • 異步引入組件
          import {defineAsyncComponent} from 'vue'
          const Child=defineAsyncComponent(()=>import('./components/Child.vue'))
          
            • 使用Suspense包裹組件,并配置好default 與 fallback
          <template>
              <div class="app">
                  <h3>我是App組件</h3>
                  <Suspense>
                      <template v-slot:default>
                          <Child/>
                      </template>
                      <template v-slot:fallback>
                          <h3>加載中.....</h3>
                      </template>
                  </Suspense>
              </div>
          </template>
          

          3.Vue2.0 和 Vue3.0 有什么區別

          1. 響應式系統的重新配置,使用proxy替換Object.defineProperty
          2. typescript支持
          3. 新增組合API,更好的邏輯重用和代碼組織
          4. v-if和v-for的優先級
          5. 靜態元素提升
          6. 虛擬節點靜態標記
          7. 生命周期變化
          8. 打包體積優化
          9. ssr渲染性能提升
          10. 支持多個根節點
          


          4.Vue 生命周期

          1.vue2.x的生命周期

          2.vue3.0的生命周期

          *   Vue3.0中可以繼續使用Vue2.x中的生命周期鉤子,但有有兩個被更名:
          
              *   `beforeDestroy`改名為 `beforeUnmount`
          ?
              *   `destroyed`改名為 `unmounted`
          ?
          *   Vue3.0也提供了 Composition API 形式的生命周期鉤子,與Vue2.x中鉤子對應關系如下:
          ?
              *   `beforeCreate`===>`setup()`
          ?
              *   `created`=======>`setup()`
          ?
              *   `beforeMount`===>`onBeforeMount`
          ?
              *   `mounted`=======>`onMounted`
          ?
              *   `beforeUpdate`===>`onBeforeUpdate`
          ?
              *   `updated`=======>`onUpdated`
          ?
              *   `beforeUnmount`==>`onBeforeUnmount`
          ?
              *   `unmounted`=====>`onUnmounted`
              
              
              
          Vue 實例有?個完整的?命周期,也就是從開始創建、初始化數據、編譯模版、掛載Dom -> 渲染、更新 -> 渲染、卸載 等?系列過程,稱這是Vue的?命周期。 
          1、beforeCreate(創建前) :數據觀測和初始化事件還未開始,此時 data 的響應式追蹤、event/watcher 都還沒有被設置,也就是說不能訪問到data、computed、watch、methods上的方法和數據。 
          2、created(創建后) :實例創建完成,實例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此時渲染得節點還未掛載到 DOM,所以不能訪問到 `$el` 屬性。 
          3、beforeMount(掛載前) :在掛載開始之前被調用,相關的render函數首次被調用。實例已完成以下的配置:編譯模板,把data里面的數據和模板生成html。此時還沒有掛載html到頁面上。 
          4、mounted(掛載后) :在el被新創建的 vm.$el 替換,并掛載到實例上去之后調用。實例已完成以下的配置:用上面編譯好的html內容替換el屬性指向的DOM對象。完成模板中的html渲染到html 頁面中。此過程中進行ajax交互。 
          5、beforeUpdate(更新前) :響應式數據更新時調用,此時雖然響應式數據更新了,但是對應的真實 DOM 還沒有被渲染。 
          6、updated(更新后):在由于數據更改導致的虛擬DOM重新渲染和打補丁之后調用。此時 DOM 已經根據響應式數據的變化更新了。調用時,組件 DOM已經更新,所以可以執行依賴于DOM的操作。然而在大多數情況下,應該避免在此期間更改狀態,因為這可能會導致更新無限循環。該鉤子在服務器端渲染期間不被調用。 
          7、beforeDestroy(銷毀前) :實例銷毀之前調用。這一步,實例仍然完全可用,`this` 仍能獲取到實例。
          8、destroyed(銷毀后) :實例銷毀后調用,調用后,Vue 實例指示的所有東西都會解綁定,所有的事件監聽器會被移除,所有的子實例也會被銷毀。該鉤子在服務端渲染期間不被調用。 
          
          


          5.都說 Composition API 和 React Hook 很像,請問他們的區別是什么

          從 React Hook 從實現的角度來看,React Hook 是基于 useState 的調用順序來確定下一個 re 渲染時間狀態從哪個 useState 開始,所以有以下幾個限制
          ?
          *   不在循環中、條件、調用嵌套函數 Hook
          *   你必須確保它總是在你這邊 React Top level 調用函數 Hook
          *   使用效果、使用備忘錄 依賴關系必須手動確定
          ?
          和 Composition API 是基于 Vue 的響應系統,和 React Hook 相比
          ?
          *   在設置函數中,一個組件實例只調用一次設置,而 React Hook 每次重新渲染時,都需要調用 Hook,給 React 帶來的 GC 比 Vue 更大的壓力,性能也相對 Vue 對我來說也比較慢
          *   Compositon API 你不必擔心調用的順序,它也可以在循環中、條件、在嵌套函數中使用
          *   響應式系統自動實現依賴關系收集,而且組件的性能優化是由 Vue 內部完成的,而 React Hook 的依賴關系需要手動傳遞,并且依賴關系的順序必須得到保證,讓路 useEffect、useMemo 等等,否則組件性能會因為依賴關系不正確而下降。
          ?
          雖然Compoliton API看起來像React Hook來使用,但它的設計思路也是React Hook的參考。
          


          6. Composition Api 與Options Api 有什么不同

          1.Options Api

          Options API,即大家常說的選項API,即以vue為后綴的文件,通過定義methods,computed,watch,data等屬性與方法,共同處理頁面邏輯

          如下圖:

          可以看到Options代碼編寫方式,如果是組件狀態,則寫在data屬性上,如果是方法,則寫在methods屬性上...

          用組件的選項 (data、computed、methods、watch) 組織邏輯在大多數情況下都有效

          然而,當組件變得復雜,導致對應屬性的列表也會增長,這可能會導致組件難以閱讀和理解

          2.Composition Api

          在 Vue3 Composition API 中,組件根據邏輯功能來組織的,一個功能所定義的所有 API 會放在一起(更加的高內聚,低耦合)

          即使項目很大,功能很多,我們都能快速的定位到這個功能所用到的所有 API

          3.對比

          下面對Composition Api與Options Api進行兩大方面的比較

          • 邏輯組織
          • 邏輯復用

          邏輯組織

          Options API

          假設一個組件是一個大型組件,其內部有很多處理邏輯關注點(對應下圖不用顏色)

          可以看到,這種碎片化使得理解和維護復雜組件變得困難

          選項的分離掩蓋了潛在的邏輯問題。此外,在處理單個邏輯關注點時,我們必須不斷地“跳轉”相關代碼的選項塊

          Compostion API

          而Compositon API正是解決上述問題,將某個邏輯關注點相關的代碼全都放在一個函數里,這樣當需要修改一個功能時,就不再需要在文件中跳來跳去

          下面舉個簡單例子,將處理count屬性相關的代碼放在同一個函數了

          inifunction useCount() {
              let count=ref(10);
              let double=computed(()=> {
                  return count.value * 2;
              });
          ?
              const handleConut=()=> {
                  count.value=count.value * 2;
              };
          ?
              console.log(count);
          ?
              return {
                  count,
                  double,
                  handleConut,
              };
          }
          


          組件上中使用count

          export default defineComponent({
              setup() {
                  const { count, double, handleConut }=useCount();
                  return {
                      count,
                      double,
                      handleConut
                  }
              },
          });
          


          再來一張圖進行對比,可以很直觀地感受到 Composition API在邏輯組織方面的優勢,以后修改一個屬性功能的時候,只需要跳到控制該屬性的方法中即可

          邏輯復用

          在Vue2中,我們是用過mixin去復用相同的邏輯

          下面舉個例子,我們會另起一個mixin.js文件

          export const MoveMixin={
            data() {
              return {
                x: 0,
                y: 0,
              };
            },
          ?
            methods: {
              handleKeyup(e) {
                console.log(e.code);
                // 上下左右 x y
                switch (e.code) {
                  case "ArrowUp":
                    this.y--;
                    break;
                  case "ArrowDown":
                    this.y++;
                    break;
                  case "ArrowLeft":
                    this.x--;
                    break;
                  case "ArrowRight":
                    this.x++;
                    break;
                }
              },
            },
          ?
            mounted() {
              window.addEventListener("keyup", this.handleKeyup);
            },
          ?
            unmounted() {
              window.removeEventListener("keyup", this.handleKeyup);
            },
          };
          


          然后在組件中使用

          <template>
            <div>
              Mouse position: x {{ x }} / y {{ y }}
            </div>
          </template>
          <script>
          import mousePositionMixin from './mouse'
          export default {
            mixins: [mousePositionMixin]
          }
          </script>
          


          使用單個mixin似乎問題不大,但是當我們一個組件混入大量不同的 mixins 的時候

          mixins: [mousePositionMixin, fooMixin, barMixin, otherMixin]
          


          會存在兩個非常明顯的問題:

          • 命名沖突
          • 數據來源不清晰

          現在通過Compositon API這種方式改寫上面的代碼

          import { onMounted, onUnmounted, reactive } from "vue";
          export function useMove() {
            const position=reactive({
              x: 0,
              y: 0,
            });
          ?
            const handleKeyup=(e)=> {
              console.log(e.code);
              // 上下左右 x y
              switch (e.code) {
                case "ArrowUp":
                  // y.value--;
                  position.y--;
                  break;
                case "ArrowDown":
                  // y.value++;
                  position.y++;
                  break;
                case "ArrowLeft":
                  // x.value--;
                  position.x--;
                  break;
                case "ArrowRight":
                  // x.value++;
                  position.x++;
                  break;
              }
            };
          ?
            onMounted(()=> {
              window.addEventListener("keyup", handleKeyup);
            });
          ?
            onUnmounted(()=> {
              window.removeEventListener("keyup", handleKeyup);
            });
          ?
            return { position };
          }
          


          在組件中使用

          <template>
            <div>
              Mouse position: x {{ x }} / y {{ y }}
            </div>
          </template>
          ?
          <script>
          import { useMove } from "./useMove";
          import { toRefs } from "vue";
          export default {
            setup() {
              const { position }=useMove();
              const { x, y }=toRefs(position);
              return {
                x,
                y,
              };
          
            },
          };
          </script>
          


          可以看到,整個數據來源清晰了,即使去編寫更多的 hook 函數,也不會出現命名沖突的問題

          小結

          • 在邏輯組織和邏輯復用方面,Composition API是優于Options API
          • 因為Composition API幾乎是函數,會有更好的類型推斷。
          • Composition API對 tree-shaking 友好,代碼也更容易壓縮
          • Composition API中見不到this的使用,減少了this指向不明的情況
          • 如果是小型組件,可以繼續使用Options API,也是十分友好的

          7.什么是SPA單頁面應用,首屏加載你是如何優化的

          單頁Web應用(single page web application,SPA),就是只有一張Web頁面的應用,是加載單個HTML頁面并在用戶與應用程序交互時動態更新該頁面的Web應用程序。我們開發的`Vue`項目大多是借助個官方的`CLI`腳手架,快速搭建項目,直接通過`new Vue`構建一個實例,并將`el:'#app'`掛載參數傳入,最后通過`npm run build`的方式打包后生成一個`index.html`,稱這種只有一個`HTML`的頁面為單頁面應用。
          ?
          當然,`vue`也可以像`jq`一樣引入,作為多頁面應用的基礎框架。
          ?
          ?
          SPA首屏優化方式
          ?
          減小入口文件積
          靜態資源本地緩存
          UI框架按需加載
          圖片資源的壓縮
          組件重復打包
          開啟GZip壓縮
          使用SSR
          


          8.對Vue項目你做過哪些性能優化

          1、`v-if`和`v-show`
          
          *   頻繁切換時使用`v-show`,利用其緩存特性
          *   首屏渲染時使用`v-if`,如果為`false`則不進行渲染
          
          2、`v-for`的`key`
          
          *   列表變化時,循環時使用唯一不變的`key`,借助其本地復用策略
          *   列表只進行一次渲染時,`key`可以采用循環的`index`
          
          3、偵聽器和計算屬性
          
          *   偵聽器`watch`用于數據變化時引起其他行為
          *   多使用`compouter`計算屬性顧名思義就是新計算而來的屬性,如果依賴的數據未發生變化,不會觸發重新計算
          
          4、合理使用生命周期
          
          *   在`destroyed`階段進行綁定事件或者定時器的銷毀
          *   使用動態組件的時候通過`keep-alive`包裹進行緩存處理,相關的操作可以在`actived`階段激活
          
          5、數據響應式處理
          
          *   不需要響應式處理的數據可以通過`Object.freeze`處理,或者直接通過`this.xxx=xxx`的方式進行定義
          *   需要響應式處理的屬性可以通過`this.$set`的方式處理,而不是`JSON.parse(JSON.stringify(XXX))`的方式
          
          6、路由加載方式
          
          *   頁面組件可以采用異步加載的方式
          
          7、插件引入
          
          *   第三方插件可以采用按需加載的方式,比如`element-ui`。
          
          8、減少代碼量
          
          *   采用`mixin`的方式抽離公共方法
          *   抽離公共組件
          *   定義公共方法至公共`js`中
          *   抽離公共`css`
          
          9、編譯方式
          
          *   如果線上需要`template`的編譯,可以采用完成版`vue.esm.js`
          *   如果線上無需`template`的編譯,可采用運行時版本`vue.runtime.esm.js`,相比完整版體積要小大約`30%`
          
          10、渲染方式
          
          *   服務端渲染,如果是需要`SEO`的網站可以采用服務端渲染的方式
          *   前端渲染,一些企業內部使用的后端管理系統可以采用前端渲染的方式
          
          11、字體圖標的使用
          
          *   有些圖片圖標盡可能使用字體圖標
          


          9.Vue組件通信的方式有哪些

          vue中8種常規的通信方案
          ?
          通過 props 傳遞
          通過 $emit 觸發自定義事件
          使用 ref
          EventBus
          $parent 或$root
          attrs 與 listeners
          Provide 與 Inject
          Vuex
          ?
          組件間通信的分類可以分成以下
          ?
          父子關系的組件數據傳遞選擇 props  與 $emit進行傳遞,也可選擇ref
          兄弟關系的組件數據傳遞可選擇$bus,其次可以選擇$parent進行傳遞
          祖先與后代組件數據傳遞可選擇attrs與listeners或者 Provide與 Inject
          復雜關系的組件數據傳遞可以通過vuex存放共享的變量
          


          10.Vue常用的修飾符有哪些

           1、表單修飾符
          ?
          (1)`.lazy`
          ?
          在默認情況下,`v-model` 在每次 `input` 事件觸發后將輸入框的值與數據進行同步 ,可以添加 `lazy` 修飾符,從而轉為在 `change` 事件之后進行同步:
          ?
          ```
          <input v-model.lazy="msg">
          ?
          ```
          ?
          (2)`.number`
          ?
          如果想自動將用戶的輸入值轉為數值類型,可以給 `v-model` 添加 `number` 修飾符:
          ?
          ```
          <input v-model.number="age" type="number">
          ?
          ```
          ?
          (3)`.trim`
          ?
          如果要自動過濾用戶輸入的首尾空白字符,可以給 `v-model` 添加 `trim` 修飾符:
          ?
          ```
          <input v-model.trim="msg">
          ?
          ```
          ?
           2、事件修飾符
          ?
           (1)`.stop`
          ?
          阻止單擊事件繼續傳播。
          ?
          ```
          <!--這里只會觸發a-->
          <div @click="divClick"><a v-on:click.stop="aClick">點擊</a></div>
          ?
          ```
          ?
          (2)`.prevent`
          ?
          阻止標簽的默認行為。
          ?
          ```
          <a href="http://www.baidu.com" v-on:click.prevent="aClick">點擊</a>
          ?
          ```
          ?
          (3)`.capture`
          ?
          事件先在有`.capture`修飾符的節點上觸發,然后在其包裹的內部節點中觸發。
          ?
          ```
          <!--這里先執行divClick事件,然后再執行aClick事件-->
          <div @click="divClick"><a v-on:click="aClick">點擊</a></div>
          ?
          ```
          ?
          (4)`.self`
          ?
          只當在 event.target 是當前元素自身時觸發處理函數,即事件不是從內部元素觸發的。
          ?
          ```
          <!--在a標簽上點擊時只會觸發aClick事件,只有點擊phrase的時候才會觸發divClick事件-->
          <div @click.self="divClick">phrase<a v-on:click="aClick">點擊</a></div>
          ?
          ```
          ?
          (5)`.once`
          ?
          不像其它只能對原生的 DOM 事件起作用的修飾符,`.once` 修飾符還能被用到自定義的組件事件上,表示當前事件只觸發一次。
          ?
          ```
          <a v-on:click.once="aClick">點擊</a>
          ?
          ```
          (6)`.passive`
          ?
          `.passive` 修飾符尤其能夠提升移動端的性能
          ?
          ```
          <!-- 滾動事件的默認行為 (即滾動行為) 將會立即觸發 -->  
          <!-- 而不會等待 `onScroll` 完成 -->  
          <!-- 這其中包含 `event.preventDefault()` 的情況 -->  
          <div v-on:scroll.passive="onScroll">...</div>
          ```
          ?
          


          因為內容太多所以需要完整版PDF的小伙伴可關注+私信【學習】自行查看

          11.Vue中的$nextTick有什么作用

          const callbacks=[]
          let pending=false
          ?
          /**
           * 完成兩件事:
           *   1、用 try catch 包裝 flushSchedulerQueue 函數,然后將其放入 callbacks 數組
           *   2、如果 pending 為 false,表示現在瀏覽器的任務隊列中沒有 flushCallbacks 函數
           *     如果 pending 為 true,則表示瀏覽器的任務隊列中已經被放入了 flushCallbacks 函數,
           *     待執行 flushCallbacks 函數時,pending 會被再次置為 false,表示下一個 flushCallbacks 函數可以進入
           *     瀏覽器的任務隊列了
           * pending 的作用:保證在同一時刻,瀏覽器的任務隊列中只有一個 flushCallbacks 函數
           * @param {*} cb 接收一個回調函數=> flushSchedulerQueue
           * @param {*} ctx 上下文
           * @returns 
           */
          export function nextTick (cb?: Function, ctx?: Object) {
            let _resolve
            // 用 callbacks 數組存儲經過包裝的 cb 函數
            callbacks.push(()=> {
              if (cb) {
                // 用 try catch 包裝回調函數,便于錯誤捕獲
                try {
                  cb.call(ctx)
                } catch (e) {
                  handleError(e, ctx, 'nextTick')
                }
              } else if (_resolve) {
                _resolve(ctx)
              }
            })
            if (!pending) {
              pending=true
              // 執行 timerFunc,在瀏覽器的任務隊列中(首選微任務隊列)放入 flushCallbacks 函數
              timerFunc()
            }
            // $flow-disable-line
            if (!cb && typeof Promise !=='undefined') {
              return new Promise(resolve=> {
                _resolve=resolve
              })
            }
          }
          


          官方對其的定義
          
          在下次 DOM 更新循環結束之后執行延遲回調。在修改數據之后立即使用這個方法,獲取更新后的 DOM
          
          什么意思呢?
          
          我們可以理解成,Vue 在更新 DOM 時是異步執行的。當數據發生變化,Vue將開啟一個異步更新隊列,視圖需要等隊列中所有數據變化完成之后,再統一進行更新
          
          Vue 的異步更新機制的核心是利用了瀏覽器的異步任務隊列來實現的,首選微任務隊列,宏任務隊列次之。
          
          當響應式數據更新后,會調用 dep.notify 方法,通知 dep 中收集的 watcher 去執行 update 方法,watcher.update 將 watcher 自己放入一個 watcher 隊列(全局的 queue 數組)。
          
          然后通過 nextTick 方法將一個刷新 watcher 隊列的方法(flushSchedulerQueue)放入一個全局的 callbacks 數組中。
          
          如果此時瀏覽器的異步任務隊列中沒有一個叫 flushCallbacks 的函數,則執行 timerFunc 函數,將 flushCallbacks 函數放入異步任務隊列。如果異步任務隊列中已經存在 flushCallbacks 函數,等待其執行完成以后再放入下一個 flushCallbacks 函數。
          
          flushCallbacks 函數負責執行 callbacks 數組中的所有 flushSchedulerQueue 函數。
          
          flushSchedulerQueue 函數負責刷新 watcher 隊列,即執行 queue 數組中每一個 watcher 的 run 方法,從而進入更新階段,比如執行組件更新函數或者執行用戶 watch 的回調函數。
          


          12.如何理解雙向數據綁定

          我們都知道 Vue 是數據雙向綁定的框架,雙向綁定由三個重要部分構成
          ?
          數據層(Model):應用的數據及業務邏輯
          視圖層(View):應用的展示效果,各類UI組件
          業務邏輯層(ViewModel):框架封裝的核心,它負責將數據與視圖關聯起來
          而上面的這個分層的架構方案,可以用一個專業術語進行稱呼:MVVM這里的控制層的核心功能便是 “數據雙向綁定” 。自然,我們只需弄懂它是什么,便可以進一步了解數據綁定的原理
          ?
          理解ViewModel
          它的主要職責就是:
          ?
          數據變化后更新視圖
          視圖變化后更新數據
          當然,它還有兩個主要部分組成
          ?
          監聽器(Observer):對所有數據的屬性進行監聽
          解析器(Compiler):對每個元素節點的指令進行掃描跟解析,根據指令模板替換數據,以及綁定相應的更新函數
          


          13.v-show和v-if有什么區別?你可以講講嗎

          v-show 與 v-if 的作用效果是相同的(不含v-else),都能控制元素在頁面是否顯示,在用法上也是相同的
          ?
          - 區別 
          控制手段不同
          編譯過程不同
          編譯條件不同
          ?
          控制手段:v-show隱藏則是為該元素添加css--display:none,dom元素依舊還在。v-if顯示隱藏是將dom元素整個添加或刪除
          ?
          編譯過程:v-if切換有一個局部編譯/卸載的過程,切換過程中合適地銷毀和重建內部的事件監聽和子組件;v-show只是簡單的基于css切換
          ?
          編譯條件:v-if是真正的條件渲染,它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷毀和重建。只有渲染條件為假時,并不做操作,直到為真才渲染
          ?
          v-show 由false變為true的時候不會觸發組件的生命周期
          ?
          v-if由false變為true的時候,觸發組件的beforeCreate、create、beforeMount、mounted鉤子,由true變為false的時候觸發組件的beforeDestory、destoryed方法
          ?
          性能消耗:v-if有更高的切換消耗;v-show有更高的初始渲染消耗
          


          14.有用過keep-alive嗎?它有什么作用

          `vue`中支持組件化,并且也有用于緩存的內置組件`keep-alive`可直接使用,使用場景為`路由組件`和`動態組件`。
          ?
          *   `activated`表示進入組件的生命周期,`deactivated`表示離開組件的生命周期
          *   `include`表示匹配到的才緩存,`exclude`表示匹配到的都不緩存
          *   `max`表示最多可以緩存多少組件
          ?
          ?
          關于keep-alive的基本用法:
          ?
          <keep-alive>
            <component :is="view"></component>
          </keep-alive>
          使用includes和exclude:
          ?
          <keep-alive include="a,b">
            <component :is="view"></component>
          </keep-alive>
          ?
          <!-- 正則表達式 (使用 `v-bind`) -->
          <keep-alive :include="/a|b/">
            <component :is="view"></component>
          </keep-alive>
          ?
          <!-- 數組 (使用 `v-bind`) -->
          <keep-alive :include="['a', 'b']">
            <component :is="view"></component>
          </keep-alive>
          匹配首先檢查組件自身的 name 選項,如果 name 選項不可用,則匹配它的局部注冊名稱 (父組件 components 選項的鍵值),匿名組件不能被匹配
          ?
          設置了 keep-alive 緩存的組件,會多出兩個生命周期鉤子(activated與deactivated):
          ?
          首次進入組件時:beforeRouteEnter > beforeCreate > created> mounted > activated > ... ... > beforeRouteLeave > deactivated
          ?
          再次進入組件時:beforeRouteEnter >activated > ... ... > beforeRouteLeave > deactivated
          


          15.你可以實現一個虛擬DOM嗎

          先看瀏覽器對HTML的理解

          <div>  
              <h1>My title</h1>  
              Some text content  
              <!-- TODO: Add tagline -->  
          </div>
          


          當瀏覽器讀到這些代碼時,它會建立一個DOM樹來保持追蹤所有內容,如同你會畫一張家譜樹來追蹤家庭成員的發展一樣。 上述 HTML 對應的 DOM 節點樹如下圖所示:

          每個元素都是一個節點。每段文字也是一個節點。甚至注釋也都是節點。一個節點就是頁面的一個部分。就像家譜樹一樣,每個節點都可以有孩子節點 (也就是說每個部分可以包含其它的一些部分)。
          
          **再看`Vue`對`HTML template`的理解**
          
          Vue 通過建立一個**虛擬 DOM** 來追蹤自己要如何改變真實 DOM。因為它所包含的信息會告訴 Vue 頁面上需要渲染什么樣的節點,包括及其子節點的描述信息。我們把這樣的節點描述為“虛擬節點 (virtual node)”,也常簡寫它為“**VNode**”。“虛擬 DOM”是我們對由 Vue 組件樹建立起來的整個 VNode 樹的稱呼。
          
          簡言之,瀏覽器對HTML的理解是DOM樹,Vue對`HTML`的理解是虛擬DOM,最后在`patch`階段通過DOM操作的api將其渲染成真實的DOM節點。
          


          如何實現虛擬DOM

          首先可以看看vue中VNode的結構

          源碼位置:src/core/vdom/vnode.js

          export default class VNode {
            tag: string | void;
            data: VNodeData | void;
            children: ?Array<VNode>;
            text: string | void;
            elm: Node | void;
            ns: string | void;
            context: Component | void; // rendered in this component's scope
            functionalContext: Component | void; // only for functional component root nodes
            key: string | number | void;
            componentOptions: VNodeComponentOptions | void;
            componentInstance: Component | void; // component instance
            parent: VNode | void; // component placeholder node
            raw: boolean; // contains raw HTML? (server only)
            isStatic: boolean; // hoisted static node
            isRootInsert: boolean; // necessary for enter transition check
            isComment: boolean; // empty comment placeholder?
            isCloned: boolean; // is a cloned node?
            isOnce: boolean; // is a v-once node?
          ?
            constructor (
              tag?: string,
              data?: VNodeData,
              children?: ?Array<VNode>,
              text?: string,
              elm?: Node,
              context?: Component,
              componentOptions?: VNodeComponentOptions
            ) {
              /*當前節點的標簽名*/
              this.tag=tag
              /*當前節點對應的對象,包含了具體的一些數據信息,是一個VNodeData類型,可以參考VNodeData類型中的數據信息*/
              this.data=data
              /*當前節點的子節點,是一個數組*/
              this.children=children
              /*當前節點的文本*/
              this.text=text
              /*當前虛擬節點對應的真實dom節點*/
              this.elm=elm
              /*當前節點的名字空間*/
              this.ns=undefined
              /*編譯作用域*/
              this.context=context
              /*函數化組件作用域*/
              this.functionalContext=undefined
              /*節點的key屬性,被當作節點的標志,用以優化*/
              this.key=data && data.key
              /*組件的option選項*/
              this.componentOptions=componentOptions
              /*當前節點對應的組件的實例*/
              this.componentInstance=undefined
              /*當前節點的父節點*/
              this.parent=undefined
              /*簡而言之就是是否為原生HTML或只是普通文本,innerHTML的時候為true,textContent的時候為false*/
              this.raw=false
              /*靜態節點標志*/
              this.isStatic=false
              /*是否作為跟節點插入*/
              this.isRootInsert=true
              /*是否為注釋節點*/
              this.isComment=false
              /*是否為克隆節點*/
              this.isCloned=false
              /*是否有v-once指令*/
              this.isOnce=false
            }
          ?
            // DEPRECATED: alias for componentInstance for backwards compat.
            /* istanbul ignore next https://github.com/answershuto/learnVue*/
            get child (): Component | void {
              return this.componentInstance
            }
          }
          


          這里對VNode進行稍微的說明:

          • 所有對象的 context 選項都指向了 Vue 實例
          • elm 屬性則指向了其相對應的真實 DOM 節點
          vue`是通過`createElement`生成`VNode
          


          源碼位置:
          src/core/vdom/create-element.js

          export function createElement (
            context: Component,
            tag: any,
            data: any,
            children: any,
            normalizationType: any,
            alwaysNormalize: boolean
          ): VNode | Array<VNode> {
            if (Array.isArray(data) || isPrimitive(data)) {
              normalizationType=children
              children=data
              data=undefined
            }
            if (isTrue(alwaysNormalize)) {
              normalizationType=ALWAYS_NORMALIZE
            }
            return _createElement(context, tag, data, children, normalizationType)
          }
          


          上面可以看到createElement 方法實際上是對 _createElement 方法的封裝,對參數的傳入進行了判斷

          export function _createElement(
              context: Component,
              tag?: string | Class<Component> | Function | Object,
              data?: VNodeData,
              children?: any,
              normalizationType?: number
          ): VNode | Array<VNode> {
              if (isDef(data) && isDef((data: any).__ob__)) {
                  process.env.NODE_ENV !=='production' && warn(
                      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
                      'Always create fresh vnode data objects in each render!',
                      context`
                  )
                  return createEmptyVNode()
              }
              // object syntax in v-bind
              if (isDef(data) && isDef(data.is)) {
                  tag=data.is
              }
              if (!tag) {
                  // in case of component :is set to falsy value
                  return createEmptyVNode()
              }
              ... 
              // support single function children as default scoped slot
              if (Array.isArray(children) &&
                  typeof children[0]==='function'
              ) {
                  data=data || {}
                  data.scopedSlots={ default: children[0] }
                  children.length=0
              }
              if (normalizationType===ALWAYS_NORMALIZE) {
                  children=normalizeChildren(children)
              } else if (===SIMPLE_NORMALIZE) {
                  children=simpleNormalizeChildren(children)
              }
            // 創建VNode
              ...
          }
          


          可以看到_createElement接收5個參數:

          • context 表示 VNode 的上下文環境,是 Component 類型
          • tag 表示標簽,它可以是一個字符串,也可以是一個 Component
          • data 表示 VNode 的數據,它是一個 VNodeData 類型
          • children 表示當前 VNode的子節點,它是任意類型的
          • normalizationType 表示子節點規范的類型,類型不同規范的方法也就不一樣,主要是參考 render 函數是編譯生成的還是用戶手寫的

          根據normalizationType 的類型,children會有不同的定義

          if (normalizationType===ALWAYS_NORMALIZE) {
              children=normalizeChildren(children)
          } else if (===SIMPLE_NORMALIZE) {
              children=simpleNormalizeChildren(children)
          }
          


          simpleNormalizeChildren方法調用場景是 render 函數是編譯生成的

          normalizeChildren方法調用場景分為下面兩種:

          • render 函數是用戶手寫的
          • 編譯 slot、v-for 的時候會產生嵌套數組

          無論是simpleNormalizeChildren還是normalizeChildren都是對children進行規范(使children 變成了一個類型為 VNode 的 Array),這里就不展開說了

          規范化children的源碼位置在:
          src/core/vdom/helpers/normalzie-children.js

          在規范化children后,就去創建VNode

          let vnode, ns
          // 對tag進行判斷
          if (typeof tag==='string') {
            let Ctor
            ns=(context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
            if (config.isReservedTag(tag)) {
              // 如果是內置的節點,則直接創建一個普通VNode
              vnode=new VNode(
                config.parsePlatformTagName(tag), data, children,
                undefined, undefined, context
              )
            } else if (isDef(Ctor=resolveAsset(context.$options, 'components', tag))) {
              // component
              // 如果是component類型,則會通過createComponent創建VNode節點
              vnode=createComponent(Ctor, data, context, children, tag)
            } else {
              vnode=new VNode(
                tag, data, children,
                undefined, undefined, context
              )
            }
          } else {
            // direct component options / constructor
            vnode=createComponent(tag, data, context, children)
          }
          


          createComponent`同樣是創建`VNode
          


          源碼位置:
          src/core/vdom/create-component.js

          export function createComponent (
            Ctor: Class<Component> | Function | Object | void,
            data: ?VNodeData,
            context: Component,
            children: ?Array<VNode>,
            tag?: string
          ): VNode | Array<VNode> | void {
            if (isUndef(Ctor)) {
              return
            }
           // 構建子類構造函數 
            const baseCtor=context.$options._base
          ?
            // plain options object: turn it into a constructor
            if (isObject(Ctor)) {
              Ctor=baseCtor.extend(Ctor)
            }
          ?
            // if at this stage it's not a constructor or an async component factory,
            // reject.
            if (typeof Ctor !=='function') {
              if (process.env.NODE_ENV !=='production') {
                warn(`Invalid Component definition: ${String(Ctor)}`, context)
              }
              return
            }
          ?
            // async component
            let asyncFactory
            if (isUndef(Ctor.cid)) {
              asyncFactory=Ctor
              Ctor=resolveAsyncComponent(asyncFactory, baseCtor, context)
              if (Ctor===undefined) {
                return createAsyncPlaceholder(
                  asyncFactory,
                  data,
                  context,
                  children,
                  tag
                )
              }
            }
          ?
            data=data || {}
          ?
            // resolve constructor options in case global mixins are applied after
            // component constructor creation
            resolveConstructorOptions(Ctor)
          ?
            // transform component v-model data into props & events
            if (isDef(data.model)) {
              transformModel(Ctor.options, data)
            }
          ?
            // extract props
            const propsData=extractPropsFromVNodeData(data, Ctor, tag)
          ?
            // functional component
            if (isTrue(Ctor.options.functional)) {
              return createFunctionalComponent(Ctor, propsData, data, context, children)
            }
          ?
            // extract listeners, since these needs to be treated as
            // child component listeners instead of DOM listeners
            const listeners=data.on
            // replace with listeners with .native modifier
            // so it gets processed during parent component patch.
            data.on=data.nativeOn
          ?
            if (isTrue(Ctor.options.abstract)) {
              const slot=data.slot
              data={}
              if (slot) {
                data.slot=slot
              }
            }
          ?
            // 安裝組件鉤子函數,把鉤子函數合并到data.hook中
            installComponentHooks(data)
          ?
            //實例化一個VNode返回。組件的VNode是沒有children的
            const name=Ctor.options.name || tag
            const vnode=new VNode(
              `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
              data, undefined, undefined, undefined, context,
              { Ctor, propsData, listeners, tag, children },
              asyncFactory
            )
            if (__WEEX__ && isRecyclableComponent(vnode)) {
              return renderRecyclableComponentTemplate(vnode)
            }
          ?
            return vnode
          }
          


          稍微提下createComponent生成VNode的三個關鍵流程:

          • 構造子類構造函數Ctor
          • installComponentHooks安裝組件鉤子函數
          • 實例化 vnode

          小結

          createElement 創建 VNode 的過程,每個 VNode 有 children,children 每個元素也是一個VNode,這樣就形成了一個虛擬樹結構,用于描述真實的DOM樹結構

          16.為什么data屬性是一個函數而不是一個對象,具體原因是什么

          是不是一定是函數,得看場景。并且,也無需擔心什么時候該將`data`寫為函數還是對象,因為`vue`內部已經做了處理,并在控制臺輸出錯誤信息。
          ?
          **場景一**:`new Vue({data: ...})`  
          這種場景主要為項目入口或者多個`html`頁面各實例化一個`Vue`時,這里的`data`即可用對象的形式,也可用工廠函數返回對象的形式。因為,這里的`data`只會出現一次,不存在重復引用而引起的數據污染問題。
          ?
          **場景二**:組件場景中的選項  
          在生成組件`vnode`的過程中,組件會在生成構造函數的過程中執行合并策略:
          ?
          ```
          // data合并策略
          strats.data=function (
            parentVal,
            childVal,
            vm
          ) {
            if (!vm) {
              if (childVal && typeof childVal !=='function') {
                process.env.NODE_ENV !=='production' && warn(
                  'The "data" option should be a function ' +
                  'that returns a per-instance value in component ' +
                  'definitions.',
                  vm
                );
          ?
                return parentVal
              }
              return mergeDataOrFn(parentVal, childVal)
            }
          ?
            return mergeDataOrFn(parentVal, childVal, vm)
          };
          ```
          ?
          如果合并過程中發現子組件的數據不是函數,即`typeof childVal !=='function'`成立,進而在開發環境會在控制臺輸出警告并且直接返回`parentVal`,說明這里壓根就沒有把`childVal`中的任何`data`信息合并到`options`中去。
          ?
          ?
          上面講到組件data必須是一個函數,不知道大家有沒有思考過這是為什么呢?
          ?
          在我們定義好一個組件的時候,vue最終都會通過Vue.extend()構成組件實例
          ?
          這里我們模仿組件構造函數,定義data屬性,采用對象的形式
          ?
          function Component(){
           
          }
          Component.prototype.data={
            count : 0
          }
          創建兩個組件實例
          ?
          const componentA=new Component()
          const componentB=new Component()
          修改componentA組件data屬性的值,componentB中的值也發生了改變
          ?
          console.log(componentB.data.count)  // 0
          componentA.data.count=1
          console.log(componentB.data.count)  // 1
          產生這樣的原因這是兩者共用了同一個內存地址,componentA修改的內容,同樣對componentB產生了影響
          ?
          如果我們采用函數的形式,則不會出現這種情況(函數返回的對象內存地址并不相同)
          ?
          function Component(){
            this.data=this.data()
          }
          Component.prototype.data=function (){
              return {
                count : 0
              }
          }
          修改componentA組件data屬性的值,componentB中的值不受影響
          ?
          console.log(componentB.data.count)  // 0
          componentA.data.count=1
          console.log(componentB.data.count)  // 0
          vue組件可能會有很多個實例,采用函數返回一個全新data形式,使每個實例對象的數據不會受到其他實例對象數據的污染
          


          17.Vue2的初始化過程你有過了解嗎,做了哪些事情

          new Vue走到了vue的構造函數中:`src\core\instance\index.js`文件。
          ?
          this._init(options)
          ?
          然后從Mixin增加的原型方法看,initMixin(Vue),調用的是為Vue增加的原型方法_init
          ?
          // src/core/instance/init.js
          ?
          function initMixin (Vue) {
            Vue.prototype._init=function (options) {
               var vm=this; 創建vm, 
               ...
               // 合并options 到 vm.$options
               vm.$options=mergeOptions(  
                 resolveConstructorOptions(vm.constructor), 
                 options || {},  
                 vm 
               );
            }
            ...
             initLifecycle(vm); //初始生命周期
             initEvents(vm); //初始化事件
             initRender(vm); //初始render函數
             callHook(vm, 'beforeCreate'); //執行 beforeCreate生命周期鉤子
             ...
             initState(vm);  //初始化data,props,methods computed,watch 
             ...
             callHook(vm, 'created');  //執行 created 生命周期鉤子
             
             if (vm.$options.el) {
                vm.$mount(vm.$options.el); //這里也是重點,下面需要用到
             }
           }
          ?
          總結
          ?
          所以,從上面的函數看來,new vue所做的事情,就像一個流程圖一樣展開了,分別是
          ?
          -   合并配置
          -   初始化生命周期
          -   初始化事件
          -   初始化渲染
          -   調用 `beforeCreate` 鉤子函數
          -   init injections and reactivity(這個階段屬性都已注入綁定,而且被 `$watch` 變成reactivity,但是 `$el` 還是沒有生成,也就是DOM沒有生成)
          -   初始化state狀態(初始化了data、props、computed、watcher)
          -   調用created鉤子函數。
          ?
          在初始化的最后,檢測到如果有 el 屬性,則調用 vm.$mount 方法掛載 vm,掛載的目標就是把模板渲染成最終的 DOM。
          


          18.Vue3初始化的一個大概流程

          - 初始化的一個大概流程
          ?
          createApp()=> mount()=> render()=> patch()=> processComponent()=> mountComponent()
          ?
          - 簡易版流程編寫
          ?
          1.Vue.createApp() 實際執行的是renderer的createApp()
          ?
          2.renderer是createRenderer這個方法創建
          ?
          3.renderer的createApp()是createAppAPI()返回的
          ?
          4.createAppApi接受到render之后,創建一個app實例,定義mount方法
          ?
          5.mount會調用render函數。將vnode轉換為真實dom
          ?
          createRenderer()=> renderer=> renderer.createApp() <=createAppApi()
          ?
          ?
          <div id="app"></div>
          ?
          <script>
              // 3.createAppAPI
              const createAppAPI=render=> {
                  return function createApp(rootComponent) {
                      // 返回應用程序實例
                      const app={
                          mount(rootContainer) {
                              // 掛載vnode=> dom
                              const vnode={
                                  tag: rootComponent
                              }
                              // 執行渲染
                              render(vnode, rootContainer)
                          }
                      }
                      return app;
                  }
              }
          ?
              // 1. 創建createApp
              const Vue={
                  createApp(options) {
                      //實際執行的為renderer的createApp()
                      // 返回app實例
                      return renderer.createApp(options)
                  }
              }
          ?
              // 2.實現renderer工廠函數
              const createRenderer=options=> {
                  // 實現patch
                  const patch=(n1, n2, container)=> {
                      // 獲取根組件配置
                      const rootComponent=n2.tag;
                      const ctx={ ...rootComponent.data()}
                      // 執行render獲取vnode
                      const vnode=rootComponent.render.call(ctx);
          ?
                      // 轉換vnode=> dom
                      const parent=options.querySelector(container)
                      const child=options.createElement(vnode.tag)
                      if (typeof vnode.children==='string') {
                          child.textContent=vnode.children
                      } else {
                          //array
                      }
                      // 追加
                      options.insert(child, parent)
                  }
          ?
                  // 實現render
                  const render=(vnode, container)=> {
                      patch(container._vnode || null, vnode, container)
                      container._vnode=vnode;
                  }
          ?
                  // 該對象就是renderer
                  return {
                      render,
                      createApp: createAppAPI(render)
                  }
              }
          ?
              const renderer=createRenderer({
                  querySelector(el) {
                      return document.querySelector(el)
                  },
                  createElement(tag) {
                      return document.createElement(tag)
                  },
                  insert(child, parent) {
                      parent.appendChild(child)
                  }
              })
          ?
              Vue.createApp({
                  data() {
                      return {
                          bar: 'hello,vue3'
                      }
                  },
                  render() {
                      return {
                          tag: 'h1',
                          children: this.bar
                      }
                  }
              }).mount('#app')
          </script>
          


          19.vue3響應式api如何編寫

          var activeEffect=null;
          function effect(fn) {
            activeEffect=fn;
            activeEffect();
            activeEffect=null; 
          }
          var depsMap=new WeakMap();
          function gather(target, key) {
            // 避免例如console.log(obj1.name)而觸發gather
            if (!activeEffect) return;
            let depMap=depsMap.get(target);
            if (!depMap) {
              depsMap.set(target, (depMap=new Map()));
            }
            let dep=depMap.get(key);
            if (!dep) {
              depMap.set(key, (dep=new Set()));
            }
            dep.add(activeEffect)
          }
          function trigger(target, key) {
            let depMap=depsMap.get(target);
            if (depMap) {
              const dep=depMap.get(key);
              if (dep) {
                dep.forEach((effect)=> effect());
              }
            }
          }
          function reactive(target) {
            const handle={
              set(target, key, value, receiver) {
                Reflect.set(target, key, value, receiver);
                trigger(receiver, key); // 設置值時觸發自動更新
              },
              get(target, key, receiver) {
                gather(receiver, key); // 訪問時收集依賴
                return Reflect.get(target, key, receiver);
              },
            };
            return new Proxy(target, handle);
          }
          ?
          function ref(name){
              return reactive(
                  {
                      value: name
                  }
              )
          }
          


          20.在Vue項目中你是如何做的SSR渲染

          與傳統 SPA (單頁應用程序 (Single-Page Application)) 相比,服務器端渲染 (SSR) 的優勢主要在于:
          ?
          *   更好的 SEO,由于搜索引擎爬蟲抓取工具可以直接查看完全渲染的頁面。
          *   更快的內容到達時間 (time-to-content),特別是對于緩慢的網絡情況或運行緩慢的設備。
          ?
          Vue.js 是構建客戶端應用程序的框架。默認情況下,可以在瀏覽器中輸出 Vue 組件,進行生成 DOM 和操作 DOM。然而,也可以將同一個組件渲染為服務器端的 HTML 字符串,將它們直接發送到瀏覽器,最后將這些靜態標記"激活"為客戶端上完全可交互的應用程序
          ?
          服務器渲染的 Vue.js 應用程序也可以被認為是"同構"或"通用",因為應用程序的大部分代碼都可以在服務器和客戶端上運行
          ?
          * Vue SSR是一個在SPA上進行改良的服務端渲染
          * 通過Vue SSR渲染的頁面,需要在客戶端激活才能實現交互
          * Vue SSR將包含兩部分:服務端渲染的首屏,包含交互的SPA
          ?
          使用ssr不存在單例模式,每次用戶請求都會創建一個新的vue實例
          實現ssr需要實現服務端首屏渲染和客戶端激活
          服務端異步獲取數據asyncData可以分為首屏異步獲取和切換組件獲取
          首屏異步獲取數據,在服務端預渲染的時候就應該已經完成
          切換組件通過mixin混入,在beforeMount鉤子完成數據獲取
          


          21.怎么看Vue的diff算法

          diff 算法是一種通過同層的樹節點進行比較的高效算法
          ?
          diff整體策略為:深度優先,同層比較
          比較只會在同層級進行, 不會跨層級比較
          比較的過程中,循環從兩邊向中間收攏
          ?
          - 當數據發生改變時,訂閱者watcher就會調用patch給真實的DOM打補丁
          - 通過isSameVnode進行判斷,相同則調用patchVnode方法
          - patchVnode做了以下操作:
            - 找到對應的真實dom,稱為el
            - 如果都有都有文本節點且不相等,將el文本節點設置為Vnode的文本節點
            - 如果oldVnode有子節點而VNode沒有,則刪除el子節點
            - 如果oldVnode沒有子節點而VNode有,則將VNode的子節點真實化后添加到el
            - 如果兩者都有子節點,則執行updateChildren函數比較子節點
          - updateChildren主要做了以下操作:
            - 設置新舊VNode的頭尾指針
            - 新舊頭尾指針進行比較,循環向中間靠攏,根據情況調用patchVnode進行patch重復流程、調用createElem創建一個新節點,從哈希表尋找 key一致的VNode 節點再分情況操作
          


          22.從0到1構建一個Vue項目你需要做哪些內容

          *   架子:選用合適的初始化腳手架(`vue-cli2.0`或者`vue-cli3.0`)
          *   請求:數據`axios`請求的配置
          *   登錄:登錄注冊系統
          *   路由:路由管理頁面
          *   數據:`vuex`全局數據管理
          *   權限:權限管理系統
          *   埋點:埋點系統
          *   插件:第三方插件的選取以及引入方式
          *   錯誤:錯誤頁面
          *   入口:前端資源直接當靜態資源,或者服務端模板拉取
          *   `SEO`:如果考慮`SEO`建議采用`SSR`方案
          *   組件:基礎組件/業務組件
          *   樣式:樣式預處理起,公共樣式抽取
          *   方法:公共方法抽離
          


          23. 介紹一下 js 的數據類型有哪些,值是如何存儲的

          JavaScript 一共有 8 種數據類型,其中有 7 種基本數據類型:Undefined、Null、Boolean、Number、String、Symbol(es6 新增,表示獨一無二的值)和 BigInt(es10 新增);
          ?
          1 種引用數據類型——Object(Object 本質上是由一組無序的名值對組成的)。里面包含 function、Array、Date 等。JavaScript 不支持任何創建自定義類型的機制,而所有值最終都將是上述 8 種數據類型之一。
          
          原始數據類型:直接存儲在**棧**(stack)中,占據空間小、大小固定,屬于被頻繁使用數據,所以放入棧中存儲。
          
          引用數據類型:同時存儲在**棧**(stack)和**堆**(heap)中,占據空間大、大小不固定。引用數據類型在棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址后從堆中獲得實體。
          


          24. JS 中Object.prototype.toString.call()判斷數據類型

          var a=Object.prototype.toString;
          console.log(a.call(2));
          console.log(a.call(true));
          console.log(a.call('str'));
          console.log(a.call([]));
          console.log(a.call(function(){}));
          console.log(a.call({}));
          console.log(a.call(undefined));
          console.log(a.call(null));https://link.juejin.cn?target=https%3A%2F%2Fsegmentfault.com%2Fa%2F1190000011467723%23articleHeader24 "https://segmentfault.com/a/1190000011467723#articleHeader24")
          


          25. null 和 undefined 的區別?

          首先 Undefined 和 Null 都是基本數據類型,這兩個基本數據類型分別都只有一個值,就是 undefined 和 null。
          ?
          undefined 代表的含義是未定義, null 代表的含義是空對象(其實不是真的對象,請看下面的**注意**!)。一般變量聲明了但還沒有定義的時候會返回 undefined,null 主要用于賦值給一些可能會返回對象的變量,作為初始化。
          ?
          其實 null 不是對象,雖然 typeof null 會輸出 object,但是這只是 JS 存在的一個悠久 Bug。在 JS 的最初版本中使用的是 32 位系統,為了性能考慮使用低位存儲變量的類型信息,000 開頭代表是對象,然而 null 表示為全零,所以將它錯誤的判斷為 object 。雖然現在的內部類型判斷代碼已經改變了,但是對于這個 Bug 卻是一直流傳下來。
          ?
          undefined 在 js 中不是一個保留字,這意味著我們可以使用 undefined 來作為一個變量名,這樣的做法是非常危險的,它 會影響我們對 undefined 值的判斷。但是我們可以通過一些方法獲得安全的 undefined 值,比如說 void 0。
          ?
          當我們對兩種類型使用 typeof 進行判斷的時候,Null 類型化會返回 “object”,這是一個歷史遺留的問題。當我們使用雙等 號對兩種類型的值進行比較時會返回 true,使用三個等號時會返回 false。
          


          26. {}和 [] 的 valueOf 和 toString 的結果是什么?

          {} 的 valueOf 結果為 {} ,toString 的結果為 "[object Object]"
          
          [] 的 valueOf 結果為 [] ,toString 的結果為 ""
          


          27. Javascript 的作用域和作用域鏈

          **作用域:** 作用域是定義變量的區域,它有一套訪問變量的規則,這套規則來管理瀏覽器引擎如何在當前作用域以及嵌套的作用域中根據變量(標識符)進行變量查找。
          ?
          **作用域鏈:** 作用域鏈的作用是保證對執行環境有權訪問的所有變量和函數的有序訪問,通過作用域鏈,我們可以訪問到外層環境的變量和 函數。
          ?
          作用域鏈的本質上是一個指向變量對象的指針列表。變量對象是一個包含了執行環境中所有變量和函數的對象。作用域鏈的前 端始終都是當前執行上下文的變量對象。全局執行上下文的變量對象(也就是全局對象)始終是作用域鏈的最后一個對象。
          ?
          當我們查找一個變量時,如果當前執行環境中沒有找到,我們可以沿著作用域鏈向后查找。
          ?
          作用域鏈的創建過程跟執行上下文的建立有關
          


          28. 談談你對 this、call、apply 和 bind 的理解

          1.  在瀏覽器里,在全局范圍內 this 指向 window 對象;
          2.  在函數中,this 永遠指向最后調用他的那個對象;
          3.  構造函數中,this 指向 new 出來的那個新的對象;
          4.  call、apply、bind 中的 this 被強綁定在指定的那個對象上;
          5.  箭頭函數中 this 比較特殊, 箭頭函數 this 為父作用域的 this,不是調用時的 this. 要知道前四種方式, 都是調用時確定, 也就是動態的, 而箭頭函數的 this 指向是靜態的, 聲明的時候就確定了下來;
          6.  apply、call、bind 都是 js 給函數內置的一些 API,調用他們可以為函數指定 this 的執行, 同時也可以傳參。
          


          29. JavaScript 原型,原型鏈? 有什么特點?

          在 js 中我們是使用構造函數來新建一個對象的,每一個構造函數的內部都有一個 prototype 屬性值,這個屬性值是一個對 象,這個對象包含了可以由該構造函數的所有實例共享的屬性和方法。當我們使用構造函數新建一個對象后,在這個對象的內部 將包含一個指針,這個指針指向構造函數的 prototype 屬性對應的值,在 ES5 中這個指針被稱為對象的原型。一般來說我們 是不應該能夠獲取到這個值的,但是現在瀏覽器中都實現了 **proto** 屬性來讓我們訪問這個屬性,但是我們最好不要使用這 個屬性,因為它不是規范中規定的。ES5 中新增了一個 Object.getPrototypeOf() 方法,我們可以通過這個方法來獲取對 象的原型。
          
          當我們訪問一個對象的屬性時,如果這個對象內部不存在這個屬性,那么它就會去它的原型對象里找這個屬性,這個原型對象又 會有自己的原型,于是就這樣一直找下去,也就是原型鏈的概念。原型鏈的盡頭一般來說都是 Object.prototype 所以這就 是我們新建的對象為什么能夠使用 toString() 等方法的原因。
          
          特點:
          
          JavaScript 對象是通過引用來傳遞的,我們創建的每個新對象實體中并沒有一份屬于自己的原型副本。當我們修改原型時,與 之相關的對象也會繼承這一改變。
          


          參考文章: 《JavaScript 深入理解之原型與原型鏈》

          30. 什么是閉包,為什么要用它?

          - 能夠訪問其它函數內部變量的函數,稱為閉包
          - 能夠訪問自由變量的函數,稱為閉包
          ?
          場景
          至于閉包的使用場景,其實在日常開發中使用到是非常頻繁的
          ?
          - 防抖節流函數
          - 定時器回調
          - 等就不一一列舉了
          ?
          優點
          閉包幫我們解決了什么問題呢
          **內部變量是私有的,可以做到隔離作用域,保持數據的不被污染性**
          ?
          缺點
          同時閉包也帶來了不小的壞處
          **說到了它的優點`內部變量是私有的,可以做到隔離作用域`,那也就是說垃圾回收機制是無法清理閉包中內部變量的,那最后結果就是內存泄漏**
          


          31. 三種事件模型是什么?

          **事件** 是用戶操作網頁時發生的交互動作或者網頁本身的一些操作,現代瀏覽器一共有三種事件模型。
          
          1.  **DOM0 級模型:** ,這種模型不會傳播,所以沒有事件流的概念,但是現在有的瀏覽器支持以冒泡的方式實現,它可以在網頁中直接定義監聽函數,也可以通過 js 屬性來指定監聽函數。這種方式是所有瀏覽器都兼容的。
          2.  **IE 事件模型:** 在該事件模型中,一次事件共有兩個過程,事件處理階段,和事件冒泡階段。事件處理階段會首先執行目標元素綁定的監聽事件。然后是事件冒泡階段,冒泡指的是事件從目標元素冒泡到 document,依次檢查經過的節點是否綁定了事件監聽函數,如果有則執行。這種模型通過 attachEvent 來添加監聽函數,可以添加多個監聽函數,會按順序依次執行。
          3.  **DOM2 級事件模型:** 在該事件模型中,一次事件共有三個過程,第一個過程是事件捕獲階段。捕獲指的是事件從 document 一直向下傳播到目標元素,依次檢查經過的節點是否綁定了事件監聽函數,如果有則執行。后面兩個階段和 IE 事件模型的兩個階段相同。這種事件模型,事件綁定的函數是 addEventListener,其中第三個參數可以指定事件是否在捕獲階段執行。
          


          32. js 數組和字符串有哪些原生方法, 列舉一下

          33. js 延遲加載的方式有哪些

          js 的加載、解析和執行會阻塞頁面的渲染過程,因此我們希望 js 腳本能夠盡可能的延遲加載,提高頁面的渲染速度。
          ?
          1.  將 js 腳本放在文檔的底部,來使 js 腳本盡可能的在最后來加載執行。
          2.  給 js 腳本添加 defer 屬性,這個屬性會讓腳本的加載與文檔的解析同步解析,然后在文檔解析完成后再執行這個腳本文件,這樣的話就能使頁面的渲染不被阻塞。多個設置了 defer 屬性的腳本按規范來說最后是順序執行的,但是在一些瀏覽器中可能不是這樣。
          3.  給 js 腳本添加 async 屬性,這個屬性會使腳本異步加載,不會阻塞頁面的解析過程,但是當腳本加載完成后立即執行 js 腳本,這個時候如果文檔沒有解析完成的話同樣會阻塞。多個 async 屬性的腳本的執行順序是不可預測的,一般不會按照代碼的順序依次執行。
          4.  動態創建 DOM 標簽的方式,我們可以對文檔的加載事件進行監聽,當文檔加載完成后再動態的創建 script 標簽來引入 js 腳本。
          


          34. js 的幾種模塊規范?

          js 中現在比較成熟的有四種模塊加載方案:
          ?
          *   第一種是 CommonJS 方案,它通過 require 來引入模塊,通過 module.exports 定義模塊的輸出接口。這種模塊加載方案是服務器端的解決方案,它是以同步的方式來引入模塊的,因為在服務端文件都存儲在本地磁盤,所以讀取非常快,所以以同步的方式加載沒有問題。但如果是在瀏覽器端,由于模塊的加載是使用網絡請求,因此使用異步加載的方式更加合適。
          *   第二種是 AMD 方案,這種方案采用異步加載的方式來加載模塊,模塊的加載不影響后面語句的執行,所有依賴這個模塊的語句都定義在一個回調函數里,等到加載完成后再執行回調函數。require.js 實現了 AMD 規范。
          *   第三種是 CMD 方案,這種方案和 AMD 方案都是為了解決異步模塊加載的問題,sea.js 實現了 CMD 規范。它和 require.js 的區別在于模塊定義時對依賴的處理不同和對依賴模塊的執行時機的處理不同。
          *   第四種方案是 ES6 提出的方案,使用 import 和 export 的形式來導入導出模塊。
          


          35. AMD 和 CMD 規范的區別?

          它們之間的主要區別有兩個方面。
          ?
          1.  第一個方面是在模塊定義時對依賴的處理不同。AMD 推崇依賴前置,在定義模塊的時候就要聲明其依賴的模塊。而 CMD 推崇就近依賴,只有在用到某個模塊的時候再去 require。
          2.  第二個方面是對依賴模塊的執行時機處理不同。首先 AMD 和 CMD 對于模塊的加載方式都是異步加載,不過它們的區別在于 模塊的執行時機,AMD 在依賴模塊加載完成后就直接執行依賴模塊,依賴模塊的執行順序和我們書寫的順序不一定一致。而 CMD 在依賴模塊加載完成后并不執行,只是下載而已,等到所有的依賴模塊都加載好后,進入回調函數邏輯,遇到 require 語句 的時候才執行對應的模塊,這樣模塊的執行順序就和我們書寫的順序保持一致了。
          ?
          // CMD
          define(function(require, exports, module) {
            var a=require("./a");
            a.doSomething();
            // 此處略去 100 行
            var b=require("./b"); // 依賴可以就近書寫
            b.doSomething();
            // ...
          });
          ?
          // AMD 默認推薦
          define(["./a", "./b"], function(a, b) {
            // 依賴必須一開始就寫好
            a.doSomething();
            // 此處略去 100 行
            b.doSomething();
            // ...
          });
          


          36. ES6 模塊與 CommonJS 模塊、AMD、CMD 的差異。

          1、語法上
          CommonJS 使用的是 module.exports={} 導出一個模塊對象,require(‘file_path’) 引入模塊對象;
          ES6使用的是 export 導出指定數據, import 引入具體數據。
          ?
          2、CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用
          ?
          CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。
          ?
          ES6 Modules 的運行機制與 CommonJS 不一樣。JS 引擎對腳本靜態分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊里面去取值。換句話說,ES6的import 有點像 Unix 系統的“符號連接”,原始值變了,import加載的值也會跟著變。因此,ES6模塊是動態引用,并且不會緩存值,模塊里面的變量綁定其所在的模塊。
          ?
          3、CommonJS 模塊是運行時加載,ES6 模塊是編譯時加載
          ?
          運行時加載: CommonJS 模塊就是對象;即在輸入時是先加載整個模塊,生成一個對象,然后再從這個對象上面讀取方法,這種加載稱為“運行時加載”。
          ?
          編譯時加載: ES6 模塊不是對象,而是通過 export 命令顯式指定輸出的代碼,import時采用靜態命令的形式。即在import時可以指定加載某個輸出值,而不是加載整個模塊,這種加載稱為“編譯時加載”
          ?
          PS:CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成
          


          37. JS 的運行機制

          異步任務分類:宏任務,微任務
          同步任務和異步任務分別進入不同的執行"場所"
          先執行主線程執行棧中的宏任務
          執行過程中如果遇到微任務,進入Event Table并注冊函數,完成后移入到微任務的任務隊列中
          宏任務執行完畢后,立即執行當前微任務隊列中的所有微任務(依次執行)
          主線程會不斷獲取任務隊列中的任務、執行任務、再獲取、再執行任務也就是常說的Event Loop(事件循環)。
          


          38. 簡單介紹一下 V8 引擎的垃圾回收機制

          v8 的垃圾回收機制基于分代回收機制,這個機制又基于世代假說,這個假說有兩個特點,一是新生的對象容易早死,另一個是不死的對象會活得更久。基于這個假說,v8 引擎將內存分為了新生代和老生代。
          ?
          新創建的對象或者只經歷過一次的垃圾回收的對象被稱為新生代。經歷過多次垃圾回收的對象被稱為老生代。
          ?
          新生代被分為 From 和 To 兩個空間,To 一般是閑置的。當 From 空間滿了的時候會執行 Scavenge 算法進行垃圾回收。當我們執行垃圾回收算法的時候應用邏輯將會停止,等垃圾回收結束后再繼續執行。這個算法分為三步:
          ?
          (1)首先檢查 From 空間的存活對象,如果對象存活則判斷對象是否滿足晉升到老生代的條件,如果滿足條件則晉升到老生代。如果不滿足條件則移動 To 空間。
          ?
          (2)如果對象不存活,則釋放對象的空間。
          ?
          (3)最后將 From 空間和 To 空間角色進行交換。
          ?
          新生代對象晉升到老生代有兩個條件:
          ?
          (1)第一個是判斷是對象否已經經過一次 Scavenge 回收。若經歷過,則將對象從 From 空間復制到老生代中;若沒有經歷,則復制到 To 空間。
          ?
          (2)第二個是 To 空間的內存使用占比是否超過限制。當對象從 From 空間復制到 To 空間時,若 To 空間使用超過 25%,則對象直接晉升到老生代中。設置 25% 的原因主要是因為算法結束后,兩個空間結束后會交換位置,如果 To 空間的內存太小,會影響后續的內存分配。
          ?
          老生代采用了標記清除法和標記壓縮法。標記清除法首先會對內存中存活的對象進行標記,標記結束后清除掉那些沒有標記的對象。由于標記清除后會造成很多的內存碎片,不便于后面的內存分配。所以了解決內存碎片的問題引入了標記壓縮法。
          ?
          由于在進行垃圾回收的時候會暫停應用的邏輯,對于新生代方法由于內存小,每次停頓的時間不會太長,但對于老生代來說每次垃圾回收的時間長,停頓會造成很大的影響。 為了解決這個問題 V8 引入了增量標記的方法,將一次停頓進行的過程分為了多步,每次執行完一小步就讓運行邏輯執行一會,就這樣交替運行。
          


          相關資料:

          《深入理解 V8 的垃圾回收原理》

          《JavaScript 中的垃圾回收》

          39. 哪些操作會造成內存泄漏?

          *   1. 意外的全局變量
          *   2. 被遺忘的計時器或回調函數
          *   3. 脫離 DOM 的引用
          *   4. 閉包
          


          因為內容太多所以需要完整版PDF的小伙伴可關注+私信【學習】自行查看

          40.ES6有哪些新特性?

          *   塊作用域
          *   類
          *   箭頭函數
          *   模板字符串
          *   加強的對象字面
          *   對象解構
          *   Promise
          *   模塊
          *   Symbol
          *   代理(proxy)Set
          *   函數默認參數
          *   展開
          


          41. 什么是箭頭函數?

          //ES5 Version
          var getCurrentDate=function (){
            return new Date();
          }
          ?
          //ES6 Version
          const getCurrentDate=()=> new Date();
          ?
          箭頭函數表達式的語法比函數表達式更簡潔,并且沒有自己的`this,arguments,super或new.target`。箭頭函數表達式更適用于那些本來需要匿名函數的地方,并且它不能用作構造函數。
          ?
          箭頭函數沒有自己的 this 值。它捕獲詞法作用域函數的 this 值,如果我們在全局作用域聲明箭頭函數,則 this 值為 window 對象。
          


          42. 什么是高階函數?

          高階函數只是將函數作為參數或返回值的函數。
          ?
          function higherOrderFunction(param,callback){
              return callback(param);
          }
          


          43. 手寫 call、apply 及 bind 函數

          1.實現call函數

          實現步驟:

          • 處理邊界:
            • 對象不存在,this指向window;
          • 將「調用函數」掛載到「this指向的對象」的fn屬性上。
          • 執行「this指向的對象」上的fn函數,并傳入參數,返回結果。
          Function.prototype.mu_call=function (context, ...args) {
              //obj不存在指向window
              if (!context || context===null) {
                context=window;
              }
              // 創造唯一的key值  作為我們構造的context內部方法名
              let fn=Symbol();
          ?
              //this指向調用call的函數
              context[fn]=this;
          ?
              // 執行函數并返回結果 相當于把自身作為傳入的context的方法進行調用了
              return context[fn](...args);
            };
          ?
            // 測試
            var value=2;
            var obj1={
              value: 1,
            };
            function bar(name, age) {
              var myObj={
                name: name,
                age: age,
                value: this.value,
              };
              console.log(this.value, myObj);
            }
            bar.mu_call(null); //打印 2 {name: undefined, age: undefined, value: 2}
            bar.mu_call(obj1, 'tom', '110'); // 打印 1 {name: "tom", age: "110", value: 1}
          


          2.實現apply函數

          實現步驟:

          • 與call一致
          • 區別于參數的形式
          Function.prototype.mu_apply=function (context, args) {
            //obj不存在指向window
            if (!context || context===null) {
              context=Window;
            }
            // 創造唯一的key值  作為我們構造的context內部方法名
            let fn=Symbol();
          ?
            //this指向調用call的函數
            context[fn]=this;
          ?
            // 執行函數并返回結果 相當于把自身作為傳入的context的方法進行調用了
            return context[fn](...args);
          };
          ?
          // 測試
          var value=2;
          var obj1={
            value: 1,
          };
          function bar(name, age) {
            var myObj={
              name: name,
              age: age,
              value: this.value,
            };
            console.log(this.value, myObj);
          }
          bar.mu_apply(obj1, ["tom", "110"]); // 打印 1 {name: "tom", age: "110", value: 1}
          


          3.實現bind函數

          Function.prototype.mu_bind=function (context, ...args) {
              if (!context || context===null) {
                context=window;
              }
              // 創造唯一的key值  作為我們構造的context內部方法名
              let fn=Symbol();
              context[fn]=this;
              let _this=this;
              //  bind情況要復雜一點
              const result=function (...innerArgs) {
                // 第一種情況 :若是將 bind 綁定之后的函數當作構造函數,通過 new 操作符使用,則不綁定傳入的 this,而是將 this 指向實例化出來的對象
                // 此時由于new操作符作用  this指向result實例對象  而result又繼承自傳入的_this 根據原型鏈知識可得出以下結論
                // this.__proto__===result.prototype   //this instanceof result=>true
                // this.__proto__.__proto__===result.prototype.__proto__===_this.prototype; //this instanceof _this=>true
                if (this instanceof _this===true) {
                  // 此時this指向指向result的實例  這時候不需要改變this指向
                  this[fn]=_this;
                  this[fn](...[...args, ...innerArgs]); //這里使用es6的方法讓bind支持參數合并
                  delete this[fn];
                } else {
                  // 如果只是作為普通函數調用  那就很簡單了 直接改變this指向為傳入的context
                  context[fn](...[...args, ...innerArgs]);
                  delete context[fn];
                }
              };
              // 如果綁定的是構造函數 那么需要繼承構造函數原型屬性和方法
              // 實現繼承的方式: 使用Object.create
              result.prototype=Object.create(this.prototype);
              return result;
            };
            function Person(name, age) {
              console.log(name); //'我是參數傳進來的name'
              console.log(age); //'我是參數傳進來的age'
              console.log(this); //構造函數this指向實例對象
            }
            // 構造函數原型的方法
            Person.prototype.say=function () {
              console.log(123);
            };
          ?
            // 普通函數
            function normalFun(name, age) {
              console.log(name); //'我是參數傳進來的name'
              console.log(age); //'我是參數傳進來的age'
              console.log(this); //普通函數this指向綁定bind的第一個參數 也就是例子中的obj
              console.log(this.objName); //'我是obj傳進來的name'
              console.log(this.objAge); //'我是obj傳進來的age'
            }
          ?
            let obj={
              objName: '我是obj傳進來的name',
              objAge: '我是obj傳進來的age',
            };
          ?
            // 先測試作為構造函數調用
            //   let bindFun=Person.mu_bind(obj, '我是參數傳進來的name');
            //   let a=new bindFun('我是參數傳進來的age');
            //   a.say(); //123
          ?
            //   再測試作為普通函數調用a;
            let bindFun=normalFun.mu_bind(obj, '我是參數傳進來的name');
            bindFun('我是參數傳進來的age');
          


          參考文章: 高頻JavaScript手寫面試題,你“行”嗎

          44. 函數柯里化的實現

          // 函數柯里化指的是一種將使用多個參數的一個函數轉換成一系列使用一個參數的函數的技術。
          ?
          function curry(fn, args) {
            // 獲取函數需要的參數長度
            let length=fn.length;
          ?
            args=args || [];
          ?
            return function() {
              let subArgs=args.slice(0);
          ?
              // 拼接得到現有的所有參數
              for (let i=0; i < arguments.length; i++) {
                subArgs.push(arguments[i]);
              }
          ?
              // 判斷參數的長度是否已經滿足函數所需參數的長度
              if (subArgs.length >=length) {
                // 如果滿足,執行函數
                return fn.apply(this, subArgs);
              } else {
                // 如果不滿足,遞歸返回科里化的函數,等待參數的傳入
                return curry.call(this, fn, subArgs);
              }
            };
          }
          ?
          // es6 實現
          function curry(fn, ...args) {
            return fn.length <=args.length ? fn(...args) : curry.bind(null, fn, ...args);
          }
          


          參考文章: 《JavaScript 專題之函數柯里化》

          45. 實現一個 new 操作符

          首先需要了解new做了什么事情:

          • 首先創建了一個空對象。
          • 將空對象proto指向構造函數的原型prototype。
          • 使this指向新創建的對象,并執行構造函數。
          • 執行結果有返回值并且是一個對象, 返回執行的結果, 否則返回新創建的對象。
          // 代碼實現
          function mu_new(fn,...arg){
              // 首先創建空對象
              const obj={};
              // 將空對象的原型proto指向構造函數的原型prototype
              Object.setPrototypeOf(obj, fn.prototype)
              // 將this指向新創建的對象,并且執行構造函數
              const result=fn.apply(obj,arg);
              // 執行結果有返回值并且是一個對象,返回執行的結果,否側返回新創建的對象
              return result instanceof Object ? result : obj;
          }
          
          // 驗證mu_new函數
          function Dog(name){
              this.name=name;
              this.say=function(){
                  console.log('my name is' + this.name);
              }
          }
          
          const dog=mu_new(Dog, "傻");
          dog.say() //my name is傻
          


          46. 可以講講Promise嗎,可以手寫實現一下嗎?

          Promise 是異步編程的一種解決方案,比傳統的解決方案——回調函數和事件——更合理和更強大。它由社區最早提出和實現,ES6 將其寫進了語言標準,統一了用法,原生提供了`Promise`對象。
          
          所謂`Promise`,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。
          
          **那我們來看看我們所熟知的`Promise`的基本原理**
          
          + 首先我們在調用Promise時,會返回一個Promise對象。
          + 構建Promise對象時,需要傳入一個executor函數,Promise的主要業務流程都在executor函數中執行。
          + 如果運行在excutor函數中的業務執行成功了,會調用resolve函數;如果執行失敗了,則調用reject函數。
          + Promise的狀態不可逆,同時調用resolve函數和reject函數,默認會采取第一次調用的結果。
          
          **結合Promise/A+規范,我們還可以分析出哪些基本特征**
          
          Promise/A+的規范比較多,在這列出一下核心的規范。[Promise/A+規范](https://link.juejin.cn/?target=https%3A%2F%2Fpromisesaplus.com%2F)
          
          + promise有三個狀態:pending,fulfilled,rejected,默認狀態是pending。
          + promise有一個value保存成功狀態的值,有一個reason保存失敗狀態的值,可以是undefined/thenable/promise。
          + promise只能從pending到rejected, 或者從pending到fulfilled,狀態一旦確認,就不會再改變。
          + promise 必須有一個then方法,then接收兩個參數,分別是promise成功的回調onFulfilled, 和promise失敗的回調onRejected。
          + 如果then中拋出了異常,那么就會把這個異常作為參數,傳遞給下一個then的失敗的回調onRejected。
          
          那`CustomPromise`,還實現不了基本原理的3,4兩條,那我們來根據基本原理與Promise/A+分析下,還缺少什么
          
          - promise有三個狀態:pending,fulfilled,rejected。
          - executor執行器調用reject與resolve兩個方法
          - 還需要有保存成功或失敗兩個值的變量
          - then接收兩個參數,分別是成功的回調onFulfilled,失敗的回調onRejected
          


          手寫實現promise

          47. 什么是 async/await 及其如何工作, 可以手寫async嗎

          1、Async—聲明一個異步函數
            - 自動將常規函數轉換成Promise,返回值也是一個Promise對象
            - 只有async函數內部的異步操作執行完,才會執行then方法指定的回調函數
            - 異步函數內部可以使用await
          2、Await—暫停異步的功能執行(var result=await someAsyncCall();)
            - 放置在Promise調用之前,await強制其他代碼等待,直到Promise完成并返回結果
            - 只能與Promise一起使用,不適用與回調
            - 只能在async函數內部使用
          


          手寫實現async

          48. instanceof 的優缺點是什么,如何實現

          優缺點:

          • 「優點」:能夠區分Array、Object和Function,適合用于判斷自定義的類實例對象
          • 「缺點」:Number,Boolean,String基本數據類型不能判斷

          實現步驟:

          • 傳入參數為左側的實例L,和右側的構造函數R
          • 處理邊界,如果要檢測對象為基本類型則返回false
          • 分別取傳入參數的原型
          • 判斷左側的原型是否取到了null,如果是null返回false;如果兩側原型相等,返回true,否則繼續取左側原型的原型。
          // 傳入參數左側為實例L, 右側為構造函數R
          function mu_instanceof(L,R){
              // 處理邊界:檢測實例類型是否為原始類型
              const baseTypes=['string','number','boolean','symbol','undefined'];
          ?
              if(baseTypes.includes(typeof L) || L===null) return false;
          ?
              // 分別取傳入參數的原型
              let Lp=L.__proto__;
              let Rp=R.prototype; // 函數才擁有prototype屬性
          ?
              // 判斷原型
              while(true){
                  if(Lp===null) return false;
                  if(Lp===Rp) return true;
                  Lp=Lp.__proto__;
              }
          }
          ?
          // 驗證
          const isArray=mu_instanceof([],Array);
          console.log(isArray); //true
          const isDate=mu_instanceof('2023-01-09',Date);
          console.log(isDate); // false
          


          49. js 的節流與防抖

          1.防抖

          函數防抖是在事件被觸發n秒后再執行回調,如果在「n秒內又被觸發」,則「重新計時」

          function debounce(fn, wait) {
              let timer=null;
              return function () {
                if (timer !=null) {
                  clearTimeout(timer);
                }
                timer=setTimeout(()=> {
                  fn();
                }, wait);
              };
            }
            // 測試
            function handle() {
              console.log(Math.random());
            }
            // 窗口大小改變,觸發防抖,執行handle
            window.addEventListener('resize', debounce(handle, 1000));
          


          2.節流

          當事件觸發時,保證一定時間段內只調用一次函數。例如頁面滾動的時候,每隔一段時間發一次請求

          實現步驟:

          • 傳入參數為執行函數fn,等待時間wait。
          • 保存初始時間now。
          • 返回一個函數,如果超過等待時間,執行函數,將now更新為當前時間。
          function throttle(fn, wait, ...args) {
              var pre=Date.now();
              return function () {
                // 函數可能會有入參
                var context=this;
                var now=Date.now();
                if (now - pre >=wait) {
                  // 將執行函數的this指向當前作用域
                  fn.apply(context, args);
                  pre=Date.now();
                }
              };
            }
          ?
            // 測試
            var name='mu';
            function handle(val) {
              console.log(val + this.name);
            }
            // 滾動鼠標,觸發防抖,執行handle
            window.addEventListener('scroll', throttle(handle, 1000, '木由'));
          


          50.HTML、XML、XHTML 的區別

          - `HTML`:超文本標記語言,是語法較為松散的、不嚴格的`Web`語言;
          - `XML`:可擴展的標記語言,主要用于存儲數據和結構,可擴展;
          - `XHTML`:可擴展的超文本標記語言,基于`XML`,作用與`HTML`類似,但語法更嚴格。
          


          51. HTML、XHTML和HTML5區別以及有什么聯系

          XHTML與HTML的區別
          ?
          - `XHTML`標簽名必須小寫;
          - `XHTML`元素必須被關閉;
          - `XHTML`元素必須被正確的嵌套;
          - `XHTML`元素必須要有根元素。
          ?
          XHTML與HTML5的區別
          ?
          - `HTML5`新增了`canvas`繪畫元素;
          - `HTML5`新增了用于繪媒介回放的`video`和`audio`元素;
          - 更具語義化的標簽,便于瀏覽器識別;
          - 對本地離線存儲有更好的支持;
          - `MATHML`,`SVG`等,可以更好的`render`;
          - 添加了新的表單控件:`calendar`、`date`、`time`、`email`等。
          ?
          HTML、XHTML、HTML5之間聯系
          ?
          - `XHTML`是`HTML`規范版本;
          - `HTML5`是`HTML`、`XHTML`以及`HTML DOM`的新標準。
          


          52.行內元素有哪些?塊級元素有哪些? 空(void)元素有那些?

          - 行內元素: `a`, `b`, `span`, `img`, `input`, `select`, `strong`;
          - 塊級元素: `div`, `ul`, `li`, `dl`, `dt`, `dd`, `h1-5`, `p`等;
          - 空元素: `<br>`, `<hr>`, `<img>`, `<link>`, `<meta>`;
          


          53. 頁面導入樣式時,使用link和@import有什么區別

          - `link`屬于`HTML`標簽,而`@import`是`css`提供的;
          - 頁面被加載時,`link`會同時被加載,而`@import`引用的css會等到頁面被加載完再加載;
          - `@import`只在`IE5`以上才能識別,而`link`是`XHTML`標簽,無兼容問題;
          - `link`方式的樣式的權重高于`@import`的權重。
          


          54. 如何理解語義化標簽

          概念
          ?
          語義化是指根據內容的結構化(內容語義化),選擇合適的標簽(代碼語義化),便于開發者閱讀和寫出更優雅的代碼的同時,讓瀏覽器的爬蟲和機器很好的解析。
          ?
          語義化的好處
          ?
          - 用正確的標簽做正確的事情;
          - 去掉或者丟失樣式的時候能夠讓頁面呈現出清晰的結構;
          - 方便其他設備解析(如屏幕閱讀器、盲人閱讀器、移動設備)以意義的方式來渲染網頁;
          - 有利于`SEO`:和搜索引擎建立良好溝通,有助于爬蟲抓取更多的有效信息:爬蟲依賴于標簽來確定上下文和各個關鍵字的權重;
          - 便于團隊開發和維護,語義化更具可讀性,遵循W3C標準的團隊都遵循這個標準,可以減少差異化。
          


          55. property和attribute的區別是什么

          - `property`是`DOM`中的屬性,是`JavaScript`里的對象;
          - `attribute`是`HTML`標簽上的特性,它的值只能夠是字符串;
          ?
          簡單的理解就是:`Attribute`就是`DOM`節點自帶的屬性,例如`html`中常用的`id`、`class`、`title`、`align`等;而`Property`是這個`DOM`元素作為對象,其附加的內容,例如`childNodes`、`firstChild`等。
          


          56. html5有哪些新特性、移除了那些元素

          新特性
          ?
          **HTML5 現在已經不是 SGML 的子集,主要是關于圖像,位置,存儲,多任務等功能的增加。**
          ?
          - 拖拽釋放`(Drag and drop)` `API`;
          - 語義化更好的內容標簽(`header`, `nav`, `footer`, `aside`, `article`, `section`);
          - 音頻、視頻API(`audio`, `video`);
          - 畫布`(Canvas)` `API`;
          - 地理`(Geolocation)` `API`;
          - 本地離線存儲 `localStorage` 長期存儲數據,瀏覽器關閉后數據不丟失;
          - `sessionStorage` 的數據在瀏覽器關閉后自動刪除;
          - 表單控件:`calendar`、`date`、`time`、`email`、`url`、`search` ;
          - 新的技術`webworker`, `websocket`, `Geolocation`等;
          ?
          移除元素
          ?
          **純表現元素**:
          ?
          - `<basefont>` 默認字體,不設置字體,以此渲染;
          - `<font>` 字體標簽;
          - `<center>` 水平居中;
          - `<u>` 下劃線;
          - `<big>`字體;
          - `<strike>`中橫字;
          - `<tt>`文本等寬;
          ?
          **對可用性產生負面影響的元素**:
          ?
          `<frameset>`,`<noframes>`和`<frame>`;
          


          57. 什么是前端的結構,樣式和行為相分離?以及分離的好處是什么?

          結構,樣式和行為分離
          ?
          若是將前端比作一個人來舉例子,結構(`HTML`)就相當于是人體的“骨架”,樣式就相當于人體的“裝飾”,例如衣服,首飾等;行為就相當于人做出的一系列“動作”。
          ?
          在結構,樣式和行為分離,就是將三者分離開,各自負責各自的內容,各部分可以通過引用進行使用。
          ?
          在分離的基礎上,我們需要做到代碼的:**精簡**, **重用**, **有序**。
          ?
          分離的好處
          ?
          - 代碼分離,利于團隊的開發和后期的維護;
          - 減少維護成本,提高可讀性和更好的兼容性;
          


          58. 如何對網站的文件和資源進行優化

          - 文件合并(目的是減少`http`請求);
          - 文件壓縮 (目的是直接減少文件下載的體積);
          - 使用緩存;
          - 使用`cdn`托管資源;
          - `gizp`壓縮需要的js和css文件;
          - 反向鏈接,網站外鏈接優化;
          - meta標簽優化(`title`, `description`, `keywords`),`heading`標簽的優化,`alt`優化;
          


          59. Html5中本地存儲概念是什么,有什么優點,與cookie有什么區別?

          `HTML5`的`Web storage`的存儲方式有兩種:`sessionStorage`和`localStorage`。
          ?
          - `sessionStorage`用于本地存儲一個會話中的數據,當會話結束后就會銷毀;
          - 和`sessionStorage`不同,`localStorage`用于持久化的本地存儲,除非用戶主動刪除數據,否則數據永遠不會過期;
          - `cookie`是網站為了標示用戶身份而儲存在用戶本地終端(`Client Side`)上的數據(通常經過加密)。
          ?
          **區別**:
          ?
          - **從瀏覽器和服務器間的傳遞看**: `cookie`數據始終在同源的http請求中攜帶(即使不需要),即`cookie`在瀏覽器和服務器間來回傳遞;而`sessionStorage`和`localStorage`不會自動把數據發給服務器,僅在本地保存。
          - **從大小看**: 存儲大小限制不同,`cookie`數據不能超過`4k`,只適合保存很小的數據;而`sessionStorage`和`localStorage` 雖然也有存儲大小的限制,但比`cookie`大得多,可以達到5M或更大。
          - **從數據有效期看**: `sessionStorage`在會話關閉會立刻關閉,因此持續性不久;`cookie`只在設置的cookie過期時間之前一直有效,即使窗口或瀏覽器關閉。而`localStorage`始終有效。
          - **從作用域看**: `sessionStorage`不在不同的瀏覽器窗口中共享,即使是同一個頁面;而`localStorage`和`cookie`都是可以在所有的同源窗口中共享的。
          


          60. 常見的瀏覽器內核有哪些

          - `Trident`內核:IE最先開發或使用的, 360瀏覽器;
          - `Webkit`內核:Google Chrome,Safari, 搜狗瀏覽器,360極速瀏覽器, 阿里云瀏覽器等;
          - `Gecko`內核: Mozilla FireFox (火狐瀏覽器) ,K-Meleon瀏覽器;
          - `Presto`內核:Opera瀏覽器;
          


          61. LocalStorage本地存儲在HTML5中有什么用途

          `localStorage`本地存儲相當于一個輕量級的數據庫,可以在本地永久的儲存數據(除非人為刪除)。此外,還可以在斷網情況下讀取本地緩存的`cookies`。
          ?
          - 使用`localStorage`保存數據: `localStorage.setItem(key, value)`;
          - 使用`localStorage`獲取保存的數據: `localStorage.getItem(key)`;
          - 清除`localStorage`保存的數據: `localStorage.removeItem(key)`;
          - 清除全部`localStorage`對象保存的數據: `localStorage.clear( )`;
          


          62. 為什么利用多個域名來存儲網站資源會更有效

          - `CDN`緩存更加方便;
          - 突破瀏覽器并發限制;
          - 節約`cookie`寬帶;
          - 節約主域名的連接數,優化頁面下響應速度;
          - 防止不必要的安全問題;
          


          63. HTML中幾種圖片格式的區別以及使用

          全稱 Domain Name System , 即域名系統。
          
          > 萬維網上作為域名和 IP 地址相互映射的一個分布式數據庫,能夠使用戶更方便的訪問互聯網,而不用去記住能夠被機器直接讀取的 IP 數串。DNS 協議運行在 UDP 協議之上,使用端口號 53。
          
          簡單的說, 通過域名, 最終得到該域名對應的 IP 地址的過程叫做域名解析(或主機名解析)。
          
          ```text
          www.zuofc.com (域名)  - DNS解析 -> 111.222.33.444 (IP地址)
          ```
          
          有 dns 的地方, 就有緩存。瀏覽器、操作系統、Local DNS、根域名服務器,它們都會對 DNS 結果做一定程度的緩存。
          
          DNS 查詢過程如下:
          
          1. 首先搜索瀏覽器自身的 DNS 緩存, 如果存在,則域名解析到此完成。
          2. 如果瀏覽器自身的緩存里面沒有找到對應的條目,那么會嘗試讀取操作系統的 hosts 文件看是否存在對應的映射關系, 如果存在,則域名解析到此完成。
          3. 如果本地 hosts 文件不存在映射關系,則查找本地 DNS 服務器 (ISP 服務器, 或者自己手動設置的 DNS 服務器), 如果存在, 域名到此解析完成。
          4. 如果本地 DNS 服務器還沒找到的話, 它就會向根服務器發出請求, 進行遞歸查詢。
          


          64.DNS是什么

          強緩存
          瀏覽器在加載資源時,會先根據本地緩存資源的 header 中的信息判斷是否命中強緩存,如果命中則直接使用緩存中的資源不會再向服務器發送請求。
          ?
          這里的 header 中的信息指的是 expires 和 cahe-control.
          ?
          Expires
          該字段是 http1.0 時的規范,它的值為一個絕對時間的 GMT 格式的時間字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT。這個時間代表著這個資源的失效時間,在此時間之前,即命中緩存。這種方式有一個明顯的缺點,由于失效時間是一個絕對時間,所以當服務器與客戶端時間偏差較大時,就會導致緩存混亂(本地時間也可以隨便更改)。
          ?
          Cache-Control(優先級高于 Expires)
          Cache-Control 是 http1.1 時出現的 header 信息,主要是利用該字段的 max-age 值來進行判斷,它是一個相對時間,例如 Cache-Control:max-age=3600,代表著資源的有效期是 3600 秒。cache-control 除了該字段外,還有下面幾個比較常用的設置值:
          ?
          no-cache:需要進行協商緩存,發送請求到服務器確認是否使用緩存。
          no-store:禁止使用緩存,每一次都要重新請求數據。
          public:可以被所有的用戶緩存,包括終端用戶和 CDN 等中間代理服務器。
          private:只能被終端用戶的瀏覽器緩存,不允許 CDN 等中繼緩存服務器對其緩存。
          Cache-Control 與 Expires 可以在服務端配置同時啟用,同時啟用的時候 Cache-Control 優先級高。
          


          65.什么是強緩存

          強緩存
          瀏覽器在加載資源時,會先根據本地緩存資源的 header 中的信息判斷是否命中強緩存,如果命中則直接使用緩存中的資源不會再向服務器發送請求。
          ?
          這里的 header 中的信息指的是 expires 和 cahe-control.
          ?
          Expires
          該字段是 http1.0 時的規范,它的值為一個絕對時間的 GMT 格式的時間字符串,比如 Expires:Mon,18 Oct 2066 23:59:59 GMT。這個時間代表著這個資源的失效時間,在此時間之前,即命中緩存。這種方式有一個明顯的缺點,由于失效時間是一個絕對時間,所以當服務器與客戶端時間偏差較大時,就會導致緩存混亂(本地時間也可以隨便更改)。
          ?
          Cache-Control(優先級高于 Expires)
          Cache-Control 是 http1.1 時出現的 header 信息,主要是利用該字段的 max-age 值來進行判斷,它是一個相對時間,例如 Cache-Control:max-age=3600,代表著資源的有效期是 3600 秒。cache-control 除了該字段外,還有下面幾個比較常用的設置值:
          ?
          no-cache:需要進行協商緩存,發送請求到服務器確認是否使用緩存。
          no-store:禁止使用緩存,每一次都要重新請求數據。
          public:可以被所有的用戶緩存,包括終端用戶和 CDN 等中間代理服務器。
          private:只能被終端用戶的瀏覽器緩存,不允許 CDN 等中繼緩存服務器對其緩存。
          Cache-Control 與 Expires 可以在服務端配置同時啟用,同時啟用的時候 Cache-Control 優先級高。
          


          66.什么是協商緩存

          當強緩存沒有命中的時候,瀏覽器會發送一個請求到服務器,服務器根據 header 中的部分信息來判斷是否命中緩存。如果命中,則返回 304 ,告訴瀏覽器資源未更新,可使用本地的緩存。
          
          這里的 header 中的信息指的是 Last-Modify/If-Modify-Since 和 ETag/If-None-Match.
          
          Last-Modify/If-Modify-Since
          瀏覽器第一次請求一個資源的時候,服務器返回的 header 中會加上 Last-Modify,Last-modify 是一個時間標識該資源的最后修改時間(只能精確到秒,所以間隔時間小于 1 秒的請求是檢測不到文件更改的。)。
          
          當瀏覽器再次請求該資源時,request 的請求頭中會包含 If-Modify-Since,該值為緩存之前返回的 Last-Modify。服務器收到 If-Modify-Since 后,根據資源的最后修改時間判斷是否命中緩存。
          
          如果命中緩存,則返回 304,并且不會返回資源內容,并且不會返回 Last-Modify。
          
          缺點:
          
          短時間內資源發生了改變,Last-Modified 并不會發生變化。
          
          周期性變化。如果這個資源在一個周期內修改回原來的樣子了,我們認為是可以使用緩存的,但是 Last-Modified 可不這樣認為, 因此便有了 ETag。
          
          ETag/If-None-Match
          Etag 是基于文件內容進行編碼的,可以保證如果服務器有更新,一定會重新請求資源,但是編碼需要付出額外的開銷。
          
          與 Last-Modify/If-Modify-Since 不同的是,Etag/If-None-Match 返回的是一個校驗碼。ETag 可以保證每一個資源是唯一的,資源變化都會導致 ETag 變化。服務器根據瀏覽器上送的 If-None-Match 值來判斷是否命中緩存。
          
          與 Last-Modified 不一樣的是,當服務器返回 304 Not Modified 的響應時,由于 ETag 重新生成過,response header 中還會把這個 ETag 返回,即使這個 ETag 跟之前的沒有變化。
          
          Last-Modified 與 ETag 是可以一起使用的,服務器會優先驗證 ETag,一致的情況下,才會繼續比對 Last-Modified,最后才決定是否返回 304。
          


          67.打開 Chrome 瀏覽器一個 Tab 頁面,至少會出現幾個進程?

          最新的 Chrome 瀏覽器包括至少四個: 1 個瀏覽器(Browser)主進程、1 個 GPU 進程、1 個網絡(NetWork)進程、多個渲染進程和多個插件進程, 當然還有復雜的情況;
          
          頁面中有 iframe 的話, iframe 會單獨在進程中
          
          有插件的話,插件也會開啟進程
          
          多個頁面屬于同一站點,并且從 a 打開 b 頁面,會共用一個渲染進程
          
          裝了擴展的話,擴展也會占用進程
          
          這些進程都可以通過 Chrome 任務管理器來查看
          


          68.即使如今多進程架構,還是會碰到單頁面卡死的最終崩潰導致所有頁面崩潰的情況,講一講你的理解?

          提供一種情況,就是同一站點, 圍繞這個展開也行。
          
          Chrome 的默認策略是,每個標簽對應一個渲染進程。但是如果從一個頁面打開了新頁面,而新頁面和當前頁面屬于同一站點時,那么新頁面會復用父頁面的渲染進程。官方把這個默認策略叫 process-per-site-instance。
          
          更加簡單的來說,就是如果多個頁面符合同一站點,這幾個頁面會分配到一個渲染進程中去, 所以有這樣子的一種情況, 一個頁面崩潰了,會導致同一個站點的其他頁面也奔潰,這是因為它們使用的是同一個渲染進程。
          
          有人會問為什么會跑到一個進程里面呢?
          
          你想一想呀, 屬于同一家的站點,比如下面三個:
          
          https://time.geekbang.org
          https://www.geekbang.org
          https://www.geekbang.org:8080
          它們在一個渲染進程中的話,它們就會共享 JS 執行環境,也就是 A 頁面可以直接在 B 頁面中執行腳本了, 有些時候就是有這樣子的需求嘛。
          


          69.TCP 建立連接過程講一講,為什么握手需要三次?

          **三次握手**
          
          第一次握手
          客戶端向服務端發送連接請求報文段。該報文段的頭部中 SYN=1,ACK=0,seq=x。請求發送后,客戶端便進入 SYN-SENT 狀態。
          
          PS1:SYN=1,ACK=0 表示該報文段為連接請求報文。
          PS2:x 為本次 TCP 通信的字節流的初始序號。
          TCP 規定:SYN=1 的報文段不能有數據部分,但要消耗掉一個序號。
          第二次握手
          服務端收到連接請求報文段后,如果同意連接,則會發送一個應答:SYN=1,ACK=1,seq=y,ack=x+1。
          該應答發送完成后便進入 SYN-RCVD 狀態。
          
          PS1:SYN=1,ACK=1 表示該報文段為連接同意的應答報文。
          PS2:seq=y 表示服務端作為發送者時,發送字節流的初始序號。
          PS3:ack=x+1 表示服務端希望下一個數據報發送序號從 x+1 開始的字節。
          第三次握手
          當客戶端收到連接同意的應答后,還要向服務端發送一個確認報文段,表示:服務端發來的連接同意應答已經成功收到。
          該報文段的頭部為:ACK=1,seq=x+1,ack=y+1。
          客戶端發完這個報文段后便進入 ESTABLISHED 狀態,服務端收到這個應答后也進入 ESTABLISHED 狀態,此時連接的建立完成!
          
          **為什么連接建立需要三次握手,而不是兩次握手**
          
          在謝希仁著《計算機網絡》第四版中講 “三次握手” 的目的是 “為了防止已失效的連接請求報文段突然又傳送到了服務端,因而產生錯誤”。在另一部經典的《計算機網絡》一書中講“三次握手” 的目的是為了解決 “網絡中存在延遲的重復分組” 的問題。這兩種不用的表述其實闡明的是同一個問題。
          
          謝希仁版《計算機網絡》中的例子是這樣的,“已失效的連接請求報文段”的產生在這樣一種情況下:client 發出的第一個連接請求報文段并沒有丟失,而是在某個網絡結點長時間的滯留了,以致延誤到連接釋放以后的某個時間才到達 server。本來這是一個早已失效的報文段。但 server 收到此失效的連接請求報文段后,就誤認為是 client 再次發出的一個新的連接請求。于是就向 client 發出確認報文段,同意建立連接。假設不采用 “三次握手”,那么只要 server 發出確認,新的連接就建立了。由于現在 client 并沒有發出建立連接的請求,因此不會理睬 server 的確認,也不會向 server 發送數據。但 server 卻以為新的運輸連接已經建立,并一直等待 client 發來數據。這樣,server 的很多資源就白白浪費掉了。采用“三次握手” 的辦法可以防止上述現象發生。例如剛才那種情況,client 不會向 server 的確認發出確認。server 由于收不到確認,就知道 client 并沒有要求建立連接。”
          
          
          **四次揮手**
          
          第一次揮手
          若 A 認為數據發送完成,則它需要向 B 發送連接釋放請求。該請求只有報文頭,頭中攜帶的主要參數為:
          FIN=1,seq=u。此時,A 將進入 FIN-WAIT-1 狀態。
          
          PS1:FIN=1 表示該報文段是一個連接釋放請求。
          PS2:seq=u,u-1 是 A 向 B 發送的最后一個字節的序號。
          第二次揮手
          B 收到連接釋放請求后,會通知相應的應用程序,告訴它 A 向 B 這個方向的連接已經釋放。此時 B 進入 CLOSE-WAIT 狀態,并向 A 發送連接釋放的應答,其報文頭包含:
          ACK=1,seq=v,ack=u+1。
          
          PS1:ACK=1:除 TCP 連接請求報文段以外,TCP 通信過程中所有數據報的 ACK 都為 1,表示應答。
          PS2:seq=v,v-1 是 B 向 A 發送的最后一個字節的序號。
          PS3:ack=u+1 表示希望收到從第 u+1 個字節開始的報文段,并且已經成功接收了前 u 個字節。
          A 收到該應答,進入 FIN-WAIT-2 狀態,等待 B 發送連接釋放請求。
          
          第二次揮手完成后,A 到 B 方向的連接已經釋放,B 不會再接收數據,A 也不會再發送數據。但 B 到 A 方向的連接仍然存在,B 可以繼續向 A 發送數據。
          
          第三次揮手
          當 B 向 A 發完所有數據后,向 A 發送連接釋放請求,請求頭:FIN=1,ACK=1,seq=w,ack=u+1。B 便進入 LAST-ACK 狀態。
          
          第四次揮手
          A 收到釋放請求后,向 B 發送確認應答,此時 A 進入 TIME-WAIT 狀態。該狀態會持續 2MSL 時間,若該時間段內沒有 B 的重發請求的話,就進入 CLOSED 狀態,撤銷 TCB。當 B 收到確認應答后,也便進入 CLOSED 狀態,撤銷 TCB。
          
          為什么 A 要先進入 TIME-WAIT 狀態,等待 2MSL 時間后才進入 CLOSED 狀態?
          為了保證 B 能收到 A 的確認應答。
          若 A 發完確認應答后直接進入 CLOSED 狀態,那么如果該應答丟失,B 等待超時后就會重新發送連接釋放請求,但此時 A 已經關閉了,不會作出任何響應,因此 B 永遠無法正常關閉。
          


          70.從輸入 URL 到頁面展示,這中間發生了什么

          URL解析
            - 首先判斷你輸入的是一個合法的URL 還是一個待搜索的關鍵詞,并且根據你輸入的內容進行對應操作
          DNS 查詢
            - DNS查詢對應ip
          TCP 連接
            - 在確定目標服務器服務器的IP地址后,則經歷三次握手建立TCP連接
          HTTP 請求
            - 當建立tcp連接之后,就可以在這基礎上進行通信,瀏覽器發送 http 請求到目標服務器
          響應請求
            - 當服務器接收到瀏覽器的請求之后,就會進行邏輯操作,處理完成之后返回一個HTTP響應消息
          頁面渲染
            - 當瀏覽器接收到服務器響應的資源后,首先會對資源進行解析:
          ?
            查看響應頭的信息,根據不同的指示做對應處理,比如重定向,存儲cookie,解壓gzip,緩存資源等等
            查看響應頭的 Content-Type的值,根據不同的資源類型采用不同的解析方式
            關于頁面的渲染過程如下:
          ?
            解析HTML,構建 DOM 樹
            解析 CSS ,生成 CSS 規則樹
            合并 DOM 樹和 CSS 規則,生成 render 樹
            布局 render 樹( Layout / reflow ),負責各元素尺寸、位置的計算
            繪制 render 樹( paint ),繪制頁面像素信息
            瀏覽器會將各層的信息發送給 GPU,GPU 會將各層合成( composite ),顯示在屏幕上
          


          71.什么是 CDN

          全稱 Content Delivery Network, 即內容分發網絡。
          ?
          摘錄一個形象的比喻, 來理解 CDN 是什么。
          ?
          10 年前,還沒有火車票代售點一說,12306.cn 更是無從說起。那時候火車票還只能在火車站的售票大廳購買,而我所在的小縣城并不通火車,火車票都要去市里的火車站購買,而從我家到縣城再到市里,來回就是 4 個小時車程,簡直就是浪費生命。后來就好了,小縣城里出現了火車票代售點,甚至鄉鎮上也有了代售點,可以直接在代售點購買火車票,方便了不少,全市人民再也不用在一個點苦逼的排隊買票了。
          ?
          簡單的理解 CDN 就是這些代售點 (緩存服務器) 的承包商, 他為買票者提供了便利, 幫助他們在最近的地方 (最近的 CDN 節點) 用最短的時間 (最短的請求時間) 買到票(拿到資源), 這樣去火車站售票大廳排隊的人也就少了。也就減輕了售票大廳的壓力(起到分流作用, 減輕服務器負載壓力)。
          ?
          用戶在瀏覽網站的時候,CDN 會選擇一個離用戶最近的 CDN 邊緣節點來響應用戶的請求,這樣海南移動用戶的請求就不會千里迢迢跑到北京電信機房的服務器(假設源站部署在北京電信機房)上了。
          ?
          CDN 緩存
          關于 CDN 緩存, 在瀏覽器本地緩存失效后, 瀏覽器會向 CDN 邊緣節點發起請求。類似瀏覽器緩存, CDN 邊緣節點也存在著一套緩存機制。CDN 邊緣節點緩存策略因服務商不同而不同,但一般都會遵循 http 標準協議,通過 http 響應頭中的
          ?
          Cache-control: max-age   //后面會提到
          的字段來設置 CDN 邊緣節點數據緩存時間。
          ?
          當瀏覽器向 CDN 節點請求數據時,CDN 節點會判斷緩存數據是否過期,若緩存數據并沒有過期,則直接將緩存數據返回給客戶端;否則,CDN 節點就會向服務器發出回源請求,從服務器拉取最新數據,更新本地緩存,并將最新數據返回給客戶端。 CDN 服務商一般會提供基于文件后綴、目錄多個維度來指定 CDN 緩存時間,為用戶提供更精細化的緩存管理。
          ?
          CDN 優勢
          CDN 節點解決了跨運營商和跨地域訪問的問題,訪問延時大大降低。
          大部分請求在 CDN 邊緣節點完成,CDN 起到了分流作用,減輕了源服務器的負載。
          


          72. 說說HTTP與HTTPS的區別

          復制代碼HTTPS是HTTP協議的安全版本,HTTP協議的數據傳輸是明文的,是不安全的,HTTPS使用了SSL/TLS協議進行了加密處理,相對更安全
          HTTP 和 HTTPS 使用連接方式不同,默認端口也不一樣,HTTP是80,HTTPS是443
          HTTPS 由于需要設計加密以及多次握手,性能方面不如 HTTP
          HTTPS需要SSL,SSL 證書需要錢,功能越強大的證書費用越高
          

          73.webpack文件指紋策略:hash chunkhash contenthash

          hash策略:是以項目為單位的,項目內容改變則會生成新的hash,內容不變則hash不變
          ?
          chunkhash策略:是以chunk為單位的,當一個文件內容改變,則整個相應的chunk組模塊的hash回發生改變
          ?
          contenthash策略:是以自身內容為單為的
          ?
          推薦使用:css :contenthash
          ?
          ?js:chunkhash
          


          74.說說webpack的構建流程

          1.初始化參數:解析webpack配置參數,合并shell傳入和webpack.config.js文件配置的參數,形成最后的配置結果。
          2.開始編譯:上一步得到的參數初始化compiler對象,注冊所有配置的插件,插件監聽webpack構建生命周期的事件節點,做出相應的反應,執行對象的 run 方法開始執行編譯。
          3.確定入口:從配置的entry入口,開始解析文件構建AST語法樹,找出依賴,遞歸下去。
          4.編譯模塊:遞歸中根據文件類型和loader配置,調用所有配置的loader對文件進行轉換,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經過了本步驟的處理。
          5.完成模塊編譯:在經過第4步使? Loader 翻譯完所有模塊后,得到了每個模塊被翻譯后的最終內容以及它們之間的依賴關系;
          6.輸出資源:根據??和模塊之間的依賴關系,組裝成?個個包含多個模塊的 Chunk,再把每個 Chunk 轉換成?個單獨的?件加?到輸出列表,這步是可以修改輸出內容的最后機會;
          7.輸出完成:在確定好輸出內容后,根據配置確定輸出的路徑和?件名,把?件內容寫?到?件系統。
          


          75.說說Loader和Plugin的區別?編寫Loader,Plugin的思路

          一、區別

          • loader 是文件加載器,能夠加載資源文件,并對這些文件進行一些處理,諸如編譯、壓縮等,最終一起打包到指定的文件中
          • plugin 賦予了 webpack 各種靈活的功能,例如打包優化、資源管理、環境變量注入等,目的是解決 loader 無法實現的其他事

          從整個運行時機上來看,如下圖所示:

          可以看到,兩者在運行時機上的區別:

          • loader 運行在打包文件之前
          • plugins 在整個編譯周期都起作用

          在Webpack 運行的生命周期中會廣播出許多事件,Plugin 可以監聽這些事件,在合適的時機通過Webpack提供的 API改變輸出結果

          對于loader,實質是一個轉換器,將A文件進行編譯形成B文件,操作的是文件,比如將A.scss或A.less轉變為B.css,單純的文件轉換過程

          二、編寫loader

          在編寫 loader 前,我們首先需要了解 loader 的本質

          其本質為函數,函數中的 this 作為上下文會被 webpack 填充,因此我們不能將 loader設為一個箭頭函數

          函數接受一個參數,為 webpack 傳遞給 loader 的文件源內容

          函數中 this 是由 webpack 提供的對象,能夠獲取當前 loader 所需要的各種信息

          函數中有異步操作或同步操作,異步操作通過 this.callback 返回,返回值要求為 string 或者 Buffer

          代碼如下所示:

          // 導出一個函數,source為webpack傳遞給loader的文件源內容
          module.exports=function(source) {
              const content=doSomeThing2JsString(source);
              
              // 如果 loader 配置了 options 對象,那么this.query將指向 options
              const options=this.query;
              
              // 可以用作解析其他模塊路徑的上下文
              console.log('this.context');
              
              /*
               * this.callback 參數:
               * error:Error | null,當 loader 出錯時向外拋出一個 error
               * content:String | Buffer,經過 loader 編譯后需要導出的內容
               * sourceMap:為方便調試生成的編譯后內容的 source map
               * ast:本次編譯生成的 AST 靜態語法樹,之后執行的 loader 可以直接使用這個 AST,進而省去重復生成 AST 的過程
               */
              this.callback(null, content); // 異步
              return content; // 同步
          }
          


          一般在編寫loader的過程中,保持功能單一,避免做多種功能

          如less文件轉換成 css文件也不是一步到位,而是 less-loader、css-loader、style-loader幾個 loader的鏈式調用才能完成轉換

          三、編寫plugin

          由于webpack基于發布訂閱模式,在運行的生命周期中會廣播出許多事件,插件通過監聽這些事件,就可以在特定的階段執行自己的插件任務

          在之前也了解過,webpack編譯會創建兩個核心對象:

          • compiler:包含了 webpack 環境的所有的配置信息,包括 options,loader 和 plugin,和 webpack 整個生命周期相關的鉤子
          • compilation:作為 plugin 內置事件回調函數的參數,包含了當前的模塊資源、編譯生成資源、變化的文件以及被跟蹤依賴的狀態信息。當檢測到一個文件變化,一次新的 Compilation 將被創建

          如果自己要實現plugin,也需要遵循一定的規范:

          • 插件必須是一個函數或者是一個包含 apply 方法的對象,這樣才能訪問compiler實例
          • 傳給每個插件的 compiler 和 compilation 對象都是同一個引用,因此不建議修改
          • 異步的事件需要在插件處理完任務時調用回調函數通知 Webpack 進入下一個流程,不然會卡住

          實現plugin的模板如下:

          class MyPlugin {
              // Webpack 會調用 MyPlugin 實例的 apply 方法給插件實例傳入 compiler 對象
            apply (compiler) {
              // 找到合適的事件鉤子,實現自己的插件功能
              compiler.hooks.emit.tap('MyPlugin', compilation=> {
                  // compilation: 當前打包構建流程的上下文
                  console.log(compilation);
                  
                  // do something...
              })
            }
          }
          


          在 emit 事件發生時,代表源文件的轉換和組裝已經完成,可以讀取到最終將輸出的資源、代碼塊、模塊及其依賴,并且可以修改輸出資源的內容

          76.提高webpack的構建速度

          優化 loader 配置
          合理使用 resolve.extensions
          優化 resolve.modules
          優化 resolve.alias
          使用 DLLPlugin 插件
          使用 cache-loader
          terser 啟動多線程
          合理使用 sourceMap
          


          77.webpack 熱更新是怎么做到的

          通過webpack-dev-server創建兩個服務器:提供靜態資源的服務(express)和Socket服務
          express server 負責直接提供靜態資源的服務(打包后的資源直接被瀏覽器請求和解析)
          socket server 是一個 websocket 的長連接,雙方可以通信
          當 socket server 監聽到對應的模塊發生變化時,會生成兩個文件.json(manifest文件)和.js文件(update chunk)
          通過長連接,socket server 可以直接將這兩個文件主動發送給客戶端(瀏覽器)
          瀏覽器拿到兩個新的文件后,通過HMR runtime機制,加載這兩個文件,并且針對修改的模塊進行更新
          


          78.webpack中常見的Loader

          style-loader: 將css添加到DOM的內聯樣式標簽style里
          css-loader :允許將css文件通過require的方式引入,并返回css代碼
          less-loader: 處理less
          sass-loader: 處理sass
          postcss-loader: 用postcss來處理CSS
          autoprefixer-loader: 處理CSS3屬性前綴,已被棄用,建議直接使用postcss
          file-loader: 分發文件到output目錄并返回相對路徑
          url-loader: 和file-loader類似,但是當文件小于設定的limit時可以返回一個Data Url
          html-minify-loader: 壓縮HTML
          babel-loader :用babel來轉換ES6文件到ES
          


          79.webpack中常見的Plugin

          80.Git常用的命令有哪些

          **基本操作**
            
          git init 初始化倉庫,默認為 master 分支
          
          git add . 提交全部文件修改到緩存區
          
          git add <具體某個文件路徑+全名> 提交某些文件到緩存區
          
          git diff 查看當前代碼 add后,會 add 哪些內容
          
          git diff --staged查看現在 commit 提交后,會提交哪些內容
          
          git status 查看當前分支狀態
          
          git pull <遠程倉庫名> <遠程分支名> 拉取遠程倉庫的分支與本地當前分支合并
          
          git pull <遠程倉庫名> <遠程分支名>:<本地分支名> 拉取遠程倉庫的分支與本地某個分支合并
          
          git commit -m "<注釋>" 提交代碼到本地倉庫,并寫提交注釋
          
          git commit -v 提交時顯示所有diff信息
          
          git commit --amend [file1] [file2] 重做上一次commit,并包括指定文件的新變化
          
          **提交規則**
          
          feat: 新特性,添加功能
          
          fix: 修改 bug
          
          refactor: 代碼重構
          
          docs: 文檔修改
          
          style: 代碼格式修改, 注意不是 css 修改
          
          test: 測試用例修改
          
          chore: 其他修改, 比如構建流程, 依賴管理
          
          **分支操作**
            
          git branch 查看本地所有分支
          
          git branch -r 查看遠程所有分支
          
          git branch -a 查看本地和遠程所有分支
          
          git merge <分支名> 合并分支
          
          git merge --abort 合并分支出現沖突時,取消合并,一切回到合并前的狀態
          
          git branch <新分支名> 基于當前分支,新建一個分支
          
          git checkout --orphan <新分支名> 新建一個空分支(會保留之前分支的所有文件)
          
          git branch -D <分支名> 刪除本地某個分支
          
          git push <遠程庫名> :<分支名> 刪除遠程某個分支
          
          git branch <新分支名稱> <提交ID> 從提交歷史恢復某個刪掉的某個分支
          
          git branch -m <原分支名> <新分支名> 分支更名
          
          git checkout <分支名> 切換到本地某個分支
          
          git checkout <遠程庫名>/<分支名> 切換到線上某個分支
          
          git checkout -b <新分支名> 把基于當前分支新建分支,并切換為這個分支
          
          **遠程操作**
          
          git fetch [remote] 下載遠程倉庫的所有變動
          
          git remote -v 顯示所有遠程倉庫
          
          git pull [remote] [branch] 拉取遠程倉庫的分支與本地當前分支合并
          
          git fetch 獲取線上最新版信息記錄,不合并
          
          git push [remote] [branch] 上傳本地指定分支到遠程倉庫
          
          git push [remote] --force 強行推送當前分支到遠程倉庫,即使有沖突
          
          git push [remote] --all 推送所有分支到遠程倉庫
          
          **撤銷操作**
          
          git checkout [file] 恢復暫存區的指定文件到工作區
          
          git checkout [commit] [file] 恢復某個commit的指定文件到暫存區和工作區
          
          git checkout . 恢復暫存區的所有文件到工作區
          
          git reset [commit] 重置當前分支的指針為指定commit,同時重置暫存區,但工作區不變
          
          git reset --hard 重置暫存區與工作區,與上一次commit保持一致
          
          git reset [file] 重置暫存區的指定文件,與上一次commit保持一致,但工作區不變
          
          git revert [commit] 后者的所有變化都將被前者抵消,并且應用到當前分支
          
          reset:真實硬性回滾,目標版本后面的提交記錄全部丟失了
          
          revert:同樣回滾,這個回滾操作相當于一個提價,目標版本后面的提交記錄也全部都有
          
          **存儲操作**
          
          git stash 暫時將未提交的變化移除
          
          git stash pop 取出儲藏中最后存入的工作狀態進行恢復,會刪除儲藏
          
          git stash list 查看所有儲藏中的工作
          
          git stash apply <儲藏的名稱> 取出儲藏中對應的工作狀態進行恢復,不會刪除儲藏
          
          git stash clear 清空所有儲藏中的工作
          
          git stash drop <儲藏的名稱> 刪除對應的某個儲藏
          


          81.標準的CSS盒子模型及其和低版本的IE盒子模型的區別?

          標準(W3C)盒子模型:width=內容寬度(content) + border + padding + margin

          低版本IE盒子模型: width=內容寬度(content + border + padding)+ margin

          圖片展示:

          區別: 標準盒子模型盒子的height和width是content(內容)的寬高,而IE盒子模型盒子的寬高則包括content+padding+border部分。

          82.幾種解決IE6存在的bug的方法

          - 由`float`引起的雙邊距的問題,使用`display`解決;
          - 由`float`引起的3像素問題,使用`display: inline -3px`;
          - 使用正確的書寫順序`link visited hover active`,解決超鏈接`hover`點擊失效問題;
          - 對于`IE` 的`z-index`問題,通過給父元素增加`position: relative`解決;
          - 使用`!important`解決`Min-height`最小高度問題;
          - 使用`iframe`解決`select`在`IE6`下的覆蓋問題;
          - 使用`over: hidden`, `zoom: 0.08`, `line-height: 1px`解決定義1px左右的容器寬度問題;
          


          83.CSS選擇符有哪些?哪些屬性可以繼承?

          常見的選擇符有一下:
          ?
          `id`選擇器(`#content`),類選擇器(`.content`), 標簽選擇器(`div`, `p`, `span`等), 相鄰選擇器(`h1+p`), 子選擇器(`ul>li`), 后代選擇器(`li a`), 通配符選擇器(`*`), 屬性選擇器(`a[rel="external"]`), 偽類選擇器(`a:hover`, `li:nth-child`)
          ?
          可繼承的樣式屬性: `font-size`, `font-family`, `color`, `ul`, `li`, `dl`, `dd`, `dt`;
          ?
          不可繼承的樣式屬性: `border`, `padding`, `margin`, `width`, `height`;
          


          84.position的值relative和absolute定位原點?

          首先,使用`position`的時候,應該記住一個規律是‘**子絕父相**’。
          ?
          `relative`(相對定位): 生成相對定位的元素,定位原點是元素本身所在的位置;
          ?
          `absolute`(絕對定位):生成絕對定位的元素,定位原點是離自己這一級元素最近的一級`position`設置為`absolute`或者`relative`的父元素的左上角為原點的。
          ?
          `fixed` (老IE不支持):生成絕對定位的元素,相對于瀏覽器窗口進行定位。
          ?
          `static`:默認值。沒有定位,元素出現在正常的流中(忽略 `top`, `bottom`, `left`, `right`、`z-index` 聲明)。
          ?
          `inherit`:規定從父元素繼承 `position` 屬性的值。
          ?
          **更新一個屬性**
          ?
          `sticky`: (新增元素,目前兼容性可能不是那么的好),可以設置 position:sticky 同時給一個 (top,bottom,right,left) 之一即可。
          ?
          **注意**:
          ?
          - 使用`sticky`時,必須指定top、bottom、left、right4個值之一,不然只會處于相對定位;
          - `sticky`只在其父元素內其效果,且保證父元素的高度要高于`sticky`的高度;
          - 父元素不能`overflow:hidden`或者`overflow:auto`等屬性。
          

          85.CSS3有哪些新特性?

          關于`CSS`新增的特性,有以下:
          ?
          - 選擇器;
          - 圓角`(border-raduis)`;
          - 多列布局`(multi-column layout)`;
          - 陰影`(shadow)`和反射`(reflect)`;
          - 文字特效`(text-shadow)`;
          - 文字渲染`(text-decoration`);
          - 線性漸變`(gradient)`;
          - 旋轉`(rotate`)/縮放`(scale)`/傾斜`(skew)`/移動`(translate)`;
          - 媒體查詢`(@media)`;
          - `RGBA`和透明度 ;
          - `@font-face`屬性;
          - 多背景圖 ;
          - 盒子大小;
          - 語音;
          


          86.用純CSS創建一個三角形的原理是什么?

          實現步驟: 1.首先保證元素是塊級元素;2.設置元素的邊框;3.不需要顯示的邊框使用透明色。

          css: 
              * {margin: 0; padding: 0;}
              .content {
                  width:0;
                  height:0;
                  margin:0 auto;
                  border:50px solid transparent;
                  border-top: 50px solid pink;
              }
          ?
          html: 
              <div class="content"></div>
          


          87.什么是響應式設計?響應式設計的基本原理是什么?如何兼容低版本的IE?

          響應式網站設計(Responsive Web design)是一個網站能夠兼容多個終端,而不是為每一個終端做一個特定的版本。

          關于原理: 基本原理是通過媒體查詢(@media)查詢檢測不同的設備屏幕尺寸做處理。

          關于兼容: 頁面頭部必須有mate聲明的viewport。

          <meta name="’viewport’" content="”width=device-width," initial-scale="1." maximum-scale="1,user-scalable=no”"/>
          


          88.CSS優化、提高性能的方法有哪些?

          - 多個`css`可合并,并盡量減少`http`請求
          - 屬性值為0時,不加單位
          - 將`css`文件放在頁面最上面
          - 避免后代選擇符,過度約束和鏈式選擇符
          - 使用緊湊的語法
          - 避免不必要的重復
          - 使用語義化命名,便于維護
          - 盡量少的使用`!impotrant`,可以選擇其他選擇器
          - 精簡規則,盡可能合并不同類的重復規則
          - 遵守盒子模型規則
          


          89.display:inline-block 什么時候會顯示間隙?

          - 有空格時候會有間隙, 可以刪除空格解決;
          - `margin`正值的時候, 可以讓`margin`使用負值解決;
          - 使用`font-size`時候,可通過設置`font-size:0`、`letter-spacing`、`word-spacing`解決;
          


          90. 什么是外邊距重疊? 重疊的結果是什么?

          首先,外邊距重疊就是 `margin-collapse`。相鄰的兩個盒子(可能是兄弟關系也可能是祖先關系)的外邊距可以結合成一個單獨的外邊距。 這種合并外邊距的方式被稱為折疊,結合而成的外邊距稱為折疊外邊距。
          ?
          折疊結果遵循下列計算原則:
          ?
          - 兩個相鄰的外面邊距是正數時,折疊結果就是他們之中的較大值;
          - 兩個相鄰的外邊距都是負數時,折疊結果是兩者絕對值的較大值;
          - 兩個外邊距一正一負時,折疊結果是兩者的相加的和;
          


          91.有哪幾種隱藏元素的方法?

          - `visibility: hidden;` 這個屬性只是簡單的隱藏某個元素,但是元素占用的空間任然存在;
          - `opacity: 0;``CSS3`屬性,設置0可以使一個元素完全透明;
          - `position: absolute;` 設置一個很大的 left 負值定位,使元素定位在可見區域之外;
          - `display: none;` 元素會變得不可見,并且不會再占用文檔的空間;
          - `transform: scale(0);` 將一個元素設置為縮放無限小,元素將不可見,元素原來所在的位置將被保留;
          - `<div hidden="hidden">` `HTML5`屬性,效果和`display:none;`相同,但這個屬性用于記錄一個元素的狀態;
          - `height: 0;` 將元素高度設為 0 ,并消除邊框;
          - `filter: blur(0);` `CSS3`屬性,括號內的數值越大,圖像高斯模糊的程度越大,到達一定程度可使圖像消失`(此處感謝小伙伴支持)`;
          


          92.對BFC規范(塊級格式化上下文:block formatting context)的理解

          `BFC`規定了內部的`Block Box`如何布局。一個頁面是由很多個`Box`組成的,元素的類型和`display`屬性,決定了這個`Box`的類型。不同類型的`box`,會參與不同的`Formatting Context`(決定如何渲染文檔的容器),因此`Box`內的元素會以不用的方式渲染,也是就是說`BFC`內部的元素和外部的元素不會相互影響。
          ?
          定位方案:
          ?
          - 內部的`box`會在垂直方向上一個接一個的放置;
          - `box`垂直方向的距離由`margin`決定,屬于同一個`BFC`的兩個相鄰`Box`的`margin`會發生重疊;
          - 每個元素`margin box`的左邊,與包含塊`border box`的左邊相接觸;
          - `BFC`的區域不會與float box重疊;
          - `BFC`是頁面上的一個隔離的獨立容器,容器里面的元素不會影響到外面的元素;
          - 計算`BFC`的高度時,浮動元素也會參與計算。
          ?
          滿足下列條件之一就可以出發BFC:
          ?
          - 根元素變化,即`html`;
          - `float`的值不為`none`(默認);
          - `overflow`的值不為`visible`(默認);
          - `display`的值為`inline-block`, `tabke-cell`,`table-caption`;
          - `position`的值為`absolute`或`fixed`;
          


          93.經常遇到的瀏覽器的兼容性有哪些?原因,解決方法是什么,常用hack的技巧 ?

          (1)、問題:`png24`位的圖片在`ie`瀏覽器上出現背景。解決: 做成`png8`;
          ?
          (2)、問題:瀏覽器默認的`margin`和`padding`不同。 解決: 添加一個全局的`*{ margin: 0; padding: 0;}`;
          ?
          (3)、問題:`IE`下,可以使用獲取常規屬性的方法來獲取自定義屬性,也可以使用`getAttribute()`獲取自定義屬性,而`Firefox`下,只能使用`getAttribute()`獲取自定義屬性。 解決: 統一通過`getAttribute()`獲取自定義屬性;
          ?
          (4)、問題: `IE`下,`event`對象有`x`,`y`屬性,但是沒有`pageX`,`pageY`屬性,而`Firefox`下,`event`對象有`pageX`,`pageY`屬性,但是沒有`x`,`y`屬性。 解決: 使用`mX(mX=event.x ? event.x : event.pageX;)`來代替`IE`下的`event.x`或者`Firefox`下的`event.pageX`。
          


          94. 怎么讓Chrome支持小于12px 的文字?

          .shrink {
              -webkit-transform: scale(0.8);
              -o-transform: scale(1);
              display: inilne-block;
          }
          


          95. :link、:visited、:hover、:active的執行順序是怎么樣的?

          `L-V-H-A`,`l(link)ov(visited)e h(hover)a(active)te`,即用喜歡和討厭兩個詞來概括
          


          96. CSS屬性overflow屬性定義溢出元素內容區的內容會如何處理?

          - 參數是`scroll`的時候,一定會出滾動條;
          - 參數是`auto`的時候,子元素內容大于父元素時出現滾動條;
          - 參數是`visible`的時候,溢出的內容出現在父元素之外;
          - 參數是`hidden`的時候,溢出隱藏;
          


          97. css樣式引入方式的優缺點對比

          內嵌樣式: 優點: 方便書寫,權重高;缺點: 沒有做到結構和樣式分離;
          內聯樣式: 優點:結構樣式相分離; 缺點:沒有徹底分離;
          外聯樣式: 優點: 完全實現了結構和樣式相分離; 缺點: 需要引入才能使用;
          

          98. position 跟 display、overflow、float 這些特性相互疊加后會怎么樣?

          - `display`屬性規定元素應該生成的框的類型;
          - `position`屬性規定元素的定位類型;
          - `float`屬性是一種布局方式,定義元素往哪個方向浮動;
          ?
          **疊加結果**:有點類似于優先機制。`position`的值-- `absolute/fixed`優先級最高,有他們在時,`float`不起作用,`display`值需要調整。`float`或者`absolute`定位的元素,只能是塊元素或者表格。
          


          99. 什么是回流(重排)和重繪以及其區別?

          - 回流(重排),`reflow`:當`render tree`中的一部分(或全部)因為元素的規模尺寸,布局,隱藏等改變時而需要重新構建;
          - 重繪`(repaint`):當`render tree`中的一些元素需要更新屬性,而這些屬性只影響元素的外觀,風格,而不會影響布局時,稱其為**重繪**,例如顏色改變等。
          ?
          - 增加或者刪除可見的`dom`元素;
          - 元素的位置發生了改變;
          - 元素的尺寸發生了改變,例如邊距,寬高等幾何屬性改變;
          - 內容改變,例如圖片大小,字體大小改變等;
          - 頁面渲染初始化;
          - 瀏覽器窗口尺寸改變,例如`resize`事件發生時等;
          - 重排(回流)一定會引發重繪**。
          
          


          100.說說 px、em、rem的區別及使用場景

          **三者的區別:**
          ?
          - px是固定的像素,一旦設置了就無法因為適應頁面大小而改變。
          - em和rem相對于px更具有靈活性,他們是相對長度單位,其長度不是固定的,更適用于響應式布局。
          - em是相對于其父元素來設置字體大小,這樣就會存在一個問題,進行任何元素設置,都有可能需要知道他父元素的大小。而rem是相對于根元素,這樣就意味著,只需要在根元素確定一個參考值。
          ?
          **使用場景:**
          ?
          - 對于只需要適配少部分移動設備,且分辨率對頁面影響不大的,使用px即可 。
          - 對于需要適配各種移動設備,使用rem,例如需要適配iPhone和iPad等分辨率差別比較挺大的設備
          


          結尾因為內容太多所以需要完整版PDF的小伙伴可關注+私信【學習】自行查看


          東港新聞》春節特輯“我的家譜故事”播出預告(2018)

          https://mp.weixin.qq.com/s/nyLPYaoqg0i5QICWvkokaA

          1928,動蕩中的國與家——民國萊陽蓋氏族譜修撰之謎(2018)

          https://mp.weixin.qq.com/s/ACUd8DtcVSzBfLiEu8QPeA

          膠東尋根:登州府萊陽縣幾甲幾社這種地名,應如何理解?(2019)

          https://mp.weixin.qq.com/s/TQ4halUv9qnG4RzGtfFIWQ

          萊陽蓋姓在歷史上多次修撰族譜,為后世留存珍貴資料(2020)

          https://mp.weixin.qq.com/s/brKDAQMEj9J-YhvZlEVHpw

          膠東古跡:清順治年間萊陽縣“廢城社”碑文中的“蓋姓人”(2023)

          https://mp.weixin.qq.com/s/SjkbYtrdtw-DmTdg90HpCw

          膠東姓氏:淺談《萊陽縣志》與《蓋氏族譜》記載的蓋通(2024)

          https://mp.weixin.qq.com/s/wXd-cvd3rcS2ZjL-8CyUUA

          萊陽蓋氏家族在乾隆四十六年修過一次族譜,可惜未能傳世(2024)

          https://mp.weixin.qq.com/s/ZLZqmv1ve4nMWGc-ykOSZA

          對于《蓋氏族譜》北三支11世蓋天賜三子名字的見解(2023)

          https://mp.weixin.qq.com/s/IEPXAUnjtBd2u0CVFL7v3Q

          膠東姓氏:淺談萊陽蓋氏家族支派的劃分(2024)

          https://mp.weixin.qq.com/s/tY3wRSES7ZvG2RpGonxHPA

          中國家譜亂象——山東萊陽蓋氏家族續譜(2024)

          https://m.toutiao.com/article/7329540972633768448

          蓋姓起源與族群(2021)

          HTTP://d.wanfangdata.com.cn/periodical/ChlQZXJpb2RpY2FsQ0hJTmV3UzIwMjMxMjI2EhpRS0JKQkQyMDIxMjAyMTEwMTUwMDAxMzM3NhoIamxveGtuYTM%3D


          萊陽蓋氏家族世系歷史解析(2022)

          https://www.qitantan.net/thesis/detail/6527828

          通過家族基因關系推算萊陽《蓋氏族譜》的鼻祖十二世蓋盛賢的生存年代(2024)

          https://mp.weixin.qq.com/s/LkkjZ1Bb3Nc4qaEHwLeZKQ

          祖源樹之蓋氏父系樹

          https://www.theytree.com/surname/%E7%9B%96

          祖源樹之山東萊陽蓋氏家族

          https://www.theytree.com/?snp=MF56411

          23魔方之蓋姓起源分布和家譜家族

          https://www.23mofang.com/ancestry/library-surname/5f34ee8eff5a3344d6a8a0af

          23魔方之山東萊陽蓋氏家族祖源分析

          https://www.23mofang.com/ancestry/family/5d4bea8ce956900001f4a60d

          華大-微基因網址

          https://www.wegene.com/

          Y染色體測序分析判讀

          https://www.yfull.com/

          中華蓋氏網

          http://www.10000xing.cn/x405/wjxsj.html


          主站蜘蛛池模板: 国产成人精品一区二区三区免费 | 免费高清在线影片一区| 国语精品一区二区三区| 中日av乱码一区二区三区乱码| 亚洲乱码一区二区三区国产精品| 日本一区二区在线不卡| 国产乱码精品一区二区三区中| 国产精品第一区第27页| 精品无码中出一区二区| 人妖在线精品一区二区三区| 久久国产精品免费一区| 国产成人av一区二区三区不卡| 国产乱码一区二区三区| 亚洲无线码一区二区三区| 日韩精品一区二区三区影院| 国产一区二区三区日韩精品| 日韩精品无码一区二区三区AV| 亚洲一区二区三区乱码A| 免费无码一区二区| 奇米精品视频一区二区三区| 天堂资源中文最新版在线一区| 亚洲无线码在线一区观看| 国产精品丝袜一区二区三区 | 日本精品一区二区三区四区| 精品无码人妻一区二区三区18 | 波多野结衣一区二区免费视频| 国产精品一区二区久久| 在线欧美精品一区二区三区 | 国产人妖视频一区二区| 日本一区二区在线| 国产精品第一区揄拍| 亚洲精品无码一区二区| 精品久久久久久中文字幕一区| 日本精品一区二区在线播放| 精品一区二区三区在线观看l | 精品女同一区二区三区免费播放 | 奇米精品一区二区三区在| 久久婷婷久久一区二区三区| 久久国产精品无码一区二区三区 | 国精产品一区一区三区有限在线| 亚洲欧美日韩一区二区三区在线 |