杏耀註冊平台官網_git同步源碼到gitee和github

如何把我們的源碼同步到gitee或github遠程倉庫中

同步方式分以下幾種:

1、命令同步   

先查看下我們是否有遠程倉庫:git remote -v

如有就要刪除遠程倉庫或是同命令覆蓋,如全新安裝就不需要!

刪除已關聯的名為origin的遠程庫: git remote rm origin  

關聯GitHub的遠程庫: git remote add github git@github.com:xxx/xxx.git

關聯Gitee的遠程庫: git remote add gitee git@gitee.com:xxx/xxx.git

2、配置修改

修改.git文件夾內的config文件:

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
[remote "git"]
    url = git@github.com:chloneda/demo.git
    fetch = +refs/heads/*:refs/remotes/github/*
[branch "master"]
    remote = origin
    merge = refs/heads/master

將上述文件內容[remote “origin”]內容複製,修改origin名稱 

3、同步流程

上傳熱媒

git add . (將變更提交至緩存區)
git commit -m "提交說明(註釋)"    - update可以輸入你需要上傳的描述

提交命令

# 提交到github
git push github master  '- 將本地變更推送至遠程倉庫master分支'
#
提交到gitee
git push gitee master   '- 將本地變更推送至遠程倉庫master分支'

 查看狀態

git status

更放心代碼

# 從github拉取更新
git pull github
# 從gitee拉取更新
git pull gitee

項目推送時遇Git推送錯誤:

error: failed to push some refs to ‘git@gitee.com:name/project.git’

1、分析:

這個問題的產生是因為遠程倉庫與本地倉庫並不一致所造成。

2、解決方案:

那麼我們把遠程庫同步到本地庫就可以了。

執行命令:

git pull --rebase origin master

將遠程倉庫中的更新合併到本地倉庫,–rebase的作用是取消掉本地倉庫中剛剛的commit
然而未果,出現錯誤:

error: src refspec master does not match any

分析:引起該錯誤的原因是,目錄中沒有文件,空目錄不能提交。

依次執行:

git pull origin master
git push origin master

踩到的坑

上述過程中,更新或提交代碼時可能會遇到fatal:refusing to merge unrelated histories (拒絕合併無關的歷史) 錯誤,解決辦法:

首先將遠程倉庫和本地倉庫關聯起來。

git branch --set-upstream-to=origin/remote_branch  your_branch

其中,origin/remote_branch是你本地分支對應的遠程分支,your_branch是你當前的本地分支。

然後使用git pull整合遠程倉庫和本地倉庫。

git pull --allow-unrelated-histories    (忽略版本不同造成的影響)

重新更新、提交即可。

注: 如遇到 Git沒有共同祖先的兩個分支合併 的情形請自行查詢!

報錯處理

git pull 的時候報如下錯誤代碼:
error: Pulling is not possible because you have unmerged files.
嘗試用git stash沒有作用。
Pull is not possible because you have unmerged files.

本地的push和merge會形成MERGE-HEAD(FETCH-HEAD), HEAD(PUSH-HEAD)這樣的引用。HEAD代表本地最近成功push后形成的引用。MERGE-HEAD表示成功pull后形成的引用。可以通過MERGE-HEAD或者HEAD來實現類型與svn revet的效果。

解決:

1.將本地的衝突文件衝掉,不僅需要reset到MERGE-HEAD或者HEAD,還需要–hard。沒有後面的hard,不會衝掉本地工作區。只會衝掉stage區。

git reset –hard FETCH_HEAD

2.git pull就會成功。

如我們現在在dev分支上,剛開發完項目,執行了下列命令:

git  add .
git commit -m '提交的備註信息'
git push -u origin dev

想將dev分支合併到master分支,操作如下:

1、首先切換到master分支上

git  checkout master

2、如果是多人開發的話 需要把遠程master上的代碼pull下來

git pull origin master
//如果是自己一個開發就沒有必要了,為了保險期間還是pull

3、然後我們把dev分支的代碼合併到master上

git  merge dev

4、然後查看狀態及執行提交命令

git status

On branch master
Your branch is ahead of 'origin/master' by 12 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
//上面的意思就是你有12個commit,需要push到遠程master上
> 最後執行下面提交命令
git push origin master

5其他命令

更新遠程分支列表
git remote update origin --prune

查看所有分支
git branch -a

刪除遠程分支Chapater6
git push origin --delete Chapater6

刪除本地分支 Chapater6
git branch -d Chapater6

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9816

沐鳴怎麼當代理?_Oss 對象服務存儲前端方法封裝

1、根據oss url 獲取文件數據流


  
   
    /** * @description 獲取文件數據流 * @author easten * @date 2020-07-16 * @export * @param {*} files 文件列表 * @returns 文件數據流對象 */ export function getFileStream(files) { return new Promise(resolve => { if (files.length > 0) { let data = []; files.forEach(item => { ossRepository .download(getFileUrl(item.url), p => {}) .then(res => { data.push({ name: `${item.name}${item.type}`, blob: res, }); if (data.length == files.length) { resolve(data); } }); }); } }); }
   
  

