# 常用的一些知识点

# 1、网页关闭前阻塞提示

如下面的效果

window.addEventListener('beforeunload', event => {
  // Cancel the event as stated by the standard.
  event.preventDefault();
  // Chrome requires returnValue to be set.
  event.returnValue = '222';
});

# 2、动态加载js脚本

function loadScript(src, done) {
  var js = document.createElement('script');
  js.src = src;
  js.onload = function() {
    done();
  };
  js.onerror = function() {
    done(new Error('Failed to load script ' + src));
  };
  document.head.appendChild(js);
}

# 3、Object.keys() Object.getOwnPropertyNames() 区别

共同点: 都是返回对象自身的属性, 不包括原型链上

区别:Object.keys()返回自身可枚举的属性,Object.getOwnPropertyNames()返回所有的属性, 包括不可枚举的。

展开:for...in 循环遍历自身和原型链上可枚举的属性。 JSON.stringify 只序列化对象自身的可枚举属性。 Object.assign() 只拷贝对象自身的可枚举的属性。

总结:想要原型链上的属性可以采用for...in。想要所有的属性采用Object.getOwnPropertyNames(), 比如用在对象深拷贝的递归中时。

# 4、乱序一个数组

// 自己随便写的
function easyShuffle(arr) {
  const source = [...arr];
  const target = [];
  while (source.length > 0) {
    const index = Math.floor(Math.random() * source.length);
    const value = source.splice(index, 1);
    target.push(value[0]);
  }
  return target;
}

// Fisher–Yates
function shuffle(a = []) {
  const arr = [...a];
  var j, x, i;
  for (i = arr.length; i; i--) {
    j = Math.floor(Math.random() * i);
    x = arr[i - 1];
    arr[i - 1] = arr[j];
    arr[j] = x;
  }
  return arr;
}

# 5、字符串分割支持多种字符

const text = '131,13123,13123、13、1321;add; adsad adasd    asdasd'
const a = text.split(/,|,|、|(\s+)|;|;/).filter((_) => _);
console.log(a);
const text = '131,13123,13123、13、1321;add; adsad adasd    asdasd';
const a = text.split(/[,,、(\s+);;]/)
console.log(a);

可以根据各种中文逗号、顿号、分号、空格等分割

# 6、html字符串的转义和逆转义

转义

function escapeHTML(str) {
  const charMap = {
    '&': '&',
    '<': '&lt;',
    '>': '&gt;',
    "'": '&#39;',
    '"': '&quot;'
  };
  return str.replace(/[&<>'"]/g, tag => {
    return charMap[tag] || tag;
  });
}

逆转义

function unescapeHTML(str) {
  const charMap = {
    '&amp;': '&',
    '&lt;': '<',
    '&gt;': '>',
    '&#39;': "'",
    '&apos;': "'",
    '&quot;': '"'
  };
  return str.replace(/&amp;|&lt;|&gt;|&#39;|&apos;|&quot;/g, tag => {
    return charMap[tag] || tag;
  });
}

# 7、全屏操作

// 全屏元素
function handleRequestFullScreen(domElement) {
	if (domElement.requestFullscreen) {
		domElement.requestFullscreen()
	} else if (domElement.webkitRequestFullScreen) {
		domElement.webkitRequestFullScreen()
	} else if (domElement.mozRequestFullScreen) {
		domElement.mozRequestFullScreen()
	} else {
		domElement.msRequestFullscreen()
	}
}
// 退出全屏
function handleExitFullscreen() {
	if (document.exitFullscreen) {
		document.exitFullscreen()
	} else if (document.mozCancelFullScreen) {
		document.mozCancelFullScreen()
	} else if (document.msExitFullscreen) {
		document.msExiFullscreen()
	} else if (document.webkitCancelFullScreen) {
		document.webkitCancelFullScreen()
	}
}

全屏事件, 可以通过 document.fullscreenElement获取当前被全屏展示的元素

document.addEventListener('fullscreenchange', () => {
	 // document.fullscreenElement
})

# 8、获取URL参数query params

通过字符串截断获取

function getAllUrlParams(
  url,
  options = {
    needDecode: true,
    ignoreCase: false
  }
) {
  const { needDecode, ignoreCase } = options;
  var queryString = url ? url.split('?')[1] : window.location.search.slice(1);
  var obj = {};
  if (queryString) {
    queryString = queryString.split('#')[0];
    if (needDecode) {
      queryString = decodeURIComponent(queryString);
    }
    var arr = queryString.split('&');
    for (var i = 0; i < arr.length; i++) {
      var a = arr[i].split('=');
      var paramName = a[0];
      var paramValue = typeof a[1] === 'undefined' ? true : a[1];

      if (ignoreCase) {
        paramName = paramName.toLowerCase();
      }
      //
      if (typeof obj[paramName] === 'string') {
        obj[paramName] = [obj[paramName]];
        obj[paramName].push(paramValue);
      } else if (typeof obj[paramName] === 'object') {
        obj[paramName].push(paramValue);
      } else {
        obj[paramName] = paramValue;
      }
    }
  }
  return obj;
}

