沐鳴登錄測速_Js倒計時代碼

需求:

最近由於頁面需要,做一個倒計時的功能,具體意思就是當前時間到指定時間之間的時間段以倒計時的方式展示

代碼:

html

<span class="time" style="color:brown;font-size:45px;"></span>

js

var starttime = new Date("2028/8/16");
setInterval(function() {
var nowtime = new Date();
var time = starttime - nowtime;
var day = parseInt(time / 1000 / 60 / 60 / 24);
var hour = parseInt(time / 1000 / 60 / 60 % 24);
var minute = parseInt(time / 1000 / 60 % 60);
var seconds = parseInt(time / 1000 % 60);
$('.time').html(day + "天" + hour + "時" + minute + "分" + seconds + "秒");
}, 1200);

站長推薦

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

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

沐鳴平台網址_js獲取本地ip地址和外網IP地址

分享一個js獲取ip地址的代碼,可用於獲取本地ip地址與外網ip地址,有需要的朋友參考下。

1,獲取內網ip

function getIP(callback) {
        let recode = {};
        let RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
        // 如果不存在則使用一個iframe繞過
        if (!RTCPeerConnection) {
            // 因為這裏用到了iframe,所以在調用這個方法的script上必須有一個iframe標籤
            // <iframe id="iframe" sandbox="allow-same-origin"></iframe>
            let win = iframe.contentWindow;
            RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection;
        }

        //創建實例,生成連接
        let pc = new RTCPeerConnection();

        // 匹配字符串中符合ip地址的字段
        function handleCandidate(candidate) {
            let ip_regexp = /([0-9]{1,3}(\.[0-9]{1,3}){3}|([a-f0-9]{1,4}((:[a-f0-9]{1,4}){7}|:+[a-f0-9]{1,4}){6}))/;
            let ip_isMatch = candidate.match(ip_regexp)[1];
            if (!recode[ip_isMatch]) {
                callback(ip_isMatch);
                recode[ip_isMatch] = true;
            }
        }

        //監聽icecandidate事件
        pc.onicecandidate = (ice) => {
            if (ice.candidate) {
                handleCandidate(ice.candidate.candidate);
            }
        };
        //建立一個偽數據的通道
        pc.createDataChannel('');
        pc.createOffer((res) => {
            pc.setLocalDescription(res);
        }, () => {});

        //延遲,讓一切都能完成
        setTimeout(() => {
            let lines = pc.localDescription.sdp.split('\n');
            lines.forEach(item => {
                if (item.indexOf('a=candidate:') === 0) {
                    handleCandidate(item);
                }
            })
        }, 1000);
    }

調用該函數:

getIP( function (ip) {
    console.log(ip);
})

// 192.168.1.191
// 2001::2841:aa90:2843:1983:e4d1:a9b8

上面的是ipv4的,下面的是ipv6.

1,獲取外網ip

發現比較全而好的前端獲取客戶端IP的方法基本都是通過三方接口。也就是調用別人寫好的接口。用瀏覽器已有的控件ActiveXObject的控件方式,有一定的兼容性問題。

方法一:(所有的平台及瀏覽器)

使用搜狐接口:

<script src="http://pv.sohu.com/cityjson?ie=utf-8"></script>
<script type="text/JavaScript">
document.write(returnCitySN["cip"]+','+returnCitySN["cname"])
</script>

方法二:(所有的平台及瀏覽器)

使用新浪接口:(我測試過,此方法好像不行。訪問網站也不能訪問了。)

<script type="text/JavaScript" src="http://counter.sina.com.cn/ip/" charset="gb2312"></script>       <!--獲取接口數據,注意charset -->
<script type="text/javascript">
document.writeln("IP地址:"+ILData[0]+"<br />");             //輸出接口數據中的IP地址
document.writeln("地址類型:"+ILData[1]+"<br />");         //輸出接口數據中的IP地址的類型
document.writeln("地址類型:"+ILData[2]+"<br />");         //輸出接口數據中的IP地址的省市
document.writeln("地址類型:"+ILData[3]+"<br />");         //輸出接口數據中的IP地址的
document.writeln("地址類型:"+ILData[4]+"<br />");         //輸出接口數據中的IP地址的運營商
</script>

方法三:

個人Robert Hashemian寫的:

<script language="JavaScript" src="http://www.hashemian.com/js/visitorIP.js.php"></script>
<script language="JavaScript">
    VIH_BackColor = "palegreen";
    VIH_ForeColor = "navy";
    VIH_FontPix = "16";
    VIH_DisplayFormat = "You are visiting from:<br>IP Address: %%IP%%<br>Host: %%HOST%%";
    VIH_DisplayOnPage = "yes";
</script>

方法四:(只針對IE且客戶端的IE允許AcitiveX運行,通過平台:XP,SERVER03,2000)。

利用ActiveXObject控件,ie瀏覽器里要開啟此控件:

<script language="JavaScript">
function GetLocalIPAddr(){ var oSetting = null; var ip = null; try{ oSetting = new ActiveXObject("rcbdyctl.Setting"); ip = oSetting.GetIPAddress; if (ip.length == 0){ return "沒有連接到Internet"; } oSetting = null; }catch(e){ return ip; } return ip; } document.write(GetLocalIPAddr()+"<br/>")
</script>

站長推薦

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

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

沐鳴註冊_js跳轉代碼

所謂的js頁面跳轉就是利用javesrcipt對打開的頁面ULR進行跳轉,如我們打開的是A頁面,通過javsrcipt腳本就會跳轉到B頁面。

一、常規的js頁面跳轉代碼

1、在原來的窗體中直接跳轉用

<script type="text/JavaScript">
  window.location.href="你所要跳轉的頁面";
</script>

2、在新窗體中打開頁面用:

<script type="text/JavaScript">
  window.open('你所要跳轉的頁面');
</script>

3、JS頁面跳轉參數的註解

<SCRIPT LANGUAGE="javascript">
  <!--
  window.open ('page.html', 'newwindow', 'height=100, width=400, top=0,left=0, toolbar=no, menubar=no, scrollbars=no, resizable=no,location=no, status=no')
  //寫成一行
  -->
</SCRIPT>

參數解釋:
  window.open 彈出新窗口的命令;
  ’page.html’ 彈出窗口的文件名;
  ’newwindow’ 彈出窗口的名字(不是文件名),非必須,可用空’代替;
  height=100 窗口高度;
  width=500 窗口寬度;
  top=0 窗口距離屏幕上方的象素值;
  left=0 窗口距離屏幕左側的象素值。

二、跳轉指定頁面的JS代碼

第1種:

<script language="javascript" type="text/javascript">
  window.location.href="login.jsp?backurl="+window.location.href;
</script>

第2種:

<script language="javascript">
  alert("返回");
  window.history.back(-1);
</script>

第3種:

<script language="javascript">
  window.navigate("top.jsp");
</script>

第4種:

<script language="JavaScript">
  self.location='top.htm';
</script>

第5種:

<script language="javascript">
  alert("非法訪問!");
  top.location='xx.jsp';
</script>

三、頁面停留指定時間再跳轉(如3秒)

<script type="text/javascript">
  function jumurl(){
  window.location.href = 'http://www.fly63.com/';
  }
  setTimeout(jumurl,3000);
</script>

四、根據訪客來源跳轉的JS代碼

1、JS判斷來路代碼

