2020-07 前端技術匯總

2020/07/30 周四

#什么是好的代碼?

在web前端方面,什么是好的代碼?好的代碼應該包含以下兩個特性

  • 高性能,低時延(性能優(yōu)化)
    • 熟悉數(shù)據(jù)結構與算法,減少時間復雜度或空間復雜度
    • 熟悉瀏覽器渲染基本原理、熟悉HTTP請求與響應細節(jié)、熟悉前端框架源碼、減少不必要的渲染開銷,提高加載速度
  • 可讀性、可維護性、可擴展性
    • 熟悉設計模式,封裝變化。代碼高內聚、低耦合、指責單一、高度復用。寫出好維護、好迭代、好擴展的代碼
    • 化繁為簡,形成特定代碼規(guī)范,注意命名、注釋。寫出人能看懂的代碼,不做騷操作。盡量保持簡單、易懂,在可擴展性和簡單之間尋找平衡

前端只要不是寫框架,性能問題會很少遇到。簡單來講,在實現(xiàn)功能的基礎上,代碼簡單、易懂、好維護迭代就很好了。技術始終是為業(yè)務需求服務的?;A建設是很重要的一個環(huán)節(jié),這樣有利于快速迭代開發(fā)

#vue使用js顯示彈窗組件

重點是使用vue的render函數(shù),把單文件vue組件,再封裝為一個函數(shù),掛載到vue的實例屬性后,其他地方直接調用該函數(shù)即可調用組件。

// 在main.js里注冊實例屬性
import showDialog from '@/views/jsDialog/index.js'
Vue.prototype.$showDialog = showDialog

// 其他地方直接使用 this.$showDialog(options) 即可調用組件

下面來看看實現(xiàn)思路,關于render函數(shù)createElement的options的配置,參見 createElement 參數(shù) - 深入數(shù)據(jù)對象(opens new window)

import Vue from "vue";
import DialogComponent from '@/views/jsDialog/src/index.vue'

let TheDialog = null
export default function showDialog(options) {
  // 如果未移除,先移除
  TheDialog && TheDialog.remove()

  TheDialog = create(DialogComponent, {
    on: {
      // 單文件組件內部可以emit該事件,銷毀TheDialog組件
      'close-dialog': () => {
        TheDialog.remove()
      }
    },
    props: {
      // 需要傳入的屬性,單文件組件需要使用props接收
      title: '標題',
      content: '內容' 
    }
    // 其他參數(shù)
    ...options
  })

  function create(Component, options) {
    // 先創(chuàng)建實例
    const vm = new Vue({
      render(h) {
        // h就是createElement,它返回VNode
        return h(Component, options);
      }
    }).$mount();

    // 手動掛載
    document.body.appendChild(vm.$el);

    // 銷毀方法
    const comp = vm.$children[0];
    comp.remove = function() {
      document.body.removeChild(vm.$el);
      vm.$destroy();
    };
    return comp;
  }
}

#2020/07/26 周日

#echarts餅圖label兩端對齊label距離引導線距離

注意echarts版本要是 v4.6 +

// 兩端對齊 + 引導線距離
{
name: '訪問來源',
type: 'pie',
minAngle: 90, // label最小扇區(qū)大小
label: {
    normal: {
        alignTo: 'edge', // label兩端對稱布局
        //  ECharts v4.6.0 版本起,提供了 'labelLine' 與 'edge' 兩種新的布局方式
        margin: 90, // 布局為兩端對稱時候需要外邊距防止圖表變形 數(shù)值隨意不要太大
        distanceToLabelLine: 0, // label距離引導線距離
        formatter: function(param) {
            return '{a|' + param.name + '}\n{hr|}\n' + '{d|' + param.value + '}';
        },
        rich: {
            a: {
                padding: [4, 10, 0, 10],  // 4邊距是文字和hr間距,此處的邊距10用于解決label和引導線有間距問題
                color: 'blue'
            },
            d: {
                padding: [0, 10, 4, 10],
                color: 'purple'
            },
            hr: {
                borderWidth: 1,
                width: '100%',
                height: 0,
                borderColor: ' '
            }
        }
    },

}

// 分隔線上線顯示內容 
label: {
    normal: {
        formatter: '{font|{c}}\n{hr|}\n{font|u0z1t8os%}',
        rich: {
            font: {
                fontSize: 20,
                padding: [5, 0],
                color: '#fff'
            },
            hr: {
                height: 0,
                borderWidth: 1,
                width: '100%',
                borderColor: '#fff'
            }
        }
    },
},
labelLine: {
    lineStyle: {
        color: '#fff'
    }
}

參考:

#element表單中,人數(shù)輸入框怎么限制只能輸入正整數(shù)

在人數(shù)這一欄,輸入時,前端需要確保輸入的只能是正整數(shù),且不能是負數(shù),且自動校正,來看看怎么實現(xiàn)

<template>
  <div>
    只能輸入正整數(shù): {{ peopleCount }}
    <el-input
      v-model="peopleCount"
      @keyup.native="keyUp"
      style="width:200px;margin:50px;"
    ></el-input>
  </div>
</template>

<script>
export default {
  data() {
    return {
      peopleCount: ""
    };
  },
  methods: {
    keyUp(e) {
      // 非數(shù)字全部轉換為''
      e.target.value = e.target.value.replace(/[^\d]/g, "");
      // 開始的0處理
      if ([0, "0"].includes(e.target.value)) {
        e.target.value = "";
      }
      this.peopleCount = e.target.value;
      return e.target.value;
    }
  }
};
</script>

有了上面的思路后,對于萬元輸入框怎么限制只能輸入最多保留兩位小數(shù)點的number類型數(shù)據(jù),可以思考下

#怎么開發(fā)vscode插件

在vue-cli項目中,每次修改vue.config.js都需要手動停止在運行,怎么一鍵就搞定呢?能不能開發(fā)個vscode插件

帶著這個問題,來看看vscode插件的開發(fā)。直接找vscode官方教程。按照文檔先來跑一個hello word

# Install Yeoman and VS Code Extension Generator with:
npm install -g yo generator-code

運行yo code,生成一個腳手架項目

guoqzuo-mac:vscodeExtension kevin$ yo code

     _-----_     ╭──────────────────────────╮
    |       |    │   Welcome to the Visual  │
    |--(o)--|    │   Studio Code Extension  │
   `---------′   │        generator!        │
    ( _′U`_ )    ╰──────────────────────────╯
    /___A___\   /
     |  ~  |     
   __'.___.'__   
 ′   `  |° ′ Y ` 

? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? zuo-restart
? What's the identifier of your extension? zuo-restart
? What's the description of your extension? A plugin use to auto restart vue.con
fig.js
? Initialize a git repository? Yes
? Which package manager to use? npm

Your extension zuo-restart has been created!

To start editing with Visual Studio Code, use the following commands:

     cd zuo-restart
     code .

Open vsc-extension-quickstart.md inside the new extension for further instructions
on how to modify, test and publish your extension.

For more information, also visit http://code.visualstudio.com and follow us @code.

這樣會創(chuàng)建一個空的項目,只注冊了helloworld命令,我們按照 vsc-extension-quickstart.md 里的說明運行demo

按F5,進入如下頁面,但并沒有像官網(wǎng)上的視頻那樣彈一個新的插件調試窗口,一直在運行中

vscode_plugin_1.png

網(wǎng)上說要裝一個 run code的 vscode插件,也裝了。后面發(fā)現(xiàn)還是不行,點擊正在生成,ctrl + c 就彈出一個名為 "擴展開發(fā)宿主" 的新窗口了,里面可以調試插件,如下圖

vscode_plugin_2.png

生成項目的入口是 extension.ts,他默認注冊了一個helloword命令,我們輸入命令就會顯示一個彈窗消息

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) {

    // Use the console to output diagnostic information (console.log) and errors (console.error)
    // This line of code will only be executed once when your extension is activated
    console.log('Congratulations, your extension "zuo-restart" is now active!');

    // The command has been defined in the package.json file
    // Now provide the implementation of the command with registerCommand
    // The commandId parameter must match the command field in package.json
    let disposable = vscode.commands.registerCommand('zuo-restart.helloWorld', () => {
        // The code you place here will be executed every time your command is executed

        // Display a message box to the user
        vscode.window.showInformationMessage('Hello World from zuo-restart!');
    });

    context.subscriptions.push(disposable);
}

// this method is called when your extension is deactivated
export function deactivate() {}

我們在插件調試窗口跑下hello world命令

vscode_plugin_3.png

出現(xiàn)如下彈窗消息,就說明跑的沒問題了

vscode_plugin_4.png

這樣hellowrod就跑起來了,vsc-extension-quickstart.md 里面有構架、發(fā)布插件的文檔說明

官網(wǎng)提供了一些簡單的demo,可以練練手,vscode-extension-samples | github (opens new window),后續(xù)有時間了繼續(xù)研究

參考:

#loadash節(jié)流與防抖理解

理論上throttle節(jié)流一般用于像監(jiān)聽resize方法,想要減少執(zhí)行頻率時使用。對于點擊按鈕提交,防止短時間內多次點擊可以用防抖

但實際使用時可根據(jù)具體情況來看,本質上都是利用setTimeout來處理執(zhí)行頻率或執(zhí)行間隔。下面是一個簡單的防抖示例

import { debounce } from 'loadsh'
export default {
  methods: {
    submitFormDebounce: debounce(function() {
      console.log('submit', +new Date())
      this.submitForm()
    }, 300, {trailing: true}),

    submitForm() {

    }
  }
}

#element源碼中節(jié)流與防抖的應用

在做input搜索時,由于input change后需要請求接口,這里el-autocomplete有個默認的300豪秒debounce,可以減少請求頻率。理論上這里減少頻率需要使用節(jié)流,但為什么是防抖呢?

我們把element源碼中對節(jié)流防抖的使用都找一找??梢钥吹絜lement使用的節(jié)流防抖庫是 throttle-debounce

發(fā)現(xiàn)節(jié)流throttle用的比較少,只找到了三個地方:

// Backtop 回到頂部
// packages/backtop/src/main.vue  滾動監(jiān)聽時用到了節(jié)流
import throttle from 'throttle-debounce/throttle';
mounted() {
  this.init();
  this.throttledScrollHandler = throttle(300, this.onScroll);
  this.container.addEventListener('scroll', this.throttledScrollHandler);
},

// Carousel 走馬燈 
// packages/carouse/src/main.vue 鼠標hover,箭頭點擊使用了節(jié)流
import throttle from 'throttle-debounce/throttle';
created() {
  this.throttledArrowClick = throttle(300, true, index => {
    this.setActiveItem(index);
  });
  this.throttledIndicatorHover = throttle(300, index => {
    this.handleIndicatorHover(index);
  });
},

// Image 圖片 滾動到區(qū)域懶加載時,使用了節(jié)流
if (_scrollContainer) {
  this._scrollContainer = _scrollContainer;
  this._lazyLoadHandler = throttle(200, this.handleLazyLoad);
  on(_scrollContainer, 'scroll', this._lazyLoadHandler);
  this.handleLazyLoad();
}

再來看看防抖的地方

el_debounce.png

總結:涉及到接口請求的基本都是防抖,對于不請求接口,防止多次執(zhí)行的情況才用節(jié)流,其他請求一律防抖

