js 实用方法

126 阅读8分钟

1.浅拷贝

数组浅拷贝 Array.prototype.concat()

// concat() 方法用于连接两个或多个数组。
// 该方法不会改变现有的数组,而是返回一个新的数组
const newArr = arr.concat()

数组浅拷贝 Array.prototype.slice()

// slice() 方法可从已有的数组中返回选定的元素
// 注意: slice() 方法不会改变原始数组,而是返回一个新的数组
const newArr = arr.slice()

对象浅拷贝 Object.assign()

Object.assign() 方法可以将任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象

const newObj = Object.assign({}, obj)

扩展运算符 ...

// 数组
const newArr = [...arr];
// 对象
const newObj = {...obj};

2.深拷贝

const obj = structuredClone(obj)

JSON.parse(JSON.stringify(obj))

不能拷贝函数和undefined, 日期,正则

const newData = JSON.parse(JSON.stringify(data))

递归深拷贝

// 深拷贝对象
export function deepClone(obj) {
	const _toString = Object.prototype.toString

	// null, undefined, non-object, function
	if (!obj || typeof obj !== 'object') {
		return obj
	}

	// DOM Node
	if (obj.nodeType && 'cloneNode' in obj) {
		return obj.cloneNode(true)
	}

	// Date
	if (_toString.call(obj) === '[object Date]') {
		return new Date(obj.getTime())
	}

	// RegExp
	if (_toString.call(obj) === '[object RegExp]') {
		const flags = []
		if (obj.global) {
			flags.push('g')
		}
		if (obj.multiline) {
			flags.push('m')
		}
		if (obj.ignoreCase) {
			flags.push('i')
		}

		return new RegExp(obj.source, flags.join(''))
	}

	const result = Array.isArray(obj) ? [] : obj.constructor ? new obj.constructor() : {}

	for (const key in obj) {
		result[key] = deepClone(obj[key])
	}

	return result
}
/**
  * 深拷贝简化版
  * @param {any} target
  * @param {WeakMap} map
  * @returns {any} cloneTarget
  */
const _completeDeepClone = (target, map = new WeakMap()) => {
    // 基本数据类型,直接返回
    if (typeof target !== 'object' || target === null) return target
    // 函数 正则 日期 ES6新对象,执行构造题,返回新的对象
    const constructor = target.constructor
    if (/^(Date|Map|Set)$/i.test(constructor.name)) return new constructor(target)
    if (constructor === Function) {
        var arr = target.toString().replace(/\n|\r/g, "").trim().match(/\((.*?)\)\s*\{(.*)\}/).slice(1);  //进行source处理
        return new Function(arr[0].trim(), arr[1]);
    }
    if(constructor === RegExp) {
        return new RegExp(target.source, target.flags);
    }

    // map标记每一个出现过的属性,避免循环引用
    if (map.get(target)) return map.get(target)
    map.set(target, true)
    const cloneTarget = Array.isArray(target) ? [] : {}
    for (prop in target) {
        if (target.hasOwnProperty(prop)) {
            cloneTarget[prop] = _completeDeepClone(target[prop], map)
        }
    }
    return cloneTarget
}

/**
  * 深拷贝加强版
  * @param {any} parent
  * @returns {any} child
  */