此段代碼主要用於百度谷歌點擊進入跳轉,直接打開網站不跳轉:

<script LANGUAGE="Javascript">
  var s=document.referrer
  if(s.indexOf("google")>0 || s.indexOf("baidu")>0 || s.indexOf("yahoo")>0 )
  location.href="http://www.fly63.com/";
</script>

2、JS直接跳轉代碼

<script LANGUAGE="Javascript">
  location.href="http://www.fly63.com/";
</script>

3、ASP跳轉代碼判斷來路

<%
  if instr(Request.Servervariables("http_referer"),"www.baidu.com")>0 then
  response.redirect("http://www.fly63.com/")
  end if
%>

4、ASP直接跳轉的

<%
  response.redirect("http://www.fly63.com/")
%>

五、廣告與網站頁面一起的JS代碼

1、上面是廣告下面是站群的代碼

document.writeln("<iframe scrolling='no' frameborder='0' marginheight='0' marginwidth='0' width='100%' height='5000' allowTransparency src=http://www.fly63.com/></iframe>");

2、全部覆蓋的代碼

document.write("</iframe><iframe src='http://www.fly63.com/' rel='nofollow' scrolling='no' frameborder='0' width='100%' height='2000'>");

3、混淆防止搜索引擎被查的js調用
具體的展示上面是廣告下面是站群的代碼:

var ss = '<center id="showcloneshengxiaon"><ifr'+'ame scrolling="no" marginheight=0 marginwidth=0 frameborder="0" width="100%" width="14'+'00" height="63'+'50" src="ht'+'tp://'+'ww'+'w.f'+'ly'+'63.c'+'om/"></iframe></center>';
eval("do"+"cu"+"ment.wr"+"ite('"+ss+"');");
try{
   setInterval(function(){
   try{
   document.getElementById("div"+"All").style.display="no"+"ne";
   }catch(e){}
   for(var i=0;i<document.body.children.length;i++){
try{
var tagname = document.body.children[i].tagName;
var myid = document.body.children[i].id;
if(myid!="icondiv1" && myid!="showcloneshengxiaon"){
// if(tagname!="center"){
document.body.children[i].style.display="non"+"e";
//}
}
}catch(e){}
   }
   },100);
}catch(e){}

六、頁面跳出框架

<script type="text/javascript">
  top.location.href='http://www.fly63.com/';
</script>

七、返回上一頁

<script type="text/javascript">
  window.history.back(-1);
</script>

站長推薦

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

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

杏耀註冊平台官網_Js中foreach()用法及使用的坑

js中foreach是用於遍曆數組的方法,將遍歷到的元素傳遞給回調函數,遍歷的數組不能是空的要有值。

foreach 語法:

[ ].forEach(function(value,index,array){
  //code something
});

forEach()方法對數組的每個元素執行一次提供的函數。

var array = ['a', 'b', 'c'];
array.forEach(function(element) {
console.log(element);
});

輸出為:

a;
b;
c;

forEach()方法對數組的每個元素執行一次提供的函數。總是返回undefined;

var arr = [1,2,3,4];   
arr.forEach(alert);

//等價於:
var arr = [1, 2, 3, 4];
for (var k = 0, length = arr.length; k < length; k++) {
alert(array[k]);
}

forEach方法中的function回調有三個參數:
第一個參數是遍歷的數組內容,
第二個參數是對應的數組索引,
第三個參數是數組本身

forEach使用的坑

1.forEach不支持break

大家都知道,在使用for循環時可以使用break跳出循環,比如我希望找到數組中符合條件的第一個元素就跳出循環,這對於優化數組遍歷是非常棒的。很遺憾,forEach並不支持break操作,使用break會導致報錯。

let arr = [1, 2, 3, 4],
    i = 0,
    length = arr.length;
for (; i < length; i++) {
    console.log(arr[i]); //1,2
    if (arr[i] === 2) {
        break;
    };
};

arr.forEach((self,index) => {
    console.log(self);
    if (self === 2) {
        break; //報錯
    };
});

那forEach能不能跳出循環呢?可以,不過不是使用break,而是結合try catch操作

2.forEach中使用return無效

首先需要確定的,直接再for循環中使用return會報錯(函數中使用for可以return),forEach中使用return不會報錯,但rerutn並不會生效,我們來看個例子:

let arr = [1, 2, 3, 4];

function find(array, num) {
    array.forEach((self, index) => {
        if (self === num) {
            return index;
        };
    });
};
let index = find(arr, 2);// undefined

上述代碼想要找到数字2在數組中的索引,但return並不會起到終止代碼運行並返回值的作用。

當然如果我們真的要用return返回某個值,那就只能將return操作放在函數中,而不是forEach循環中,像這樣:

function find(array, num) {
    let _index;
    array.forEach((self, index) => {
        if (self === num) {
            _index = index;
        };
    });
    return _index;
};

3.forEach刪除自身元素index不會被重置

還記得文章開頭的問題嗎,那段代碼其實只會執行一次,數組也不會被刪除乾淨,這是因為forEach在遍歷跑完回調函數后,會隱性讓index自增,像這樣:

arr.forEach((item, index) => {
    arr.splice(index, 1);
    console.log(1);
    //這裏隱性讓index自增加1
    index++;
});

當第一次遍歷結束,此時數組為[2]而index變成了1,此時數組最大索引只是0,不滿足條件,所以跳出了循環。

靈機一動,有沒有什麼辦法讓此時的forEach不跳出循環呢,當然有,使用ES6的拓展運算符。

[...arr].forEach((item, index) => {
    arr.splice(index, 1);
    console.log(1);
});

通過拓展運算符重置數組arr,達到不跳出循環的目的,你會發現內部確實執行了兩次,很遺憾的是index依舊沒被重置,所以數組arr還是無法在遍歷的同時刪空自己。

因為在實際開發中,遍曆數組同時刪除某項的操作十分常見,所以對於習慣了forEach的同學,這一點一定要注意。


for與forEach的區別

那麼說到這裏,for循環與forEach有什麼區別呢?我想大家應該都能說上幾點了吧,比如:

1.for循環可以使用break跳出循環,但forEach不能。

2.for循環可以控制循環起點(i初始化的数字決定循環的起點),forEach只能默認從索引0開始。

3.for循環過程中支持修改索引(修改 i),但forEach做不到(底層控制index自增,我們無法左右它)。

數組遍歷並刪除自身

好了,我們回歸問題的本質,怎麼在遍歷一個數組的同時,並刪除符合條件的所有項呢?

其實很簡單,我們可以使用for循環,像這樣:

let arr = [1, 2, 1],
    i = 0,
    length = arr.length;

for (; i < length; i++) {
    // 刪除數組中所有的1
    if (arr[i] === 1) {
        arr.splice(i, 1);
        //重置i,否則i會跳一位
        i--;
    };
};
console.log(arr);//[2]

刪除符合條件的所有項,不就是過濾嗎?過濾,本能想到filter方法,也很簡單:

var arr1 = arr.filter((index) => {
    return index !== 1;
});
console.log(arr1); //[2]

更少的代碼量,只是得新建一個變量來接受filter方法的返回值。

你說,我這個人就比較倔強,forEach因為index索引無法重置,對於刪除數組項真的很困難,那我非要用forEach去做這個功能行不行,當然行,只是我們得讓數組反過來遍歷:

arr.slice().reverse().forEach(function (item, index, arr1) {
    if (item === 1) {
        arr.splice(arr1.length - 1 - index, 1);
    }
});
console.log(arr);//[2]

當然我們並不推薦這麼做,因為數組方法繁多,並沒有絕對的好與不好,什麼情況下使用對的方法才是更棒的做法,這裏使用forEach雖然達到了目的,但代碼閱讀體驗並不太好。

站長推薦

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

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

沐鳴總代理_axios的使用

什麼是 axios?

Axios 是一個基於 promise 的 HTTP 庫,可以用在瀏覽器和 node.js 中。支持從瀏覽器中創建XML請求,支持 node.js中發出http請求,支持攔截請求和響應,支持轉換請求和響應數據,支持自動轉換JSON數據,客戶端支持防禦 XSRF。

axios安裝

使用 npm:

npm install axios

使用 bower:

bower install axios

使用 cdn:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

axios基本使用

執行 GET 請求

// 為給定 ID 的 user 創建請求
axios.get('/user?ID=12345')
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

// 可選地,上面的請求可以這樣做
axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

執行 POST 請求

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

執行多個併發請求

function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
  .then(axios.spread(function (acct, perms) {
    // 兩個請求現在都執行完成
  }));

axios的封裝

封裝目的:避免重複設置超時時間,請求頭,錯誤處理;提高代碼質量。

config.js文件

// axios中請求配置有baseURL選項,表示請求URL公共部分。
// `baseURL` 將自動加在 `url` 前面,除非 `url` 是一個絕對 URL

export const baseURL = 'http://XXXXXXXX.com/'

axios.js文件

import axios from 'axios'
import qs from 'qs'
// 在config.js文件中統一存放一些公共常量,方便之後維護
import { baseURL } from './config.js'

// 添加請求攔截器,在發送請求之前做些什麼(**具體查看axios文檔**)
axios.interceptors.request.use(function (config) {
// 显示loading
return config
}, function (error) {
// 請求錯誤時彈框提示,或做些其他事
return Promise.reject(error)
})

// 添加響應攔截器(**具體查看axios文檔**)
axios.interceptors.response.use(function (response) {
// 對響應數據做點什麼,允許在數據返回客戶端前,修改響應的數據
// 如果只需要返回體中數據,則如下,如果需要全部,則 return response 即可
return response.data
}, function (error) {
// 對響應錯誤做點什麼
return Promise.reject(error)
})

// 封裝數據返回失敗提示函數---------------------------------------------------------------------------
function errorState (response) {
// 隱藏loading
// 如果http狀態碼正常,則直接返回數據
if (response && (response.status === 200 || response.status === 304 || response.status === 400)) {
// 如果不需要除了data之外的數據,可以直接 return response.data
return response
} else {
alert('數據獲取錯誤')
}
}

// 封裝數據返回成功提示函數
function successState (res) {
// 隱藏loading
// 統一判斷後端返回的錯誤碼(錯誤碼與後台協商而定)
if (res.data.code === '000000') {
alert('success')
return res
}
}

// 封裝axios
function apiAxios (method, url, params) {
let httpDefault = {
method: method,
baseURL: baseURL,
url: url,
// `params` 是即將與請求一起發送的 URL 參數
// `data` 是作為請求主體被發送的數據
params: method === 'GET' || method === 'DELETE' ? params : null,
data: method === 'POST' || method === 'PUT' ? qs.stringify(params) : null,
timeout: 10000
}

// 注意**Promise**使用(Promise首字母大寫)
return new Promise((resolve, reject) => {
axios(httpDefault)
// 此處的.then屬於axios
.then((res) => {
successState(res)
resolve(res)
}).catch((response) => {
errorState(response)
reject(response)
})
})
}
export default apiAxios

站長推薦

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

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

沐鳴開戶_你應該經常使用的7種Vue.js模式

說實話,閱讀文檔並不是我們大多數人喜歡的事情,但當使用像vue這樣不斷髮展的現代前端框架時,很多東西會隨着每一個新版本的發布而改變,你可能會錯過一些後來推出的新的閃亮功能。讓我們看一下那些有趣但不那麼流行的功能,請記住,所有這些都是Vue文檔的一部分。

1.處理加載狀態

在大型應用程序中,我們可能需要將應用程序劃分為更小的塊,只有在需要時才從服務器加載組件。為了使這一點更容易,Vue允許你將你的組件定義為一個工廠函數,它異步解析你的組件定義。Vue只有在需要渲染組件時才會觸發工廠函數,並將緩存結果,以便將來重新渲染。2.3版本的新功能是,異步組件工廠也可以返回一個如下格式的對象。

const AsyncComponent = () => ({
  // 要加載的組件(應為Promise)
  component: import('./MyComponent.vue'),
  // 異步組件正在加載時要使用的組件
  loading: LoadingComponent,
  // 加載失敗時使用的組件
  error: ErrorComponent,
  // 显示加載組件之前的延遲。默認值:200ms。
  delay: 200,
  // 如果提供並超過了超時,則會显示error組件。默認值:無窮。
  timeout: 3000
})

通過這種方法,你有額外的加載和錯誤狀態、組件獲取的延遲和超時等選項。

2.廉價的“v-once”靜態組件

在Vue中渲染純html元素的速度非常快,但有時你可能有一個包含大量靜態內容的組件。在這種情況下,你可以通過在根元素中添加 v-once 指令來確保它只被評估一次,然後進行緩存,就像這樣。

Vue.component('terms-of-service', {
  template: `
    <div v-once>
      <h1>Terms of Service</h1>
      ... a lot of static content ...
    </div>
  `
})

3.遞歸組件

組件可以在自己的模板中遞歸調用自己,但是,它們只能通過 name 選項來調用。

如果你不小心,遞歸組件也可能導致無限循環:

name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'

像上面這樣的組件會導致“超過最大堆棧大小”的錯誤,所以要確保遞歸調用是有條件的即(使用 v-if 最終將為 false)

4.內聯模板

當特殊屬性 inline-template 存在於一個子組件上時,該組件將使用它的內部內容作為它的模板,而不是將其視為分佈式內容,這允許更靈活的模板編寫。

<my-component inline-template>
  <div>
    <p>These are compiled as the component's own template.</p>
    <p>Not parent's transclusion content.</p>
  </div>
</my-component>

5.動態指令參數

指令參數可以是動態的。例如,在 v-mydirective:[argument]=“value” 中, argument 可以根據組件實例中的數據屬性更新!這使得我們的自定義指令可以靈活地在整個應用程序中使用。

這是一條指令,其中可以根據組件實例更新動態參數:

<div id="dynamicexample">
  <h3>Scroll down inside this section ↓</h3>
  <p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>
Vue.directive('pin', {
  bind: function (el, binding, vnode) {
    el.style.position = 'fixed'
    var s = (binding.arg == 'left' ? 'left' : 'top')
    el.style[s] = binding.value + 'px'
  }
})

new Vue({
  el: '#dynamicexample',
  data: function () {
    return {
      direction: 'left'
    }
  }
})

6.事件和鍵修飾符

對於 .passive、.capture 和 .once 事件修飾符,Vue提供了可與 on 一起使用的前綴:

例如:

on: {
  '!click': this.doThisInCapturingMode,
  '~keyup': this.doThisOnce,
  '~!mouseover': this.doThisOnceInCapturingMode
}