#log2n對數(shù)在前端的應用場景:把文件大小或金額自動添加合適的單位

在寫下載/導出文件接口時,由于接口文件數(shù)據(jù)是流的形式而非buffer,導致total為0,無法獲取進度。只能通過loaded知道當前下載了多少字節(jié)。前端顯示時,怎么給出合適的單位,是KB、MB,還是G?

// Math.pow(2, 0) // B
// > Math.pow(2, 10) // KB
// > Math.pow(2, 20) // MB
// > Math.pow(2, 30) // GB
// > Math.pow(2, 40) // TB
// > Math.pow(2, 50) // PB
// 以此類推...

可以通過對數(shù)來快速確定區(qū)間

/**
 * @description 格式化文件size
 * @param { Number } value 文件大小 B 字節(jié)
 * @returns 轉換后的文件大小及單位數(shù)組,保留兩位小數(shù)
 * @example
 * formatFileSize(100) =>  [100, "B"]
 * formatFileSize(10000) => [9.77, "KB"]
 * formatFileSize(100000000) => [95.37, "MB"]
 */
function formatFileSize(value) {
  let unitArr = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB','YB']
  let index = Math.floor(Math.log2(value) / 10) // 計算該value的值為2的多少次方,向下取整
  // 如果超出范圍取最大值
  if (index > unitArr.length - 1) {
    index = unitArr.length - 1
  }
  let result = value / Math.pow(2, index * 10) // 裝換為合適的單位
  result = (result * 100).toFixed() / 100 

  return [result, unitArr[index]]
}

依此類推,假設給定單位為元,將值轉換為合適的單位:元/萬/億/兆(萬億),10的4次方 萬,10的8次方 億,10的12次方 兆

/**
 * @description 格式化人民幣
 * @param { Number } value 元
 * @returns 轉換后的人民幣及單位數(shù)組,保留兩位小數(shù)
 * @example
 * formartMoney(1000) => [1000, "元"]
 * formartMoney(98000) => [9.8, "萬"]
 * formartMoney(100000000) => [1, "億"]
 */
function formartMoney(value) {
  let unitArr = ['元', '萬', '億', '兆'] 
  let index = Math.floor(Math.log10(value) / 4)
  // 如果超出范圍取最大值
  if (index > unitArr.length - 1) {
    index = unitArr.length - 1
  }
  let result = value / Math.pow(10, index * 4) // 裝換為合適的單位

  result = (result * 100).toFixed() / 100 

  return [result, unitArr[index]]
}

擴展:

#關于常用組件、樣式、工具函數(shù)封裝代碼快速復用的思考

怎么讓搬磚更有效率,整理常用工具庫utils、搜集各種場景常用的代碼片段,快速ctrl+c、ctrl+v

接口請求、表單校驗(正則)、表單/表格/通用樣式、表格分頁、圖表等常用的碎片化代碼搜集整理,形成文檔,以便快速復用

#2020/07/25 周六

#el-input類型為textarea時不能使用v-model.trim

el-input如果type為textarea,不能使用.trim修飾符,否則輸入內容時會無法換行,如果需要去掉收尾空格,可以在提交數(shù)據(jù)時,手動執(zhí)行.trim()去空格

<template>
  <div>
    <el-input
      type="textarea"
      v-model.trim="text"
      rows="5"
      style="width:200px;margin:100px;"
    ></el-input>
  </div>
</template>

<script>
export default {
  data() {
    return {
      text: ""
    };
  }
};
</script>

#2020/07/24 周四

#git修改上上次的commit備注信息

由于提交代碼時有鉤子函數(shù),信息里面沒有包含前置的code會無法提交。所以如果commit信息寫的有問題需要修改后才能提交

對于修改上一次commit備注信息,我們可以使用 --amend -m 來修改。但它無法修改上上次提交信息,這種情況我們可以使用rebase來做處理,下面來做一個測試

本地做兩次提交,第一次提交信息為"測試第一次提交", 第二次提交信息為 "第二次提交",先不push,我們需要修改上上次的提交信息,也就是修改"測試第一次提交"的內容

# 查看git記錄
guoqzuo-mac:fedemo kevin$ git log
commit 3814855781da539d21e2072e42a53558587497c6 (HEAD -> master)
Author: guoqzuo <guoqzuo@gmail.com>
Date:   Mon Aug 10 22:28:15 2020 +0800

    第二次提交

commit e889c7ecbcb024037701eb48c9bfe3b9c22f9490
Author: guoqzuo <guoqzuo@gmail.com>
Date:   Mon Aug 10 20:04:27 2020 +0800

    測試第一次提交

commit d5c2f2f3193cf02d6ac1ae995ca00c4082e36cad (origin/master, origin/HEAD)
Author: guoqzuo <guoqzuo@gmail.com>
Date:   Mon Aug 10 00:56:34 2020 +0800

    update cookie研究,合并單元格研究demo
:

執(zhí)行 git rebase -i HEAD~2,如下圖可以看到最近兩次提交,進入一個vim編輯頁面

edit_commit_rebase_1.png

按ESC, 再按a進入INSERT模式, 將上上次提交的信息前的pick改為edit,如下圖,按ESC, shift + : 進入命令模式,輸入x 或wq保存,不熟悉vim操作的可以搜索下vim教程

edit_commit_rebase_2.png

保存后,會看到下面log

guoqzuo-mac:fedemo kevin$ git rebase -i HEAD~2
Stopped at e889c7e...  測試第一次提交
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue
guoqzuo-mac:fedemo kevin$ 

運行 git commit --amend 會進入下面的修改頁面,可以修改上上次的信息

edit_commit_rebase_3.png

這里我們把上上次信息改為 "測試第一次提交,修改第一次提交的內容",保存后,結果如下