export const clone = parent => {
    // 判断类型
    const isType = (obj, type) => {
        if (typeof obj !== "object") return false;
        const typeString = Object.prototype.toString.call(obj);
        let flag;
        switch (type) {
            case "Array":
                flag = typeString === "[object Array]";
                break;
            case "Date":
                flag = typeString === "[object Date]";
                break;
            case "RegExp":
                flag = typeString === "[object RegExp]";
                break;
            case "Set":
                flag = typeString === "[object Set]";
                break;
            case "Map":
                flag = typeString === "[object Map]";
                break;
            default:
                flag = false;
        }
        return flag;
    };
​
    // 处理正则
    const getRegExp = re => {
        var flags = "";
        if (re.global) flags += "g";
        if (re.ignoreCase) flags += "i";
        if (re.multiline) flags += "m";
        return flags;
    };
    // 维护储存循环引用的对象
    const cached = new WeakMap;
​
    const _clone = parent => {
        if (parent === null) return null;
        if (typeof parent !== "object") return parent;
​
        // 循环引用则直接返回
        if (cached.get(parent)) return cached.get(parent)
​
        let child, proto;
​
        if (isType(parent, "Array")) {
            // 对数组做特殊处理
            child = [];
        } else if (isType(parent, "RegExp")) {
            // 对正则对象做特殊处理
            child = new RegExp(parent.source, getRegExp(parent));
            if (parent.lastIndex) child.lastIndex = parent.lastIndex;
        } else if (isType(parent, "Date")) {
            // 对Date对象做特殊处理
            child = new Date(parent.getTime());
        } else if (isType(parent, "Set")) {
            // 对Set对象做特殊处理
            child = new Set();
            parent.forEach(val => {
                child.add(_clone(val))
            })
        } else if (isType(parent, "Map")) {
            // 对Map对象做特殊处理
            child = new Map();
            parent.forEach((val, key) => {
                child.set(key, _clone(val))
            })
        } else {
            // 处理对象原型
            proto = Object.getPrototypeOf(parent);
            // 利用Object.create切断原型链
            child = Object.create(proto);
        } 
        cached.set(parent, child)
​
        for (let i in parent) {
            // 递归
            child[i] = _clone(parent[i]);
        }
​
        return child;
    };
    return _clone(parent);
};
​

3. 防抖

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这n 秒内事件又被触发,则重新计时。

使用场景: 搜索框、按钮提交

普通防抖

function debounce(fn, delay = 200) {
    // 记录上⼀次的延时器
    var timer = null;
    return function (...args) {
        // 清除上⼀次延时器
        timer && clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    }
}

自执行函数版

const debounce = (() => {
    let timer = null;
    return (fn, delay = 200) => {
        timer && clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    }
})()

vue、vue3版

function debounce(fn, delay = 300) {
    // 记录上⼀次的延时器
    var timer = null;
    return function (this: unknown, ...args) {
        timer && clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, delay);
    };
}

使用防抖

// vue2
methods: {
    loadList:debounce(() => {
            console.log('加载数据')
    }, 500)
}
// vue3
const loadList = debounce(() => {
      console.log('加载数据')
}, 500)
// pinia中使用
selecFilter: debounce(async function (this, value, name) {
      // this获取state的值
}, 1000),

4. 节流

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

拖拽场景、缩放场景、动画场景、scroll滚动

定时器版 (最后一次也会触发)

function throttle(fn, delay = 300){
    let timer = null;
    return function(...args) {
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(this, args);
                clearTimeout(timer);
                timer = null;
            }, delay)            
        }
    }
}
​
function throttle(fn, delay = 300) {
    let isThrottling = false
    // 核心思路,函数多次执行只有当 isThrottling 为 false 时才会进入函数体
    return function (...args) {
        if (!isThrottling) {
            isThrottling = true;
            setTimeout(() => {
                isThrottling = false;
                fn.apply(this, args);
            }, delay)
        }
    }
}

时间戳版 (第一次就触发)

function throttle(fn, delay = 200){
    var lastTime = 0;
    return function (...args) {
        var nowTime = Date.now();
        if (nowTime - lastTime > delay) {
            lastTime = nowTime;
            fn.apply(this, args);
        }
    }
}

控制第一次和最后一次

function throttle3(fn, delay, op = {}) {
        let timer = null;
        let pre = 0;
        return function (...args) {
            let now = Date.now();
            if (now - pre > delay) {
                if (pre == 0 && !op.bengin) {
                    pre = now
                    return
                }
                if (timer) {
                    clearTimeout(timer)
                    timer = null
                }
                fn.apply(this, args);
                pre = now
            } else if (!timer && op.end) {
                timer = setTimeout(() => {
                    fn.apply(this, args);
                    timer = null
                }, delay)
            }
        }
}

vue2, vue3版

function throttle(fn, delay = 300) {
    var lastTime = Date.now();
    return function (this: unknown, ...args) {
        var nowTime = Date.now();
        if (nowTime - lastTime > delay) {
            lastTime = nowTime;
            fn.apply(this, args);
        }
    };
}