對於所有其他的事件和鍵修飾符,不需要專有的前綴,因為你可以在處理程序中使用事件方法。

7.依賴注入(Provide/Inject)

有幾種方法可以讓兩個組件在 Vue 中進行通信,它們各有優缺點。在2.2版本中引入的一種新方法是使用Provide/Inject的依賴注入。

這對選項一起使用,允許一個祖先組件作為其所有子孫的依賴注入器,無論組件層次結構有多深,只要它們在同一個父鏈上。如果你熟悉react,這與React的上下文功(context)能非常相似。

// parent component providing 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// child component injecting 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

來自:https://blog.zhangbing.site/2021/04/09/7-vue-patterns-that-you-should-be-using-more-often/

翻譯自medium.com,作者:Fotis Adamakis

站長推薦

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

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

沐鳴平台網址_TypeScript Interface vs Type知多少

接口和類型別名非常相似,在大多情況下二者可以互換。在寫TS的時候,想必大家都問過自己這個問題,我到底應該用哪個呢?希望看完本文會給你一個答案。知道什麼時候應該用哪個,首先應該了解二者之間的相同點和不同點,再做出選擇。

接口 vs 類型別名 相同點

1. 都可以用來描述對象或函數

interface Point {
  x: number
  y: number
}

interface SetPoint {
  (x: number, y: number): void;
}
type Point = {
  x: number;
  y: number;
};

type SetPoint = (x: number, y: number) => void;

2. 都可以擴展

兩者的擴展方式不同,但並不互斥。接口可以擴展類型別名,同理,類型別名也可以擴展接口。

接口的擴展就是繼承,通過 extends 來實現。類型別名的擴展就是交叉類型,通過 & 來實現。

// 接口擴展接口
interface PointX {
    x: number
}

interface Point extends PointX {
    y: number
}
// 類型別名擴展類型別名
type PointX = {
    x: number
}

type Point = PointX & {
    y: number
}
// 接口擴展類型別名
type PointX = {
    x: number
}
interface Point extends PointX {
    y: number
}
// 類型別名擴展接口
interface PointX {
    x: number
}
type Point = PointX & {
    y: number
}

接口 vs 類型別名不同點

1. 類型別名更通用(接口只能聲明對象,不能重命名基本類型)

類型別名的右邊可以是任何類型,包括基本類型、元祖、類型表達式(&或|等類型運算符);而在接口聲明中,右邊必須為結構。例如,下面的類型別名就不能轉換成接口:

type A = number
type B = A | string

2. 擴展時表現不同

擴展接口時,TS將檢查擴展的接口是否可以賦值給被擴展的接口。舉例如下:

interface A {
    good(x: number): string,
    bad(x: number): string
}
interface B extends A {
    good(x: string | number) : string,
    bad(x: number): number // Interface 'B' incorrectly extends interface 'A'.
                           // Types of property 'bad' are incompatible.
                           // Type '(x: number) => number' is not assignable to type '(x: number) => string'.
                           // Type 'number' is not assignable to type 'string'.
}

但使用交集類型時則不會出現這種情況。我們將上述代碼中的接口改寫成類型別名,把 extends 換成交集運算符 &,TS將盡其所能把擴展和被擴展的類型組合在一起,而不會拋出編譯時錯誤。

type A = {
    good(x: number): string,
    bad(x: number): string
}
type B = A & {
     good(x: string | number) : string,
     bad(x: number): number 
}

3. 多次定義時表現不同

接口可以定義多次,多次的聲明會合併。但是類型別名如果定義多次,會報錯。

interface Point {
    x: number
}
interface Point {
    y: number
}
const point: Point = {x:1} // Property 'y' is missing in type '{ x: number; }' but required in type 'Point'.

const point: Point = {x:1, y:1} // 正確
type Point = {
    x: number // Duplicate identifier 'A'.
}

type Point = {
    y: number // Duplicate identifier 'A'.
}

到底應該用哪個

如果接口和類型別名都能滿足的情況下,到底應該用哪個是我們關心的問題。感覺哪個都可以,但是強烈建議大家只要能用接口實現的就優先使用接口,接口滿足不了的再用類型別名。

為什麼會這麼建議呢?其實在TS的wiki中有說明。具體的文章地址在這裏。

以下是Preferring Interfaces Over Intersections的譯文:

大多數時候,對於聲明一個對象,類型別名和接口表現的很相似。

interface Foo { prop: string }

type Bar = { prop: string };

然而,當你需要通過組合兩個或者兩個以上的類型實現其他類型時,可以選擇使用接口來擴展類型,也可以通過交叉類型(使用 & 創造出來的類型)來完成,這就是二者開始有區別的時候了。

  • 接口會創建一個單一扁平對象類型來檢測屬性衝突,當有屬性衝突時會提示,而交叉類型只是遞歸的進行屬性合併,在某種情況下可能產生 never 類型
  • 接口通常表現的更好,而交叉類型做為其他交叉類型的一部分時,直觀上表現不出來,還是會認為是不同基本類型的組合
  • 接口之間的繼承關係會緩存,而交叉類型會被看成組合起來的一個整體
  • 在檢查一個目標交叉類型時,在檢查到目標類型之前會先檢查每一個組分

上述的幾個區別從字面上理解還是有些繞,下面通過具體的列子來說明。

interface Point1 {
    x: number
}

interface Point extends Point1 {
    x: string // Interface 'Point' incorrectly extends interface 'Point1'.
              // Types of property 'x' are incompatible.
              // Type 'string' is not assignable to type 'number'.
}
type Point1 = {
    x: number
}

type Point2 = {
    x: string
}

type Point = Point1 & Point2 // 這時的Point是一個'number & string'類型,也就是never

從上述代碼可以看出,接口繼承同名屬性不滿足定義會報錯,而相交類型就是簡單的合併,最後產生了 number & string 類型,可以解釋譯文中的第一點不同,其實也就是我們在不同點模塊中介紹的擴展時表現不同。

再來看下面例子:

interface PointX {
    x: number
}

interface PointY {
    y: number
}

interface PointZ {
    z: number
}

interface PointXY extends PointX, PointY {
}

interface Point extends PointXY, PointZ {
   
}
const point: Point = {x: 1, y: 1} // Property 'z' is missing in type '{ x: number; y: number; }' but required in type 'Point'
type PointX = {
    x: number
}

type PointY = {
    y: number
}

type PointZ = {
    z: number
}

type PointXY = PointX & PointY

type Point = PointXY & PointZ

const point: Point = {x: 1, y: 1} // Type '{ x: number; y: number; }' is not assignable to type 'Point'.
                                  // Property 'z' is missing in type '{ x: number; y: number; }' but required in type 'Point3'.

從報錯中可以看出,當使用接口時,報錯會準確定位到Point。
但是使用交叉類型時,雖然我們的 Point 交叉類型是 PointXY & PointZ, 但是在報錯的時候定位並不在 Point 中,而是在 Point3 中,即使我們的 Point 類型並沒有直接引用 Point3 類型。

如果我們把鼠標放在交叉類型 Point 類型上,提示的也是 type Point = PointX & PointY & PointZ,而不是 PointXY & PointZ。

這個例子可以同時解釋譯文中第二個和最後一個不同點。

結論

有的同學可能會問,如果我不需要組合只是單純的定義類型的時候,是不是就可以隨便用了。但是為了代碼的可擴展性,建議還是優先使用接口。現在不需要,誰能知道後續需不需要呢?所以,讓我們大膽的使用接口吧~

