字符串常用技巧
時間對比:時間個位數形式需補0
const time1 = "2019-03-31 21:00:00";
const time2 = "2019-05-01 09:00:00";
const overtime = time1 > time2;
// overtime => false
替換圖片的class類正則
let reg = /(<\s*img[^>]*)class[=\s\"\']+[^\"\']*[\"\']?([^>]*>)/gi;
格式化金錢
const ThousandNum = num => num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
const money = ThousandNum(19941112);
// money => "19,941,112"
生成隨機ID
const RandomId = len => Math.random().toString(36).substr(3, len);
const id = RandomId(10);
// id => "jg7zpgiqva"
操作URL查詢參數
const params = new URLSearchParams(location.search.replace(/\?/ig, "")); // location.search = "?name=yajun&sex=female"
params.has("yajun"); // true
params.get("sex"); // "female"
Number常用技巧
取整:代替正數的Math.floor(),代替負數的Math.ceil()
const num1 = ~~ 1.69;
const num2 = 1.69 | 0;
const num3 = 1.69 >> 0;
// num1 num2 num3 => 1 1 1
補零
const FillZero = (num, len) => num.toString().padStart(len, "0");
const num = FillZero(169, 5);
// num => "00169"
轉數值::只對null、""、false、數值字符串有效
const num1 = +null;
const num2 = +"";
const num3 = +false;
const num4 = +"169";
// num1 num2 num3 num4 => 0 0 0 169
時間戳
const timestamp = +new Date("2019-03-31");
// timestamp => 1553990400000
精確小數
const RoundNum = (num, decimal) => Math.round(num * 10 ** decimal) / 10 ** decimal;
const num = RoundNum(1.69, 1);
// num => 1.7
判斷奇偶
const OddEven = num => !!(num & 1) ? "odd" : "even";
const num = OddEven(2);
// num => "even"
取最小最大值
const arr = [0, 1, 2];
const min = Math.min(...arr);
const max = Math.max(...arr);
// min max => 0 2
生成范圍隨機數
const RandomNum = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const num = RandomNum(1, 10);
Boolean常用技巧
短路運算符
const a = d && 1; // 滿足條件賦值:取假運算,從左到右依次判斷,遇到假值返回假值,后面不再執(zhí)行,否則返回最后一個真值
const b = d || 1; // 默認賦值:取真運算,從左到右依次判斷,遇到真值返回真值,后面不再執(zhí)行,否則返回最后一個假值
const c = !d; // 取假賦值:單個表達式轉換為true則返回false,否則返回true
判斷數據類型:undefined、null、string、number、boolean、array、object、symbol、date、regexp、function、asyncfunction、arguments、set、map、weakset、weakmap
function DataType(tgt, type) {
const dataType = Object.prototype.toString.call(tgt).replace(/\[object /g, "").replace(/\]/g, "").toLowerCase();
return type ? dataType === type : dataType;
}
DataType("yajun"); // "string"
DataType(19941112); // "number"
DataType(true); // "boolean"
DataType([], "array"); // true
DataType({}, "array"); // false
是否為空數組
const arr = [];
const flag = Array.isArray(arr) && !arr.length;
// flag => true
是否為空對象
const obj = {};
const flag = DataType(obj, "object") && !Object.keys(obj).length;
// flag => true
Array常用技巧
克隆數組
const _arr = [0, 1, 2];
const arr = [..._arr];
// arr => [0, 1, 2]
合并數組
const arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
const arr = [...arr1, ...arr2];
// arr => [0, 1, 2, 3, 4, 5];
去重數組
const arr = [...new Set([0, 1, 1, null, null])];
// arr => [0, 1, null]
混淆數組
const arr = [0, 1, 2, 3, 4, 5].slice().sort(() => Math.random() - .5);
// arr => [3, 4, 0, 5, 1, 2]
清空數組
const arr = [0, 1, 2];
arr.length = 0;
// arr => []
截斷數組
const arr = [0, 1, 2];
arr.length = 2;
// arr => [0, 1]
交換賦值
let a = 0;
let b = 1;
[a, b] = [b, a];
// a b => 1 0
過濾空值:undefined、null、""、0、false、NaN
const arr = [undefined, null, "", 0, false, NaN, 1, 2].filter(Boolean);
// arr => [1, 2]
解構數組成員嵌套
const arr = [0, 1, [2, 3, [4, 5]]];
const [a, b, [c, d, [e, f]]] = arr;
// a b c d e f => 0 1 2 3 4 5
解構數組成員別名
const arr = [0, 1, 2];
const { 0: a, 1: b, 2: c } = arr;
// a b c => 0 1 2
Object常用技巧
克隆對象
const _obj = { a: 0, b: 1, c: 2 }; // 以下方法任選一種
const obj = { ..._obj };
const obj = JSON.parse(JSON.stringify(_obj));
// obj => { a: 0, b: 1, c: 2 }
合并對象
const obj1 = { a: 0, b: 1, c: 2 };
const obj2 = { c: 3, d: 4, e: 5 };
const obj = { ...obj1, ...obj2 };
// obj => { a: 0, b: 1, c: 3, d: 4, e: 5 }
對象變量屬性
const flag = false;
const obj = {
a: 0,
b: 1,
[flag ? "c" : "d"]: 2
};
// obj => { a: 0, b: 1, d: 2 }
刪除對象無用屬性
const obj = { a: 0, b: 1, c: 2 }; // 只想拿b和c
const { a, ...rest } = obj;
// rest => { b: 1, c: 2 }
解構對象屬性嵌套
const obj = { a: 0, b: 1, c: { d: 2, e: 3 } };
const { c: { d, e } } = obj;
// d e => 2 3
解構對象屬性默認值
const obj = { a: 0, b: 1, c: 2 };
const { a, b = 2, d = 3 } = obj;
// a b d => 0 1 3
js數組去重
function unique1(array){
var n=[]; //一個新的臨時數組
//遍歷當前數組
for(var i=0;i<array.length;i++){
//如果當前數組的第i已經保存進了臨時數組,那么跳過, 否則把當前項push到臨時數組里面
if(n.indexOf(array[i])==-1)
n.push(array[i]);
}
return n;
}
數組偏平化處理
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
var b = arr.toString().split(",").sort((a,b) => { return a - b}).map(Number)
ES6 flat
function flat5 (arr=[]) {
// flat() 方法會移除數組中的空項
return arr.flat(Infinity)
}
flat5(arr) // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
js書寫的幾條基本規(guī)范
- 不要在同一行聲明多個變量。
- 請使用 ===/!==來比較true/false或者數值
- 不要使用全局函數。
- Switch語句必須帶有default分支
- 函數不應該有時候有返回值,有時候沒有返回值。
- For循環(huán)必須使用大括號
- If語句必須使用大括號
- for-in循環(huán)中的變量 應該使用var關鍵字明確限定作用域,從而避免作用域污染。
JS基本類型和引用類型的區(qū)別?
基本類型: undefined,boolean,number,string,null,symbol(ES6)
引用類型:object,arrary,date,RegExp(正則),Function
基本數據類型是簡單的數據段。
引用類型是由多個值構成的對象,其實都是Object的實例。
基本類型可以直接訪問,而引用類型的訪問是按照對象在內存中的地址,再按照地址去獲取對象的值,叫做引用訪問。
當從一個變量向另一個變量賦值引用類型的值時,同樣也會將存儲在變量中的對象的值復制一份放到為新變量分配的空間中。前面講引用類型的時候提到,
保存在變量中的是對象在堆內存中的地址,所以,與簡單賦值不同,這個值的副本實際上是一個指針,而這個指針指向存儲在堆內存的一個對象。那么賦值操作后,
兩個變量都保存了同一個對象地址,則這兩個變量指向了同一個對象。因此,改變其中任何一個變量,都會相互影響,從而引發(fā)了對象的深拷貝和淺拷貝的問題。
獲取兩個日期中間的所有日期
function getAllDateCN(startTime, endTime) {
let start = new Date(startTime);
let end = new Date(endTime);
let date_all = [];
let i = 0;
while ((end.getTime() - start.getTime()) >= 0) {
let year = start.getFullYear();
let month = start.getMonth() + 1;
let day = start.getDate();
date_all[i] = year + '-' + month + '-' + day;
start.setDate(start.getDate() + 1);
i += 1;
}
return date_all
}
柯理化函數
在數學和計算機科學中,柯里化是一種將使用多個參數的一個函數轉換成一系列使用一個參數的函數的技術。所謂柯里化就是把具有較多參數的函數轉換成具有較少參數的函數的過程。
舉個例子
//普通函數
function fn(a, b, c, d, e) {
console.log(a, b, c, d, e)
}
//生成的柯里化函數
let _fn = curry(fn)
_fn(1, 2, 3, 4, 5) // print: 1,2,3,4,5
_fn(1)(2)(3, 4, 5) // print: 1,2,3,4,5
_fn(1, 2)(3, 4)(5) // print: 1,2,3,4,5
_fn(1)(2)(3)(4)(5) // print: 1,2,3,4,5
柯理化函數的實現
function add () {
let x=0;
for(let i=0;i<arguments.length;i++){
x+=arguments[i];
}
return x;
}
// 對求和函數做curry化
let f1 = curry(add, 1, 2, 3)
console.log('復雜版', f1()) // 6
// 對求和函數做curry化
let f2 = curry(add, 1, 2)
console.log('復雜版', f2(3)) // 6
// 對求和函數做curry化
let f3 = curry(add)
console.log('復雜版', f3(1, 2, 3)) // 6
// 復雜版curry函數可以多次調用,如下:
console.log('復雜版', f3(1)(2)(3)) // 6
console.log('復雜版', f3(1, 2)(3)) // 6
console.log('復雜版', f3(1)(2, 3)) // 6
// 復雜版(每次可傳入不定數量的參數,當所傳參數總數不少于函數的形參總數時,才會執(zhí)行)
function curry(fn) {
// 閉包
// 緩存除函數fn之外的所有參數
let args = Array.prototype.slice.call(arguments, 1)
return function() {
// 連接已緩存的老的參數和新傳入的參數(即把每次傳入的參數全部先保存下來,但是并不執(zhí)行)
let newArgs = args.concat(Array.from(arguments))
if (newArgs.length < fn.length) {
// 累積的參數總數少于fn形參總數
// 遞歸傳入fn和已累積的參數
return curry.call(this, fn, ...newArgs)
} else {
// 調用
return fn.apply(this, newArgs)
}
}
}
柯里化的用途
function checkByRegExp(regExp, string) {
return regExp.test(string)
}
checkByRegExp(/^1\d{10}$/, '18642838455') // 校驗電話號碼
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com') // 校驗郵箱
我們每次進行校驗的時候都需要輸入一串正則,再校驗同一類型的數據時,相同的正則我們需要寫多次, 這就導致我們在使用的時候效率低下,并且由于 checkByRegExp 函數本身是一個工具函數并沒有任何意義。此時,我們可以借助柯里化對 checkByRegExp 函數進行封裝,以簡化代碼書寫,提高代碼可讀性。
//進行柯里化
let _check = curry(checkByRegExp)
//生成工具函數,驗證電話號碼
let checkCellPhone = _check(/^1\d{10}$/)
//生成工具函數,驗證郵箱
let checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/)
checkCellPhone('18642838455') // 校驗電話號碼
checkCellPhone('13109840560') // 校驗電話號碼
checkCellPhone('13204061212') // 校驗電話號碼
checkEmail('test@163.com') // 校驗郵箱
checkEmail('test@qq.com') // 校驗郵箱
checkEmail('test@gmail.com') // 校驗郵箱
compose 函數
compose 就是組合函數,將子函數串聯起來執(zhí)行,一個函數的輸出結果是另一個函數的輸入參數,一旦第一個函數開始執(zhí)行,會像多米諾骨牌一樣推導執(zhí)行后續(xù)函數。
const greeting = name => `Hello ${name}`
const toUpper = str => str.toUpperCase()
toUpper(greeting('Onion')) // HELLO ONION
compose 函數的特點
- compose 接受函數作為參數,從右向左執(zhí)行,返回類型函數
- fn()全部參數傳給最右邊的函數,得到結果后傳給倒數第二個,依次傳遞
var compose = function(...args) {
var len = args.length // args函數的個數
var count = len - 1
var result
return function func(...args1) {
// func函數的args1參數枚舉
result = args[count].call(this, args1)
if (count > 0) {
count--
return func.call(null, result) // result 上一個函數的返回結果
} else {
//回復count初始狀態(tài)
count = len - 1
return result
}
}
}
舉個例子
var greeting = (name) => `Hello ${name}`
var toUpper = str => str.toUpperCase()
var fn = compose(toUpper, greeting)
console.log(fn('jack'))
大家熟悉的 webpack 里面的 loader 執(zhí)行順序是從右到左,是因為webpack 選擇的是 compose 方式,從右到左依次執(zhí)行 loader,每個 loader 是一個函數。
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] }
]
如上,webpack 使用了 style-loader 和 css-loader,它是先用 css-loader 加載.css 文件,然后 style-loader 將內部樣式注入到我們的 html 頁面。
webpack 里面的 compose 代碼如下:
const compose = (...fns) => {
return fns.reduce(
(prevFn, nextFn) => {
return value =>prevFn(nextFn(value))
},
value => value
)
}
尾調用和尾遞歸
尾調用(Tail Call)是函數式編程的一個重要概念,本身非常簡單,一句話就能說清楚。就是指某個函數的最后一步是調用另一個函數。
function g(x) {
console.log(x)
}
function f(x) {
return g(x)
}
console.log(f(1))
//上面代碼中,函數f的最后一步是調用函數g,這就是尾調用。
上面代碼中,函數 f 的最后一步是調用函數 g,這就是尾調用。尾調用不一定出現在函數尾部,只要是最后一步操作即可。
函數調用自身,稱為遞歸。如果尾調用自身,就稱為尾遞歸。遞歸非常耗費內存,因為需要同時保存成千上百個調用幀,很容易發(fā)生棧溢出錯誤。但是隊伍尾遞歸來說,由于只存在一個調用幀,所以永遠不會發(fā)生棧溢出錯誤。
function factorial(n) {
if (n === 1) {
return 1
}
return n * factorial(n - 1)
}
上面代碼是一個階乘函數,計算 n 的階乘,最多需要保存 n 個調用數據,復雜度為 function(n),如果改寫成尾調用,只保留一個調用記錄,復雜度為 function(1)。簡單來說 每次的返回結果 n * factorial(n - 1) 都會被緩存在內存中,以便于下次遞歸時候使用,而緩存函數,瀏覽器就會自動開辟內存空間。而下面這種寫法,函數執(zhí)行后的結果,函數執(zhí)行過后則會自動銷毀。
function factor(n, total = 1) {
if (n === 1) {
return total
}
return factor(n - 1, n * total)
}
最經典的例子則是斐波拉切數列的尾調用。
function Fibonacci(n) {
if (n <= 1) {
return 1
}
return Fibonacci(n - 1) + Fibonacci(n - 2)
}
//尾遞歸
function Fibona(n, ac1 = 1, ac2 = 1) {
if (n <= 1) {
return ac2
}
return Fibona(n - 1, ac2, ac1 + ac2)
}
函數防抖和節(jié)流
1. 函數防抖
- 非立即執(zhí)行版 (非立即執(zhí)行版的意思是觸發(fā)事件后函數不會立即執(zhí)行,而是在 n 秒后執(zhí)行,如果在 n 秒內又觸發(fā)了事件,則會重新計算函數執(zhí)行時間。)
function debounce(func, wait) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args)
}, wait);
}
}
- 立即執(zhí)行版(立即執(zhí)行版的意思是觸發(fā)事件后函數會立即執(zhí)行,然后 n 秒內不觸發(fā)事件才能繼續(xù)執(zhí)行函數的效果)
function debounce(func,wait) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
let callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
}
2.函數節(jié)流 (使得一定時間內只觸發(fā)一次函數。原理是通過判斷是否到達一定時間來觸發(fā)函數。)
- 時間戳版
function throttle(func, wait) {
let previous = 0;
return function() {
let now = Date.now();
let context = this;
let args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
- 定時器版
function throttle(func, wait) {
let timeout;
return function() {
let context = this;
let args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
常見算法
1.全排列
function permutate(str) {
var array = str.split('');
function loop(array, pre = []) {
if (array.length == 1) {
return [pre.concat(array).join('')];
}
let res = [];
for (let index = 0; index < array.length; index++) {
var first = array.pop();
res = res.concat(loop(array, [...pre, first]));
array.unshift(first);
}
return res;
}
return Array.from(new Set(loop(array)))
}
2.二分搜索
function BinarySearch1 (arr, target) {
return search(arr, target, 0, arr.length - 1)
function search (arr, target, from, to) {
if (from > to) {
return -1
}
const mid = Math.floor((from + to)/2)
if (arr[mid] > target) {
return search(arr, target, from, mid - 1)
} else if (arr[mid] < target) {
return search(arr, target, mid + 1, to)
} else {
return mid
}
}
}
function BinarySearch2 (arr, target) {
let from = 0
let to = arr.length - 1
let mid = Math.floor((from + to)/2)
while (from <= to) {
mid = Math.floor((from + to)/2)
if (arr[mid] > target) {
to = mid - 1
} else if (arr[mid] < target) {
from = mid + 1
} else {
return mid
}
}
return -1
}
console.log(BinarySearch1([1,2,3,4,5,6,7,8,9],5)) // 4
console.log(BinarySearch2([1,2,3,4,5,6,7,8,9],3)) // 2
3.冒泡排序
function BubbleSort (arr) {
const length = arr.length
for (let i = 0; i < length; i++) {
for (let j = 1; j < length-i; j++) {
if (arr[j] < arr[j - 1]) {
const temp = arr[j]
arr[j] = arr[j - 1]
arr[j - 1] = temp
}
}
}
return arr
}
4.選擇排序
function SelectionSort (arr) {
const length = arr.length
for (let i = 0; i < length; i++ ) {
let minIndex= i
for (let j = i + 1; j < length; j++) {
minIndex = arr[minIndex] <= arr[j] ? minIndex : j
}
if (minIndex !== i) {
const temp = arr[i]
arr[i] = arr[minIndex]
arr[minIndex] = temp
}
}
return arr
}
5.插入排序
function InsertionSort (arr) {
const length = arr.length
for (let i = 1; i < length; i++) {
const temp = arr[i]
let j
for (j = i - 1; j >= 0 && temp < arr[j]; j--) {
arr[j+1] = arr[j]
}
arr[j+1] = temp
}
return arr
}
6.希爾排序
function ShellSort (arr) {
const length = arr.length
let gap = Math.floor(length)
while (gap) {
for (let i = gap; i < length; i++) {
const temp = arr[i]
let j
for (j = i - gap; j >= 0 && temp < arr[j]; j = j - gap) {
arr[j + gap] = arr[j]
}
arr[j + gap] = temp
}
gap = Math.floor(gap / 2)
}
return arr
}
7.歸并排序
function MergeSort (arr, low, high) {
const length = arr.length
if (low === high) {
return arr[low]
}
const mid = Math.floor((low + high)/2)
MergeSort(arr, low, mid)
MergeSort(arr, mid + 1, high)
merge(arr, low, high)
return arr
}
function merge (arr, low, high) {
const mid = Math.floor((low + high)/2)
let left = low
let right = mid + 1
const result = []
while (left <= mid && right <= high) {
if (arr[left] <= arr[right]) {
result.push(arr[left++])
} else {
result.push(arr[right++])
}
}
while (left <= mid) {
result.push(arr[left++])
}
while (right <= high) {
result.push(arr[right++])
}
arr.splice(low, high-low+1, ...result)
}
const test = [2, 34, 452,3,5, 785, 32, 345, 567, 322,5]
console.log(MergeSort(test, 0, test.length - 1))
8.堆排序
function HeapSort (arr) {
const length = arr.length
// 調整初始堆,調整完其實也確定了最大值
// 但此時最大值是在 arr[0] 中
for (let i = Math.floor(length/2) - 1; i >= 0; i--) {
adjustHeap(arr, i, length)
}
// 把 arr[0](最大值)換到后面
for (let i = length - 1; i >=0; i--) {
const temp = arr[0]
arr[0] = arr[i]
arr[i] = temp
adjustHeap(arr, 0, i)
}
return arr
}
// size 是還需要調整的堆的大小
// 隨著一個個最大值的確定,size 會越來越小
function adjustHeap (arr, position, size) {
const left = position * 2 + 1
const right = left + 1
let maxIndex = position
if (left < size && arr[left] > arr[maxIndex]) {
maxIndex = left
}
if (right < size && arr[right] > arr[maxIndex]) {
maxIndex = right
}
if (maxIndex !== position) {
const temp = arr[position]
arr[position] = arr[maxIndex]
arr[maxIndex] = temp
adjustHeap(arr, maxIndex, size)
}
return arr
}
數組去重es5的高階寫法
var array = [{value: 1}, {value: 1}, {value: 2}];
function unique(array) {
var obj = {};
return array.filter(function(item, index, array){
console.log(typeof item + JSON.stringify(item))
return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true)
})
}
console.log(unique(array)); // [{value: 1}, {value: 2}]
數組去重es6的幾種寫法
function unique(array) {
return Array.from(new Set(array));
}
function unique(array) {
return [...new Set(array)];
}
function unique (arr) {
const seen = new Map()
return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}