[detached HEAD 45f5911] 測試第一次提交,修改第一次提交的內容
 Date: Mon Aug 10 20:04:27 2020 +0800
 1 file changed, 2 insertions(+)
guoqzuo-mac:fedemo kevin$ 

然后運行 git rebase --continue,這樣就修改好了

guoqzuo-mac:fedemo kevin$ git rebase --continue
Successfully rebased and updated refs/heads/master.
guoqzuo-mac:fedemo kevin$ 

再來git log 看看提交記錄,修改上上次的提交信息已ok

guoqzuo-mac:fedemo kevin$ git log
commit e498bcabf2d2e4c97f47320e1d72693cb82d9db8 (HEAD -> master)
Author: guoqzuo <guoqzuo@gmail.com>
Date:   Mon Aug 10 22:28:15 2020 +0800

    第二次提交

commit 45f591157be44100073de14f5808b816104a8f2b
Author: guoqzuo <guoqzuo@gmail.com>
Date:   Mon Aug 10 20:04:27 2020 +0800

    測試第一次提交,修改第一次提交的內容

參考:修改上上次的commit信息(opens new window)

#2020/07/22

#echarts動態(tài)改變option里dataZoom的值沒有實時生效的問題

這里我們雖然修改了options的值,但不會實時生效,需要手動調用下echarts實例的resize()方法

另外在做echarts時,對于自適應縮放的圖表,一定要注意在窗口縮放時,重新調用resize()

#http請求有哪幾種傳參方式

在swagger文檔里,有一個傳參類型的描述 Parameter Type,一般有四種

  • header 通過請求頭傳參,也就是參數(shù)加到首部 headers 里
  • path 參數(shù)放到url路徑里,比如 /user/123 這里 123是用戶id
  • query 查詢參數(shù),也就是url后面 ? 符號之后的傳參,一般用于get請求傳參,比如 /user/123?a=xx&b=xx
  • body 參數(shù)放到請求體,一般用于post請求,相對get請求來說,安全性好,可以傳的數(shù)據(jù)更多

#Object.assgin時是否會忽略null,undefined,空字符串

一般我們在需要設置某個對象的多個值時Object.assgin是一種很好的方法,但又怕當某個屬性的值為空字符串、null或undefined時,會自動跳過的情況。這里來做一個簡單的測試

objA = {a: 'a', b: 'b', c: 'c', d: 'd'}
Object.assign(objA, {a: 1, b: undefined, c: null, d: ''})
objA // {a: 1, b: undefined, c: null, d: ""}

Object_assign.png

綜上,Object.assgin可以放心用

#git將遠程倉庫A分支合并到B分支

假設要將遠程分支的 A 分支合并到 B 分支,一般我會先在A分支將B分支merge,再切到B分支,merge A分支。

以將遠程倉庫的 dev1.3.4 分支合并到遠程的 test1.3.4 分支為例,下面是我一般的合并過程

# 1\. 本地切到 dev1.3.4 分支
# 2\. merge遠程的test1.3.4分支,命令如下
git merge origin/test1.3.4
# 3.如果有沖突(conflict),修改沖突文件
# 4.修改沖突后提交代碼到遠程倉庫,命令如下
git add 修改沖突相關的文件
git commit '修改沖突,fix conflict'
git push
# 5.切換到test1.3.4分支
# 6.merge本地的dev1.3.4,因為本地的dev1.3.4是最新的代碼,命令如下
git merge dev1.3.4 => git push

另外,養(yǎng)成習慣,在git push前,先git pull

#2020/07/17 周五

#前端修改cookie后,相關cookie改動會傳到后臺嗎

首先我們來捋一捋,什么是cookie?與cookie相關的知識點有兩個:

  1. 前端獲取/設置cookie,使用 document.cookie
  2. HTTP請求與響應相關cookie

我們先下個結論:他們之間是相互關聯(lián)的,接口響應頭設置cookie,會對document.cookie的值產(chǎn)生影響;前端設置docuemnt.cookie也會對請求頭cookie值產(chǎn)生影響,但如果后端寫到前端的cookie如果使用了HttpOnly屬性,前端是無法通過document.cookie做修改的

#根據(jù)功能點寫測試demo

紙上得來終覺淺,這里為了弄懂這里面的關系,我們來寫一個demo做測試,將涉及的知識點都串起來,首先要有一個html頁面,有兩個用處

  1. 在該頁面打開F12,在console里通過命令查看或設置document.cookie信息
  2. 在該頁面中請求接口,在F12 Network里觀察請求頭,響應頭里cookie的信息

另外需要寫兩個接口,用戶觀察請求響應頭里cookie的信息,這里我們用koa來寫兩個簡單的接口。

下面是demo目錄層級

# demo 目錄層級
├── public
│   └── index.html # 靜態(tài)頁面
├── index.js # 接口
└── package.json # npm init 創(chuàng)建,用于安裝index.js引入的koa等npm包

index.html代碼如下

<body>
  <h1>test測試</h1>
  <button id="userInfoBtn">獲取user信息</button>
  <button id="editInfoBtn">修改user信息</button>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script>
    let userInfoBtn = document.querySelector('#userInfoBtn')
    let eidtInfoBtn = document.querySelector('#userInfoBtn')
    // 請求獲取用戶信息接口
    // 用于測試響應頭設置 'Set-Cookie' 對前端docuemnt.cookie的影響
    userInfoBtn.onclick = () => {
      axios.get('/user').then((res) => {
        console.log(res)
      }).catch((e) => {
        console.error(e.message)
      })
    }
    // 請求修改用戶信息接口
    // 用于測試document.cookie設置后或者第一次請求響應頭設置Set-Cookie后,對下次接口請求頭的影響
    editInfoBtn.onclick = () => {
      axios.put('/user').then((res) => {
        console.log(res)
      }).catch((e) => {
        console.error(e.message)
      })
    }
  </script>