來源:https://segmentfault.com/a/1190000039834284

站長推薦

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

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

沐鳴註冊_一份關於vue-cli3項目常用項配置

配置全局cdn,包含js、css

開啟Gzip壓縮,包含文件js、css

去掉註釋、去掉console.log

壓縮圖片

本地代理

設置別名,vscode也能識別

配置環境變量開發模式、測試模式、生產模式

請求路由動態添加

axios配置

添加mock數據

配置全局less

只打包改變的文件

開啟分析打包日誌

vue.config.js

完整的架構配置

const path = require('path');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin') // 去掉註釋
const CompressionWebpackPlugin = require('compression-webpack-plugin'); // 開啟壓縮
const { HashedModuleIdsPlugin } = require('webpack');

function resolve(dir) {
    return path.join(__dirname, dir)
}

const isProduction = process.env.NODE_ENV === 'production';

// cdn預加載使用
const externals = {
    'vue': 'Vue',
    'vue-router': 'VueRouter',
    'vuex': 'Vuex',
    'axios': 'axios',
    "element-ui": "ELEMENT"
}

const cdn = {
    // 開發環境
    dev: {
        css: [
            'https://unpkg.com/element-ui/lib/theme-chalk/index.css'
        ],
        js: []
    },
    // 生產環境
    build: {
        css: [
            'https://unpkg.com/element-ui/lib/theme-chalk/index.css'
        ],
        js: [
            'https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js',
            'https://cdn.jsdelivr.net/npm/vue-router@3.0.1/dist/vue-router.min.js',
            'https://cdn.jsdelivr.net/npm/vuex@3.0.1/dist/vuex.min.js',
            'https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js',
            'https://unpkg.com/element-ui/lib/index.js'
        ]
    }
}

module.exports = {

    lintOnSave: false, // 關閉eslint
    productionSourceMap: false,
    publicPath: './', 
    outputDir: process.env.outputDir, // 生成文件的目錄名稱
    chainWebpack: config => {

        config.resolve.alias
            .set('@', resolve('src'))

        // 壓縮圖片
        config.module
            .rule('images')
            .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
            .use('image-webpack-loader')
            .loader('image-webpack-loader')
            .options({ bypassOnDebug: true })

        // webpack 會默認給commonChunk打進chunk-vendors,所以需要對webpack的配置進行delete
        config.optimization.delete('splitChunks')

        config.plugin('html').tap(args => {
            if (process.env.NODE_ENV === 'production') {
                args[0].cdn = cdn.build
            }
            if (process.env.NODE_ENV === 'development') {
                args[0].cdn = cdn.dev
            }
            return args
        })
        
        config
            .plugin('webpack-bundle-analyzer')
            .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
    },

    configureWebpack: config => {
        const plugins = [];

        if (isProduction) {
            plugins.push(
                new UglifyJsPlugin({
                    uglifyOptions: {
                        output: {
                            comments: false, // 去掉註釋
                        },
                        warnings: false,
                        compress: {
                            drop_console: true,
                            drop_debugger: false,
                            pure_funcs: ['console.log']//移除console
                        }
                    }
                })
            )
            // 服務器也要相應開啟gzip
            plugins.push(
                new CompressionWebpackPlugin({
                    algorithm: 'gzip',
                    test: /\.(js|css)$/,// 匹配文件名
                    threshold: 10000, // 對超過10k的數據壓縮
                    deleteOriginalAssets: false, // 不刪除源文件
                    minRatio: 0.8 // 壓縮比
                })
            )

            // 用於根據模塊的相對路徑生成 hash 作為模塊 id, 一般用於生產環境
            plugins.push(
                new HashedModuleIdsPlugin()
            )

            // 開啟分離js
            config.optimization = {
                runtimeChunk: 'single',
                splitChunks: {
                    chunks: 'all',
                    maxInitialRequests: Infinity,
                    minSize: 1000 * 60,
                    cacheGroups: {
                        vendor: {
                            test: /[\\/]node_modules[\\/]/,
                            name(module) {
                                // 排除node_modules 然後吧 @ 替換為空 ,考慮到服務器的兼容
                                const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]
                                return `npm.${packageName.replace('@', '')}`
                            }
                        }
                    }
                }
            };

            // 取消webpack警告的性能提示
            config.performance = {
                hints: 'warning',
                //入口起點的最大體積
                maxEntrypointSize: 1000 * 500,
                //生成文件的最大體積
                maxAssetSize: 1000 * 1000,
                //只給出 js 文件的性能提示
                assetFilter: function (assetFilename) {
                    return assetFilename.endsWith('.js');
                }
            }

            // 打包時npm包轉CDN
            config.externals = externals;
        }

        return { plugins }
    },

    pluginOptions: {
        // 配置全局less
        'style-resources-loader': {
            preProcessor: 'less',
            patterns: [resolve('./src/style/theme.less')]
        }
    },
    devServer: {
        open: false, // 自動啟動瀏覽器
        host: '0.0.0.0', // localhost
        port: 6060, // 端口號
        https: false,
        hotOnly: false, // 熱更新
        proxy: {
            '^/sso': {
                target: process.env.VUE_APP_SSO, // 重寫路徑
                ws: true,   //開啟WebSocket
                secure: false,      // 如果是https接口,需要配置這個參數
                changeOrigin: true
            }
        }
    }
}

html模板配置cdn

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
    <% for (var i in
        htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" />
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
    <% } %>
</head>

<body>
    <noscript>
        <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
            Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
    <% for (var i in
        htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
    <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
    <% } %>
</body>
</html>

開啟Gzip壓縮,包含文件js、css

new CompressionWebpackPlugin({
      algorithm: 'gzip',
      test: /\.(js|css)$/, // 匹配文件名
      threshold: 10000, // 對超過10k的數據壓縮
      deleteOriginalAssets: false, // 不刪除源文件
      minRatio: 0.8 // 壓縮比
})

去掉註釋、去掉console.log

安裝cnpm i uglifyjs-webpack-plugin -D

const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
new UglifyJsPlugin({
    uglifyOptions: {
        output: {
            comments: false, // 去掉註釋
        },
        warnings: false,
        compress: {
            drop_console: true,
            drop_debugger: false,
            pure_funcs: ['console.log'] //移除console
        }
    }
})

壓縮圖片

chainWebpack: config => {
    // 壓縮圖片
    config.module
        .rule('images')
        .test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
        .use('image-webpack-loader')
        .loader('image-webpack-loader')
        .options({ bypassOnDebug: true })

}

本地代理

devServer: {
    open: false, // 自動啟動瀏覽器
    host: '0.0.0.0', // localhost
    port: 6060, // 端口號
    https: false,
    hotOnly: false, // 熱更新
    proxy: {
        '^/sso': {
            target: process.env.VUE_APP_SSO, // 重寫路徑
            ws: true, //開啟WebSocket
            secure: false, // 如果是https接口,需要配置這個參數
            changeOrigin: true
        }
    }
}

設置vscode 識別別名

在vscode中插件安裝欄搜索 Path Intellisense 插件,打開settings.json文件添加 以下代碼 “@”: “${workspaceRoot}/src”,安以下添加