使用节流

// vue2
methods:{
    appSearch:debounce(function(){
        this.getList()
    },300)
}
// vue3
const appSearch = debounce(function(){
    this.getList()
},300)

5. 将连字符转化为转驼峰

'on-click' => 'onClick'

/**
 * 将连字符转化为转驼峰
 * @param {string} str 连字符
 * @returns {string}
 */
const camelize = str => {
  return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
}

6. 将小驼峰转化为连字符字符串

'onClick' => 'on-click'

/**
 * 将小驼峰转化为连字符字符串
 * @param {string} str 驼峰字符
 * @returns {string}
 */
const hyphenate = (str: string) => str.replace(/\B([A-Z])/g,'-$1').toLowerCase()

7. 日期转换并自动补零

将不同的日期格式转换成统一的格式 2023-07-04 09:54:00

// const num1 = 0.13; num1.toLocaleString('zh',{style:'percent'}));  得到 13%

/**
 * 日期转换并自动补零
 */
formatTime(time) {
 if (time) {
        const date = new Date(time).toLocaleString('zh', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' });
//时间字符串两位显示 2203/02/15 16:41:02
        return date;
    }
    return '';
}
/**
 * 日期转换并自动补零
 * @param {type} 参数
 * @returns {type} 返回值
 */
formatTime(time) {
    if (time) {
    const date = new Date(time).toLocaleString().split(' ');
    const date1 = date[0]
      .split('/')
      .map((item) => {
        if (item.length === 1) {
          return '0' + item;
        }
        return item;
      })
      .join('-');
    return date1 + ' ' + date[1];
    }
    return '';
    //return time? new Date(time).toLocaleString().replace(/(\d{4})\/(\d{1,2})\/(\d{1,2})/, (match, year, month, day) => {return `${year}/${month.padStart(2, '0')}/${day.padStart(2, '0')}`}): ''
}

/**
 * 表格时间格式化
 */
export function formatDate(cellValue) {
  if (cellValue == null || cellValue == "") return "";
  var date = new Date(cellValue) 
  var year = date.getFullYear()
  var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
  var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() 
  var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() 
  var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() 
  var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
  return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
}

/**
 * 格式化时间
 */
export const formateTime = (data?: string) => {
  const da = data || new Date().getTime();
  const dt = new Date(da);
  const y = dt.getFullYear();
  const m = (`${dt.getMonth() + 1}`).padStart(2, '0');
  const d = (`${dt.getDate()}`).padStart(2, '0');
  const hh = (`${dt.getHours()}`).padStart(2, '0');
  const mm = (`${dt.getMinutes()}`).padStart(2, '0');
  const ss = (`${dt.getSeconds()}`).padStart(2, '0');
  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
};

8. 获取本日/本周/本月的开始时间和当前时间

/**
 * 获取近24小时、近一周、近一月、近一年的时间范围
 */
export const useGetDate = (period: string) => {
  const now = new Date();

  const endTime: any = new Date();
  const startTime: any = now;
  switch (period) {
    case 'day':
      startTime.setHours(now.getHours() - 24);
      break;

    case 'week':
      startTime.setDate(now.getDate() - 7);
      break;

    case 'month':
      startTime.setMonth(now.getMonth() - 1);
      break;

    case 'year':
      startTime.setFullYear(now.getFullYear() - 1);
      break;

    default:
      break;
  }

  return [formatTime(startTime), formatTime(endTime)];
};

/**
 * 获取日期
 * @param {string} type
 * @returns {object}
 */
const getDate = (type: string) => {
    let startTime = '';
    let endTime = '';
    if (type === '本月') {
        startTime = formatTime(new Date().getFullYear() + '-' + (new Date().getMonth() + 1) + '-1') + ' 00:00:00';
        endTime = formatTime(new Date()) + ' 23:59:59';
    }
    if (type === '本周') {
        // 当天的时间戳
        const today = Date.parse(new Date().toLocaleDateString());
        // 当天星期几换算时间戳
        const week = (new Date().getDay() - 1) * 24 * 60 * 60 * 1000;

        startTime = formatTime(new Date(today - week)) + ' 00:00:00';
        endTime = formatTime(new Date()) + ' 23:59:59';
    }
    if (type === '今日') {
        startTime = formatTime(new Date()) + ' 00:00:00';
        endTime = formatTime(new Date()) + ' 23:59:59';
    }
    return { startTime, endTime };
};

9. 月份范围转月份列表

获取如 2023-05 到2023-07之间的所有月份['2023-05','2023-06','2023-07']

/**
 * 获取日期
 * @param {string} start 开始日期
 * @param {string} end 结束日期
 * @returns {array}
 */
 getMonthBetween(start, end) {
      //传入的格式YYYY-MM
      var result = [];
      var s = start.split("-");
      var e = end.split("-");
      var min = new Date();
      var max = new Date();
      var yearMonthCode;
      min.setFullYear(s[0], s[1] * 1 - 1, 1);//开始日期
      max.setFullYear(e[0], e[1] * 1, 1);//结束日期
      var curr = max;
      while (curr > min) {
        const year = curr.getFullYear()
        const month = curr.getMonth()
        yearMonthCode = month > 9 ? (year + '-' + month) : (year + '-0' + month)

        result.push(yearMonthCode);
        curr.setMonth(month - 1);
      }
      return result;
    },

11 日期范围转日期列表

function getDateList(startDate, endDate) {
  const dateList = [];
  
  // 将开始日期和结束日期转换为 Date 对象
  const start = new Date(startDate);
  const end = new Date(endDate);
  
  // 循环遍历日期范围
  while (start <= end) {
    // 获取当前日期的月份和日期,并格式化为 'MM-DD' 的形式
    const month = String(start.getMonth() + 1).padStart(2, '0');
    const day = String(start.getDate()).padStart(2, '0');
    const formattedDate = `${month}-${day}`;
    
    // 将格式化后的日期添加到日期列表中
    dateList.push(formattedDate);
    
    // 递增日期
    start.setDate(start.getDate() + 1);
  }
  
  return dateList;
}

// 示例用法
const startDate = '2023-08-02';
const endDate = '2023-08-05';

const result = getDateList(startDate, endDate);
console.log(result); // 输出:['08-02', '08-03', '08-04', '08-05']

12 年份范围转年份列表

function getYearList(startYear, endYear) {
  const yearList = [];
  
  // 将开始年份和结束年份转换为数字
  const start = parseInt(startYear);
  const end = parseInt(endYear);
  
  // 循环遍历年份范围
  for (let year = start; year <= end; year++) {
    // 将当前年份添加到年份列表中
    yearList.push(String(year));
  }
  
  return yearList;
}

// 示例用法
const startYear = '2020';
const endYear = '2023';

const result = getYearList(startYear, endYear);
console.log(result); // 输出:['2020', '2021', '2022', '2023']

13. 二维数组展开

[[1,2,3],4,[5,6]] 展开成[1,2,3,4,5,6]

const arr1 = [[1,2,3],4,[5,6]]
const arr2 = arr1.reduce((pre, cur) => pre.concat(cur), [])

14. 根据数组子项的属性去重

[{id: 1},{id: 2},{id: 1}] 根据id去重得到 [{id: 1},{id: 2}]

const arr1 = [{id: 1},{id: 2},{id: 1}]
const arr2 = arr1.reduce((pre, cur) => {
    if (!pre.find(v=>v.id === cur.id)){
        pre.push(cur)
    }
    return pre
},[])

15. 数字千分位加逗号

console.log(toThousands(1234567.65432)); // 1,234,567.65

/**
 * 数字千分位加逗号
 */
const toThousands = (value: number | string) => {
  return parseFloat(value || 0).toLocaleString();
};
/**
 * 数字千分位加逗号
 * @param {type} 参数
 * @returns {type} 返回值
 */
function toThousands(num) {
    let newNum = num.toString()
    if (newNum === '0' || newNum === '0%') return '-'
    if (newNum.includes('%') || !newNum) return newNum
    let pointNum = '' // 小数点尾数
    if (newNum.includes('.')) {
        const arr = newNum.split('.')
        newNum = arr[0]
        pointNum = arr[1].slice(0, 2)
    }
    let result = ''
    while (newNum.length > 3) {
        result = ',' + newNum.slice(-3) + result
        newNum = newNum.slice(0, -3)
    }
    return pointNum ? (newNum + result + '.' + pointNum) : (newNum + result)
}

15.数字转中文

/*
 * 数字转中文
 */
function changeNumToHan (num) {
  var arr1 = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
  var arr2 = ['', '十', '百', '千', '万', '十', '百', '千', '亿', '十', '百', '千', '万', '十', '百', '千', '亿']
  if (!num || isNaN(num)) return '零'
  var english = num.toString().split('')
  var result = ''
  for (var i = 0; i < english.length; i++) {
    var des_i = english.length - 1 - i// 倒序排列设值
    result = arr2[i] + result
    var arr1_index = english[des_i]
    result = arr1[arr1_index] + result
  }
  result = result.replace(/零(千|百|十)/g, '零').replace(/十零/g, '十') // 将【零千、零百】换成【零】 【十零】换成【十】
  result = result.replace(/零+/g, '零') // 合并中间多个零为一个零
  result = result.replace(/零亿/g, '亿').replace(/零万/g, '万') // 将【零亿】换成【亿】【零万】换成【万】
  result = result.replace(/亿万/g, '亿') // 将【亿万】换成【亿】
  result = result.replace(/零+$/, '') // 移除末尾的零
  // 将【一十】换成【十】
  result = result.replace(/^一十/g, '十')
  return result
}

16.数组数据转换

  • 数组转树状 - 递归

数组转树状 - 递归

function listToTree(list, pid = 0) {
    // 如果有一个 pid ,找出对应层级的部门
    const res = []
    list.forEach(item => {
        if (item.pid === pid) {
            // 每找到一个当前层级的部门,就以当前部门的 id
            // 作为下一层的 pid 进行搜索
            const children = listToTree(list, item.id)
            // 如果找到,就放入当前的部门中
            if (children.length > 0) {
                item.children = children
            }
            res.push(item)
        }
    })
    return res
}
  • 数组转树状 - 给父节点添加children
function listToTree(list, pid = 0) {
    // 删除 所有 children,以防止多次调用
    list.forEach(function(item) {
        delete item.children;
    });
    list.forEach(item => {
        // item.pid===pid说明没有父部门
        if (item.pid !== pid) {
            const parent = list.find(el => el.id === item.pid)
            if (parent) {
                parent.children = parent.children || []
                parent.children.push(item)
            }
        }
    })
    return list.filter(item => item.pid === pid)
}
  • 树状结构转一维数组

树状结构转一维数组

function treeToArray(list) {
    const arr = []
    list.forEach(item => {
        arr.push(item)
        if (item.children && item.children.length) {
            arr.push(...treeToArray(item.children))
        }
    })
    // 删除 所有 children
    arr.forEach(function (item) {
        delete item.children;
    });
    return arr
}
  • 数组扁平化 - flat

数组扁平化 - flat

  let arr = [
      [1, 2, 2],
      [3, 4, 5, 5],
      [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
    ]
    //flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
    arr = arr.flat(Infinity)
    console.log(arr); //[1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
  • 数组扁平化 - toString + parseFloat

数组扁平化 - toString + parseFloat

    let arr = [
      [1, 2, 2],
      [3, 4, 5, 5],
      [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
    ]
    arr = arr.toString().split(',').map(item => parseFloat(item))
    console.log(arr); //[1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
  • 数组扁平化 - JSON.stringify

数组扁平化 - JSON.stringify

   let arr = [
      [1, 2, 2],
      [3, 4, 5, 5],
      [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
    ]
    arr = JSON.stringify(arr).replace(/(\[|\])/g,'').split(',').map(item => parseFloat(item))
    console.log(arr); //[1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]
  • 数组扁平化 - while + Array.isArray + concat

数组扁平化 - while + Array.isArray + concat

    let arr = [
      [1, 2, 2],
      [3, 4, 5, 5],
      [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10
    ]
    // 循环验证是否为数组
    // some() 方法测试数组中是不是至少有1个元素通过了被提供的函数测试。它返回的是一个Boolean类型的值
    while(arr.some(item => Array.isArray(item))) {
      // concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组
      arr = [].concat(...arr)
    }
    console.log(arr);

17. replace 正则手机号去敏感

function tranPhone(num) {
    let str = String(num)
    str = str.replace(/^(\d{3})\d{4}(\d{4})/,"$1****$2")
    return str
}
​
console.log(tranPhone(15655656485));

18. 字符串保留#号后面的内容

"赤盛#104" 只保留 "#104"

const str = "赤盛#104"; 
const newStr = str.replace(/.*(#.*)/, "$1"); 
console.log(newStr); // 输出:#104

19.匹配字符串中第一个数字

是多少#32' 保留32

// '是多少#32'.match(/#(\d+)/)   ['#32', '32', index: 3, input: '是多少#32', groups: undefined]
'是多少#32'.match(/#(\d+)/)[1] // 32

20.判断数据类型

JavaScript 数据类型

原始类型(值类型、基本类型) :字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。

引用数据类型(对象类型) :对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)

typeof

let obj={
    name:'dawn',
    age:21
}
let fn=function() {
    console.log('我是 function 类型');
}
console.log(typeof 1);       //number
console.log(typeof 'abc');   //string
console.log(typeof true);    //boolean
console.log(typeof undefined);  //undefined 
console.log(typeof fn);      //function
console.log(typeof (new Date) );  //object
console.log(typeof null);     //object
console.log(typeof [1,2,3]);  //object
console.log(typeof obj);      //object

instanceof

let arr=[1,2,3,4,5,6,7]
let obj={
    name:'dawn',
    age:21
}
let fn=function() {
    console.log('我是 function 类型');
}
​
console.log(arr instanceof Array);  //true
console.log(obj instanceof Object);  //true
console.log(fn instanceof Function);  //true
console.log((new Date) instanceof Date);  //true

Object.prototype.toString.call

let obj = {
    name: 'dawn',
    age: 21
}
let fn = function () {
    console.log('我是 function 类型');
}
​
console.log(Object.prototype.toString.call(1));        // [object Number]
console.log(Object.prototype.toString.call('Hello tomorrow')); // [object String ]
console.log(Object.prototype.toString.call(true));     // [object Boolean]
console.log(Object.prototype.toString.call(undefined));  // [object Undefined]
console.log(Object.prototype.toString.call(fn));   // [object Function]
console.log(Object.prototype.toString.call(new Date));  // [object Date]
console.log(Object.prototype.toString.call(null));   // [object Null]
console.log(Object.prototype.toString.call([1, 2, 3]));  // [object Array]
console.log(Object.prototype.toString.call(obj));       // [object Object]

constructor

let arr = [1, 2, 3, 4, 5, 6, 7]
let obj = {
    name: 'dawn',
    age: 21
}
let fn = function () {
    console.log('我是 function 类型');
}
​
console.log((9).constructor === Number);  //true
console.log('hello'.constructor === String);  //true
console.log(true.constructor === Boolean);  //true
console.log(fn.constructor === Function);  //true
console.log((new Date).constructor === Date);  //true
console.log(obj.constructor === Object);  //true
console.log([1, 2, 3].constructor === Array);  //true

判断数组额外方法

  • Array.isArray()
let arr = []
console.log(Array.isArray(arr)) //true
  • Object.getPrototypeOf()
let arr = []
console.log(Object.getPrototypeOf(arr) == Array.prototype)  //true
  • Array.prototype.isPrototypeof()
let arr = []
console.log(Array.prototype.isPrototypeof(arr)) //true

判断对象额外方法

  • Object.getPrototypeOf()
Object.getPrototypeOf(val) === Object.prototype // true 代表为对象

21.发布订阅模式 + 单例模式实现一处绑定多处调用

发布订阅模式, 使用场景: 类似事件总线, 可以跨组件绑定事件和触发事件

// class版 发布订阅者模式
class EventEmitter {
    static instance: EventEmitter;
    constructor() {
        // key: 事件名, value: callback[] 回调数组
        this.events = {}
    }
    on(name, callback) {
        if (typeof callback !== 'function') return console.error('请传入正确的回调函数');
        const events = this.events[name]
        if (events) {
            events.push(callback)
        } else {
            this.events[name] = [callback]
        }
    }
    once(name, callback) {
        if (typeof callback !== 'function') return console.error('请传入正确的回调函数');
        const onceCallback = (...args) => {
            callback(...args)
            this.off(name)
        }
        this.on(name, onceCallback)
    }
    emit(name, ...args) {
        const events = this.events[name]
        if (!events) return console.warn(`${name}事件不存在`);
        for (const event of events) {
            event(...args);
        }
    }
    off(name, callback) {
        if (!this.events[name]) return console.warn(`${name}事件不存在`);
        if (!callback) {
            // 没有callback 就删除整个事件
            delete this.events[name]
        }
        this.events[name] = this.events[name].filter(item => item !== callback)
    }
    // 获取实例
    static getInstance():EventEmitter {
       //如果有实例则返回,没有则创建并返回 
       return this.instance || (this.instance = new EventEmitter())
    }
}
// vue3 hooks版发布订阅模式
const events = new Map()
const EventEmitter = function () {
    function on(name, callback) {
        if (typeof callback !== 'function') return console.error('请传入正确的回调函数');
        let event = events.get(name)
        if (!event) {
            event.set(name, (event = new Set()))
        } 
        event.add(callback)
    }
    function once(name, callback) {
        if (typeof callback !== 'function') return console.error('请传入正确的回调函数');
        const onceCallback = (...args) => {
            callback(...args)
            off(name)
        }
        on(name, onceCallback)
    }
    function emit(name, ...args) {
        const event = events.get(name)
        if (!event) return console.warn(`${name}事件不存在`);
        event.forEach(fn => fn(...args))
    }
    function off(name, callback) {
        const event = events.get(name)
        if (!event) return console.warn(`${name}事件不存在`);
        if (!callback) {
            // 没有callback 就删除整个事件
            events.delete(name)
        }
        event.delete(callback)
    }
    return { on, once, emit, off }
}

22.观察者模式 + 单例模式实现多处绑定,集体消息通知

观察者模式, 使用场景: 绑定子类的更新方法, 再父类统一遍历触发

// class模式版
// 观察者模式
class Observerd {
    static instance: Observerd;
    constructor() {
        this.observerList = {}
    }
    addObserver(name, observer) {
        const list = this.observerList[name]
        if (list) {
            list.push(observer)
        } else {
            this.observerList[name] = [observer]
        }
    }
    notify(name) {
        const list = this.observerList[name]
        if (!list) return
        // list.forEach(observer => observer.update())
        list.forEach(observer => observer())
    }
        // 获取实例
    static getInstance():Observerd {
       //如果有实例则返回,没有则创建并返回 
       return this.instance || (this.instance = new Observerd())
    }
}

// vue3 + hooks版
const observerList = new Map()
const Observerd = function () {
    function addObserver(name, observer) {
        let list = observerList.get(name)
        if (!list) {
            observerList.set(name, (list = new Set()))
        }
        event.add(observer)
    }
    function notify(name) {
        const list = observerList.get(name)
        if (!list) return
        // list.forEach(observer => observer.update())
        list.forEach(observer => observer())
    }
    return { addObserver, notify }
}

base64 编码和解码

直接用btoa编码str会报错字符串包含超出 Latin1 范围的字符,所有要用封装后的方法

/**
  * 编码base64
  */
export function Encode64(data) {
  const str = typeof data === 'string'? data : JSON.stringify(data)
  return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
    function toSolidBytes(match, p1) {
      return String.fromCharCode('0x' + p1);
    }));
}
/**
* 解码base64
*/
function Decode64(str) {
  return decodeURIComponent(atob(str).split('').map(function (c) {
    return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
  }).join(''));
}