</body>

index.js接口代碼

const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router()

// 靜態(tài)服務,用于使用 http://127.0.0.1:9000/ 訪問 public下的index.html頁面
app.use(new require('koa-static')('./public'))

// GET /user  接口,設置Set-Cookie響應頭測試
router.get('/user', ctx => {
  ctx.set({
    // 'Set-Cookie': 'token=123;path=/;max-age=100;HttpOnly',
    'Set-Cookie': ['token=123;path=/;max-age=100;HttpOnly','mark=9;path=/;']
  })
  ctx.body = {
    name: '張三',
    age: 20
  }
})

// PUT /user  接口,用于觀察接口請求頭
router.put('/user', ctx => {
  ctx.body = {
    code: 0,
    data: {},
    msg: '成功'
  }
})

app.use(router.routes())

// 開啟本地HTTP服務,9000端口
app.listen(9000, () => {
  console.log('server listen on 9000 port')
})

#document.cookie設置對請求頭的影響

首先我們可以先看 Document.cookie - Web API 接口參考 | MDN (opens new window)這個文檔,對document.cookie有一個大概的了解。

我們先運行demo,nodemon index.js,訪問 http://127.0.0.1:9000/ 進入頁面,查看cookie信息,如下圖

what_cookie_1.png

這時,我們發(fā)送一個put請求,看接口請求頭信息。再通過 document.cookie = "a=1" 設置cookie,然后再發(fā)送一個put請求,對比請求頭之前的區(qū)別,如下圖,我們初步得出結論:前端設置cookie后,下次請求,在請求頭里會攜帶這個cookie

what_cookie_2.png

#document.cookie操作方法

document.cookie API的設計不怎么友好,一般會使用一個庫,來操作docuemnt.cookie,需要注意

document.cookie = "a=1" // 如果cookie中沒有a這個key,可以添加key為a,值為1的cookie,否則修改對應的值為1
document.cookie = "" // 不會清空cookie,對cookie基本無影響
// 怎么刪除 a=1 的cookie呢?將該cookie的有效時間設置為過去的時間即可
document.cookie = "a=1;expires=" + new Date('1970-1-1')

MDN document.cookie文檔里有推薦的一個操作cookie的封裝方法,可以參考下

/*\
|*|
|*|  :: cookies.js ::
|*|
|*|  A complete cookies reader/writer framework with full unicode support.
|*|
|*|  https://developer.mozilla.org/en-US/docs/DOM/document.cookie
|*|
|*|  This framework is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
|*|  Syntaxes:
|*|
|*|  * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
|*|  * docCookies.getItem(name)
|*|  * docCookies.removeItem(name[, path], domain)
|*|  * docCookies.hasItem(name)
|*|  * docCookies.keys()
|*|
\*/

var docCookies = {
  getItem: function (sKey) {
    return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
  },
  setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
    if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
    var sExpires = "";
    if (vEnd) {
      switch (vEnd.constructor) {
        case Number:
          sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
          break;
        case String:
          sExpires = "; expires=" + vEnd;
          break;
        case Date:
          sExpires = "; expires=" + vEnd.toUTCString();
          break;
      }
    }
    document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
    return true;
  },
  removeItem: function (sKey, sPath, sDomain) {
    if (!sKey || !this.hasItem(sKey)) { return false; }
    document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : "");
    return true;
  },
  hasItem: function (sKey) {
    return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
  },
  keys: /* optional method: you can safely remove it! */ function () {
    var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
    for (var nIdx = 0; nIdx < aKeys.length; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
    return aKeys;
  }
};

#HTTP cookies后端接口向前端頁面寫入cookie

可以先看下面兩個文檔,對http cookie有一個基本的了解

  1. http請求頭或響應頭參數(shù)cookie Cookie - HTTP Headers | MDN(opens new window)
  2. http cookies HTTP cookies - HTTP | MDN(opens new window)

An HTTP cookie (web cookie, browser cookie) is a small piece of data that a server sends to the user's web browser. The browser may store it and send it back with later requests to the same server. Typically, it's used to tell if two requests came from the same browser — keeping a user logged-in, for example. It remembers stateful information for the stateless HTTP protocol.(HTTP Cookie 是服務器發(fā)送到用戶瀏覽器并保存在本地的一小塊數(shù)據(jù),它會在瀏覽器下次向同一服務器再發(fā)起請求時被攜帶并發(fā)送到服務器上。通常,它用于告知服務端兩個請求是否來自同一瀏覽器,如保持用戶的登錄狀態(tài)。Cookie 使基于無狀態(tài)的HTTP協(xié)議記錄穩(wěn)定的狀態(tài)信息成為了可能。)

Cookies are mainly used for three purposes(cookie主要由以下三個應用場景):

  • Session management (會話管理)
    • Logins, shopping carts, game scores, or anything else the server should remember(用戶登錄狀態(tài)、購物車、游戲分數(shù)或其它需要記錄的信息)
  • Personalization (個性化)
    • User preferences, themes, and other settings(用戶自定義配置,主題或其他設置)
  • Tracking (用戶行為跟蹤)
    • Recording and analyzing user behavior (用于記錄和分析用戶行為)

#cookie設置屬性詳解

服務端通過在接口響應頭設置 'Set-Cookie' ,將對應的cookie寫到前端,先來看看設置cookie的格式

Set-Cookie: “name=value;domain=.domain.com;path=/;expires=Sat, 11 Jun 2016 11:29:42 GMT;HttpOnly;secure;samesite”

