在此記錄在開發項目時遇到的問題,或是好用的方法~
創建N個空對象
當我們需要創建N個數組內容可以這麼寫:
Array(3).fill('')
// > Array ["", "", ""]
那麼如果想要創建N個空的對象時則可以:
const students = Array.apply(null, {length: 3}).map(() => ({}));
同時發出多個請求,並統一處理回應/錯誤
使用Promise.allcatch每個函數。結果只統一處理
;!async function () {
let res = await Promise.all([
fun01().catch(err => err),
fun02().catch(err => err),
fun03().catch(err => err),
])
console.log('res', res); // ["成功:11111", "失败:22222", "成功:33333"]
function fun01 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功:11111')
}, 2000)
})
}
function fun02 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('失败:22222')
}, 2000)
})
}
function fun03 () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('成功:33333')
}, 2000)
})
}
} ();
從某個對象中取出某些屬性並創建新的對象
這是我在專案中最常碰到的事情,後臺傳來的參數中有很多是前端並不需要的資料,也就是只有幾項欄位需要而已,並且還會對這些參數進行格式化或過濾等等,因此淺拷貝(新建一個獨立的對象)是完全有必要的
forEach方法
最簡單的方法就是使用forEach了
const cloneAndPluck = function(sourceObject, keys) {
const newObject = {};
keys.forEach((obj, key) => { newObject[key] = sourceObject[key]; });
return newObject;
};
const subset = cloneAndPluck(elmo, ["color", "height"]);
解構方式:
如果數量只有兩三筆的話可以選擇使用解構方式
const object = {
a: 'a',
b: 'b',
c: 'c',
d: 'd',
}
// Remove "c" and "d" fields from original object:
const {c, d, ...partialObject} = object;
const subset = {c, d};
console.log(partialObject) // => { a: 'a', b: 'b'}
console.log(subset) // => { c: 'c', d: 'd'};
js庫方法: ._pick
同樣的在一些js庫(loadsh.js等等) 當中可以使用_.pick方法,同樣的適合只有要抽取幾筆資料的時候
_.pick({name: 'moe', age: 50, userid: 'moe1'}, 'name', 'age');
=> {name: 'moe', age: 50}
_.pick({name: 'moe', age: 50, userid: 'moe1'}, ['name', 'age']);
=> {name: 'moe', age: 50}
_.pick({name: 'moe', age: 50, userid: 'moe1'}, function(value, key, object) {
return _.isNumber(value);
});
=> {age: 50}
在vue當中,通常我們會先聲明要使用的變數
這個時候可以利用reduce的前後參數比較特性,直接將後台數據中和vue聲明中相等的欄位抽取出來
const getSubset = (obj, ...keys) => keys.reduce((acc, curr) => {
acc[curr] = obj[curr]
return acc
}, {})
const elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
}
const subset = getSubset(elmo, 'annoying', 'height', 'meta')
console.log(subset)
reduce方法
還有常見的reduce用法
const getSubset = (obj, ...keys) => keys.reduce((acc, curr) => {
acc[curr] = obj[curr]
return acc
}, {})
const elmo = {
color: 'red',
annoying: true,
height: 'unknown',
meta: { one: '1', two: '2'}
}
const subset = getSubset(elmo, 'annoying', 'height', 'meta')
console.log(subset)
其他js原生方法
其他還有很多方法,也可以配合著搭配使用,像是和filter或entries等等
Object.keys(obj)
.filter((key) => ['blacklisted', 'keys'].indexOf(key) < 0)
.reduce((newObj, key) => Object.assign(newObj, { [key]: obj[key] }), {})
/*------------------------------------------*/
Object.entries(obj)
.filter(([key]) => !['blacklisted', 'keys'].includes(key))
.reduce((obj, [key, val]) => Object.assign(obj, { [key]: val }), {});
/*------------------------------------------*/
Object.fromEntries(
Object.entries(obj)
.filter(([key]) => !['blacklisted', 'keys'].includes(key))
);
將A對象值賦予給B對象
objAssign(objA,objB){
const objAssign= Object.keys(objA).forEach(key => {
objA[key] = objB[key] || arrA[key]})
}
計算各時區時間
new Date().toLocaleString('language', {...options});
例如,要將時間轉為台灣時間:
new Date().toLocaleString('zh-TW', {timeZone: 'Asia/Taipei'}); //輸出 2020 /3/5 下午4:58:17
如果是美國時間的則:
new Date().toLocaleString('en-US', {timeZone: 'America/New_York'});
而 options 裡面還有很多參數可以用,例如如果要 24 小時制的話,可以加上 hour12: false 的選項,詳細可以參考 MDN 的介紹。
今天到新的公司遇到個需求是要向UI插件傳遞今日的日期,且格式為 yyyy-mm-dd, 因為本身代碼已經很冗長所以不想再另外寫一大堆, 所以使用了
new Date().toLocaleDateString('en-CA')
判斷時間是否在特定時間範圍內
項目當中後台會傳來 時:分 而前端要判斷時間跟現在相比,是否已經過去或者還沒到來,這裡比較特殊的是,參數只顯示 時跟分,參考到很有趣的用法是可以直接使用將時間轉換為字符串後進行比較,ex: “10:00” < “20:00” // true
但是前提是:
- 必須為24小時制
- 個位數字前面必須補0
切忌少了一個條件則會出現不準確的結果
var date = new Date();
const HursAndMins = `${this.hurNow}:${this.minNow}`
const promtedStartTime = this.startTime
const promtedEndTime = this.endTime
const isInTime = HursAndMins >= promtedStartTime && HursAndMins <= promtedEndTime
小時跟分鐘相減
小小的紀錄一下,如果將小時跟分鐘的時間格式基礎上減去 指定的分鐘,
以下的方法可以避免將時間轉換為毫秒後互減又要再轉換回來這樣冗長的代碼
例如 假設現在為 14:09 ,則 14:09 減去 15分鐘,則可以這樣做 :
var date = new Date();
date.setMinutes(date.getMinutes()-15);
console.log(`realTime = ${date.getHours()}:${date.getMinutes()}`)
補充一下 new Date().setMinutes的意思,簡單來說就是改變從 new Date()產生的分鐘
在这里插入代码片
按照時間排序
升序
function sortByCreatTime (arr) {
return arr.sort(function (a, b) {
return a.createdAt > b.createdAt ? 1 : -1
})
}
倒序
改變成小於即可
function sortByCreatTime (arr) {
return arr.sort(function (a, b) {
return a.createdAt < b.createdAt ? 1 : -1
})
}
UTC和本地時間的轉換
a.UTC轉本地
此方法換算出UTC和本地的相差時間,之後再跟本地時間進行相減或相加的運算
var x = new Date();
var currentTimeZoneOffsetInHours = x.getTimezoneOffset() / 60;
更簡單的方法就是使用 toString 就可以換算出時間(iso格式)
let value = '2021-02-01T05:23:49.164Z'
new Date(value).toString() // 輸出 Mon Feb 01 2021 13:23:49 GMT+0800 (台北標準時間)"
b 本地轉UTC
如果知道相差的時間的話,同理也是能夠直接相加相減的。這裡紀錄最簡單的方法 :
new Date() //Wed Jan 24 2018 11:06:29 GMT+0800 (CST)
new Date().toISOString() // "2018-01-24T03:06:22.861Z"
MomentJs
或是借用插件的幫助,來轉換,這裡項目用的是,momentjs
import Moment from 'moment'
const moment = extendMoment(Moment)
moment('2020-12-25 15:00:00').add(-8, 'hours').format('YYYY-MM-DD HH:mm:ss')
格式化時間 format yyyy-mm-dd
使用new Date()產生的時間格式往往不是我們需求的格式,以往是按照取出年、月、日再將它們拼接成我們要的格式,但是此方法代碼變的冗長。此時可以使用在es5 新增的 toISOString(),再配合字符串擷取即可達成目的。 (需注意在轉換前拿到的時間是否有時差問題)
new Date(yourDateStr).toISOString().split('T')[0]
new Date(yourDateStr).toISOString().substr(0, 10)
new Date(yourDateStr).toISOString().slice(0,10);
2022/6/13 這次的新的問題是來的新公司以後碰到的, ui的需求是要顯示 13/Jun/2022
這樣的格式, 可以看到的是月份那部分要求的是顯示英文而不是數字
藉由toLocaleString就可以直接轉換, 而month參數的short則表示使用月份的縮寫, 如果要使用完整的話則可以
改成 long
const date = new Date()
date.toLocaleString('en-CA', { month: 'short' })
格式化時間 小於10則前面補0
直接上代碼~
const date = new Date(2018, 2, 1)
const result = date.toLocaleDateString("en-GB", { // you can use undefined as first argument
year: "numeric",
month: "2-digit",
day: "2-digit",
})
console.log(result) // outputs “01/03/2018”
// 如果有大量的需求則可以
const formatter = new Intl.DateTimeFormat("en-GB", { // <- re-use me
year: "numeric",
month: "2-digit",
day: "2-digit",
})
const date = new Date(2018, 2, 1) // can also be a Temporal object
const result = formatter.format(date)
console.log(result) // outputs “01/03/2018”
更詳細的方法可以至
https://stackoverflow.com/questions/3605214/javascript-add-leading-zeroes-to-date
設定選取時間範圍
這個方式是碰到一個業務邏輯是需要讓客戶來設定活動的生效時間。
起始日期如果是2012.1.1,则结束日期是2012.12.31
起始日期如果是2012.1.2,则结束日期是2013.1.1,以此类推。
const getDateRage=(()=>{
let start=new Date().toISOString().substr(0, 10)
// 輸出yyyy-mm-dd格式
let d2=new Date(start);
d2.setFullYear(d2.getFullYear()+1);
// 以開始為基礎的年份在多加一年作為結束
d2.setDate(d2.getDate()-1);
// 日期減1天是為了 1/1 ~ 12/31這種效果
let end=d2.toISOString().substr(0, 10)
return {min:start,max:end}
})
判斷時間是否為過去時間
import Moment from 'moment'
export default {
name: 'History_Obtain',
setup (props, {root}) {
//(以上代碼省略)
let expired = Moment(el.expiredAt).isBefore(new Date())
console.log(expired) // true為過去 false反之
return { expired }
}
}
獲取兩個時間的時間差
注意以下data1跟date2變量為Date objects
var hours = Math.abs(date1 - date2) / (60*60*1000)
// 60*60*1000 可以用 3.6e6 科學符號來代替,但是如果為了可讀性(因為並不是每個人都能理解這個符號意思) 還是普遍選擇以3600000來使用
按照長度切割字符串並放入數組
let String = '202101201316'
String.cardNo.match(/.{1,4}/g)
優化時間戳轉換
現在接手上一位留下的代碼中,看見時間戳的轉換,心裡一驚記得轉換好像沒有這麼的麻煩
let localNowTime = moment(new Date())
var delta = (localDueTime - localNowTime) / 1000
let localNowTime = moment(new Date())
var delta = (localDueTime - localNowTime) / 1000
var days = Math.floor(delta / 86400)
delta -= days * 86400
var hours = Math.floor(days% 86400 / 3600) % 24
delta -= hours * 3600
var minutes = Math.floor(delta / 60) % 60
delta -= minutes * 60
var seconds = Math.floor(delta % 60)
優化的方法就是直接利用餘數的方法進行下一次的計算,這樣就不用每次計算又要先減去上一次計算餘數
// 原生方法將方法轉換為時間戳
let localDueTime = new Date('2021-02-03T04:23:49.159Z').getTime().toString()
let localNowTime = moment(new Date())
var delta = (localDueTime - localNowTime) / 1000
var days = Math.floor(delta / 86400);
var hours = Math.floor((delta % 86400) / 3600);
var minutes = Math.floor(((delta % 86400) % 3600) / 60);
var seconds = Math.floor(((delta % 86400) % 3600) % 60);
var duration = days + "天" + hours + "小时" + minutes + "分" + seconds + "秒";
}
IOS 系統不支援YYYY-MM-DD格式
做移動端時發現在IOS上是沒辦法解析這種格式的,只要把它轉變成可解析格式 ( YYYY/MM/DD)即可
function GetDateDiff(startDiffTime, endDiffTime) {
//将xxxx-xx-xx的时间格式,转换为 xxxx/xx/xx的格式
startTime = startDiffTime.replace(/\-/g, "/");
endTime = endDiffTime.replace(/\-/g, "/");
};
確認某個屬性是否存在
項目經過歷代的改版時,不是每個欄位都存在於每筆資料中,需求為,先判斷某個屬性是否存在,之後再進行賦值。因為如果直接賦值得話當找不到某個屬性時,js就會報錯,整個項目就會卡在那裏無法運行
如果傳入的屬性存在,則可以獲取該值,否則回傳undefined or null
const get = function(obj, key) {
return key.split(".").reduce(function(o, x) {
return (typeof o == "undefined" || o === null) ? o : o[x];
}, obj);
}
get(user, 'loc.lat') // 50
get(user, 'loc.foo.bar') // undefined
如果只是單純判斷是否存在而不需要返回值得話
const has = function(obj, key) {
return key.split(".").every(function(x) {
if(typeof obj != "object" || obj === null || ! x in obj)
return false;
obj = obj[x];
return true;
});
}
if(has(user, 'loc.lat')) ...
另外種方法概念一樣,使用方法不同
const checkValueExist = function (target, s) {
s = s.split('.')
var obj = target[s.shift()]
while (obj && s.length) obj = obj[s.shift()]
return obj
}
checkValueExist(checkoutRequestStatus, 'product.productOptions')
還有很多方法在stackflow上都能看得到,這裡紀錄一下,有時間來詳讀學習!!
var test = {
level1: {
level2: {
level3: 'level3'
}
}
};
/*----------------------nestedPropertyExists-----------------------------*/
function nestedPropertyExists(obj, props) {
var prop = props.shift();
return prop === undefined ? true : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;
}
var r1 = nestedPropertyExists(test, ['level1', 'level2', 'level3']);
var r2 = nestedPropertyExists(test, ['level1', 'level2', 'foo']);
/*----------------------get_if_exists-----------------------------*/
function get_if_exist(str) {
try { return eval(str) }
catch(e) { return undefined }
}
var r1 = get_if_exist(test, "level1.level2.level3");
var r2 = get_if_exist(test, "level1.level2.foo");
/*----------------------checkNestedFast-----------------------------*/
function checkNestedFast(obj) {
for (var i = 1; i < arguments.length; i++) {
if (!obj.hasOwnProperty(arguments[i])) {
return false;
}
obj = obj[arguments[i]];
}
return true;
}
var r1 = checkNestedFast(test, 'level1', 'level2', 'level3');
var r2 = checkNestedFast(test, 'level1', 'level2', 'foo');
/*----------------------deeptest-----------------------------*/
function deeptest(target, s){
s = s.split('.');
var obj= target[s.shift()];
while(obj && s.length) obj= obj[s.shift()];
return obj;
}
var r1 = deeptest(test, 'level1.level2.level3');
var r2 = deeptest(test, 'level1.level2.foo');
/*----------------------objHasKeys-----------------------------*/
function objHasKeys(obj, keys) {
var next = keys.shift();
return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}
var r1 = objHasKeys(test, ['level1', 'level2', 'level3']);
var r2 = objHasKeys(test, ['level1', 'level2', 'foo']);
/*----------------------validChain-----------------------------*/
function validChain( object, ...keys ) {
return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}
var r1 = validChain( test, "level1", "level2", "level3" );
var r2 = validChain( test, "level1", "level2", "foo" );
/*--------------------Object wrap (sad clowns)-------------------------------*/
var o = function(obj) { return obj || {} };
var r1 = o(o(o(o(test).level1).level2).level3);
var r2 = o(o(o(o(test).level1).level2).foo);
/*--------------------_.get -------------------------------*/
_.get(countries, 'greece.sparta.playwright');
var r1 = _.get(test, 'level1.level2.level3');
var r2 = _.get(test, 'level1.level2.foo');
確認某個屬性是否存在於對象中
let Info = {
name:'Ivy',
age:'24'
}
if (name in Info ) {
// do something
}
或者使用 hasOwnProperty 方法
let Info = {
name:'Ivy',
age:'24'
}
Info.hasOwnProperty('name')
waited to write list :
- flat(),flatMap()
- difference between hasOwnProperty and in function
- _.pick
判斷對象是否為空
要判斷對象或數組此類型可不能用 if (array)
通过 JSON 自带的 stringify() 方法
if (JSON.stringify(data) === '{}') {
return false // 如果为空,返回false
}
return true // 如果不为空,则会执行到这一步,返回true
ES6 新增的方法 Object.keys()
我們可以根據Object.keys()這個方法通過判斷它的長度來知道它是否為空。
var a = {}
Object.keys(a) // []
if (Object.keys(object).length === 0) {
return false
}
return true
選取內容
function selectContent(obj) {
if (window.getSelection && document.createRange) {
let sel = window.getSelection()
let range = document.createRange()
range.selectNodeContents(obj)
sel.removeAllRanges()
sel.addRange(range)
} else if (document.selection && document.body.createTextRange) {
let textRange = document.body.createTextRange()
textRange.moveToElementText(obj)
textRange.select()
}
}
if …else if 優化
當判斷需求多了起來,此時if else if也成了容易產生衝擊波的地方,好一點的寫法就是使用switch
var add_level = 0;
switch(add_step){
case 5 : add_level = 1;
break;
case 10 : add_level = 2;
break;
case 12 : add_level = 3;
break;
case 15 : add_level = 4;
break;
default : add_level = 0;
break;
}
但是如果需求改成:
成长速度为>12显示4个箭头;
成长速度为>10显示3个箭头;
成长速度为>5显示2个箭头;
成长速度为>0显示1个箭头;
成长速度为<=0显示0个箭头。
那么用switch实现起来也很麻烦了。
此時可以使用 ||和&&
簡單舉個例子
if(a >=5){
alert(“你好”);
}
//可以写成:
a >= 5 && alert("你好");
首先我们来梳理一下一个概念,请你一定要记住:在js逻辑运算中,0、”“、null、false、undefined、NaN都会判为false,其他都为true(好像没有遗漏了吧,请各位确认下)。这个一定要记住,不然应用||和&&就会出现问题。
这里顺便提下:经常有人问我,看到很多代码if(!!attr),为什么不直接写if(attr);
其实这是一种更严谨的写法:
请测试 typeof 5和typeof !!5的区别。!!的作用是把一个其他类型的变量转成的bool类型。
var add_level = (add_step==5 && 1) || (add_step==10 && 2) || (add_step==12 && 3) || (add_step==15 && 4) || 0;
再来看看||:
代码:var attr = attr || “”;这个运算经常用来判断一个变量是否已定义,如果没有定义就给他一个初始值,这在给函数的参数定义一个默认值的时候比较有用。因为js不像php可以直接在型参数上定义func($attr=5)。再次提醒你记住上面的原则:如果实参需要是0、”“、null、false、undefined、NaN的时候也会当false来处理。
var add_level={'5':1,'10':2,'12':3,'15':4}[add_step] || 0;
函數過多參數要傳參時 使用arguments
arguments參數可以使用在過多參數或是不確定有多少參數要傳送時使用。
它是類陣列,並非真的陣列,雖然能用arguments.length來取得裡面參數的數量以及可以直接使用arguments[index]來取得或修改特定位置的參數,但它並不能使用陣列的操作方法。
function fruit() {
let appStatus = arguments[0]
let bananaStatus = arguments [1]
console.log (appStatus , bananaStatus)
// 輸出 正甜呢 熟透了
}
fruit ('正甜呢', '熟透了')
其餘參數
在業務上碰到個問題是要從後台的參數中剃除某幾個參數,由於剔除的參數為少數,因此決定想想要怎麼把不要的屬性拿掉而不是把需要的屬性加入
此時我們可以使用解構方式在對象裡面將要留下的參數使用 ‘其餘參數代替’ ,而將要剃除的參數列出,方法如下 :
state = {
displaySocialInputs: false,//显示添加社交账号内容
errors: {},
handle: '',
company: '',
website: '',
location: '',
status: '',
skills: '',
githubusername: '',
bio: '',
wechat: '',
QQ: '',
tengxunkt: '',
wangyikt: '',
}
// 将剩余数组赋值给一个变量
let { displaySocialInputs, errors, ...profileData } = this.state
console.log('profileData', profileData);
倒數計時器
業務邏輯是從後端傳來特定時間,前端計算與現在相差時間並渲染出倒數計時
let dueTime = '2021-02-05T09:00:00.628Z'
let localDueTime = moment(dueTime)
let localNowTime = moment(new Date())
var du = Moment.duration(localDueTime - localNowTime, 'ms'),
days = du.get('days'),
hours = du.get('hours'),
mins = du.get('minutes'),
ss = du.get('seconds');
console.log('天'+ days)
console.log('小時'+ hours)
console.log('分鐘'+ mins)
console.log('秒'+ ss)
var days = Math.floor(delta / 86400)
var hours = Math.floor(delta % 86400 / 3600)
var minutes = Math.floor(delta % 86400 % 3600 / 60)
var seconds = Math.floor(delta % 86400 % 3600 % 60)
// console.log(`delta ${delta} minutes ${minutes} seconds ${seconds}`)
item.days = days
item.hours = hours
item.minutes = minutes
item.seconds = seconds
calculateTimeDiff (dueTime){
let localDueTime = moment(dueTime)
let localNowTime = moment(new Date())
let timeDiff = (localDueTime - localNowTime) / 1000
console.log('')
return timeDiff
},
判斷用戶是否滑到最底部
項目項目中有個功能是當用戶沒有到最底部時,要顯示動畫提醒用戶往下滑
所以要判斷用戶是否滑到最底,el.scrollHeight - el.scrollTop === el.clientHeight 這段話是核心
,大致意思就是 整體的滾動條 減去 滾動的頭部 等於了 可視化窗口 即表示滾到了頁面底部(實在不清楚的話就畫張圖幫助自己釐清)
const isShowSrcollDownPrompt = () => {
let el = document.querySelector('.shoppingItems')
if (el.scrollHeight - el.scrollTop === el.clientHeight) {
srcollDownPrompt.value = false
return
}
srcollDownPrompt.value = true
}
然而秉持著用戶著體驗至上的心情,我決定將動畫在只有用戶停止滑動時才顯時,而滑動時候動畫停止,每次滾動時會監聽一個計時器,在等待500毫秒後才執行顯示動畫,如果沒有到500毫秒又執行的話就會一直關閉並清除監聽直到等待500毫秒
#注意這裡用監聽滾動事件的話,別忘了在頁面切換前手動銷毀監聽事件!
const isShowSrcollDownPrompt = () => {
let el = document.querySelector('.shoppingItems')
if (el.scrollHeight - el.scrollTop === el.clientHeight) {
srcollDownPrompt.value = false
clearTimeout(isInterval)
return
}
srcollDownPrompt.value = false
clearTimeout(isInterval) // 滚动时清除定时器
isInterval = setTimeout(function () {
srcollDownPrompt.value = true
}, 500) // 当停止滚动时定时器执行
}
簡單方式 在vue裡將對象數組淺拷貝到新的數組
業務需求是要將後端傳來的參數在不改變原參數的前提下進行格式化,之後在將新的參數渲染在畫面中,綜所皆知在vue2中如果直接用等號賦值很可能導致沒有觸發到更新,從而造成數據改變了但是畫面沒有改變。而使用push是常見的解決方式之一,一般來說原始參數本身就是一個數組裡面包含著許多對象,而我的需求是要把這些對象push到新的數組中,一開始有我常使用的兩個解決方案:
for循環
缺點是很顯而易見的,代碼變的冗長
/*--以上省略--*/
setup(props) {
const list = reactive([])
onMounted(() => {
Checkout(requestedData)
.then(function (res) {
for(let i=0; i<res.length; i++){
list.push(i)
}
})
.catch(function (_error) {
console.log(_error)
})
})
/*--以下省略--*/
在對象裡新增一個屬性
這個方法就是繞過了push 直接利用了reactive響應式的功能就可以直接賦予
但是這個方法還是不夠完美因為必須得再對象屬性裡再增加一個屬性,如果特定而為之,代碼的感覺還是不夠簡潔
setup(props) {
const data= reactive({
list: []
})
onMounted(() => {
Checkout(requestedData)
.then(function (res) {
data.list = res
})
.catch(function (_error) {
console.log(_error)
})
})
解構式賦值
因為這個問題常常會在業務中遇到,再有天發現了解構的用法。
使用了解構,可以很輕易的將對象從對象數組中取之在push到新的數組
感興趣的朋友可以去了解一下解構,對於代碼簡潔有大大的幫助!
setup(props) {
const data= reactive({
list: []
})
onMounted(() => {
Checkout(requestedData)
.then(function (res) {
list.push(...res)
})
.catch(function (_error) {
console.log(_error)
})
})
實現模糊查詢
1. indexof 方法
說明:
該方法將從頭到尾地檢索字符串 stringObject,看它是否含有子串 searchvalue。開始檢索的位置在字符串的 fromindex 處或字符串的開頭(沒有指定 fromindex 時)。如果找到一個 searchvalue,則返回 searchvalue 的第一次出現的位置。 stringObject 中的字符位置是從 0 開始的。如果沒有找到,將返回 -1。
/**
* 使用indexof方法实现模糊查询
* @param {Array} list 进行查询的数组
* @param {String} keyWord 查询的关键词
* @return {Array} 查询的结果
*/
function fuzzyQuery(list, keyWord) {
var arr = [];
for (var i = 0; i < list.length; i++) {
if (list[i].indexOf(keyWord) >= 0) {
arr.push(list[i]);
}
}
return arr;
}
2. split 方法
說明:
該方法通過在 separator 指定的邊界處將字符串 stringObject 分割成子串並返回子串數組。返回的數組中的字串不包括 separator 自身。如果 stringObject 中不存在 separator,將返回一個只包含stringObject的數組。故可以根據返回數組的長度來判斷是否存在子字符串 separator 。
/**
* @param {Array} list 進行查询的数组
* @param {String} keyWord 查詢關鍵字
* @return {Array} 查詢節國
*/
function fuzzyQuery(list, keyWord) {
var arr = [];
for (var i = 0; i < list.length; i++) {
if (list[i].split(keyWord).length > 1) {
arr.push(list[i]);
}
}
return arr;
}
3. match 方法
/**
* 使用match方法实现模糊查询
* @param {Array} list 進行查詢的數組
* @param {String} keyWord 查詢關鍵詞
* @return {Array} 查詢結果
*/
function fuzzyQuery(list, keyWord) {
var arr = [];
for (var i = 0; i < list.length; i++) {
if (list[i].match(keyWord) != null) {
arr.push(list[i]);
}
}
return arr;
}
4. test方法(正则匹配)
說明:
該方法用於檢測一個字符串是否匹配某個模式。如果字符串 string 中含有與 RegExpObject 匹配的文本,則返回 true,否則返回 false。
/**
* 使用test方法实现模糊查询
* @param {Array} list 原數組
* @param {String} keyWord 查詢關鍵詞
* @return {Array} 查詢結果
*/
function fuzzyQuery(list, keyWord) {
var reg = new RegExp(keyWord);
var arr = [];
for (var i = 0; i < list.length; i++) {
if (reg.test(list[i])) {
arr.push(list[i]);
}
}
return arr;
}
性能檢測
测试条件:一个长度为100的数组,每个方法测试50次,取平均值。
indexof 方法耗费时间: 0.048ms
split 方法耗费时间: 0.037ms
match 方法耗费时间: 0.178ms
test 方法耗费时间: 0.039ms
disable 父元素中所有能按的按鈕
業務需求:
假設有會員點數折抵需求,但在可用會員點數為0時,要disabled所有跟會員點數功能有關的按鈕,這時候在每個元素上加上disable判斷是不意代碼維護已經造成冗長代碼的。
可以利用css 中的pointer-events: none ,並綁定在父元素中,但判斷成立時,所有點擊效果都會失效再搭配在opacity的效果達成disabled的目的
<div
class="point_container"
:class="{ pointedUseDisable: !pointData.totalPoint }"
:style="!pointData.totalPoint || item.promoteAmount ==0? 'pointer-events: none;' :''"
>
//以下省略
判斷文字是否換行
需求為某一行為客戶後台設定內容,在沒辦法限制字數且因為UI需求而無法換行。此時UI更改為,如果字數過長,則使用跑馬燈來顯示此行文字,反之則正常顯示。
這裡我使用了vant的跑馬燈,如果字數過長則顯示跑馬燈,反之則顯示原本標籤元素,因此這裡最主要的邏輯是判斷文字是否換行?
<van-notice-bar
v-if="slecetedProduct.scrollable"
:style="{height: textLineHeight}"
class="num"
background="#fff"
scrollable
:text="slecetedProduct.title"
/>
<h5
v-else
class="num product-title"
>{{ slecetedProduct.title }}</h5>
來分析下代碼
- 首先一定要確保html加載完後才調用函數,這裡使用了await nextTick(),即等待html元素都加載完以後才繼續接下來動作
- 獲取要判斷的文字容器,這裡的是.product-title,以及此容器lineHeight
- 如果文字的行高normal的話則先動態插入一個文字,獲取其高來作為此元素的行高,最後別忘記移除動態插入的文字
- 再來保證所有文字的行高跟高度一致 (此步驟可以避免line-height影響到真的文字的高度)
- 最後就可以透過scrollHeight(整個容器的高度)和元素的offsetHeight比較,因為上一個步驟中line-hegiht和高度是一致的,所以 target.offsetHeight < target.scrollHeight 的話則表示文字換行!
- 最後的最後別忘了復原原來元素的style,這就是為什麼剛開始的時候我們要將style存放在orgStyle變量。
const checkTextWidth = async (el) => {
if (!el) return
await nextTick()
var target = document.querySelector('.product-title')
var orgStyle = target.getAttribute('style')
var lineHeight = window.getComputedStyle(target).lineHeight
// 如果line-height 為normal則轉換為px
if (lineHeight === 'normal') {
var temp = document.createElement('div')
temp.innerText = '字'
document.body.appendChild(temp)
lineHeight = temp.offsetHeight + 'px'
document.body.removeChild(temp)
}
// 讓元素高等於其line-height
target.style.height = lineHeight
textLineHeight.value = lineHeight
// 然後判斷內容是否超過容器,即換行
if (target.offsetHeight < target.scrollHeight) {
el.scrollable = true
} else {
el.scrollable = false
}
orgStyle && target.setAttribute('style', orgStyle)
}
清除多個計時器
最近的業務邏輯常常接觸到計時器方法,有時候一個頁面不只有一個計時器
,如果清除沒有乾淨就會反覆觸發,是個很嚴重的bug
當我們重複調用計時器的函數時,即使你調用了清除函數,卻並沒有完全清除計時器。如以下的例子,如果調用超過一次以上那麼即使計時器永遠指向timer一個變數,仍然沒有辦法只單單使用window.clearInterval()來清除
function callTimer(params) {
let timer = setTimeout(() => {
console.log('test~~~~')
}, 5000)
}
而這時候我們只需要使用循環一個一個地將它清除即可
while (timeObj > 0) {
window.clearInterval(timeObj)
timeObj--
}
這邊要注意的是,當你在一個頁面使用循環來清除的時候,也許你有三個計時器,而你單單只想清除其中幾個而非全部時,當你調用循環來清除計時器,也會全部被清除,而不是像你預期的那樣只是清除你指定的定時器。
在下面的例子中,假設timer多次被調用,而我們使用循環來清除時,可以看到的是即使你並沒有在clearInterval放了timer2,timer2一樣會被清除掉
registerForm.onclick = function () {
while (timer > 0) {
clearInterval(timer)
timer--
}
};
// 即使obj沒有調用清除也會被清除
let timer2 = setTimeout(() => {
console.log('test2~~~~')
}, 800);
let timer = setTimeout(() => {
console.log('test~~~~')
}, 5000);
目前解決方法暫時只有想到, 這樣就可以跳過指定不被清除的計時器
registerForm.onclick = function () {
while (timer > 0) {
if(timer !== excludes) {
clearInterval(timer)
}
timer--
}
};
const excludes = setTimeout(() => {
console.log('test2~~~~')
}, 800);
let timer = setTimeout(() => {
console.log('test~~~~')
}, 5000);
new Promise reject以後還要return
這是在業務上遇到的一個小坑,一直以為reject以後後面代碼不會執行,而實際測試是會的,此時return的作用就在于rejection后终止函数的执行,并且阻止后面代码的执行。
function divide(numerator, denominator) {
return new Promise((resolve, reject) => {
if (denominator === 0) {
reject("Cannot divide by 0");
return; //函数执行到此结束
}
resolve(numerator / denominator);
});
}
當axios需要傳送application/x-www-form-urlencoded格式參數的問題
前端部分在使用line login API時一直不成功,後面發現官方文檔寫著這API要以application/x-www-form-urlencoded傳遞,採了了坑記錄下。
const axios = require("axios");
const querystring = require('querystring');
axios.defaults.maxRedirects = 0;
const data = {
code:'xxx',
status:'xxx'
}
axios.post('https://www.google.com//login',qs.stringify(data))
.catch(function (error) {
console.log(error.response.headers)
})
或者利用axios裡的
var options = {
method:'post',
url: 'https://www.test.net/user/login',
data: {
code:'xxx',
status:'xxx'
},
maxRedirects:0,
transformRequest: [function (data, headers) {
// 对 data 进行任意转换处理
return qs.stringify(data);
}]
};
axios.post('https://www.google.com//login',qs.stringify(options ))
.catch(function (error) {
console.log(error.response.headers)
})
判斷是否為雙擊
在vue中可以直接使用v-on:dblclick 來調用方法,但是我遇到的需求是,判斷用戶在樹梅派上gpio的按鈕雙擊,簡單來說是沒辦法透過頁面交互而必須得使用js來判斷
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>點擊判斷單擊還是雙擊</title>
</head>
<body>
<p>點擊判斷單擊還是雙擊</p>
<script>
let p = document.getElementsByTagName('p')[0]
//上一次 的定時器 返回的ID
let lastTapTimeFunc
//上一次時間戳 默認給0
var lastTapDiffTime = 0
const handClick = function () {
let _this = this;
//點擊時時間戳
let curT = new Date().getTime()
//上一次時間戳
let lastT = _this.lastTapDiffTime;
//對上一次時間戳重新賦值
_this.lastTapDiffTime = curT
//做差
let diff = curT - lastT
//規定300ms內點擊兩下判斷為雙擊
if (diff < 300) {
console.log("雙擊");
//清除上一次單擊的定時器ID
clearTimeout(_this.lastTapTimeFunc)
} else {
//定時器id
_this.lastTapTimeFunc = setTimeout(function () {
console.log("單擊");
}, 300)
}
}
p.addEventListener('click', handClick)
</script>
</body>
</html>
每隔四個字符就空格
str.replace(/(.{4})/g,'$1 ').trim()
獲取多個經緯度的中間點
新的專案中使用到了 mapbpx-gl 這個插件, 其中有個需求是在地圖上顯示多個地標, 這時候有個問題來了
那麼一開始載入時應該以哪個地方為預設顯示畫面呢? 這個時候就想到可以聚焦在所有地標的中心點,
實際的代碼在stackoverflow上有人提供方法的~
function rad2degr(rad) { return rad * 180 / Math.PI; }
function degr2rad(degr) { return degr * Math.PI / 180; }
/**
* @param latLngInDeg array of arrays with latitude and longtitude
* pairs in degrees. e.g. [[latitude1, longtitude1], [latitude2
* [longtitude2] ...]
*
* @return array with the center latitude longtitude pairs in
* degrees.
*/
function getLatLngCenter(latLngInDegr) {
var LATIDX = 0;
var LNGIDX = 1;
var sumX = 0;
var sumY = 0;
var sumZ = 0;
for (var i=0; i<latLngInDegr.length; i++) {
var lat = degr2rad(latLngInDegr[i][LATIDX]);
var lng = degr2rad(latLngInDegr[i][LNGIDX]);
// sum of cartesian coordinates
sumX += Math.cos(lat) * Math.cos(lng);
sumY += Math.cos(lat) * Math.sin(lng);
sumZ += Math.sin(lat);
}
var avgX = sumX / latLngInDegr.length;
var avgY = sumY / latLngInDegr.length;
var avgZ = sumZ / latLngInDegr.length;
// convert average x, y, z coordinate to latitude and longtitude
var lng = Math.atan2(avgY, avgX);
var hyp = Math.sqrt(avgX * avgX + avgY * avgY);
var lat = Math.atan2(avgZ, hyp);
return ([rad2degr(lat), rad2degr(lng)]);
}
去除所有空格
說到去除空格,第一個想到的就是trim()了, 但是這個方法只能夠去除前後空格,
那麼如果是要去除字符串中所有的空格,就可以使用正則
var str='Something is wrong.'; alert(str.replace(/\s/g, '');
JS新語法 雙問號??
這個方法不清楚是js哪個版本新增的,之前看到過幾次,最近了解後發現很好使用
很常時候會看到這樣的代碼
const result = response?.settings?.n === undefined ? 100 : response?.settings?.n
現在呢,可以直接這樣使用:
const result = response?.settings?.n ?? 100
這個 ?? 的意思是,如果 ?? 左邊的值是 null 或者 undefined,那麼就返回右邊的值。否則一率返回前面的值
那麼它和 || 有點相似:
區別則是 || 是判斷左邊的值如果為轉為布爾後為false則取右邊如:如undefined、null、false、空字符串和分數 0
這裡要注意的是雙問號的用法是當左邊為 null 或 undefined時才會返回右邊的, 所以當左邊為false時仍舊是返回false的
安全地獲取複雜類型nested的值
有的時候前端需要獲取某個欄位但是在其他情況下後端回傳的參數中又不存在這個欄位, 這個時候進行取值是很容易報錯的
比如個人的例子是, 最近換了新公司遇到新的後端, 人都有百百種各何況後端?
比如某個api 正常狀況是後端會返回個數組, 如果沒有參數的話也應該返回個空數組, 奇耙的是這個後端返回了個null
const respoense = {content: [{...}, {...}]} // 後端傳回參數
let data = respoense.content[0] // 不會報錯
/*---沒有值得情況下回傳了 null ....---*/
const respoense = {content: null } // 後端傳回參數
let data = respoense.content[0] // 會報錯
也許這裡可以先判斷respoense.content是否為真以後再進行下面流程但是諸如此類的事情還有很多, 有時候更是深層的參數, 一個一個的加上判斷是否存在是不現實的,因此在這裡記錄下應對這種狀況不同的寫法
Oliver Steele’s Nested Object Access Pattern
我個人很早期實在使用的方法
const concertLocation = ((response.tours || {}).nearMe || {}).location;
// undefined
問號點 ?.
let obj ={}
console.log(obj?.a?.b ?? 233 ) //233
let obj={a:{b:1}}
console.log(obj?.a?.b??233) //1
response = {settings: {n:false}}
someObject?.data?.[1]?.attributes?.arr?.[0]
Lodash 插件 - get
// 記得先安裝插件 => npm install lodash
// get(object, path, [defaultValue])
const _ = require('lodash');
console.log(
_.get(someObject,
'data[1].attributes.color',
'not found')
)
// not found
console.log(
_.get(someObject,
'data[1].attributes.arr[0]')
)
// undefined
loadsh 自定義排序方法
前端除了UI上的呈現還有就是數據的處理了,隨著業務的需求增多,功能也複雜起來
其中一個就是要按照自定義的字符串或是其他類型來排序
const data = [{name:"Junior"},{name:"Senior"},{name:"Freshman"},{name:"Sophomore"}]
let list = _.sortBy(data, function(element){
var rank = {
"Junior" : 3,
"Senior" : 4,
"Freshman" :1,
"Sophomore" :2
};
return rank[element.name];
});
console.dir(list);
// 或是更簡潔方法
list = _.sortBy(data, (o) => order.indexOf(o));
以上不定時更新