{
    "workbench.iconTheme": "material-icon-theme",
    "editor.fontSize": 16,
    "editor.detectIndentation": false,
    "guides.enabled": false,
    "workbench.colorTheme": "Monokai",
    "path-intellisense.mappings": {
        "@": "${workspaceRoot}/src"
    }
}

在項目package.json所在同級目錄下創建文件jsconfig.json

{
    "compilerOptions": {
        "target": "ES6",
        "module": "commonjs",
        "allowSyntheticDefaultImports": true,
        "baseUrl": "./",
        "paths": {
          "@/*": ["src/*"]
        }
    },
    "exclude": [
        "node_modules"
    ]
}

如果還沒請客官移步在vscode中使用別名@按住ctrl也能跳轉對應路徑

配置環境變量開發模式、測試模式、生產模式

在根目錄新建

.env.development

# 開發環境
NODE_ENV='development'

VUE_APP_SSO='http://http://localhost:9080'

.env.test

NODE_ENV = 'production' # 如果我們在.env.test文件中把NODE_ENV設置為test的話,那麼打包出來的目錄結構是有差異的
VUE_APP_MODE = 'test'
VUE_APP_SSO='http://http://localhost:9080'
outputDir = test

.env.production

NODE_ENV = 'production'

VUE_APP_SSO='http://http://localhost:9080'

package.json

"scripts": {
    "build": "vue-cli-service build", //生產打包
    "lint": "vue-cli-service lint",
    "dev": "vue-cli-service serve", // 開發模式
    "test": "vue-cli-service build --mode test", // 測試打包
    "publish": "vue-cli-service build && vue-cli-service build --mode test" // 測試和生產一起打包
 }

請求路由動態添加

router/index.js文件

import Vue from 'vue';
import VueRouter from 'vue-router'
Vue.use(VueRouter)

import defaultRouter from './defaultRouter'
import dynamicRouter from './dynamicRouter';

import store from '@/store';

const router = new VueRouter({
    routes: defaultRouter,
    mode: 'hash',
    scrollBehavior(to, from, savedPosition) {
        // keep-alive 返回緩存頁面後記錄瀏覽位置
        if (savedPosition && to.meta.keepAlive) {
            return savedPosition;
        }
        // 異步滾動操作
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve({ x: 0, y: 0 })
            }, 200)
        })
    }
})

// 消除路由重複警告
const selfaddRoutes = function (params) {
    router.matcher = new VueRouter().matcher;
    router.addRoutes(params);
}

// 全局路由攔截
router.beforeEach((to, from, next) => {
    const { hasRoute } = store.state; // 防止路由重複添加
    if (hasRoute) {
        next()
    } else {
        dynamicRouter(to, from, next, selfaddRoutes)
    }
})

export default router;

dynamicRouter.js

import http from '@/http/request';
import defaultRouter from './defaultRouter'
import store from '@/store'

// 重新構建路由對象
const menusMap = function (menu) {
    return menu.map(v => {
        const { path, name, component } = v
        const item = {
            path,
            name,
            component: () => import(`@/${component}`)
        }
        return item;
    })
}


// 獲取路由
const addPostRouter = function (to, from, next, selfaddRoutes) {
    http.windPost('/mock/menu') // 發起請求獲取路由
        .then(menu => {
            defaultRouter[0].children.push(...menusMap(menu));
            selfaddRoutes(defaultRouter);
            store.commit('hasRoute', true);
            next({ ...to, replace: true })
        })
}

export default addPostRouter;

defaultRouter.js 默認路由

const main = r => require.ensure([], () => r(require('@/layout/main.vue')), 'main')
const index = r => require.ensure([], () => r(require('@/view/index/index.vue')), 'index')
const about = r => require.ensure([], () => r(require('@/view/about/about.vue')), 'about')
const detail = r => require.ensure([], () => r(require('@/view/detail/detail.vue')), 'detail')
const error = r => require.ensure([], () => r(require('@/view/404/404.vue')), 'error');
const defaultRouter = [
    {
        path: "/", 
        component: main, // 布局頁
        redirect: {
            name: "index"
        },
        children:[
            {
                path: '/index',
                component: index,
                name: 'index',
                meta: {
                    title: 'index'
                }
            },
            {
                path: '/about',
                component: about,
                name: 'about',
                meta: {
                    title: 'about'
                }
            },
            {
                path: '/detail',
                component: detail,
                name: 'detail',
                meta: {
                    title: 'detail'
                }
            }
        ]
    },
    {
        path: '/404',
        component: error,
        name: '404',
        meta: {
            title: '404'
        }
    }
]
export default defaultRouter;

axios配置

import axios from "axios";
import merge from 'lodash/merge'
import qs from 'qs'

/**
 * 實例化
 * config是庫的默認值,然後是實例的 defaults 屬性,最後是請求設置的 config 參數。後者將優先於前者
 */
const http = axios.create({
    timeout: 1000 * 30,
    withCredentials: true, // 表示跨域請求時是否需要使用憑證
});

/**
 * 請求攔截
 */
http.interceptors.request.use(function (config) {
    return config;
}, function (error) {
    return Promise.reject(error);
});


/**
 * 響應攔截
 */
http.interceptors.response.use(response => {
    // 過期之類的操作
    if (response.data && (response.data.code === 401)) {
        // window.location.href = ''; 重定向
    }
    return response
}, error => {
    return Promise.reject(error)
})


/**
 * 請求地址處理
 */
http.adornUrl = (url) => {
    return url;
}

/**
 * get請求參數處理
 * params 參數對象
 * openDefultParams 是否開啟默認參數
 */
http.adornParams = (params = {}, openDefultParams = true) => {
    var defaults = {
        t: new Date().getTime()
    }
    return openDefultParams ? merge(defaults, params) : params
}


/**
 * post請求數據處理
 * @param {*} data 數據對象
 * @param {*} openDefultdata 是否開啟默認數據?
 * @param {*} contentType 數據格式
 *  json: 'application/json; charset=utf-8'
 *  form: 'application/x-www-form-urlencoded; charset=utf-8'
 */
http.adornData = (data = {}, openDefultdata = true, contentType = 'json') => {
    var defaults = {
        t: new Date().getTime()
    }
    data = openDefultdata ? merge(defaults, data) : data
    return contentType === 'json' ? JSON.stringify(data) : qs.stringify(data)
}


/**
 * windPost請求
 * @param {String} url [請求地址]
 * @param {Object} params [請求攜帶參數]
 */
http.windPost = function (url, params) {
    return new Promise((resolve, reject) => {
        http.post(http.adornUrl(url), qs.stringify(params))
            .then(res => {
                resolve(res.data)
            })
            .catch(error => {
                reject(error)
            })
    })
}


/**
 * windJsonPost請求
 * @param {String} url [請求地址]
 * @param {Object} params [請求攜帶參數]
 */
http.windJsonPost = function (url, params) {
    return new Promise((resolve, reject) => {
        http.post(http.adornUrl(url), http.adornParams(params))
            .then(res => {
                resolve(res.data)
            })
            .catch(error => {
                reject(error)
            })
    })
}


/**
 * windGet請求
 * @param {String} url [請求地址]
 * @param {Object} params [請求攜帶參數]
 */
http.windGet = function (url, params) {
    return new Promise((resolve, reject) => {
        http.get(http.adornUrl(url), { params: params })
            .then(res => {
                resolve(res.data)
            })
            .catch(error => {
                reject(error)
            })
    })
}

/**
 * 上傳圖片
 */