屬性 說明 默認值
name 一個唯一確定的cookie名稱。如有特殊字符需要編碼(encodeURIComponent) /
value 存儲在cookie中的字符串值。如有特殊字符需要編碼 空字符串
domain cookie對于哪個域是有效,如果指定了一個域,那么子域也包含在內。.xx.com,對于xx.com的所有子域都有效 當前url所在的域名
path 表示這個cookie影響到的路徑,瀏覽器跟會根據(jù)這項配置,像指定域中匹配的路徑在請求頭發(fā)送cookie信息 當前url所在的path
expires 失效時間,是一個具體的時間,這個值是GMT時間格式,expires=" + new Date('1970-1-1') 這種即可。如果客戶端和服務器端時間不一致,使用expires就會存在偏差。注意兩點:1.設置一個過去的時間,可用于刪除該cookie 2.當Cookie的過期時間被設定時,設定的日期和時間只與客戶端相關,而不是服務端。max-age也是如此 如果沒有定義,cookie會在對話結束時(關閉瀏覽器)過期
max-age 與expires作用相同,用來告訴瀏覽器此cookie多久過期(單位是秒),而不是一個固定的時間點。正常情況下,max-age的優(yōu)先級高于expires。 同上面的expires
HttpOnly 告知瀏覽器不允許通過腳本document.cookie去更改這個值,同樣這個值在document.cookie中也不可見。但在http請求張仍然會攜帶這個cookie。注意這個值雖然在腳本中不可獲取,但仍然在瀏覽器安裝目錄中以文件形式存在。這項設置通常在服務器端設置。有助于緩解跨站點腳本(XSS)攻擊。 不設置
secure 安全標志,指定后,只有在使用SSL鏈接(https)時候才能發(fā)送到服務器,如果是http鏈接則不會傳遞該信息。就算設置了secure 屬性也并不代表他人不能看到你機器本地保存的 cookie 信息,因此未加密的重要信息盡量不要放cookie了 不設置
samesite 當跨域請求是瀏覽器是否發(fā)送cookie,可以阻止跨站請求偽造攻擊CSRF。1. 值為None: 瀏覽器會在同站請求、跨站請求下繼續(xù)發(fā)送 cookies,不區(qū)分大小寫。2.Strict:瀏覽器將只在訪問相同站點時發(fā)送 3.Lax:與 Strict 類似,但用戶從外部站點導航至URL時(例如通過鏈接)除外。 Same-site cookies 將會為一些跨站子請求保留,如圖片加載或者 frames 的調用,但只有當用戶從外部站點導航到URL時才會發(fā)送。如 link 鏈接 以默認值都是None,現(xiàn)在基本新的瀏覽器基本都是Lax了

#后端設置'Set-Cookie'響應頭對前端的影響

我們在 GET /user 接口做了處理,當訪問這個接口時,向前端設置cookie, 一個HttpOnly的,一個非httpOnly,看看效果

ctx.set({
  // 單個cookie設置
  // 'Set-Cookie': 'token=123;domain=;path=/;max-age=100;HttpOnly', 
  // 多個cookie設置
  'Set-Cookie': ['token=123;path=/;max-age=100;HttpOnly','mark=9;path=/;max-age=60;']
})

先刪除之前的cookie,保證document.cookie為空

what_cookie_3.png
  1. 先請求獲取user信息接口,在F12里看響應頭,Respons e Headers可以看到HttpOnly的cookie
  2. 使用document.cookie看下前端cookie信息。這里獲取不到設置了HttpOnly的cookie
  3. 請求修改user信息接口,看請求頭。后端設置的cookie,下次請求會攜帶
  4. 100秒后,發(fā)現(xiàn)document.cookie以及application面板都沒有cookie值了,因為我們設置了有效時間,再次發(fā)送修改user的put請求,請求頭不會攜帶任何cookie信息
what_cookie_4.png
  1. 再次請求獲取user信息接口,然后用document.cookie修改token的值,發(fā)現(xiàn)HttpOnly屬性的cookie前端無法通過document.cookie來修改,且再次請求put接口,token值還是原先后端設置的值,所以HttpOnly屬性的cookie前端無法修改
what_cookie_5.png

#參考文檔

#element合并單元格利用css自定義表格border

有個較為特殊的表格,需要合并單元格,且改變表格border,看看element el-table怎么實現(xiàn)這種表格

special_table.png
<template>
  <div class="table-test">
    <el-table
      :data="dataList"
      border
      size="mini"
      :span-method="arraySpanMethod"
      :header-cell-style="{ background: '#f7f7f7' }"
    >
      <el-table-column
        v-for="item in ['a', 'b', 'c', 'd']"
        :key="item"
        :prop="item"
        :label="item"
      ></el-table-column>
    </el-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      dataList: []
    };
  },
  mounted() {
    this.dataList = [1, 2, 3, 4].map(() => {
      return { a: "1", b: "2", c: "3", d: 4 };
    });
  },
  methods: {
    arraySpanMethod({ row, column, rowIndex, columnIndex }) {
      console.log(row, column, rowIndex, columnIndex);
      // 只是遍歷表格td內容,不包含th表頭
      // 對第一列,進行合并列
      if (columnIndex === 0) {
        if (rowIndex === 0) {
          // 第一列,第一行,默認
          return {
            rowspan: 1,
            colspan: 1
          };
        } else if (rowIndex === 1) {
          // 第一列,第二行,合并,占this.dataList.length - 1行
          return {
            rowspan: this.dataList.length - 1,
            colspan: 1
          };
        } else if (rowIndex >= 2) {
          // 第一列,剩余行,為空
          return {
            rowspan: 0,
            colspan: 0
          };
        }
      }
    }
  }
};
</script>