使用api-URLSearchParams获取

function getAllUrlParamsByURLSearchParams(
  url,
  options = {
    needDecode: false,
    ignoreCase: false
  }
) {
  let urlIns;
  const { needDecode, ignoreCase } = options;
  try {
    if (!url) {
      urlIns = new URL(window.location.href);
    } else {
      // 考虑URL参数格式不对
      urlIns = new URL(url);
    }
  } catch (error) {
    console.error(error);
    return {};
  }
  let params = new URLSearchParams(urlIns.search);
  const obj = {};
  for (let param of params) {
    let [key, value] = param;
    // 使用 URLSearchParams 其实可以不用decodeURIComponent
    if (needDecode) {
      value = decodeURIComponent(value);
    }
    if (ignoreCase) {
      key = key.toLowerCase();
    }
    if (typeof obj[key] === 'string') {
      obj[key] = [obj[key]];
      obj[key].push(value);
    } else if (typeof obj[key] === 'object') {
      obj[key].push(value);
    } else {
      obj[key] = value;
    }
  }
  return obj;
}

# 9、使用object标签嵌入PDF

宽度设置为800*1200看起来更为合理

<object data="./1.pdf" type="application/pdf" width="800" height="1200">
  <p>
    You don't have a PDF plugin, but you can
    <a href="1.pdf">download the PDF file. </a>
  </p>
</object>

# 10、判断是否点击元素外部

clickoutside.js

let seed = 0;
const ctx = '@@clickoutsideContext';
const nodeList = [];
let inited = false;
function initDocumentMouseHandler() {
  let startClickEvent = null;
  document.addEventListener('mousedown', e => {
    // console.log(e.target);
    startClickEvent = e;
  });
  document.addEventListener('mouseup', e => {
    // console.log(e.target);
    nodeList.forEach(node => {
      node[ctx].documentHandler(e, startClickEvent);
    });
  });
}

function createDocumentHandler(el, callback) {
  return function (mouseUpEvent, mouseDownEvent) {
    if (
      el.contains(mouseUpEvent.target) ||
      el.contains(mouseDownEvent.target) ||
      el === mouseUpEvent.target
    ) {
      return;
    }
    callback.apply(mouseUpEvent.target, [mouseUpEvent]);
  };
}

function bindClickOutside(el, callback) {
  if (!inited) {
    initDocumentMouseHandler();
    inited = true;
  }
  nodeList.push(el);
  const id = seed++;
  el[ctx] = {
    id,
    documentHandler: createDocumentHandler(el, callback)
  };
}

function unbindClickOutside(el) {
  for (let i = 0, len = nodeList.length; i < len; i++) {
    if (nodeList[i][ctx].id === el[ctx].id) {
      nodeList.splice(i, 1);
      break;
    }
  }
  delete el[ctx];
}

export { bindClickOutside, unbindClickOutside };

用法

import { bindClickOutside, unbindClickOutside } from 'clickoutside.js';

const helloEl = document.querySelector('.hello');
bindClickOutside(helloEl, function (e) {
  console.log(this, e);
  console.log('clickoutside');
  //   unbindClickOutside(helloEl);
});

# 11、获取客户端滚动条宽度

let scrollBarWidth;

export default function () {
  if (scrollBarWidth !== undefined) return scrollBarWidth;

  const outer = document.createElement('div');
  outer.className = 'xd-scrollbar__wrap';
  outer.style.visibility = 'hidden';
  outer.style.width = '100px';
  outer.style.position = 'absolute';
  outer.style.top = '-9999px';
  document.body.appendChild(outer);

  const widthNoScroll = outer.offsetWidth;
  outer.style.overflow = 'scroll';

  const inner = document.createElement('div');
  inner.style.width = '100%';
  outer.appendChild(inner);

  const widthWithScroll = inner.offsetWidth;
  outer.parentNode.removeChild(outer);
  scrollBarWidth = widthNoScroll - widthWithScroll;

  return scrollBarWidth;
}

# 12、轻量的并不高效的UUID生成函数

function generateUUID() {
  var d = new Date().getTime(); //Timestamp
  var d2 = (performance && performance.now && performance.now() * 1000) || 0; //Time in microseconds since page-load or 0 if unsupported
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    var r = Math.random() * 16; //random number between 0 and 16
    if (d > 0) {
      //Use timestamp until depleted
      r = (d + r) % 16 | 0;
      d = Math.floor(d / 16);
    } else {
      //Use microseconds since page-load if supported
      r = (d2 + r) % 16 | 0;
      d2 = Math.floor(d2 / 16);
    }
    return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
  });
}

# 13、判断两个对象是否相等

简易版本: 判断两个字面量对象的值是否相等,而且只判断第一层