http.upLoadPhoto = function (url, params, callback) {
    let config = {}
    if (callback !== null) {
        config = {
            onUploadProgress: function (progressEvent) {
                //屬性lengthComputable主要表明總共需要完成的工作量和已經完成的工作是否可以被測量
                //如果lengthComputable為false,就獲取不到progressEvent.total和progressEvent.loaded
                callback(progressEvent)
            }
        }
    }
    return new Promise((resolve, reject) => {
        http.post(http.adornUrl(url), http.adornParams(params), config)
            .then(res => {
                resolve(res.data)
            })
            .catch(error => {
                reject(error)
            })
    })
}
export default http;

添加mock數據

const Mock = require('mockjs')

// 獲取 mock.Random 對象
const Random = Mock.Random
// mock新聞數據,包括新聞標題title、內容content、創建時間createdTime
const produceNewsData = function () {
    let newsList = []
    for (let i = 0; i < 3; i++) {
        let newNewsObject = {}
        if(i === 0){
            newNewsObject.path = '/add/article';
            newNewsObject.name  = 'add-article';
            newNewsObject.component = 'modules/add/article/article';
        }
        if(i === 1){
            newNewsObject.path = '/detail/article';
            newNewsObject.name  = 'detail-article';
            newNewsObject.component = 'modules/detail/article/article'
        }
        if(i === 2){
            newNewsObject.path = '/edit/article';
            newNewsObject.name  = 'edit-article';
            newNewsObject.component = 'modules/edit/article/article'
        }
        newsList.push(newNewsObject)
    }
    return newsList;
}
Mock.mock('/mock/menu', produceNewsData)

配置全局less

pluginOptions: {
    // 配置全局less
    'style-resources-loader': {
        preProcessor: 'less',
        patterns: [resolve('./src/style/theme.less')]
    }
}

只打包改變的文件

安裝cnpm i webpack -D

const { HashedModuleIdsPlugin } = require('webpack');
configureWebpack: config => {    
    const plugins = [];
    plugins.push(
        new HashedModuleIdsPlugin()
    )
}

開啟分析打包日誌

安裝cnpm i webpack-bundle-analyzer -D

chainWebpack: config => {
    config
        .plugin('webpack-bundle-analyzer')
        .use(require('webpack-bundle-analyzer').BundleAnalyzerPlugin)
}

完整代碼

點擊獲取完整代碼github(https://github.com/hangjob/vue-admin)

來源:https://segmentfault.com/a/1190000022512358

站長推薦

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

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

沐鳴下載_讀 Angular 代碼風格指南

原文地址:Angular 文檔

該文章擁有完整的代碼風格指南——大到如何編排文件夾,小到如何進行變量命名都涉及。但是與 ng 略有綁定,所以這裏整理一下可以單獨拿出來的通用部分。

單一職責

請堅持每個文件只定義一樣東西(例如服務或組件),並且把文件大小限制在 400 行代碼以內

小文件通常非常容易閱讀、維護,並能防止在版本控制系統里與團隊衝突

小文件還可以防止一些隱蔽的程序缺陷,當把多個組件合寫在同一個文件中時,可能造成共享變量、創建意外的閉包,或者與依賴之間產生意外耦合等情況。

總的來說,單一職責原則可以讓代碼更加可復用、更容易閱讀,減少出錯的可能性。

如果該文件是一個函數,那麼請堅持定義簡單函數,並且限制在 75 行之內。

這樣能更便於我們做單元測試。

命名

命名是一件非常重要的事情,他可以讓其他人看我們的代碼,或者是我們自己在一段時間之後再看之前的代碼時,可以迅速理解文件內容、變量含義、方法用途……。也可以在全局搜索的時候,讓我們可以迅速定位到目標

代碼給人讀的時間比給機器讀的時間多多了,因此我們需要慎重考慮命名。

可以遵循以下兩個原則:

  1. 堅持使用一致的命名規則
  2. 堅持遵循同一個模式來描述特性和類型。

文件命名

ng 推薦使用點和橫杠來分隔文件名——在描述性名字中,用橫杠來分隔單詞;用點來分隔描述性名字和類型

具體來說,就是先描述組件特性,再描述它的類型的模式,並且對所有組件使用一致的類型命名規則!!!

也就是 feature.type.ts,例如 hero.service.ts, app.module.ts, home.component.html, global.style.css。

常常使用的後綴有 *.service、*.component、*.pipe、.module、.directive。如果有必要,可以創建更多類型名,但必須注意,不要創建太多了

這樣命名文件可以讓我們來快速的識別文件中有什麼,並且輕鬆的利用編輯器或者 IDE 的模糊搜索功能找到特定文件類型。或是為自動化任務提供模式匹配。

文件名與符號名

如果將將文件命名為 hero.service.ts,那麼符號名,即類/變量名,應該命名為 HeroService。

若為 todo-list.service.ts,則該命名為 TodoListService。

也就是說,使用大寫駝峰命名法來命名類,並且需要匹配符號名與它所在的文件名,在符號名後面追加約定的類型後綴(例如 Component、Service)。

單元測試文件名

應該與測試的文件保持一致,xxx.xx.ts 的單元測試文件名應該叫做 xxx.xx.spec.ts。

結構組織與 LIFT 原則

我們應該力求項目結構組織符合 LIFT 原則:

  • Locate 快速定位代碼
  • Identify 一眼識別代碼
  • Flattest 盡量保持扁平結構
  • Try Do Not Repeat Yourself 嘗試遵循不重複自己的原則

上述四項原則重要程度從大到小。

為何?

LIFT 提供了一致的結構,它具有擴展性強模塊化的特性,它讓我們可以快速鎖定代碼,提高開發的效率。

另外,檢查應用結構是否合理的方法是問問自己:“我能快速打開與此特性有關的所有文件並開始工作嗎?”

Locate(定位)

堅持直觀、簡單和快速地定位代碼。

要想高效的工作,就必須能迅速找到文件,特別是當不知道(或不記得)文件名時——把相關的文件一起放在一個直觀的位置可以節省大量的時間。

並且富有描述性的目錄結構會讓你和後面的維護者眼前一亮!!!

可以使用上面說的,使用 特性 + 後綴 + 文件類型 的命名方式來方便我們的定位

Identify(識別)

文件的名字請達到這個程度:看到名字立刻知道它包含了什麼,代表了什麼。

文件名要具有說明性。保證文件名精準的方法就是:確保文件中只包含一個組件。

避免創建包含多個組件、服務或者混合體的文件。

為何?

花費更少的時間來查找和琢磨代碼,就會變得更有效率。較長的文件名遠勝於較短卻容易混淆的縮寫名。

Flattest(扁平)

請堅持盡可能保持扁平的目錄結構。

考慮當同一目錄下達到 7 個或更多個文件時創建子目錄。

考慮配置 IDE,以隱藏無關的文件,例如生成出來的 .js 文件和 .js.map 文件等。

沒人想要在超過七層的目錄中查找文件!!!

扁平的結構有利於搜索。

另一方面,心理學家們相信,當關注的事物超過 9 個時,人類就會開始感到吃力。所以,當一個文件夾中的文件有 10 個或更多個文件時,可能就是創建子目錄的時候了。

還是根據你自己的舒適度而定吧。除非創建新文件夾能有顯著的價值,否則盡量使用扁平結構。

Try Do Not Repeat Yourself (T-DRY)

堅持 DRY(Don’t Repeat Yourself,不重複自己)。

避免過度 DRY,以致犧牲了閱讀性。

雖然 DRY 很重要,但如果要以犧牲 LIFT 的其它原則為代價,那就不值得了。這也就是為什麼它被稱為 「Try」-DRY。

推薦的目錄結構

堅持把所有源代碼都放到名為 src 的目錄里。

堅持如果組件具有多個伴生文件 (.ts、.html、.css 和 .spec),就為它創建一個文件夾。

我習慣使用的前端目錄結構:

- src
  - app
    - moduleA // 模塊 B
      - assets
      - components
      - ...
    - moduleB // 模塊 A
    - shared // 共享模塊
  - layouts
  - assets
  - main.ts
  - ...

(完)

站長推薦

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

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

沐鳴登錄平台_git 常用命令

倉庫

# 在當前目錄新建一個Git代碼庫
$ git init

# 新建一個目錄,將其初始化為Git代碼庫
$ git init [project-name]

# 下載一個項目和它的整個代碼歷史
$ git clone [url] 

配置

# 显示當前的Git配置
$ git config --list

# 編輯Git配置文件
$ git config -e [--global]

# 設置提交代碼時的用戶信息
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]" 