<style lang="less" scoped>
.table-test {
  width: 500px;
  margin: 100px;
  // border處理
  // 去掉表頭單元格th右邊框
  /deep/ .el-table th:not(:first-child) {
    border-right: 0;
  }
  // 去掉表格內容單元格td的右側邊框、底部邊框
  /deep/ .el-table td {
    border-right: 0;
    border-bottom: 0;
  }
  // 為第一行td增加底部border
  /deep/ .el-table__row:first-child td {
    border-bottom: 1px solid #eaeaea;
  }
  // 為第一行第一列td增加右側border
  /deep/ .el-table__row:first-child td:first-child,
  // 為第二行(合并后的)第一列td設置右側border
  /deep/ .el-table__row:nth-child(2) td:first-child {
    border-right: 1px solid #eaeaea;
  }
}
</style>

#2020/07/12 周日

#github clone下載太慢怎么解決

以clone vue源碼為例,默認git clone下載非常慢,我們可以把github.com鏈接改為鏡像github.com.cnpmjs.org,這樣下載速度就很快了,過程如下

# git clone 下載vue源碼
guoqzuo-mac:source kevin$ git clone https://github.com/vuejs/vue.git
Cloning into 'vue'...
remote: Enumerating objects: 56366, done.
^Cceiving objects:   5% (2823/56366), 556.01 KiB | 10.00 KiB/s   
guoqzuo-mac:source kevin$ 
guoqzuo-mac:source kevin$ git clone https://github.com.cnpmjs.org/vuejs/vue.git
Cloning into 'vue'...
remote: Enumerating objects: 56366, done.
remote: Total 56366 (delta 0), reused 0 (delta 0), pack-reused 56366
Receiving objects: 100% (56366/56366), 26.75 MiB | 1.22 MiB/s, done.
Resolving deltas: 100% (39568/39568), done.
guoqzuo-mac:source kevin$ 

github_clone_slow.png

#js獲取location.href不真實的問題

macOS 修改host文件 /etc/hosts 后,本地訪問某個域名會按照host指定的ip去解析,就會造成前端location.href不準確的問題,下面來看看

# 默認沒有寫的權限,無法編輯
guoqzuo-mac:~ kevin$ ls -l /etc/hosts 
-rw-r--r--  1 root  wheel  244  3 24  2019 /etc/hosts
# 新增寫的權限
guoqzuo-mac:~ kevin$ sudo chmod 0666 /etc/hosts
Password:
# 再次查看權限
guoqzuo-mac:~ kevin$ ls -l /etc/hosts 
-rw-rw-rw-  1 root  wheel  244  3 24  2019 /etc/hosts
# 使用vi修改該文件
guoqzuo-mac:~ kevin$ vi /etc/hosts
# 新增a.com解析,在本地將a.com解析到47.107.190.93的服務器,也就是zuo11.com解析到的服務器
47.107.190.93 a.com

macOS修改host是實時生效的,修改后,本地瀏覽器訪問a.com,會訪問47.107.190.93的服務器,顯示的是zuo11.com的內容。這時用location.href就是a.com,而不是zuo11.com。下面是百度統(tǒng)計的數(shù)據(jù),顯示的就是a.com

a_com_#png

#2020/07/09 周四

#表格怎么畫斜線

在最近的需求中,有個表格表頭里有斜線,我特意翻了HTML5權威指南的筆記,發(fā)現(xiàn)并沒有介紹怎么畫表頭的斜線。找了下網(wǎng)上的實現(xiàn),一般都是通過css來實現(xiàn),效果如下,在線預覽地址: 表格畫斜線 | github(opens new window)

table_slash.png

基本實現(xiàn)都是純css,有的用到的偽元素。我這里直接用了三個元素來實現(xiàn),如果想要完全理解,需要用到一點點數(shù)學方面的知識。

  1. 比如計算斜線的長度。勾股定理 a2 + b2 = c2
/* 斜邊邊長 */
/* Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) */
/* Math.sqrt(Math.pow(150, 2) + Math.pow(80, 2)) = 170 */

  1. 根據(jù)th單元格的寬高計算斜線的rotate角度。給定直角三角形的邊長,怎么計算角度? 這里我們知道寬高,不知道斜邊邊長,假設角度A,正切tanA = 對邊(高) / 鄰邊(寬),我們知道這個角度A的正切值,怎么反向計算A的角度呢。就需要用到反正切函數(shù) Math.atan了,他會返回一個弧度值。在JS中 180度對應的值為 Math.PI,計算出來的值乘以 (180 / Math.PI) 就是可以在css中使用的度數(shù)了,單位為 deg
/* 角度計算公式 */ 
/*  Math.atan(height / width) * 180 / Math.PI  */
/*  Math.atan(80 / 150) * 180 / Math.PI  = 28.072486935852954 */

完整代碼如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    /* 基本表格元素 */
    table {
      border-collapse: collapse;
    }
    th,td {
      border: 1px solid #666;
      padding: 5px;
    }

    /* th單元格 */
    .slash-wrap {
      position: relative;
      box-sizing: border-box;
      width: 150px;
      height: 80px;
    }

    /* 斜線 */
    .slash {
      position: absolute;
      display: block;
      top: 0;
      left: 0;
      /* 斜邊邊長 */
      /* Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)) */
      /* Math.sqrt(Math.pow(150, 2) + Math.pow(80, 2)) = 170 */
      width: 170px;
      height: 1px;
      background-color:#000;
      /* 旋轉角度計算公式 */ 
      /*  Math.atan(height / width) * 180 / Math.PI  */
      /*  Math.atan(80 / 150) * 180 / Math.PI  = 28.072486935852954 */
      transform: rotate(28.072486935852954deg); 
      transform-origin: top left;
    }

    /* 左下角文字 */
    .left {
      position: absolute;
      /* 左下角 left:0; bottom: 0; */
      left: 15px;
      bottom: 15px;
    }

    /* 右上角文字 */
    .right {
      position: absolute;
      /* 右上角 right:0; top: 0; */
      right: 15px;
      top: 15px;
    }
  </style>