function eqPlainObject(objA, objB, fields) {
  if (!objA || !objB) {
    return false;
  }
  let fieldsA = [];
  let fieldsB = [];
  if (!fields) {
    fieldsA = Object.keys(objA);
    fieldsB = Object.keys(objB);
    fields = fieldsA;
  } else {
  }
  if (fieldsA.length !== fieldsB.length) {
    return false;
  }
  let flag = true;
  for (let key of fields) {
    if (objA[key] !== objB[key]) {
      flag = false;
      break;
    }
  }

  return flag;
}

全功能版本

/* 
NaN 和 NaN 是相等
[1] 和 [1] 是相等
{value: 1} 和 {value: 1} 是相等
不仅仅是这些长得一样的,还有

1 和 new Number(1) 是相等
'Curly' 和 new String('Curly') 是相等
true 和 new Boolean(true) 是相等
*/

var _toString = Object.prototype.toString;

function isFunction(obj) {
  return _toString.call(obj) === '[object Function]';
}

function eq(a, b, aStack, bStack) {
  // === 结果为 true 的区别出 +0 和 -0
  if (a === b) return a !== 0 || 1 / a === 1 / b;

  // typeof null 的结果为 object ,这里做判断,是为了让有 null 的情况尽早退出函数
  if (a == null || b == null) return false;

  // 判断 NaN
  if (a !== a) return b !== b;

  // 判断参数 a 类型,如果是基本类型,在这里可以直接返回 false
  var type = typeof a;
  if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;

  // 更复杂的对象使用 deepEq 函数进行深度比较
  return deepEq(a, b, aStack, bStack);
}

function deepEq(a, b, aStack, bStack) {
  // a 和 b 的内部属性 [[class]] 相同时 返回 true
  var className = _toString.call(a);
  if (className !== _toString.call(b)) return false;

  switch (className) {
    case '[object RegExp]':
    case '[object String]':
      return '' + a === '' + b;
    case '[object Number]':
      if (+a !== +a) return +b !== +b;
      return +a === 0 ? 1 / +a === 1 / b : +a === +b;
    case '[object Date]':
    case '[object Boolean]':
      return +a === +b;
  }

  var areArrays = className === '[object Array]';
  // 不是数组
  if (!areArrays) {
    // 过滤掉两个函数的情况
    if (typeof a != 'object' || typeof b != 'object') return false;

    var aCtor = a.constructor,
      bCtor = b.constructor;
    // aCtor 和 bCtor 必须都存在并且都不是 Object 构造函数的情况下,aCtor 不等于 bCtor, 那这两个对象就真的不相等啦
    if (
      aCtor !== bCtor &&
      !(
        isFunction(aCtor) &&
        aCtor instanceof aCtor &&
        isFunction(bCtor) &&
        bCtor instanceof bCtor
      ) &&
      'constructor' in a &&
      'constructor' in b
    ) {
      return false;
    }
  }

  aStack = aStack || [];
  bStack = bStack || [];
  var length = aStack.length;

  // 检查是否有循环引用的部分
  while (length--) {
    if (aStack[length] === a) {
      return bStack[length] === b;
    }
  }

  aStack.push(a);
  bStack.push(b);

  // 数组判断
  if (areArrays) {
    length = a.length;
    if (length !== b.length) return false;

    while (length--) {
      if (!eq(a[length], b[length], aStack, bStack)) return false;
    }
  }
  // 对象判断
  else {
    var keys = Object.keys(a),
      key;
    length = keys.length;

    if (Object.keys(b).length !== length) return false;
    while (length--) {
      key = keys[length];
      if (!(b.hasOwnProperty(key) && eq(a[key], b[key], aStack, bStack))) return false;
    }
  }

  aStack.pop();
  bStack.pop();
  return true;
}

export default eq;

# 14、Vue2 父子组件通信

/* 一般子组件的mounted会先执行, 所以子组件先$on 绑定一个事件,然后父组件在mounted钩子 broadcast */
/* 父组件会先created, 然后子组件再created, 所以父组件的$on 可以在created执行,子组件可以在其声明周期钩 dispatch */
function broadcast(componentName, eventName, params) {
  this.$children.forEach(child => {
    var name = child.$options.componentName;

    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}
export default {
  methods: {
    dispatch(componentName, eventName, params) {
      var parent = this.$parent || this.$root;
      var name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};

# 15、数字截取和补零

// 保留两位小数,不进行4舍5入
function numFixed(num, floatLen = 2) {
  num = String(num);
  const str = num.split('.');
  let insStr = str[0];
  let floatStr = str[1];
  if (floatStr) {
    floatStr = floatStr.substring(0, floatLen);
    if (!insStr) {
      insStr = '0';
    }
  }
  return [insStr, floatStr].filter(Boolean).join('.');
}

// 前置补零
function pad(val, len = 2) {
  val = String(val);
  len = len || 2;
  while (val.length < len) {
    val = '0' + val;
  }
  return val;
}

// 后置补0
function suffixPad(val, len = 2) {
  val = String(val);
  len = len || 2;
  while (val.length < len) {
    val = val + '0';
  }
  return val;
}
上次更新: 1/22/2025, 9:39:13 AM