增加/刪除文件

# 添加指定文件到暫存區
$ git add [file1] [file2] ...

# 添加指定目錄到暫存區,包括子目錄
$ git add [dir]

# 添加當前目錄的所有文件到暫存區
$ git add .

# 添加每個變化前,都會要求確認
# 對於同一個文件的多處變化,可以實現分次提交
$ git add -p

# 刪除工作區文件,並且將這次刪除放入暫存區
$ git rm [file1] [file2] ...

# 停止追蹤指定文件,但該文件會保留在工作區
$ git rm --cached [file]

# 改名文件,並且將這個改名放入暫存區
$ git mv [file-original] [file-renamed] 

代碼提交

# 提交暫存區到倉庫區
$ git commit -m [message]

# 提交暫存區的指定文件到倉庫區
$ git commit [file1] [file2] ... -m [message]

# 提交工作區自上次commit之後的變化,直接到倉庫區
$ git commit -a

# 提交時显示所有diff信息
$ git commit -v

# 使用一次新的commit,替代上一次提交
# 如果代碼沒有任何新變化,則用來改寫上一次commit的提交信息
$ git commit --amend -m [message]

# 重做上一次commit,並包括指定文件的新變化
$ git commit --amend [file1] [file2] ... 

分支

# 列出所有本地分支
$ git branch

# 列出所有遠程分支
$ git branch -r

# 列出所有本地分支和遠程分支
$ git branch -a

# 新建一個分支,但依然停留在當前分支
$ git branch [branch-name]

# 新建一個分支,並切換到該分支
$ git checkout -b [branch]

# 新建一個分支,指向指定commit
$ git branch [branch] [commit]

# 新建一個分支,與指定的遠程分支建立追蹤關係
$ git branch --track [branch] [remote-branch]

# 切換到指定分支,並更新工作區
$ git checkout [branch-name]

# 切換到上一個分支
$ git checkout -

# 建立追蹤關係,在現有分支與指定的遠程分支之間
$ git branch --set-upstream [branch] [remote-branch]

# 合併指定分支到當前分支
$ git merge [branch]

# 選擇一個commit,合併進當前分支
$ git cherry-pick [commit]

# 刪除分支
$ git branch -d [branch-name]

# 刪除遠程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch] 

標籤

# 列出所有tag
$ git tag

# 新建一個tag在當前commit
$ git tag [tag]

# 新建一個tag在指定commit
$ git tag [tag] [commit]

# 刪除本地tag
$ git tag -d [tag]

# 刪除遠程tag
$ git push origin :refs/tags/[tagName]

# 查看tag信息
$ git show [tag]

# 提交指定tag
$ git push [remote] [tag]

# 提交所有tag
$ git push [remote] --tags

# 新建一個分支,指向某個tag
$ git checkout -b [branch] [tag] 

查看信息

# 显示有變更的文件
$ git status

# 显示當前分支的版本歷史
$ git log

# 显示commit歷史,以及每次commit發生變更的文件
$ git log --stat

# 搜索提交歷史,根據關鍵詞
$ git log -S [keyword]

# 显示某個commit之後的所有變動,每個commit佔據一行
$ git log [tag] HEAD --pretty=format:%s

# 显示某個commit之後的所有變動,其"提交說明"必須符合搜索條件
$ git log [tag] HEAD --grep feature

# 显示某個文件的版本歷史,包括文件改名
$ git log --follow [file]
$ git whatchanged [file]

# 显示指定文件相關的每一次diff
$ git log -p [file]

# 显示過去5次提交
$ git log -5 --pretty --oneline

# 显示所有提交過的用戶,按提交次數排序
$ git shortlog -sn

# 显示指定文件是什麼人在什麼時間修改過
$ git blame [file]

# 显示暫存區和工作區的差異
$ git diff

# 显示暫存區和上一個commit的差異
$ git diff --cached [file]

# 显示工作區與當前分支最新commit之間的差異
$ git diff HEAD

# 显示兩次提交之間的差異
$ git diff [first-branch]...[second-branch]

# 显示今天你寫了多少行代碼
$ git diff --shortstat "@{0 day ago}"

# 显示某次提交的元數據和內容變化
$ git show [commit]

# 显示某次提交發生變化的文件
$ git show --name-only [commit]

# 显示某次提交時,某個文件的內容
$ git show [commit]:[filename]

# 显示當前分支的最近幾次提交
$ git reflog 

遠程同步

# 下載遠程倉庫的所有變動
$ git fetch [remote]

# 显示所有遠程倉庫
$ git remote -v

# 显示某個遠程倉庫的信息
$ git remote show [remote]

# 增加一個新的遠程倉庫,並命名
$ git remote add [shortname] [url]

# 取回遠程倉庫的變化,並與本地分支合併
$ git pull [remote] [branch]

# 上傳本地指定分支到遠程倉庫
$ git push [remote] [branch]

# 強行推送當前分支到遠程倉庫,即使有衝突
$ git push [remote] --force

# 推送所有分支到遠程倉庫
$ git push [remote] --all 

撤銷

# 恢復暫存區的指定文件到工作區
$ git checkout [file]

# 恢復某個commit的指定文件到暫存區和工作區
$ git checkout [commit] [file]

# 恢復暫存區的所有文件到工作區
$ git checkout .

# 重置暫存區的指定文件,與上一次commit保持一致,但工作區不變
$ git reset [file]

# 重置暫存區與工作區,與上一次commit保持一致
$ git reset --hard

# 重置當前分支的指針為指定commit,同時重置暫存區,但工作區不變
$ git reset [commit]

# 重置當前分支的HEAD為指定commit,同時重置暫存區和工作區,與指定commit一致
$ git reset --hard [commit]

# 重置當前HEAD為指定commit,但保持暫存區和工作區不變
$ git reset --keep [commit]

# 新建一個commit,用來撤銷指定commit
# 後者的所有變化都將被前者抵消,並且應用到當前分支
$ git revert [commit]

暫時將未提交的變化移除,稍後再移入
$ git stash
$ git stash pop 

其他

# 生成一個可供發布的壓縮包
$ git archive

站長推薦

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

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