</head>

<body>
  <div>
    <table>
      <tr>
        <th class="slash-wrap">
          <span class="left">姓名</span>
          <span class="slash"></span>
          <span class="right">科目</span>
        </th>
        <th>語文</th>
        <th>數(shù)學</th>
      </tr>
      <tr>
        <td>張三</td>
        <td>89</td>
        <td>80</td>
      </tr>
      <tr>
        <td>李四</td>
        <td>89</td>
        <td>80</td>
      </tr>
    </table>
  </div>
</body>

</html>

參考:

#權限code狀態(tài)管理設計

一般使用vuex狀態(tài)管理,提供一個getter方法獲取對應的角色權限,假設getter名為roleMuster,通過mapGetters導入,然后使用

let { role_admin, role_a, role_b, role_c } = this.roleMuster
// 如果有對應的權限,值則為true

在getter的邏輯里可以對角色對應的code進行轉換,使用好理解變量代替,消除魔術字符串

#2020/07/08 周三

#pc樣式自適應rem

pc端如果做官網(wǎng)、展示類的UI,就需要使用rem了。為了好還原UI圖,1rem通常設計為100px。rem是相對于html元素font-size來計算的,我們可以通過動態(tài)的改變html元素的fontSize,來實現(xiàn)頁面自適應

怎么動態(tài)設置html元素的font-size呢?來看看

export default {
  created() {
    const recalc = () => {
      let designSize = 1920
      let minWidth = 1280
      let html = document.documentElement
      let w = html.clientWidth < minWidth ? minWidth : html.clientWidth
      let rem = ( w / designSize) * 100
      this.rem = rem // 當前頁面 使用時寬高使用 this.rem * (設計稿標記尺寸/100)
      // html.style.fontSize = `${rem}px`  // 會影響所有頁面
    }
    this.recalc = recalc
    recalc()
    // 窗口變更后,變更rem
    window.addEventListener('resize', recalc)
  },
  // 組件銷毀時移除resize事件的recalc
  beforeDestroy() {
    window.removeEventListener('resize', this.recalc, false)
  }
}

這里就凸顯出 window.addEventListener('resize') 相對于 window.onresize 的優(yōu)勢,假設項目很大,其他代碼已經(jīng)監(jiān)聽了window.onresize如果你再用window.onresize可能會覆蓋原來的方法,要特別小心,而window.addEventListener就不用擔心這個問題,你只需要注意remove的時候,只移除你自己的監(jiān)聽函數(shù)即可。

#background設置圖片背景相關

HTML5權威指南這本書對background的簡寫貌似有點不正確,使用起來會有問題,這次讓圖片在某個區(qū)域完全顯示,是分開寫的,如下:

div {
  background: #fff url('/images/xxx.png') no-repeat;
  background-size: cover;
}

后面仔細測試下這塊

#移動端rem自適應

設計圖750 * xx(iphone 6/7/8 或 全面屏),以100為基準

function initRem() {
  let html = document.documentElement
  let resizeEventName = 'orientationchange' in window ? 'orientationchange' : 'resize'
  let recalc = () => {
    let w = html.clientWidth < 320 ? 320 : html.clientWidth
    let fontSize = w > 750 ? 200 : ((w / 375) * 100)
    Object.assign(html.style, { fontSize })
  }
  recalc()
  [resizeEventName, 'DOMContentLoaded'].map(eventName => {
    document.addEventListener(eventName, recalc, false)
  })
}

#2020/07/05 周日

#vue實現(xiàn)一個tree組件

樹形組件主要是遞歸的問題,組件自己調用自己,來寫個簡單的例子

<template>
  <div>
    <z-tree :data="treeData"></z-tree>
  </div>
</template>

<script>
export default {
  components: {
    ZTree: () => import("./ZTree")
  },
  data() {
    return {
      treeData: [
        {
          label: "冰箱"
        },
        {
          label: "水果",
          list: [
            { label: "蘋果" },
            { label: "梨子" },
            { label: "葡萄" },
            {
              label: "喜歡的水果",
              list: [{ label: "水果1" }, { label: "水果2" }, { label: "水果3" }]
            },
            { label: "香蕉" }
          ]
        },
        {
          label: "茶葉",
          list: [
            { label: "鐵觀音" },
            { label: "西湖龍井" },
            { label: "毛尖" },
            {
              label: "紅茶",
              list: [{ label: "紅茶1" }, { label: "紅茶2" }, { label: "紅茶3" }]
            }
          ]
        }
      ]
    };
  }
};
</script>

ZTree.vue實現(xiàn)

<template>
  <!-- z-tree遞歸組件實現(xiàn) -->
  <div>
    <ul>
      <li v-for="item in data" :key="item.label">
        {{ item.label }}
        <i class="iconfont el-icon-arrow-right" v-if="item.list"></i>
        <z-tree v-if="item.list" :data="item.list"></z-tree>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  components: {
    ZTree: () => import("./ZTree")
  },
  props: {
    data: {
      type: Array,
      required: true
    }
  },
  data() {
    return {};
  }
};
</script>

#select渲染上萬條數(shù)據(jù)卡頓的問題

一般下拉選擇時會使用select組件,但如果select數(shù)據(jù)過萬,可能會產(chǎn)生卡頓,其實這種數(shù)據(jù)大的情況使用select就體驗很差了。

可以做成彈窗選擇或搜索框,那怎么保持select有數(shù)萬條數(shù)據(jù)而不卡呢?我的理解是,使用觸底刷新,滾動到底部時加載后面的內容

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容