2、文件單個/多個下載


  
   
    export function fileDownload(files) { if (files instanceof Array) { // 多文件打包下載 getFileStream(files).then(datas => { // 拼裝壓縮包格式 if (datas.length > 0) { SaveMultipleFile(`附件.zip`, datas).then(() => { console.log('下載成功'); }); } }); } else { // 單文件下載 ossRepository .download(getFileUrl(files.url), progress => {}) .then(blob => { SaveSingleFile(`${files.name}${files.type}`, files.size, blob).then(a => { console.log('下載成功'); }); }); } } /** * @description 批量下載 * @author easten * @date 2020-07-13 * @export * @param {*} zipName 壓縮包名稱 * @param {*} files 需要壓縮的文件信息 */ export function SaveMultipleFile(zipName, files) { return new Promise((resolve,error) => { // 首先定義一個文件夾 let zip=new jsZIP(); files.forEach(a=>{ zip.file(a.name,a.blob); }); zip.generateAsync({type:"blob"}) .then(function(content) { saveAs(content, zipName); resolve(); }).catch(()=>{ error(); }); }); } 
   
  

3、常用的文件類型


  
   
    export let resourceIcon = { folder: 'folder-open', xls: 'file-excel', xlsx: 'file-excel', md: 'file-markdown', pdf: 'file-pdf', ppt: 'file-ppt', txt: 'file-text', doc: 'file-word', unknown: 'file-unknown', zip: 'file-zip', other: 'file', jpg: 'file-image', }; // 創建文件格式對應表 export const FileTypes = [ { k: 'audio/3gpp', v: '3gpp' }, { k: 'video/3gpp', v: '3gpp' }, { k: 'audio/ac3', v: 'ac3' }, { k: 'allpication/vnd.ms-asf', v: 'asf' }, { k: 'audio/basic', v: 'au' }, { k: 'text/css', v: 'css' }, { k: 'text/csv', v: 'csv' }, { k: 'application/msword ', v: 'dot' }, { k: 'application/xml-dtd', v: 'dtd' }, { k: 'image/vnd.dwg', v: 'dwg' }, { k: 'image/vnd.dxf', v: 'dxf' }, { k: 'image/gif', v: 'gif' }, { k: 'text/htm', v: 'htm' }, { k: 'text/html', v: 'html' }, { k: 'image/jp2', v: 'jp2' }, { k: 'image/jpeg', v: 'jpeg' }, { k: 'text/JavaScript', v: 'js' }, { k: 'application/JavaScript', v: 'js' }, { k: 'application/json', v: 'json' }, { k: 'audio/mpeg', v: 'mp2' }, { k: 'audio/mp4', v: 'mp4' }, { k: 'video/mpeg', v: 'mpeg' }, { k: 'application/vnd.ms-project', v: 'mpp' }, { k: 'application/ogg', v: 'ogg' }, { k: 'audio/ogg', v: 'ogg' }, { k: 'application/pdf', v: 'pdf' }, { k: 'image/png', v: 'png' }, { k: 'application/vnd.ms-powerpoint', v: 'ppt' }, { k: 'application/rtf', v: 'rtf' }, { k: 'text/rtf', v: 'rtf' }, { k: 'image/vnd.svf', v: 'svf' }, { k: 'image/tiff', v: 'tif' }, { k: 'text/plain', v: 'txt' }, { k: 'application/vnd.ms-works', v: 'wdb' }, { k: 'text/xml', v: 'xml' }, { k: 'application/xhtml+xml', v: 'xhtml' }, { k: 'application/xml', v: 'xml' }, { k: 'application/vnd.ms-excel', v: 'xls' }, { k: 'aplication/zip', v: 'zip' }, { k: 'pplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet', v: 'xlsx' }, ];
   
  

4、文件大小轉換


  
   
    /** * @description 文件大小轉換 * @author easten * @date 2020-07-08 * @export * @param {*} fileSize * @returns kb GB G */ export function FileSizeTrans(fileSize) { let size = ''; if (fileSize < 0.1 * 1024) { //如果小於0.1KB轉化成B size = fileSize.toFixed(2) + 'B'; } else if (fileSize < 0.1 * 1024 * 1024) { //如果小於0.1MB轉化成KB size = (fileSize / 1024).toFixed(2) + 'KB'; } else if (fileSize < 0.1 * 1024 * 1024 * 1024) { //如果小於0.1GB轉化成MB size = (fileSize / (1024 * 1024)).toFixed(2) + 'MB'; } else { //其他轉化成GB size = (fileSize / (1024 * 1024 * 1024)).toFixed(2) + 'GB'; } 
   
  

本文作者:
Easten
本文鏈接:https://www.cnblogs.com/dongteng/archive/2020/11/04/13926426.html

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9815

沐鳴下載_JavaScript 實現歸併排序

在本文中,我們學習 Merge Sort 背後的邏輯,並用 JavaScript 實現。最後,在空間和時間複雜度方面將歸併排序與其他算法進行比較。

歸併排序背後的邏輯

歸併排序使用分而治之的概念對給定的元素列表進行排序。它將問題分解為較小的子問題,直到它們變得足夠簡單以至可以直接解決為止。

以下是歸併排序的步驟:

  1. 將給定的列表分為兩半(如果列表中的元素數為奇數,則使其大致相等)。
  2. 以相同的方式繼續劃分子數組,直到只剩下單個元素數組。
  3. 從單個元素數組開始,合併子數組,以便對每個合併的子數組進行排序。
  4. 重複第 3 步單元,直到最後得到一個排好序的數組。

以數組 [4, 8, 7, 2, 11, 1, 3] 為例,讓我們看一下歸併排序是如何工作的:


用 JavaScript 實現歸併排序

首先實現一個將兩個已排序子數組合併為一個已排序數組的函數 merge() 。要注意着兩個子數組是已經被排好序的,這一點非常重要, merge() 函數只用於其進行合併。

可以通過遍歷這兩個子數組來實現:

function merge(left, right) {
    let arr = []
    // 如果任何一個數組為空,就退出循環
    while (left.length && right.length) {
        // 從左右子數組的最小元素中選擇較小的元素
        if (left[0] < right[0]) {
            arr.push(left.shift())  
        } else {
            arr.push(right.shift()) 
        }
    }
    
    // 連接剩餘的元素,防止沒有把兩個數組遍歷完整
    return [ ...arr, ...left, ...right ]
}

在這個函數中,通過把兩個排好序的子數組(left、right)合併來獲得一個排好序的大數組。首先,創建一個空數組。之後在 left 和 right 兩個子數組中最小元素中的較小的一個,並將其添加到空數組。我們只需要檢查 left 和 right 子數組中的第一個元素,因為它們是已排好序的。

在這個過程中,從子數組中刪除了被選擇的元素(通過 shift() 函數實現)。繼續這個過程,直到其中一個子數組變為空。最後把非空子數組的剩餘元素(因為它們已經被排序)插入主數組的最後面。

現在有了合併兩個已排序數組的代碼,接下來為實現歸併排序算法的最終代碼。這意味着要繼續分割數組,直到最終只包含一個元素的數組為止:

function mergeSort(array) {
  const half = array.length / 2
  
  if(array.length < 2){
    return array 
  }
  
  const left = array.splice(0, half)
  return merge(mergeSort(left),mergeSort(array))
}

在代碼中先確定中點,並用 splice() 函數將數組分為兩個子數組。如果元素數量為奇數,則左側的元素數量會少一個。不斷的劃分數組,直到剩下單個元素的數組(array.length < 2)。然後用之前實現的 merge() 函數合併子數組。

代碼實現後用前面的用例測試一下:

array = [4, 8, 7, 2, 11, 1, 3];
console.log(mergeSort(array));

輸出符合預期:

1,2,3,4,7,8,11

歸併排序的效率

歸併排序的最差時間複雜度為 $O(n\\log n)$,與快速排序的最佳情時間複雜度相同。歸併排序是目前最快的排序算法之一。

與快速排序不同,歸併排序不是in-place排序算法,這意味着除了輸入數組之外,它還會佔用額外的空間。這是因為我們使用了輔助數組來存儲子數組。歸併排序的空間複雜度為 $O(n)$。

歸併排序的另一個優點是非常適合多線程,因為每個被劃分出的一半都可以單獨排序。另一種常見的減少歸併排序運行時間的方法是在到達相對較小的子數組時(大約 7 個元素)使用插入排序。這是因為插入排序在處理小型或幾乎排好序的數組時表現非常好。

總結

在本文中,我們了解了Merge Sort算法背後的邏輯,並用 JavaScript 實現。它是基本排序算法之一,可以幫助我們更好的了解分治法策略。

原文:https://stackabuse.com/merge-sort-in-javascript/

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/9812

    沐鳴註冊_為Vue的惰性加載加一個進度條

    簡介

    通常用 vue.js 編寫單頁應用(SPA)時,當加載頁面時,所有必需的資源(如 JavaScript 和 css 文件)都會被一起加載。在處理大文件時,這可能會導致用戶體驗不佳。

    藉助 webpack,可以用 import() 函數而不是 import 關鍵字在 vue.js 中按需加載頁面。

    為什麼要按需加載?

    Vue.js 中 SPA 的典型工作方式是將所有功能和資源打包一併交付,這樣可以使用戶無需刷新頁面即可使用你的應用。如果你沒有為了按需加載頁面針對自己的應用進行明確的設計,那麼所有的頁面會被立即加載,或者提前使用大量內存進行不必要的預加載。

    這對有許多頁面的大型 SPA 非常不利,會導致使用低端手機和低網速的用戶體驗會很差。如果通過按需加載,用戶將不需要下載他們當前不需要的資源。

    Vue.js 沒有為動態模塊提供任何加載指示器相關的控件。即使進行了預取和預加載,也沒有對應的空間讓用戶知道加載的過程,所以還需要通過添加進度條來改善用戶體驗。

    準備項目

    首先需要一種讓進度條與 Vue Router 通信的方法。事件總線模式比較合適。

    事件總線是一個 Vue 實例的單例。由於所有 Vue 實例都有一個使用 $on和 $emit 的事件系統,因此可以用它在應用中的任何地方傳遞事件。

    首先在 components 目錄中創建一個新文件 eventHub.js:

    import Vue from 'vue'
    export default new Vue()

    然後把 webpack 配置為禁用預取和預加載,這樣就可以針對每個函數單獨執行此類操作,當然你也可以全局禁用它。在根文件夾中創建一個 vue.config.js 文件並添加禁用預取和預加載的相關配置:

    module.exports = {
        chainWebpack: (config) => {
            // 禁用預取和預加載
            config.plugins.delete('prefetch')
            config.plugins.delete('preload')
        },
    }

    添加路由和頁面

    用 npx 安裝 Vue router 並使用:

    $ npx vue add router

    編輯位於 router/index.js 下的 router 文件並更新路由,以便可以用 import() 函數代替 import 語句:

    以下默認配置:

    import About from '../views/About.vue'
    {
        path: '/about',
        name: 'About',
        component: About
    },

    將其改為:

    {
        path: '/about',
        name: 'About',
        component: () => import('../views/About.vue')
    },

    如果希望可以選擇按需加載某些頁面,而不是全局禁用預取和預加載,可以用特殊的 Webpack 註釋,不要在 vue.config.js 中配置 Webpack:

    import(
        /* webpackPrefetch: true */
        /* webpackPreload: true */
        '../views/About.vue'
    )

    import() 和 import 之間的主要區別是在運行時加載由 import() 加載的 ES 模塊,在編譯時加載由 import 加載的 ES 模塊。這就意味着可以用 import() 延遲模塊的加載,並僅在必要時加載。

    實現進度條

    由於無法準確估算頁面的加載時間(或完全加載),因此我們無法真正的去創建進度條。也沒有辦法檢查頁面已經加載了多少。不過可以創建一個進度條,並使它在頁面加載時完成。

    由於不能真正反映進度,所以描繪的進度只是進行了隨機跳躍。

    先安裝 lodash.random,因為在生成進度條的過程中將會用這個包產生一些隨機數:

    $ npm i lodash.random

    然後,創建一個 Vue 組件 components/ProgressBar.vue:

    <template>
        <div :class="{'loading-container': true, loading: isLoading, visible: isVisible}">
            <div class="loader" :style="{ width: progress + '%' }">
                <div class="light"></div>
            </div>
            <div class="glow"></div>
        </div>
    </template>

    接下來向該組件添加腳本。在腳本中先導入 random 和 $eventHub,後面會用到:

    <script>
    import random from 'lodash.random'
    import $eventHub from '../components/eventHub'
    </script>

    導入之後,在腳本中定義一些後面要用到的變量:

    // 假設加載將在此時間內完成。
    const defaultDuration = 8000 
    // 更新頻率
    const defaultInterval = 1000 
    // 取值範圍 0 - 1. 每個時間間隔進度增長多少
    const variation = 0.5 
    // 0 - 100. 進度條應該從多少開始。
    const startingPoint = 0 
    // 限制進度條到達加載完成之前的距離
    const endingPoint = 90 

    然後編碼實現異步加載組件的邏輯:

    export default {
        name: 'ProgressBar',
        
        data: () => ({
            isLoading: true, // 加載完成后,開始逐漸消失
            isVisible: false, // 完成動畫后,設置 display: none
            progress: startingPoint,
            timeoutId: undefined,
        }),
    
        mounted() {
            $eventHub.$on('asyncComponentLoading', this.start)
            $eventHub.$on('asyncComponentLoaded', this.stop)
        },
    
        methods: {
            start() {
                this.isLoading = true
                this.isVisible = true
                this.progress = startingPoint
                this.loop()
            },
    
            loop() {
                if (this.timeoutId) {
                    clearTimeout(this.timeoutId)
                }
                if (this.progress >= endingPoint) {
                    return
                }
                const size = (endingPoint - startingPoint) / (defaultDuration / defaultInterval)
                const p = Math.round(this.progress + random(size * (1 - variation), size * (1 + variation)))
                this.progress = Math.min(p, endingPoint)
                this.timeoutId = setTimeout(
                    this.loop,
                    random(defaultInterval * (1 - variation), defaultInterval * (1 + variation))
                )
            },
    
            stop() {
                this.isLoading = false
                this.progress = 100
                clearTimeout(this.timeoutId)
                const self = this
                setTimeout(() => {
                    if (!self.isLoading) {
                        self.isVisible = false
                    }
                }, 200)
            },
        },
    }

    在 mounted() 函數中,用事件總線來偵聽異步組件的加載。一旦路由告訴我們已經導航到尚未加載的頁面,它將會開始加載動畫。

    最後其添加一些樣式:

    <style scoped>
    .loading-container {
        font-size: 0;
        position: fixed;
        top: 0;
        left: 0;
        height: 5px;
        width: 100%;
        opacity: 0;
        display: none;
        z-index: 100;
        transition: opacity 200;
    }
    
    .loading-container.visible {
        display: block;
    }
    .loading-container.loading {
        opacity: 1;
    }
    
    .loader {
        background: #23d6d6;
        display: inline-block;
        height: 100%;
        width: 50%;
        overflow: hidden;
        border-radius: 0 0 5px 0;
        transition: 200 width ease-out;
    }
    
    .loader > .light {
        float: right;
        height: 100%;
        width: 20%;
        background-image: linear-gradient(to right, #23d6d6, #29ffff, #23d6d6);
        animation: loading-animation 2s ease-in infinite;
    }
    
    .glow {
        display: inline-block;
        height: 100%;
        width: 30px;
        margin-left: -30px;
        border-radius: 0 0 5px 0;
        box-shadow: 0 0 10px #23d6d6;
    }
    
    @keyframes loading-animation {
        0% {
            margin-right: 100%;
        }
        50% {
            margin-right: 100%;
        }
        100% {
            margin-right: -10%;
        }
    }
    </style>

    最後將 ProgressBar 添加到 App.vue 或布局組件中,只要它與路由視圖位於同一組件中即可,它在應用的整個生命周期中都可用:

    <template>
        <div>
            <progress-bar></progress-bar>
            <router-view></router-view>
            <!--- 你的其它組件 -->
        </div>
    </template>
    
    <script>
    import ProgressBar from './components/ProgressBar.vue'
    export default {
           components: { ProgressBar },
    }
    </script>

    然後你就可以在頁面頂端看到一個流暢的進度條

    為延遲加載觸發進度條

    現在 ProgressBar 正在事件總線上偵聽異步組件加載事件。當某些資源以這種方式加載時應該觸發動畫。現在向路由添加一個路由守護來接收以下事件:

    import $eventHub from '../components/eventHub'
    
    router.beforeEach((to, from, next) => {
        if (typeof to.matched[0]?.components.default === 'function') {
            $eventHub.$emit('asyncComponentLoading', to) // 啟動進度條
        }
        next()
    })
    
    router.beforeResolve((to, from, next) => {
        $eventHub.$emit('asyncComponentLoaded') // 停止進度條
        next()
    })

    為了檢測頁面是否被延遲加載了,需要檢查組件是不是被定義為動態導入的,也就是應該為 component:() => import(‘…’) 而不是component:MyComponent。

    這是通過 typeof to.matched[0]?.components.default === ‘function’ 完成的。帶有 import 語句的組件不會被歸為函數。

    總結

    在本文中,我們禁用了在 Vue 應用中的預取和預加載功能,並創建了一個進度條組件,該組件可显示以模擬加載頁面時的實際進度。

    原文:https://stackabuse.com/lazy-loading-routes-with-vue-router/

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/9765

    沐鳴平台網址_淺析script標籤的async和defer屬性

    前端當然要從 html 開始,我們來聊聊在 script 標籤中加上 async/defer 時的功能及差異。

    都明白的道理

    我們都知道,瀏覽器解析 html 是一行一行按照順序向後讀取的,在傳統的寫法中,當瀏覽器讀到 <script> 時,便會暫停解析 DOM,同時立即開始下載 <script> 中定義的資源,並在下載完成后立刻執行。由於這樣的特性,可能會造成 DOM 樹在還沒有完全解析時就開始執行 JavaScript,需要操作 DOM 的程序可能因此無法正確執行,從而造成許多問題;或是由於 <script> 中的資源下載、執行時間過程,用戶會卡在白畫面,並會產生覺得網站太慢不好用之類的體驗。

    而解決方法也很簡單,我們需要把 <script> 標籤的位置都放到 <body> 的最後一行來避免 DOM 樹解析不完全的問題,但是在複雜的網站中, HTML、JavaScript 的個頭都很大,需要等到整個 DOM 樹都載入完成才開始下載 <script> 內的資源,從網站讀取完成到可操作,會產生明顯的延遲感。

    那這種問題該怎麼解決呢?

    從HTML4 開始,<script> 多了 defer 屬性,而 HTML5 則多了 async,兩者都是用來幫助開發者控制 <script> 內資源的載入及執行順序,以及避免 DOM 的解析被資源下載卡住的。


    defer

    defer 的意思是延遲(Deferred),在 HTML4.01 規範 中規定:

    設置后,這個布爾屬性會向用戶代理提示該腳本將不會生成任何網頁內容(例如,JavaScript中不會生成 “document.write”),因此,用戶代理可以繼續解析和渲染。

    也就是說,在加上 defer 屬性后,瀏覽器會繼續解析、渲染畫面,而不會因為需要載入<script> 內的資源而卡住;實際執行時,會在 DOMContentLoaded 執行之前,由上到下的依照擺放順序觸發。

    聽起來很方便對吧?但要提醒各位,雖然 W3C 規範上說 defer 屬性會是一個布爾值,但 IE9 以前的版本是自定義的,即使寫成 <script defer=”false”> 仍然會有 defer 的效果,使用時要特別注意。

    又是你這個老不死的 IE……

    async

    async 的意思是異步(Asynchronous),在 HTML5 規範 中規定:

    …如果存在 async 屬性,則腳本將會在可用時立即異步執行 …

    在 <script> 標籤中加上 async 屬性后,與defer 的相同點是也會在後台執行下載,但不同的是當下載完成會馬上暫停 DOM 解析(如果還沒有解析完成的話),並開始執行 JavaScript。因為下載完成後會立即執行,加上 async 屬性后,就無法保證執行順序了。

    這個屬性在標準中,同時也支持通過 JavaScript 動態插入 <script> 的情況。例如:

    const script = document.createElement('script')
    script.src = "/something/awesome.js"
    document.body.append(script)

    動態創建的 <script>,默認就是異步載入;但可以通過設定屬性將它關閉:

    script.async = false

    type=”module”

    在主流的現代瀏覽器中,<script> 的屬性可以加上 type=”module”。這時瀏覽器會認為這個文件是一個JavaScript 模塊,其中的解析規則、執行環境會略有不同;這時 <script> 的默認行為會像是 defer 一樣,在後台下載,並且等待 DOM 解析、渲染完成之後才會執行,所以 defer 屬性無法在 type=”module” 的情況下發生作用。但同樣可以通過 async 屬性使它在下載完成后即刻執行。

    用法

    現在你應該明白這兩個屬性的特點了,那麼該怎樣正確地使用呢?

    defer 由於後台載入、不打斷渲染及確保執行順序的特點,基本上在沒特殊需求的情況下,在 <script> 中設置一下就行了;當然 <script> 本身的擺放順序還是要稍微留心一下。

    async 比較特別,因為在下載後會立刻執行,且不保證執行順序,一般常見的應用是設定在完全獨立的小小模塊中,例如背景Logo、頁面廣告等,在避免造成使用者體驗變差的同時,盡量早的產生效果。

    現在前端開發大都通過 webpack 等打包工具來輔助處理,很少有自己設定這些屬性的機會;開發者可以通過 script-ext-html-webpack-plugin 等插件的幫助,將切分好的 Chunk 設定個別需要的 <script> 屬性。

    總結

    async 及 defer 是 <script> 專屬的屬性,對於網頁中的其他資源,可以通過 <link> 的preload、prefetch 屬性,來幫我們延遲加載 未來才需要用到的資源。

    雖然 <script> 的async、defer 這些屬性的設置大都已經包含在現代框架的打包流程中了,但只有紮實的認識這些網頁最基礎的規範,才能明白自己寫出來的代碼最後會產生什麼效果。

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/9829

    沐鳴平台_PWA的概念以及用途

    PWA英文為Progressive Web Apps,即漸進式Web應用,是提升Web Apps體驗的一種新方法。

    PWA簡介

    PWA 能做到原生應用的體驗不是靠特指某一項技術,而是經過應用一些新技術進行改進,在安全、性能和體驗三個方面都有很大提升,PWA 本質上是 Web App,藉助一些新技術也具備了 Native App 的一些特性,兼具 Web App 和 Native App 的優點。

    PWA的特點

    • 採用https協議(PWA的基礎,沒有 HTTPS,就沒有 Service Worker)
    • 使用Service Worker(離線或者網絡較差的情況下正常訪問)
    • Manifest(允許將站點添加至主屏幕)
    • Notifications(允許服務器向用戶提示一些信息)

    Service Worker – 離線和緩存

    前端有很多性能優化的手段,包括 CDN、css Sprite、文件的合併壓縮、異步加載、資源緩存等等。其實絕大部分情況是在干一件事情,那就是盡量降低一個頁面的網絡請求成本從而縮短頁面加載資源的時間並降低用戶可感知的延時。當然減少用戶可感知的延時也不僅僅是在網絡請求成本層面,還有瀏覽器渲染效率,代碼質量等等,而Service Worker就是一種提升體驗的的東東。

    什麼是 Service Worker

    Service Worker最重要的特點是**持久的離線緩存**,這是它區別於Web Worker的主要能力。

    Service Worker功能和特性:

    • 必須在HTTPS環境下才能工作
    • 獨立的 worker 線程
    • 一旦被 install,就永遠存在,除非被手動 unregister
    • 用到的時候可以直接喚醒,不用的時候自動睡眠
    • 能向客戶端推送消息
    • 不能直接操作DOM
    • 異步實現

    Service Worker的作用域

    Scope規定了Service Worker的作用範圍。它在註冊的時候已經明確了,如代碼navigator.serviceWorker.register(‘/sw.js’, {scope: ‘/’}),其作用的範圍只能是它本身與它的子路徑。

    也就是說sw.js文件在根目錄下,則它的作用域在根目錄及子路徑下,如果sw.js在/assets,則它的作用域只能在/assets及其子路徑下,其他目錄的serviceWorker不生效。

    關於作用域的誤解,Service Worker的作用域是指在瀏覽器地址欄上輸入的url作用域,比如我設置作用域為/test/,那麼url地址上包含/test/的才會被緩存,其他比如我請求/a/index.html就算這個頁面裡面包含/test/img.jpg這張圖片也不會被緩存。我們可以通過navigator.serviceWorker.controller(在控制的輸入,查看是否為actived)來判斷某個client是否受控於service worker之下。

    Service Worker的調試

    Service Worker必須在HTTPS環境和本地環境下(127.0.0.1和localhost)才能被註冊成功。

    • offline 可以將 DevTools 切換至離線模式。

    • Update on reload 強制Service Worker線程在每次頁面加載時更新。

    • Bypass for network 繞過Service Worker線程並強制瀏覽器轉至網絡尋找請求的資源。

    • Update 對指定的Service Worker線程執行一次更新。

    • Push 在沒有負載的情況下模擬推送通知。

    • Sync 模擬後台同步事件。

    • Unregister 註銷指定的Service Worker線程。

    • Source 當前正在運行的Service Worker線程的安裝時間。

    • Status Service Worker線程的狀態。此行上的数字(上方截圖中的#514)指示Service Worker線程已被更新的次數。如果啟用update on reload複選框,您會注意到每次頁面加載時此数字都會增大。

    • Clients 告訴Service Worker線程作用域的原點。 如果您已啟用 show all 複選框,focus 按鈕將非常實用。 在此複選框啟用時,系統會列出所有註冊的 Service Worker 線程。如果您點擊正在不同標籤中運行的Service Worker線程旁的focus 按鈕,Chrome會聚焦到該標籤。

    怎麼使用 Service Worker

    註冊

    要安裝 Service Worker, 我們需要通過在 js 主線程(常規的頁面里的 js )註冊 Service Worker 來啟動安裝,這個過程將會通知瀏覽器我們的 Service Worker 線程的 JavaScript 文件在什麼地方獃著。

    如下,register 方法的 scope 參數是可選的,用於指定你想讓 Service Worker 控制的內容的子目錄。

    if('serviceWorker' in navigator){
        // 在頁面onload時候註冊Service Worker
        window.addEventListener('load',function(){
            navigator.serviceWorker.register('/test.js',{scope: '/'})
            .then(function(success){
                console.log('註冊成功')
            })
            .catch(function(err){
    
            })
        })
    }
    

    查看是否註冊成功

    在chrome 瀏覽器輸入 chrome://inspect/#service-workers查看是否安裝成功。還可以通過 chrome://serviceworker-internals 來查看服務工作線程詳情

    安裝

    Service Worker註冊成功之後,我們的瀏覽器中已經有了一個屬於你自己web App的worker context啦, 在此時,瀏覽器就會馬不停蹄的嘗試為你的站點裏面的頁面安裝並激活它,並且在這裏可以把靜態資源的緩存給辦了。

    // 監聽 service worker 的 install 事件
    this.addEventListener('install',function(event){
        // 如果service worker已安裝,則會調用waitUntil回調函數
        event.waitUntil(
            // 安裝成功后操作緩存
            // caches.open()方法創建一個緩存
            caches.open('test-caches')
            .then(function(cache){
                // 添加需要緩存的資源
                return cache.addAll([
                    '/',
                    '/index.html',
                    '/main.css',
                    '/main.js'
                ])
            })
        )
    })
    

    自定義請求響應

    每次任何被 Service Worker 控制的資源被請求到時,都會觸發 fetch 事件,這些資源包括了指定的 scope 內的 html 文檔,和這些 html 文檔內引用的其他任何資源(比如 index.html 發起了一個跨域的請求來嵌入一個圖片,這個也會通過 Service Worker),這下 Service Worker 代理服務器的形象開始慢慢露出來了,而這個代理服務器的鈎子就是憑藉 scope 和 fetch 事件兩大利器就能把站點的請求管理的井井有條。

    話扯這麼多,代碼怎麼實現呢?你可以給 Service Worker 添加一個 fetch 的事件監聽器,接着調用 event 上的 respondWith() 方法來劫持我們的 HTTP 響應,然後你可以用自己的魔法來更新他們。

    // 監聽fetch方法
    this.addEventListener('fetch',function(event){
        // event.respondWith劫持http的響應內容
        event.respondWith(
            caches.match(event.request)
            .then(function(response){
                // 如果Service Worker有自己的返回,就直接返回,減少一次http請求
                if(response){
                    return response;
                }
                // 如果service worker沒有返回,那就得直接請求真實遠程服務
                var request = event.request.clone(); // 拷貝原始請求
                return fetch(request)
                    .then(function(httpRes){
                        // 請求失敗了,直接返回失敗的結果
                        if(!httpRes||httpRes.status!==200){
                            return httpRes;
                        }
                        // 請求成功,則緩存結果
                        var responseClone = httpRes.clone();
                        caches.open('test-caches')
                        .then(function(cache){
                            cache.put(event.request, responseClone);
                        });
                        return httpRes;
                    })
            })
        )
    })
    

    我們可以在 install 的時候進行靜態資源緩存,也可以通過 fetch 事件處理回調來代理頁面請求從而實現動態資源緩存。兩種方式可以比較一下:

    • on install 的優點是第二次訪問即可離線,缺點是需要將需要緩存的 URL 在編譯時插入到腳本中,增加代碼量和降低可維護性;
    • on fetch 的優點是無需更改編譯過程,也不會產生額外的流量,缺點是需要多一次訪問才能離線可用。

    Service Worker 版本更新

    test.js控制着頁面資源和請求的緩存,那麼如果緩存策略需要更新呢?也就是如果test.js有更新怎麼辦?test.js自身該如何更新?

    如果test.js內容有更新,當訪問網站頁面時瀏覽器獲取了新的文件,逐字節比對 /sw.js 文件發現不同時它會認為有更新啟動 更新算法,於是會安裝新的文件並觸發 install 事件。但是此時已經處於激活狀態的舊的 Service Worker 還在運行,新的 Service Worker 完成安裝後會進入 waiting 狀態。直到所有已打開的頁面都關閉,舊的 Service Worker 自動停止,新的 Service Worker 才會在接下來重新打開的頁面里生效。

    如果希望在有了新版本時,所有的頁面都得到及時自動更新怎麼辦呢?可以在 install 事件中執行 self.skipWaiting() 方法跳過 waiting 狀態,然後會直接進入 activate 階段。接着在 activate 事件發生時,通過執行 self.clients.claim() 方法,更新所有客戶端上的 Service Worker。

    // 安裝階段跳過等待,直接進入 active
    this.addEventListener('install', function (event) {
        event.waitUntil(self.skipWaiting());
    });
    self.addEventListener('activate', function (event) {
        event.waitUntil(
            Promise.all([
                // 更新客戶端
                self.clients.claim(),
                // 清理舊版本
                caches.keys().then(function (cacheList) {
                    return Promise.all(
                        cacheList.map(function (cacheName) {
                            if (cacheName !== 'test-caches') {
                                return caches.delete(cacheName);
                            }
                        })
                    );
                })
            ])
        );
    });
    

    Service Worker的生命周期

    Service Worker的生命周期

    • 安裝( installing ):這個狀態發生在 Service Worker 註冊之後,表示開始安裝,觸發 install 事件回調指定一些靜態資源進行離線緩存。install 事件回調中有兩個方法:
      • event.waitUntil():傳入一個 Promise 為參數,等到該 Promise 為 resolve 狀態為止。
      • self.skipWaiting():self 是當前 context 的 global 變量,執行該方法表示強制當前處在 waiting 狀態的 Service Worker 進入 activate 狀態。
    • 安裝后( installed ):Service Worker 已經完成了安裝,並且等待其他的 Service Worker 線程被關閉。

    • 激活( activating ):在這個狀態下沒有被其他的 Service Worker 控制的客戶端,允許當前的 worker 完成安裝,並且清除了其他的 worker 以及關聯緩存的舊緩存資源,等待新的 Service Worker 線程被激活。activate 回調中有兩個方法:
      • event.waitUntil():傳入一個 Promise 為參數,等到該 Promise 為 resolve 狀態為止。
      • self.clients.claim():在 activate 事件回調中執行該方法表示取得頁面的控制權, 這樣之後打開頁面都會使用版本更新的緩存。舊的 Service Worker 腳本不再控制着頁面,之後會被停止。
    • 激活后( activated ):在這個狀態會處理 activate 事件回調 (提供了更新緩存策略的機會)。並可以處理功能性的事件 fetch (請求)、sync (後台同步)、push (推送)。

    • 廢棄狀態 ( redundant ):這個狀態表示一個 Service Worker 的生命周期結束。

    Service Worker支持的事件

    • install:Service Worker 安裝成功后被觸發的事件,在事件處理函數中可以添加需要緩存的文件

    • activate:當 Service Worker 安裝完成后並進入激活狀態,會觸發 activate 事件。通過監聽 activate 事件你可以做一些預處理,如對舊版本的更新、對無用緩存的清理等。

    • message:Service Worker 運行於獨立 context 中,無法直接訪問當前頁面主線程的 DOM 等信息,但是通過 postMessage API,可以實現他們之間的消息傳遞,這樣主線程就可以接受 Service Worker 的指令操作 DOM。

    • fetch:當瀏覽器在當前指定的 scope 下發起請求時,會觸發 fetch 事件,並得到傳有 response 參數的回調函數,回調中就可以做各種代理緩存的事情了。

    • push:push 事件是為推送準備的。

    • sync:sync 事件由 background sync (後台同步)發出。background sync 配合 Service Worker 推出的 API,用於為 Service Worker 提供一個可以實現註冊和監聽同步處理的方法。

    其他

    報錯The path of the provided scope (‘/’) is not under the max scope allowed (‘/assets/’)

    Scope規定了Service Worker的作用範圍。例如,一個註冊在https://www.example.com/list路徑下的Service Worker,其作用的範圍只能是它本身與它的子路徑:https://www.sample.com/list、https://www.sample.com/list/test/等,而在https://www.sample.com/是無效的。同時,scope的默認值為./(注意,這裏所有的相對路徑不是相對於應用,而是相對於sw.js腳本的)。

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/9838

    沐鳴代理_如何開發一款前端工具

    基本上,使用任何成熟的語言都可以開發 cli 工具,作為一個前端小白,還是 JavaScript 比較順手,因此我們選用nodejs作為開發語言,開發一款node-cli工具。

    類似於腳手架工具,Node工具會自動去詢問你一些預設的問題,然後將你回答的結果結合一些模板文件,給你生成一個項目結構。

    那接下來我們以一個小型的腳手架工具為例,通過Nodejs完成一個Node工具,再來去深入體會一下Node工具的工作過程。

    那我們知道腳手架工具實際上就是一個node-cli應用,那創建腳手架就是創建一個node-cli應用,那這裏我們具體來操作一下,我們首先進入到命令行,通過mkdir去創建一個工具目錄

    mkdir samlpe-cli
    cd sample-cli

    在這個目錄下面我們通過yarn init 方式去初始化一個package.json文件

    yarn init

    有了這個文件之後通過編輯器打開這個目錄,緊接着我們需要在package.json中添加一個bin字段,用於去指定一下我們cli應用的入口文件, 我們這裏叫cli.js

    {
      "name": "sample-cli",
      "bin": "cli.js",
      ...
    }

    再然後我們添加這個cli.js文件,跟以往我們在Node中書寫的文件有所不同,cli的入口文件必須要有一個特定的文件頭, 也就是在這個文件頂部寫上這樣一句話 #! /usr/bin/env node 我們在這個文件中console.log一句話。

    #! /usr/bin/env node
    
    console.log('cli working')

    如果說你的操作系統是linux或者mac系統你還還需要去修改這個文件的讀寫權限,把他修改成755,這樣才可以作為一個cli的入口文件。

    我們回到命令行,我們通過yarn link 將這個模塊映射到全局

    yarn link

    這時候我們就可以在命令行使用sample這樣一個命令, 通過執行這個命令我們的console.log成功打印出來,表示代碼執行了。也就意味着我們這個cli基礎就已經ok了。

    sample-cli

    接下來我們實現一下腳手架的具體業務,也就是我們腳手架的工作過程。

    首先我們需要通過命令行交互的的方式去詢問用戶的一些信息,然後緊接着呢根據用戶反饋回來的結果我們去生成文件,

    1. 通過命令行交互的方式詢問用戶信息

    2. 根據用戶反饋回來的結果生成文件

    在Node當中去發起命令行交互詢問我們使用inquirer這樣一個模塊,那我們需要通過npm安裝一下這個模塊,我這裏使用yarn,安裝在依賴文件當中。

    yarn add inquirer --dev

    那有了這個模塊過後就可以在代碼中去載入, inquirer這個模塊提供一個叫做prompt的方法用於發起一個命令行的詢問。

    他可以接收一個數組參數,數組中每一個成員就是一個問題,可以通過type指定問題輸入方式,然後name指定返回值的鍵,message去指定屏幕上給用戶的一個提示,在promise的then裏面拿到這個問題接收到用戶的答案。

    我們這裏不着急往下寫,我們先通過console.log去打印一下。

    const inquirer = require('inquirer');
    
    inquirer.prompt([
        {
            type: 'input',
            name: 'name',
            message: 'Project name'
        }
    ]).then(answer => {
        console.log(answer);
    })

    回到控制台,我們命令行執行sample-cli, 此時就會提示我們需要輸入項目的名稱。

    sample-cli

    這樣就可以看到問題和返回的結果。這也就證明inquirer確實可以幫我們發起命令行交互詢問。

    那有了inquirer之後接下來我們要考慮的就是動態的去生成我們的項目文件。

    我們一般會根據模板去生成,所以我們在項目的跟目錄下新建一個templates目錄,在這個目錄下我們去新建一些模板。

    由於我們這裡是討論腳手架的工作過程,所以我們也不去關心模板裏面有什麼,我們就隨便寫點什麼。我們可以通過 <%%>去替換詢問過程中得到的答案。

    index.html

    <head>
        <title><%= name %></title>
    </head>

    我們還可以添加一些其他的模板文件,比如style.css

    style.css

    body {
        margin: 0;
        background-color: red;
    }

    回到cli.js文件, 這時候我們可以在得到問題答案的位置,根據用戶回答的問題去生成文件。不過在生成前我們一般會先將模板路徑和目標目錄確定下來。

    模板的目錄應該是項目當前目錄的templates,我們可以通過path獲取。

    const path = require('path');
    
    // 工具當前目錄
    const tmplDir = path.join(__dirname, 'templates');

    輸出的目標目錄一般是我們命令行在哪個目錄去執行就應該是哪個路徑,也就是cwd目錄

    const path = require('path');
    
    // 工具當前目錄
    const tmplDir = path.join(__dirname, 'templates');
    // 命令行所在目錄
    const destDir = process.cwd();

    明確這兩個目錄,我們就可以通過fs模塊去讀取一下模板目錄下一共有哪些文件。把這些文件全部輸入到我們的目標目錄,我們通過fs的readDir方法,這個方法會自動掃描目錄下的所有文件

    fs.readdir(tmplDir, (err, files) => {
        if (err) {
            throw err;
        }
        files.forEach(file => {
            console.log(file); // 得到每個文件的相對路徑
        })
    })

    我們可以通過模板引擎去渲染路徑對應的文件,先去安裝一款模板引擎,這裏我們使用ejs

    yarn add ejs --dev

    安裝過後,回到代碼中引入這個模板引擎, 通過模板引擎提供的renderFile去渲染這個路徑對應的文件。

    第一個參數是文件的絕對路徑,第二個參數是模板引擎在工作的時候的數據上下文,第三個參數是回調函數,也就是我們在渲染成功過後的回調函數,當然如果你在渲染過程中出現了意外那你可以通過throw err的方式把這個錯誤拋出去。

    我們可以先把result通過打印的方式打印出來看一下。

    const fs = require('fs');
    const path = require('path');
    const inquirer = require('inquirer');
    const ejs = require('ejs');
    
    // 工具當前目錄
    const tmplDir = path.join(__dirname, 'templates');
    // 命令行所在目錄
    const destDir = process.cwd();
    
    inquirer.prompt([
        {
            type: 'input',
            name: 'name',
            message: 'Project name'
        }
    ]).then(answer => {
        fs.readdir(tmplDir, (err, files) => {
            if (err) {
                throw err;
            }
            files.forEach(file => {
                ejs.renderFile(path.join(tmplDir, file), answer, (err, result) => {
                    if (err) {
                        throw err;
                    }
                    console.log(result);
                })
            })
        })
    })

    編輯完成之後我們運行一下腳手架工具。

    sample-cli

    此時打印出來的這個結果其實是已經經過模板引擎工作過後的結果,我們只需要將這個結果通過文件寫入的方式寫入到目標目錄就可以了,那目標目錄應該是通過path.join把我們destDir以及我們的file做一個拼接。內容就是我們這裏的result。

    files.forEach(file => {
        ejs.renderFile(path.join(tmplDir, file), answer, (err, result) => {
            if (err) {
                throw err;
            }
            fs.writeFileSync(path.join(destDir, file), result);
        })
    })

    完成過後我們找到一個新的目錄,使用一下這個腳手架

    sample-cli

    我們輸入項目名稱過後,就會發現他會自動把我們模板裏面的文件自動生成到對應的目錄裏面,至此我們就已經完成了一個非常簡單,非常小型的一個腳手架應用。

    那我們也回顧了一下腳手架的工作過程,其實腳手架的工作原理並不複雜,但是他的意義卻是很大的,因為他確實在創建項目環節大大提高了我們的效率。

    我們可以將自己的工具發布至npm上,提供給更多的人使用。

    至於發布npm也非常的簡單,首先我們需要註冊npm賬號,有兩種方式可以註冊,一種是登錄npm官網https://www.npmjs.com/, 另一種是使用命令npm adduser。

    npm adduser

    會提示你輸入用戶名,密碼,以及郵箱。

    註冊好后登錄npm賬號。

    npm login

    依次輸入第二步中第一種方法註冊的用戶名、密碼和郵箱。

    登錄成功后執行npm發布命令。

    npm publish

    注意:如果報錯:’You do not have permission to publish “samlpe-cli”. Are you logged in as the correct user?’

    表示包samlpe-cli名字已經在包管理器已經存在被別人用了,需要更該包名稱,我們可以前往package.json中的name中換一個名字。

    {
      "name": "sample-cli1",
      "version": "1.0.0",
      "bin": "cli.js",
      ...
    }

    再次執行publish命令。出現 +sample-cli1@1.0.0即表示發布成功。

    如果發布時報錯:no_perms Private mode enable, only admin can publish this module:

    表示當前不是原始鏡像,要切換回原始的npm鏡像

    npm config set registry https://registry.npmjs.org/

    至此你的node工具就可以提供給其他人使用了。

    如果需要更新你的工具,只要繼續執行npm publish就可以更新發布了,不過需要注意,每次發布都需要修改版本號version的值,同一個版本不允許發布兩次。

    {
      "name": "sample-cli1",
      "version": "1.0.1",
      "bin": "cli.js",
      ...
    }

    如果想要撤銷本次發布可以執行

    npm unpublish

    不過需要注意,只有在發包的24小時內才允許撤銷發布的包,超過24小時,就無法撤回了。

    作者:隱冬

    鏈接:https://juejin.cn/post/6899446879487180808

    來源:掘金

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/9893

    沐鳴登陸地址_Fetch還是Axios,哪個更適合HTTP請求?

    前端開發最重要的部分之一是通過發出HTTP請求與後端進行通信,我們有幾種方法可以異步地在JavaScript中進行API調用。

    幾年前,大多數應用程序都使用Ajax發送HTTP請求,Ajax代表異步JavaScript和XML。但是現在,開發人員通常會決定在 .fetch() API和Axios之間進行選擇。

    在本文中,我想比較這兩種方法,並簡要介紹一下基本知識和語法。除此之外,我還將比較在兩種情況下以及在錯誤處理中將數據轉換為jsON格式的過程。我還將討論HTTP攔截和下載進度。

    開始吧!

    Fetch概述和語法

    在構建Javascript項目時,我們可以使用window對象,並且它帶有許多可以在項目中使用的出色方法。這些功能之一是Fetch API,它提供了一種簡單的全局 .fetch() 方法,這是一種從API異步獲取數據的邏輯解決方案。

    讓我們看一下 .fetch() 方法的語法。

    fetch(url)
      .then((res) => 
        // handle response
      )
      .catch((error) => {
        // handle error
      })

    在上面的示例中,您可以看到簡單的獲取GET請求的語法。在 .fetch() 方法中,我們有一個強制性參數url,它返回一個Promise,可以使用Response對象來解決。

    .fetch() 方法的第二個參數是選項,它是可選的。如果我們不傳遞 options,請求總是GET,它從給定的URL下載內容。

    在選項參數裏面,我們可以傳遞方法或頭信息,所以如果我們想使用POST方法或其他方法,我們必須使用這個可選的數組。

    正如我之前提到的,Promise會返回Response對象,正因為如此,我們需要使用另一個方法來獲取響應的主體。有幾種不同的方法可以使用,取決於我們需要的格式:

    response.json()
    response.text()
    response.formData()
    response.blob()
    response.arrayBuffer()

    讓我們看一下帶有可選參數的代碼示例。

    fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(data)
    });
      .then((response) => response.json())
      .catch((error) => console.log(error))

    在上面的代碼示例中,你可以看到簡單的POST請求,包括 method、header 和 body params。然後我使用 json() 方法將響應轉換為JSON格式。

    現在,讓我們仔細看看axios

    Axios概述和語法

    Axios是一個Javascript庫,用於從Node.js或XMLHttpRequests或瀏覽器發出HTTP請求。作為一個現代的庫,它是基於Promise API的。

    axios 有一些優勢,比如對XSRF的保護或取消請求。

    為了能夠使用 axios 庫,我們必須將其安裝並導入到我們的項目中。可以使用CDN,npm或bower安裝 axios。現在,讓我們來看一個簡單的GET方法的語法。

    axios.get(url)
      .then(response => console.log(response));
      .catch((error) => console.log(error));

    在上面的代碼中,你可以看到我使用 .get() 方法創建一個簡單的GET請求。如果你想在函數中使用POST方法,那麼只需使用 .post() 方法代替,並將請求數據作為參數傳遞即可。

    當我們創建配置對象時,我們可以定義一堆屬性,最常見的是:

    baseUrl
    params
    headers
    auth
    responseType

    作為響應,axios 返回一個promise,該promise將與響應對象或錯誤對象一起解析。在響應對象中,具有以下值:

    data,這是實際的響應主體


    status,調用的HTTP狀態,例如200或404


    statusText,以文本消息形式返回的HTTP狀態,例如 ok


    headers,服務器發回標頭


    config,請求配置


    request,XMLHttpRequest對象

    現在,讓我們看一下帶有數據的POST方法的代碼示例。

    axios.post({
      '/url', 
      { name: 'John', age: 22},
      { options }
    })

    在上面的代碼中,你可以看到 post 方法,我們把config對象作為param,其中有URL、數據和附加選項。

    我們還可以將config對象定義為變量,然後像下面的示例一樣將其傳遞給 axios。

    const config = {
      url: 'http://api.com',
      method: 'POST',
      header: {
        'Content-Type': 'application/json'
      },
      data: {
        name: 'John',
        age: 22
      }
    }
    axios(config);

    在這裏,你可以看到所有的參數,包括URL、數據或方法,都在config對象中,所以在一個地方定義所有的東西可能更容易。

    JSON

    如前所述,當我們在使用 .fetch() 方法的時候,需要對響應數據使用某種方法,當我們在發送帶有請求的body時,需要對數據進行字符串化。

    在 axios 中,它是自動完成的,所以我們只需在請求中傳遞數據或從響應中獲取數據。它是自動字符串化的,所以不需要其他操作。

    讓我們看看如何從 fetch() 和 axios 獲取數據。

    // fetch
    fetch('url')
      .then((response) => response.json())
      .then((data) => console.log(data))
      .catch((error) => console.log(error))
    // axios
    axios.get('url')
      .then((response) => console.log(response))
      .catch((error) => console.log(error))

    在上面的例子中,你可以看到,使用 axios 我們沒有額外的一行代碼,在 .fetch()的例子中,我們必須將數據轉換為JSON格式。在一個較大的項目中,如果你創建了大量的調用,那麼使用 axios 來避免重複代碼會更舒服。

    錯誤處理

    在這一點上,我們還需要給 axios 點贊,因為處理錯誤是非常容易的。如果出現像404這樣的錯誤響應,promise就會被拒絕並返回一個錯誤,所以我們需要捕獲一個錯誤,我們可以檢查它是什麼類型的錯誤,就是這樣。讓我們看看代碼示例。

    axios.get('url')
      .then((response) => console.log(response))
      .catch((error) => {
        if (error.response) {
          // When response status code is out of 2xx range 
          console.log(error.response.data)
          console.log(error.response.status)
          console.log(error.response.headers)
        } else if (error.request) {
          // When no response was recieved after request was made
          console.log(error.request)
        } else {
          // Error
          console.log(error.message)
        }
      })

    在上面的代碼中,當響應良好時,我返回了數據,但是如果請求以任何方式失敗,我就能夠檢查 .catch() 部分中的錯誤類型並返回正確的消息。

    對於 .fetch() 方法,就比較複雜了。每次我們從 .fetch() 方法中得到響應時,我們需要檢查狀態是否成功,因為即使不是,我們也會得到響應。在 .fetch() 的情況下,只有當請求沒有完成時,promise才會被解決。讓我們看一下代碼示例。

    fetch('url')
      .then((response) => {
        if (!response.ok) {
          throw Error(response.statusText);
        }
        return response.json()
      })
      .then((data) => console.log(data))
      .catch((error) => console.log(error))

    在這段代碼中,我已經在承諾對象中檢查了代碼的狀態,如果響應有狀態 ok,那麼我就可以處理並使用 .json() 方法,但如果沒有,我必須在 .then() 裏面返回錯誤。

    為了方便和正確的錯誤處理,對於你的項目來說,axios 絕對會是一個更好的解決方案,但如果你正在構建一個只有一兩個請求的小項目,使用 .fetch() 是可以的,但你需要記住正確處理錯誤。

    下載進度

    當我們需要下載大量的數據時,一種跟蹤進度的方法會很有用,特別是當用戶的網絡速度很慢時。早期,為了實現進度指標,開發者使用了 XMLHttpRequest.onprogress 回調。在 .fetch() 和 axios 中,有不同的方法來實現。

    為了在 .fetch() 中跟蹤下載進度,我們可以使用其中一個 response.body 屬性,一個 ReadableStream 對象。它逐塊提供主體數據,並允許我們計算時間消耗了多少數據。

    在axios中,實現一個進度指示器也是可能的,而且更容易,因為存在一個現成的模塊,可以安裝和實現,它叫做Axios Progress Bar。

    如果你有大量的大數據要下載,你想跟蹤進度指標的進度,你可以用 axios 來管理,更容易更快,但 .fetch() 也提供了這種可能性,只是它需要更多的代碼來開發同樣的結果。

    HTTP攔截

    當我們需要檢查或改變我們從應用程序到服務器的HTTP請求時,或者以其他方式,例如,為了驗證,HTTP攔截可能是重要的。

    在 axios 的情況下,HTTP攔截是這個庫的關鍵功能之一,這就是為什麼我們不需要創建額外的代碼來使用它。讓我們看一下代碼示例,看看我們能做到多麼容易。

    // 請求攔截
    axios.interceptors.request.use((config) => {
      console.log('Request sent');
    })
    // 響應攔截
    axios.interceptors.response.use((response) => {
      // do an operation on response
      return response
    })
    axios.get('url')
      .then((response) => console.log(response))
      .catch((error) => console.log(error))

    在代碼中,您可以看到請求攔截和響應攔截。在第一種情況下,我創建了一個 console.log,告知發送請求的情況,在響應攔截中,我們可以對響應做任何操作,然後返回。

    .fetch() 默認不提供HTTP攔截功能,我們可以覆蓋 .fetch() 方法,定義發送請求過程中需要發生的事情,當然,這需要更多的代碼,可能比使用 axios 功能更複雜。

    總結

    在這篇文章中,我比較了用於創建HTTP請求的兩種方法,從簡單的概述開始,通過語法和一些重要的功能,如下載進度或錯誤處理。

    通過比較可以看出,對於有大量HTTP請求,需要良好的錯誤處理或HTTP攔截的應用,Axios是一個更好的解決方案。在小型項目的情況下,只需要幾個簡單的API調用,Fetch也是一個不錯的解決方案。

    在選擇項目的最佳解決方案時,還要注意一個因素,這是非常重要的。大多數瀏覽器和Node.js環境都支持Axios,而現代瀏覽器僅支持Fetch,並且某些版本可能會與舊版本一起發布。

    通過這些知識的了解,希望大家能夠選擇出最適合自己的方案,也希望大家覺得這個比較有幫助。

    來源:https://medium.com
    作者:Harsh Patel

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/9885

    沐鳴下載_二進制數與位運算符

    位運算符是基於二級制數進行操作的,即表示数字的 32 個數位,它由0和1組成…

    二進制數表示整數18

    ECMAScript整數有兩種類型,即有符號整數(允許用正數和負數)和無符號整數(只允許用正數)。在ECMAScript中,所有整数字面量默認都是有符號整數。

    有符號整數使用31位(第0到第30位)表示整數的數值,用第32位(第31位)表示整數的符號,0表示正數,1表示負數。數值範圍從-2147483648到2147483647。

    前31位中的每一位都表示2的冪,從第1 位(位 0)開始,表示20,第2位(位1)表示21。沒用到的位用0填充,即忽略不計。例如,下圖展示的是數18的表示法。

    18的二進製版本只用了前5位,它們是這個数字的有效位。把数字轉換成二進制字符串,就能看到有效位:

    var Num = 18;
    alert(Num.toString(2));	//輸出 "10010"
    

    這段代碼只輸出”10010”,而不是18的32位表示。其他的數位並不重要,因為僅使用前5位即可確定這個十進制數值。如下圖所示:

    二級制數表示負數-18

    負數也存儲為二進制代碼,不過採用的形式是二進制補碼。計算数字二進制補碼的步驟有三步:

    1.確定該数字的非負版本的二進製表示(例如,要計算-18的二進制補碼,首先要確定18的二進製表示) 2.求得二進制反碼,即要把0替換為1,把1替換為0 3.在二進制反碼上加1

    按照如上步驟計算:

    1)獲取18的二級製表示

    0000 0000 0000 0000 0000 0000 0001 0010
    

    2)求得二進制反碼

    1111 1111 1111 1111 1111 1111 1110 1101
    

    3)在二進制反碼上加1

    1111 1111 1111 1111 1111 1111 1110 1101
                                          1
    ---------------------------------------
    1111 1111 1111 1111 1111 1111 1110 1110
    

    因此,-18的二進製表示即1111 1111 1111 1111 1111 1111 1110 1110。記住,在處理有符號整數時,開發者不能訪問31位。

    特別需要注意的是,把負整數轉換成二進制字符串后,ECMAScript並不以二進制補碼的形式显示,而是用数字絕對值的標準二進制代碼前面加負號的形式輸出。例如:

    var iNum = -18;
    alert(iNum.toString(2));	//輸出 "-10010"
    

    這段代碼輸出的是”-10010”,而非二進制補碼,這是為避免訪問位31。所有整数字面量都默認存儲為有符號整數。只有ECMAScript的位運算符才能創建無符號整數。

    二級制數表示小數

    現代計算機中,一般都以IEEE 754標準存儲浮點數,這個標準的在內存中存儲的形式實際上是一種科學計數法 ,用符號,指數和尾數來表示,底數為2,也就是把浮點數表示為尾數乘以2的指數次方再添加上符號的形式。

      符號位 階碼 尾數 總長度
    float 1 8 23 32
    double 1 11 52 64

    浮點數在內存中的是使用科學計數法來表示的,我們先將浮點數用二進制的形式表示出來。

    // 整數部分
    // 例如12可以表示為
    // 2³*1 + 2²*1 + 2¹*0 + 2º*0  = 12
    // 8*1 + 4*1 + 2*0 + 1*0  = 12
    // 二級製表示為1100
    
    //小數部分
    // 例如0.18的小數部分可以表示為
    // ½*0 + ¼*0 +  ...  = 0.18
    // 0.5*0 + 0.25*0 + 0.125*1 + 0.0625*0 + 0.03125*1 + ...  = 0.18
    // 也就是說小數可以表示為2的附一、負二、負三次方...這樣的數相加
    // 二級製表示為0.001011100001010001111010111000010100011110101110000101
    

    12.18表示為double

    1)獲取二進製表示

    12.18使用二級製表示為1100.001011100001010001111010111000010100011110101110000101。

    2)獲取科學計數法的表示

    double類型尾數為52位,使用科學計數法表示,不足52位補0,超出部分刪除,最終結果為1.1000010111000010100011110101110000101000111101011100*2³。

    3)計算階碼

    double類型階碼共11位,可以表示-1024~1023,因為指數可以為負數,為了方便表示,先加上1023變為非負數,上面的3表示為3+1023=1026,二進製為10000000010。

    4)最終結果

    符號位,0為正,1為負。所以最終結果是

    0 10000000010 1000010111000010100011110101110000101000111101011100

    12.18表示為float

    1)獲取二進製表示

    12.18使用二級製表示為1100.001011100001010001111010111000010100011110101110000101。

    2)獲取科學計數法的表示

    float類型尾數為23位,使用科學計數法表示,不足52位補0,超出部分刪除,最終結果為1.10000101110000101000111*2³。

    3)計算階碼

    float類型階碼共8位,表示的範圍是-128~127,因為指數可以為負數,為了方便表示,先加上127變為非負數,上面的3表示為3+127=130,二進製為10000010。

    4)最終結果

    符號位,0為正,1為負。所以最終結果是

    0 10000010 10000101110000101000111

    二級制與8進制、16進制之間的轉換

    十進制數由0~9共10個数字字符組成,在十進制數的每一位上滿十進一,在十進制每一位中表示最大数字為9.

    二進制數由0和1兩個数字字符組成,在二進制中“逢二進一”,在二進制每一位中表示最大数字為1.

    八進制是由0~7共8個数字字符組成,在八進制中“逢八進一”,在八進制中每一位中表示最大数字為7.

    十六進制是由0~9、A、B、C、D、E、F共16個字符組成,在十六進制中“逢十六進一”,在十六進制中最大的數是F。

    // 如十進制的123使用二進制、八進制、十六進製表示
    // 二進制
    // 123 = 64*1 + 32*1 + 16*1 + 8*1 + 4*0 + 2*1 + 1*1
    // 1111011
    (123).toString(2)
    
    // 八進制
    // 123 = 8*8*1 + 8*7 + 1*3
    // 173
    (123).toString(8)
    
    // 十六進制
    // 123 = 16*7 + 1*11
    // 7b
    (123).toString(16)

    位運算操作

    程序中的所有數在計算機內存中都是以二進制的形式儲存的。位運算就是直接對整數在內存中的二進制位進行操作。

    1、位運算 NOT(~)

    位運算NOT由否定號(~)表示,位運算NOT是三步的處理過程:1)把運算數轉換成32位数字2)把二進制數轉換成它的二進制反碼3)把二進制數轉換成浮點數。它實質上是對数字求負,然後減1。

    var iNum1 = 25;		//25 等於 00000000000000000000000000011001
    var iNum2 = ~iNum1; //轉換為 11111111111111111111111111100110
    alert(iNum2);		//輸出 "-26"
    

    2、位運算 AND(&)

    兩個都為1才為1,否則為0。

    第一個数字中的數位 第二個数字中的數位 結果
    1 1 1
    1 0 0
    0 1 0
    0 0 0
    **3、位運算 OR( )**

    兩个中有一個為1結果就為1,否則為0。

    第一個数字中的數位 第二個数字中的數位 結果
    1 1 1
    1 0 1
    0 1 1
    0 0 0

    4、位運算 XOR(^)

    兩个中有且僅有一個為1結果就為1,否則為0。

    第一個数字中的數位 第二個数字中的數位 結果
    1 1 0
    1 0 1
    0 1 1
    0 0 0

    位運算常見使用場景

    1、判斷一個數是奇數還是偶數

    利用and運算 & 判斷一個數是奇數還是偶數,這是因為二進制的最末位為0表示該數為偶數,最末位為1表示該數為奇數。因此只需要把數和1進行and運算,根據結果就可以判斷。

    var val = 15;
    if(val&1 === 0){
        console.log('值為偶數')
    }
    if(val&1 === 1){
        console.log('值為奇數')
    }
    

    來自:https://www.kelede.win/posts/二進制數與位運算符/

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/9840

    沐鳴平台首頁_TypeScript的索引類型與映射類型,以及常用工具泛型的實現

    相信現在很多小夥伴都在使用 TypeScript(以下簡稱 TS),在 TS 中除了一些常用的基本類型外,還有一些稍微高級一點的類型,這些就是我本次文章要講的內容:索引類型與映射類型,希望小夥伴們看過這篇文章后能對 TS 有更深一步的理解。

    索引類型

    下面我通過一個官方的例子來說明下什麼是索引類型:

    function pluck(o, names) {
      return names.map((n) => o[n])
    }
    

    這是個簡單的函數,names 是一個數組,裏面是 key 值,我們可以從“o”裏面取出這些 key 值,理想情況下 names 裏面的 key 應該都是“o”裡面包含的,否則最終的結果裏面就會有 undefined,這個函數返回的結果也應該是“o”中都包含的 value 值,那麼我們如何才能做到這些類型約束呢,如果只用一些基礎類型,很難達到滿意的效果,下面使用索引類型改寫下:

    function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
      return names.map((n) => o[n])
    }
    
    interface Person {
      name: string
      age: number
    }
    let person: Person = {
      name: 'Jarid',
      age: 35
    }
    let strings: string[] = pluck(person, ['name']) // ok, string[]
    

    改寫后這個函數是一個泛型函數,泛型為 T 和 K,其中 K 有點特殊,K extends keyof T,是什麼意思呢,其中 keyof 就是索引類型查詢操作符,我們從字面意思理解,它就是 T 的 key,就是 T 上已知的公共屬性名的聯合,對於上面的代碼,keyof Person就是’name’|’age’,那麼K extends keyof T就是K extends ‘name’|’age’,這樣我們就獲取到了 Person 上所有 key 組成的一個聯合類型,然後參數o: T, names: K[],就很好理解了,names 就是 K 組成的一個數組。返回值中T[K][]我們需要拆開來看 T[K]和[],就是 T[K]組成的一個數組,那麼 T[K]是什麼類型呢,它就是索引訪問操作符,類似於 js 中對象的取值操作,不過這裏取的是類型,因為 K 是’name’|’age’,所以 T[K]就是string|number,這些就是索引類型,其實也不難理解,下面再說下映射類型,它和索引類型結合起來可以做很多事情。

    映射類型

    映射類型也很容易理解,我們先看一個簡單的例子

    type Keys = 'option1' | 'option2'
    type Flags = { [K in Keys]: boolean }
    

    這個就是一個簡單的映射類型,其中的in可以理解為是我們平時用的for…in,就是去遍歷 Keys,然後把 boolean 賦給每一個 key,上面的 Flags 得到的結果就是

    type Flags = {
      option1: boolean
      option2: boolean
    }
    

    很簡單吧,那麼這個東西有什麼用處呢,請看下面的例子:

    // Person
    type Person {
        name: string
        age: number
    }
    

    我們想把這個 Person 裏面的屬性都變成只讀的,像這樣:

    // Readonly Person
    type Person {
        readonly name: string
        readonly age: number
    }
    

    如果我們有很多這樣的類型,那麼改起來會很麻煩,因為每次都要把這個類型重新寫一遍。其實我們可以使用剛才的索引類型和映射類型來寫一個泛型:

    type Readonly<T> = {
      readonly [P in keyof T]: T[P]
    }
    

    [P in keyof T]就是遍歷 T 中的 key,T[P]就是當前的 key 的類型,其實[P in keyof T]: T[P]就是把 T 遍歷了一遍,但是我們在屬性前面加了個 readonly,這樣我們調用這個泛型的時候,它就會把傳入的類型的 key 遍歷一遍,遍歷的同時在前面加個 readonly,最終給我們返回一個新的類型。我們在調用的時候只需要這麼用:

    type Readonly<T> = {
      readonly [P in keyof T]: T[P]
    }
    type Person {
        name: string
        age: number
    }
    type ReadonlyPerson = Readonly<Person>
    

    索引類型和映射類型除了能實現 Readonly,還能實現很多有意思的東西,我們平時在使用 TS 的時候,TS 已經內置了一些常用的輔助泛型,剛才的 Readonly 就是其一,另外還有很多,我從 TS 的類型定義文件里找了一些,這些泛型從簡單到複雜的都有,但基本上都是用上面提到的兩個類型實現的,下面我們一起來分析一下。

    TS 常用的輔助泛型及其實現方式

    首先來看第一個

    /**
     * Make all properties in T optional
     */
    type Partial<T> = {
      [P in keyof T]?: T[P]
    }
    

    相信這個泛型很多人都用過,就是把類型都變成可選的,和剛才的 Readonly 是類似的實現方式,只是這個是在後面加了個問號,這樣一來屬性就變成可選的了。
    與之相對的還有一個 Required

    /**
     * Make all properties in T required
     */
    type Required<T> = {
      [P in keyof T]-?: T[P]
    }
    

    注意這個稍有點不同,它是-?,其實就是減去問號,這樣就可以把問號去掉,從而變成必選的屬性。再來看下一個

    /**
     * From T, pick a set of properties whose keys are in the union K
     */
    type Pick<T, K extends keyof T> = {
      [P in K]: T[P]
    }
    

    如果你理解了最開始的那個 pluck 函數,這個就很好理解了,我們傳入 T 和 K,其中 K 是 T 的 keys 組成的聯合類型,再看返回值[P in K]: T[P],就是把 K 遍歷了一遍,同時賦值上原類型,那麼綜合來看 Pick 就是幫我們提取出某些類型的,比如通過Pick<Person, ‘name’>我們就可以得到{name: string},再來看下一個

    /**
     * Exclude from T those types that are assignable to U
     */
    type Exclude<T, U> = T extends U ? never : T
    

    這個泛型傳入一個 T 和 U,然後它判斷了 T 是否屬於 U,屬於的話返回 never 否則返回原類型 T,注意 never 在最終的類型中是不會存在的,所以它可以幫助我們消除某些屬性,其實這個 Exclude 就是消除了T extends U的類型,比如我們使用Exclude<‘a’|’b’,’b’|’c’>,最終會得到’a’,與之相反的有:

    /**
     * Extract from T those types that are assignable to U
     */
    type Extract<T, U> = T extends U ? T : never
    

    這個正好相反,是從 T 中取出 U 中擁有的類型。

    有了 Exclude,我們就可以和 Pick 結合來實現另外一個:

    /**
     * construct a type with the properties of T except for those in type K.
     */
    type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
    

    這個泛型是先使用了Exclude<keyof T, K>,去除了 keyof T 中的 K,然後又使用 Pick 取出了這些類型,這樣我們就可以從 T 中去除 K 裡面包含的 keys 了,達到和 Pick 相反的效果。

    我們再來看另一個稍微複雜一點的

    type NonNullObject<O> = Pick<
      O,
      {
        [K in keyof O]: O[K] extends null | undefined ? never : K
      }[keyof O]
    >
    

    這個不是 TS 內置的類型,但也是一個很有用的類型,我們來一點一點分析。首先這個泛型使用了 Pick,我們知道 Pick 就是取出一些屬性,我們先看傳給 Pick 的第二個參數

    {
      [K in keyof O]: O[K] extends null | undefined ? never : K
    }[keyof O]
    
    

    它遍歷了 O 的 keys,然後進行了一個判斷,如果是extends null | undefined則返回 never,否則返回 K,K 就是 O 中的 key 值,注意這裏和之前的一些泛型有些不一樣,之前的都是O[K],而這裏的屬性的值還是 K,最終我們得到的是類似K:K這樣的東西,比如{name: string, age: null}這個,經過上面的轉化會變成{name:’name’, age:never},可能有些小夥伴還不清楚為什麼要這樣轉換,我們接着往下分析,經過這個轉換之後,又進行了一個操作[keyof O],對於 Person,keyof O 就是’name’|’age’,那麼這裏就就是{name:’name’, age:never}[‘name’|’age’],這樣就很清晰了,其實就是一個取值操作,這樣我們就可以得到’name’|never,還記得 never 的特性嗎,它可以幫我們消除一些類型,那麼最終的就是’name’,這也是為什麼我們寫成類似 K:K 這樣,就是要把 null|undefined 對應的 key 轉換成 never,然後再通過 keyof 把他們全都取出來,別忘了最外面還有一個 Pick,這樣我們就從原始類型中去除了 null|undefined。

    另外還有一個比較有用的是 ReturnType

    /**
     * Obtain the return type of a function type
     */
    type ReturnType<T extends (...args: any) => any> = T extends (
      ...args: any
    ) => infer R
      ? R
      : any
    

    它可以幫我們取到函數返回值的類型,這個 ReturnType 接收的一個參數是函數,然後進行了一個判斷T extends (…args: any) => infer R,就是判斷是否是函數,這裡有個東西是 infer,通過這個操作符我們可以獲取 R 的引用,就是函數的返回值,最終再把 R 返回出去,就獲得了函數 T 的返回值。

    其實除了我分析的這些泛型,TS 還內置了其他的很多泛型,比如還有獲取函數的參數的,獲取構造函數類型的,總的來說各種泛型基本上都可以用索引類型和映射類型實現,希望大家看過這篇文章后能多多使用這兩種類型,在自己的項目里也能開發一些常用的輔助泛型,來提升工作效率。

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/9892